I have seen DDD Domain implementations where entities rely on the primary key ID when building Equals/GetHashCode methods. I understand why it is a good idea, as the primary key might be the only member which is not mutable. But there might also be situations where it is no good idea, though.
Think about a Dictionary, holding entities which where just instantiated. The primary key (assume it is an auto-incrementing value, not a "business-related" value) is not assigned yet, it might be 0 on each entity. Now, the entities within the Dictionary will be saved. This implies a change of the primary key resulting in a different hash code.
My question: what pattern should be used regarding GetHashCode if no business-related primary key is available and all members are mutable?
Thank you in advance.
In such cases, I only rely on the Id in the Equals and GetHashcode methods, if the Id has been assigned.
In other cases, I compare for equality using the reference.
The same goes for the GetHashCode implementation; I only create a hashcode based on the 'Id', if the Id has been assigned. (That is, if the entity is not transient).
The code below, is what I use. This is somewhat based on the entities in Sharp architecture:
public override bool Equals( object obj )
{
Entity<TId> other = obj as Entity<TId>;
if( other == null || this.GetType() != other.GetType() )
{
return false;
}
bool otherIsTransient = Equals (other.Id, default(TId));
bool thisIsTransient = Equals (this.Id, default (TId));
if( otherIsTransient && thisIsTransient )
{
return ReferenceEquals (this, other);
}
return Id.Equals (other.Id);
}
First, I check whether both entities are of the same type. (Actually, you could create a typed version of this method as well, by implementing the correct interface (IEquality<T>)
When both entities are of the same type, I check whether one of them is transient. I just do it by checking their Id property: if it contains the default value, it means that they haven't been saved in the DB yet.
If both of them are transient, I compare both entities on their reference. Otherwise, I can compare them on their ID.
The GetHashCode method that I use, makes sure that the value that's being returned, never changes:
private int? _oldHashCode;
public override int GetHashCode()
{
// Once we have a hash code we'll never change it
if( _oldHashCode.HasValue )
{
return _oldHashCode.Value;
}
bool thisIsTransient = Equals (Id, default(TId));
// When this instance is transient, we use the base GetHashCode()
// and remember it, so an instance can NEVER change its hash code.
if( thisIsTransient )
{
_oldHashCode = base.GetHashCode ();
return _oldHashCode.Value;
}
return Id.GetHashCode ();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With