I have a class with some read only properties that I want to store using mongodb.
User.cs
public class User: ValueObject<User>
{
public UserId Id { get; }
public string Firstname { get; }
public string Lastname { get; }
public User(UserId id, string firstname, string lastname)
{
Id = new UserId(id.Id);
Firstname = firstname;
Lastname = lastname;
}
public User(string id, string firstname, string lastname)
{
Id = new UserId(id);
Firstname = firstname;
Lastname = lastname;
}
protected override bool MembersEquals(User other)
{
return Id == other.Id && Firstname == other.Firstname && Lastname == other.Lastname;
}
protected override int MembersHashCode()
{
return Id.GetHashCode() ^ Firstname.GetHashCode() ^ Lastname.GetHashCode();
}
}
Class map registration:
BsonClassMap.RegisterClassMap<User>(cm =>
{
cm.AutoMap();
cm.MapProperty(user => user.Id);
cm.MapProperty(user => user.Firstname);
cm.MapProperty(user => user.Lastname);
cm.MapCreator(user => new User(user.Id, user.Firstname, user.Lastname));
});
this class is stored as a subdocument. when i try to store the whole document mongodb driver tell me that the MongoDB.Bson.BsonSerializationException: Creator map for class Box.Domain.User has 3 arguments, but none are configured.. I didn't really understand what does it mean.
NB: the UserId class has a registred serializer
public class UserIdBsonSerializer : SerializerBase<UserId>
{
public override UserId Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var currentBsonType = context.Reader.GetCurrentBsonType();
return currentBsonType switch
{
BsonType.String => new UserId(context.Reader.ReadString()),
_ => throw new NotSupportedException($"Cannot deserialize {currentBsonType} to an UserId")
};
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, UserId value)
{
context.Writer.WriteString(value.Id);
}
}
EDIT
ValueObject.cs
public abstract class ValueObject<T> : IEquatable<T>
where T : ValueObject<T>
{
// Verify the value object members equality.
protected abstract bool MembersEquals(T other);
// Generate a hash code depending on the value object members values.
protected abstract int MembersHashCode();
#region Equality
public bool Equals([AllowNull] T other)
{
if (ReferenceEquals(other, null))
return false;
if (ReferenceEquals(this, other))
return true;
return MembersEquals(other);
}
public override bool Equals(object obj)
{
var other = obj as T;
return Equals(other);
}
public static bool operator ==(ValueObject<T> lhs, ValueObject<T> rhs)
{
if (ReferenceEquals(lhs, null) && ReferenceEquals(rhs, null))
return true;
if (ReferenceEquals(lhs, null))
return false;
return lhs.Equals(rhs);
}
public static bool operator !=(ValueObject<T> lhs, ValueObject<T> rhs) => !(lhs == rhs);
#endregion
public override int GetHashCode()
{
return MembersHashCode();
}
}
UserId.cs
public class UserId: ValueObject<UserId>
{
public string Id { get; }
public UserId(string id)
{
if (id.Length < 5)
throw new ArgumentException($"user id must be 5 characters length. {id}");
// TODO: Add more validation rules for user id
Id = id;
}
protected override bool MembersEquals(UserId other)
{
return Id == other.Id;
}
protected override int MembersHashCode()
{
return Id.GetHashCode();
}
}
Looks like the cm.AutoMap(); in your register class map is creating 2 creators before adding your own.
If you remove that it'll start working.
BsonClassMap.RegisterClassMap<User>(cm =>
{
cm.MapProperty(user => user.Id);
cm.MapProperty(user => user.Firstname);
cm.MapProperty(user => user.Lastname);
cm.MapCreator(user => new User(user.Id, user.Firstname, user.Lastname));
});
An alternative would be to remove the ImmutableTypeClassMapConvention from the default convention pack.
ConventionRegistry.Remove("__defaults__");
var pack = new ConventionPack();
var defaultConventions = DefaultConventionPack.Instance.Conventions;
pack.AddRange(defaultConventions.Except(
defaultConventions.OfType<ImmutableTypeClassMapConvention>()
));
ConventionRegistry.Register(
"__defaults__",
pack,
t => true);
It is a bit late now but still in case anybody would be interested in the root cause of this issue then please see the following expression && GetMemberType(memberInfos[0]) == parameter.ParameterType.
To summarize:
it is required that both -
constructor parameters be matched to
properties using case insensitive name matching and to be of the exact
same type.
For even more details please see CSHARP-3526
Related
How can I simplify the code below to avoid to pass the object for type inference on the generic method?
using System;
using System.Linq.Expressions;
namespace lambda
{
class Program
{
static void Main(string[] args)
{
var area = new Area { Name = "New Area" };
var propertyName = area.GetPropertyName(area, a => a.Name); // propertyName is COMPILE time checked
Console.WriteLine(propertyName);
}
}
public class Area
{
public int Id;
public string Name { get; set; }
}
public static class Extension
{
public static string GetPropertyName<T>(this Area entity, T e, Expression<Func<T, object>> path) // T e for type inference
{
var member = path.Body as MemberExpression;
if (member == null) throw new ArgumentException();
return member.Member.Name;
}
}
}
I mean instead of calling the extension method with area.GetPropertyName(area, a => a.Name)
just do a call like this area.GetPropertyName(a => a.Name), avoid to pass there area object just for type inference
I guess that I can’t do unless I refactor the signature of the method to GetPropertyName(this IEntity entity, Expression> path)
But in that case will be less obvius want I want at code writing time since I will need to specify the type on every call
I mean area.GetPropertyName( a => a.Name) seems to bel for me less clear writing code than writing area.GetPropertyName(area, a => a.Name)
The example code below works fine with asked requirements, no need to pass the object itself for type inference when calling the extension method
I used a base class and an interface that for my case works fine for all my domain class.
See code below
namespace UnitTestProject
{
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Linq.Expressions;
public interface IEntity<T>
{
}
public abstract class Entity<T> : IEntity<T> where T : class
{
}
public class Area : Entity<Area>
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Created { get; set; }
public bool Closed { get; set; }
public string Description { get; set; }
}
public static class EntityExtension
{
public static string GetPropertyName<T>(this IEntity<T> entity, Expression<Func<T, object>> expr) // T e for type inference
{
var unary = expr.Body as UnaryExpression;
var member = expr.Body as MemberExpression;
return member != null ? member.Member.Name : (unary != null ? ((MemberExpression)unary.Operand).Member.Name : String.Empty);
}
}
[TestClass]
public class UnitTest
{
[TestMethod]
public void GetPropertyName_Tests()
{
var area = new Area();
var x = area.GetPropertyName(a => a.Id);
var y = area.GetPropertyName(a => a.Name);
var v = area.GetPropertyName(a => a.Created);
var w = area.GetPropertyName(a => a.Closed);
var z = area.GetPropertyName(a => a.Description);
Assert.AreEqual(x, "Id");
Assert.AreEqual(y, "Name");
Assert.AreEqual(v, "Created");
Assert.AreEqual(w, "Closed");
Assert.AreEqual(z, "Description");
}
}
}
I am using entityframework 5 with code first model and existing database.I am implementing repository pattern.I have a BaseEntity class like below:
public abstract partial class BaseEntity
{
/// <summary>
/// Gets or sets the entity identifier
/// </summary>
public virtual int Id { get; set; }
public override bool Equals(object obj)
{
return Equals(obj as BaseEntity);
}
private static bool IsTransient(BaseEntity obj)
{
return obj != null && Equals(obj.Id, default(int));
}
private Type GetUnproxiedType()
{
return GetType();
}
public virtual bool Equals(BaseEntity other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (!IsTransient(this) &&
!IsTransient(other) &&
Equals(Id, other.Id))
{
var otherType = other.GetUnproxiedType();
var thisType = GetUnproxiedType();
return thisType.IsAssignableFrom(otherType) ||
otherType.IsAssignableFrom(thisType);
}
return false;
}
public override int GetHashCode()
{
if (Equals(Id, default(int)))
return base.GetHashCode();
return Id.GetHashCode();
}
public static bool operator ==(BaseEntity x, BaseEntity y)
{
return Equals(x, y);
}
public static bool operator !=(BaseEntity x, BaseEntity y)
{
return !(x == y);
}
protected virtual void SetParent(dynamic child)
{
}
protected virtual void SetParentToNull(dynamic child)
{
}
protected void ChildCollectionSetter<T>(ICollection<T> collection, ICollection<T> newCollection) where T : class
{
if (CommonHelper.OneToManyCollectionWrapperEnabled)
{
collection.Clear();
if (newCollection != null)
newCollection.ToList().ForEach(x => collection.Add(x));
}
else
{
collection = newCollection;
}
}
protected ICollection<T> ChildCollectionGetter<T>(ref ICollection<T> collection, ref ICollection<T> wrappedCollection) where T : class
{
return ChildCollectionGetter(ref collection, ref wrappedCollection, SetParent, SetParentToNull);
}
protected ICollection<T> ChildCollectionGetter<T>(ref ICollection<T> collection, ref ICollection<T> wrappedCollection, Action<dynamic> setParent, Action<dynamic> setParentToNull) where T : class
{
if (CommonHelper.OneToManyCollectionWrapperEnabled)
return wrappedCollection ?? (wrappedCollection = (collection ?? (collection = new List<T>())).SetupBeforeAndAfterActions(setParent, SetParentToNull));
return collection ?? (collection = new List<T>());
}
}
Now I have a table Customer which has CustomerId as primary key .How should I map such like fields with Id of BaseEntity.If I have a composite key how should I map this to the BaseEntity ID.
Please Help me.
Make sure you set the initialize for your DBContext to false:
context.Database.Initialize(false);
Here is a good article on database initializers for code first:
http://www.codeguru.com/csharp/article.php/c19999/Understanding-Database-Initializers-in-Entity-Framework-Code-First.htm
I have two Classes: LicenseType and EntityType.
[Table("LicenseType")]
public class LicenseType : ComplianceBase, INotifyPropertyChanged
{
private List<Certification> _certifications = new List<Certification>();
private List<EntityType> _entityTypes = new List<EntityType>();
public List<EntityType> EntityTypes
{
get { return _entityTypes; }
set { _entityTypes = value; }
}
public List<Certification> Certifications
{
get { return _certifications; }
set { _certifications = value; }
}
}
and
[Table("EntityType")]
public class EntityType : ComplianceBase, INotifyPropertyChanged
{
private List<LicenseType> _licenseTypes = new List<LicenseType>();
public List<LicenseType> LicenseTypes
{
get { return _licenseTypes; }
set
{
_licenseTypes = value;
// OnPropertyChanged();
}
}
}
The both derive from ComplianceBase,
public class ComplianceBase
{
private int _id;
private string _name;
private string _description;
public string Description
{
get { return _description; }
set
{
if (_description == value) return;
_description = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public int Id
{
get { return _id; }
set
{
if (value == _id) return;
_id = value;
OnPropertyChanged();
}
}
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
What I want is to be able to do is associate an EntityType with one or more LicenseTypes, so for instance, an EntityType "Primary Lender" could be associated with say two LicenseTypes, "Lender License" and "Mortgage License". In this situation, I want one record in the EntityType table, "Primary Lender" and two records in my LicenseType table: "Lender License" and "Mortgage License".
The code for adding related LicenseTypes to my EntityType is done by calling:
_currentEntity.LicenseTypes.Add(licenseType);
and then calling _context.SaveChanges();
There is an additional table, "EntityTypeLicenseTypes" that serves as the lookup table to relate these two tables. There are two records to join the EntityType with the two related LicenseTypes.
And this works. However, my code also adds (it duplicates) the LicenseType record and adds it in the LicenseType table for those records that are being associated.
How can I stop this from happening?
In order to avoid the duplication you must attach the licenseType to the context:
_context.LicenseTypes.Attach(licenseType);
_currentEntity.LicenseTypes.Add(licenseType);
_context.SaveChanges();
I'm using EF Code First and have an entity which has a Telephone property of type PhoneNumber. The PhoneNumber class has some validation and convenience methods, but essentially holds a string with a correct phone number.
I want to persist this to the database as a simple string. How would I go about this?
Do I need to register some sort of type converter, or do I really need to expose a public property on my PhoneNumber class which is used as complex type (I'd rather not have a public property like this, since I would like PhoneNumber to be immutable).
This is my PhoneNumber class:
public class PhoneNumber {
private string _number;
public PhoneNumber(string number) {
if (!IsValid(number)) {
throw new ArgumentException("Invalid Dutch phone number.");
}
_number = number;
}
private static string Sanitize(string number) {
return number.Trim()
.Replace("-", String.Empty)
.Replace(" ", String.Empty);
}
public bool IsValid() {
return IsValid(this);
}
public static bool IsValid(string number) {
if (number == null) {
return false;
}
var cleanNumber = Sanitize(number);
return Regex.IsMatch(cleanNumber, #"^06\d{8}$");
}
public static implicit operator PhoneNumber(string number) {
return new PhoneNumber(number);
}
public static implicit operator string(PhoneNumber number) {
return number.ToString();
}
}
You will have to add this to your code:
public string Number
{
get { return _number; }
private set { _number = value; }
}
private PhoneNumber() {}
and map your class as complex type:
modelBuilder.ComplexType<PhoneNumber>();
What do you think about this Person class? Is it a bad idea or best practise to override Equals and GetHashCode like that?
public class Person {
public int PersonId { get; set; }
public string Name { get; set; }
public override bool Equals(object obj) {
var person = obj as Person;
return PersonId == person.PersonId;
}
public override int GetHashCode() {
return PersonId;
}
}
Usage :
static void Main(string[] args) {
var list = new List<Person>();
list.Add(new Person(){ PersonId = 1, Name = "Mike"});
list.Add(new Person() { PersonId = 2, Name = "Michael Sync" });
list.Add(new Person(){ PersonId = 1, Name = "Mike"});
var list1 = new List<Person>();
list1.Add(new Person() { PersonId = 1, Name = "Mike" });
list1.Add(new Person() { PersonId = 3, Name = "Julia" });
var except = list.Except(list1);
foreach (var item in except) {
Console.WriteLine(item.Name);
}
Console.ReadKey();
}
A few points:
It's not null safe or "different type" safe. Try this:
new Person().Equals(new Object());
or
new Person().Equals(null);
Bang.
Classes defining equality operations should usually be immutable IMO. Changing the contents of an object after using it as a dictionary key is a Bad Thing, for example.
Consider implementing IEquatable<Person>
A quick reimplementation, which still assumes you want equality based solely on ID.
public sealed class Person : IEquatable<Person> {
private readonly int personId;
public int PersonId { get { return personId; }
private readonly string name;
public string Name { get { return name; } }
public Person(int personId, string name) {
// Is a null name valid? If not, throw here.
this.personId = personId;
this.name = name;
}
public override bool Equals(object obj) {
return Equals(obj as Person);
}
public Equals(Person other) {
return other != null && other.personId == personId;
}
public override int GetHashCode() {
return personId;
}
}
Yes this is wrong. You should never use a mutable property as part of the calculation for GetHashCode. Doing so opens you up to numerous hard to track down bugs.
One problem I can see is that you'll get lots of collisions for new (unsaved) records (lots of zeros) - unless you do something like have consecutive -ve ids for those... But do you really need to use Person as a key? Personally I don't think I'd bother...