How can the following problem be dealt with?
We\'re using lazy loaded NHibernate properties and whenever we\'re calling Equals()
or GetHashCode()<
If you are using identity equality, you should be able to access the key without triggering a load:
public virtual bool Equals(ClassB other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
// needs to check for null Id
return Equals(other.ClassC.Id, ClassC.Id) && Equals(other.ClassD.Id, ClassD.Id);
}
You can handle comparisons between objects before and after persisting by caching the hash code when it was transient. This leaves a small gap in the Equals contract in that a comparison between an existing object that was transient will not generate the same hash code as a newly-retrieved version of the same object.
public abstract class Entity
{
private int? _cachedHashCode;
public virtual int EntityId { get; private set; }
public virtual bool IsTransient { get { return EntityId == 0; } }
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
var other = obj as Entity;
return Equals(other);
}
public virtual bool Equals(Entity other)
{
if (other == null)
{
return false;
}
if (IsTransient ^ other.IsTransient)
{
return false;
}
if (IsTransient && other.IsTransient)
{
return ReferenceEquals(this, other);
}
return EntityId.Equals(other.EntityId);
}
public override int GetHashCode()
{
if (!_cachedHashCode.HasValue)
{
_cachedHashCode = IsTransient ? base.GetHashCode() : EntityId.GetHashCode();
}
return _cachedHashCode.Value;
}
}
Two entities are equal if they are of the same type and has the same primary key.
If you have integers for keys:
If you have GUIDs for keys:
If I have integers for keys I usually have something like this Equal-override in a base class for my entities:
public virtual bool Equals(EntityBase other)
{
if (other == null)
{
return false;
}
if (ReferenceEquals(other, this))
{
return true;
}
var otherType = NHibernateProxyHelper.GetClassWithoutInitializingProxy(other);
var thisType = NHibernateProxyHelper.GetClassWithoutInitializingProxy(this);
if (!otherType.Equals(thisType))
{
return false;
}
bool otherIsTransient = Equals(other.Id, 0);
bool thisIsTransient = Equals(Id, 0);
if (otherIsTransient || thisIsTransient)
return false;
return other.Id.Equals(Id);
}
Now if you entities that inherit from others using table per hierarchy you will face the problem that GetClassWithoutInitializingProxy will return the base class of the hierarchy if it's a proxy and the more specific type if it's a loaded entity. In one project I got around that by traversing the hierarchy and thus always comparing the base types - proxy or not.
In these days though I would always go for using GUIDs as keys and do as described here: http://nhibernate.info/doc/patternsandpractices/identity-field-equality-and-hash-code.html
Then there is no proxy type mismatch problem.
I use the following rules:
If entity has a POID property (remember that there is not need of property or any member just omit the name="XX", not sure if activerecord or the mapping strategy you are using supoprt this)
If entity doesn't have a POID property, for sure you will need a natural-id. Use natural id for equality and GetHashCode.
If you have a natural-id with many-to-one, instead of doing FooProperty.Equals(other.FooProperty), use FooProperty.Id.Equals(other.FooProperty.Id). Accessing the ID doesn't trigger the initialization of the lazy reference.
Last but not least, using composite-id is discourage, and composite id with key-many-to-one is very discourage.