Introduction
NHibernate uses an Identity Map to maintain a single instance of each persistent entity in memory. This allows you to run round a domain model without having to worry about which instance of an entity you are modifying.
NHibernate also uses Lazy Load. The default implementation for this uses Dynamic Proxy (DP) to generate a class at runtime that inherits from your real class. This generated class intercepts all of the overridable methods and properties in your class to load the instance from the database if required.
What Identity Map Allows
Identity Map allows you to load up the same object twice but to have your references point to the same underlying instance.
The following example NUnit test demonstrates loading 3 objects, two of them with an ID of 1, and the other with an ID of 2:
[Test]
public void TestIdentityMap()
{
MyClass myInstance1 = Session.Load<MyClass>(1);
MyClass myInstance2 = Session.Load<MyClass>(1);
MyClass myInstance3 = Session.Load<MyClass>(2);
Assert.AreSame(myInstance1, myInstance2);
Assert.AreNotSame(myInstance1, myInstance3);
}
The following diagram shows how the references relate to the objects created on the heap.
The Identity Map ensures that there is only one instance of the object with ID equal to 1 in memory.
Under the hood of NHibernate
The basic principle of Identity Map is easy to pick up. However, there is a further complication because of the implementation of the proxies used for Lazy Load.
When you ask NHibernate for an object, it returns you a instance of a class created at runtime by DP. This class intercepts any overridable methods/properties and redirects them to a second object instance that has the 'real' class type.
So in the above example, the following is actually created on the heap:
Limitations of the current implementation
Since there are really two instances created on the heap when NHibernate gives you a proxy, there is an identity problem that you could run into.
Problem 1, the 'this' pointer:
Consider the following class:
public MyClass
{
public virtual bool IsMe(MyClass anotherMyClass)
{
return anotherMyClass == this;
}
}
The following test will fail because the reference myInstance points to the proxy, while the 'this' pointer is the real class:
[Test]
public void TestIsMe()
{
MyClass myInstance = Session.Load<MyClass>(1);
Assert.IsTrue(myInstance.IsMe(myInstance));
// The above line will fail
}
It is easy to imagine an example where a child entity notifies its parent of an event, and the parent then updates all its children except the child that initiated the event. The parent or child might have to be careful about reference comparison in such an example.
Problem 2, private methods/fields:
NHibernate relies on DP intercepting access to an instance of a class, and redirecting these calls to the 'real' instance. However DP is powerless to intercept private field and method access.
Situations where this could occur might be:
- sibling objects of a common parent object that communicate with each other;
- parent/child objects of the same type that communicate with each other (e.g., a tree);
- static methods that reference an instance of the same type.
Consider the following class:
public MyClass
{
private int _calculation = -1;
public virtual int Calculation
{
get { return _calculation; }
}
public static void SetCalculation(
MyClass instance,
int calculation)
{
instance._calculation = calculation;
}
}
The following test will fail because the static method sets the field not on the 'real' class but the proxy object, while the property accessor is intercepted and redirected to the 'real' instance.
[Test]
public void TestCalculation()
{
MyClass myInstance = _session.Load<MyClass>(1);
MyClass.SetCalculation(myInstance, 7);
Assert.AreEqual(7, myInstance.Calculation);
// the above line will fail
}
It is not uncommon for a class to have an interface for talking to other instances of itself while keeping that interface private from outsiders.
Conclusion
I suspect NHibernate could be improved to make the proxies self-proxy. If this could be done then there would be no second instance created, and no potential identity problem.
To avoid problems with the 'this' pointer, simply try to avoid using it for comparison with another object.
To avoid problems with one instance of a class talking to another instance of the same class, prefer virtual methods over non-virtual ones to allow Dynamic Proxy to intercept any calls made on the instance. (i.e., avoid using private fields or methods from one instance to another instance of the same class.)
A complete example of the above, with failing NUnit tests, can be downloaded here: NHibernateProxyDemo.zip