Lets assume you have defined an equals operator on your entity class, e.g. something like resharpers default implementation:
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof (Category)) return false;
return ((Category)obj).Id == Id;
}
Now, the problem occurs when you want to use this for an NHibernate proxy object. First of all the GetType will fail, because it is a sub class, not the actual class.
Secondly, if one of the two objects is indeed a proxy object we may never have loaded it from the database. And if we never loaded it from the database, then the call to .Id will cause NHibernate to load the object. And that was NOT what we wanted.
The solution is to make a very complicated equals method that take the proxies into account:
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
Stays the same
INHibernateProxy thisproxy = this as INHibernateProxy;
int thisId;
Type thisType = GetType();
if (ReferenceEquals(null, thisproxy))
{
thisId = Id;
}
else
{
thisType = thisType.BaseType;
if (thisproxy.HibernateLazyInitializer.IsUninitialized)
thisId = (int) thisproxy.HibernateLazyInitializer.Identifier;
else
thisId = Id;
}
This is where it becomes complicated. We cast this to INHibernateProxy, and if we find that it is a proxy object that is not initialized, then we use the Lazy loaders notion of an Id to compare to.
So the full Equals method ends up as:
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
// ReSharper disable SuspiciousTypeConversion.Global
INHibernateProxy thisproxy = this as INHibernateProxy;
int thisId;
Type thisType = GetType();
if (ReferenceEquals(null, thisproxy))
{
thisId = Id;
}
else
{
thisType = thisType.BaseType;
if (thisproxy.HibernateLazyInitializer.IsUninitialized)
thisId = (int) thisproxy.HibernateLazyInitializer.Identifier;
else
thisId = Id;
}
INHibernateProxy otherproxy = obj as INHibernateProxy;
int otherId;
Type otherType = obj.GetType();
if (ReferenceEquals(null, otherproxy))
{
otherId = ((EntityBase) obj).Id;
}
else
{
otherType = otherType.BaseType;
if (otherproxy.HibernateLazyInitializer.IsUninitialized)
otherId = (int)otherproxy.HibernateLazyInitializer.Identifier;
else
otherId = ((EntityBase)obj).Id;
}
if (otherType != thisType) return false;
// ReSharper restore SuspiciousTypeConversion.Global
return otherId == thisId;
}
It would have been even better if the proxy was slightly more intelligent and didn’t init itself when fetching the value that is the key.