We use an Entity interceptor to create audit records when we update most tables. We also do a lot of our data access through .NET remoting. This means that we open a session to load the object, the object is detached, serialized, modified, then reattached to the session when we want to persist the changes.
We noticed that in the cases where we are updating detached objects, although the update was being applied correctly there were no audit records appearing in the database. On investigation we realised that the previousState parameter passed into the onFlushDirty method of our interceptor was null.
In the Save method of our data access class we were using the SaveOrUpdate method of the NHibernate session to persist the changes. Section 10.7 of the documentation states:
saveOrUpdate() does the following:
- if the object is already persistent in this session, do nothing
- if another object associated with the session has the same identifier, throw
- if the object has no identifier property, save() it
- if the object’s identifier has the value assigned to a newly instantiated
object, save() it
- if the object is versioned (by a or ), and
the version property value is the same value assigned to a newly instantiated
object, save() it otherwise update() the object
and merge() is very different:
- if there is a persistent instance with the same identifier currently
associated with the session, copy the state of the given object onto the
- if there is no persistent instance currently associated with the session,
try to load it from the database, or create a new persistent instance
- the persistent instance is returned
- the given instance does not become associated with the session, it remains
As our detached objects had an Id and version when we called SaveOrUpdate() they were being updated, and seeing as all update() does is reattach an object to the session, the session had no record of the changes that had been made to that object since it was loaded.
The solution was to use the merge() method (new in NHibernate 2.0). Merge() checks the first level cache to see if an object with the given identifier has previously been loaded. If so it loads that object out of the first level cache and updates it’s properties using the detached object. This means that the session is now able to track the changes made to the object so that when the flush occurs the previousState is no longer null.