I want my entity classes to rely on their ID when checking equality, the ID is populated from the repository when I retrieve an object and automatically generated when I save a new entity.
This poses the problem of how I compare two entities before they have been saved while working with them in my domain, or, when I have ‘newed’ them up myself for unit testing. The answer is to use a transient ID.
Normally an Entity Base class might look like this:
public abstract class EntityBase : IEquatable<EntityBase> { public virtual int Id { get; protected set; } public override bool Equals(object other) { return Equals(other as EntityBase); } public override int GetHashCode() { return Id; } public virtual bool Equals(EntityBase other) { if (other == null) return false; return other.Id == Id && other.GetType() == GetType(); } }
If two new objects are created from a derived class then object1.Equals(object2) will be true as Id will be 0 for both objects. To avoid this I need to check if an object has not yet been saved:
private bool IsTransient { get { return Id == 0; } }
And just check for referential equality if this is the case:
public virtual bool Equals(EntityBase other) { if (other == null) return false; if (IsTransient) return ReferenceEquals(this, other); return other.Id == Id && other.GetType() == GetType(); }
GetHashCode() needs to be overridden to reflect this:
public override int GetHashCode() { if (IsTransient) return base.GetHashCode(); return Id; }
The full code for EntityBase is:
public abstract class EntityBase : IEquatable<EntityBase> { public virtual int Id { get; protected set; } private bool IsTransient { get { return Id == 0; } } public override bool Equals(object other) { return Equals(other as EntityBase); } public override int GetHashCode() { if (IsTransient) return base.GetHashCode(); return Id; } public virtual bool Equals(EntityBase other) { if (other == null) return false; if (IsTransient) return ReferenceEquals(this, other); return other.Id == Id && other.GetType() == GetType(); } }
GetHashCode() value must not change for the lifetime of the object if you add the object into a dictionary or hashtable. http://stackoverflow.com/questions/462451/gethashcode-guidelines-in-c-sharp http://stackoverflow.com/questions/7687551/ddd-gethashcode-and-primary-id
Thanks Xhafan,
Those are good points and there are other solutions such as caching the hashcode for the lifetime of the object. But there is a trade off, whichever solution you take. As mentioned in the links, not changing the hashcode is a guideline rather than a rule, but it is definitely worth pointing out that a persisted object != an unpersisted one.
The actual rule is:
Rule: the integer returned by GetHashCode must never change while the object is contained in a data structure that depends on the hash code remaining stable.
Thanks for pointing that out.
Have you noticed (resolved) an issue with a given entity’s existance in child collection after removing from its DbSet. Using your implementation of overridden Equality when removing a Child, the Child does not get removed from it’s parent collection. Without the overridden Equality EF behaves as expected.
Example:
public Store
{
public virtual ICollection Movies {get;set;}
}
Steps
1. Spin up DbContext
2. Create new Movie (Set.Create())
3. Add new Movie to store.Movies collection
4. DbContext.SaveChanges()
5. DbSet.Remove(newMovie)
6. Error – store.Movies collection still contains newMovie (without the overridden Equality newMovie gets removed from the store.Movies collection.
Interested in your thoughts.