Thursday, May 15, 2008

Implementing NHibernate Interceptors

One of the areas where NHibernate really shines is it's high extensibility features. The NHibernate API provides a massive amount of interfaces that can be implemented for your own custom extension pleasure.

For me this is a nice change as opposed to the many sealed API's put out by the Redmond collective (WCF being the exception, the Entity Framework confirming the rule).

Anyhow, with great power comes great responsibility. Today I encountered a rather difficult problem with NHibernate that could have been easily prevented by reading the documentation (note to self: use the CTRL-Q shortcut of Resharper more often).

We have implemented an interceptor for automatically configuring audit information as described by this great post from Ray Houston.

public class AuditInformationInterceptor : EmptyInterceptor { Boolean OnSave(Object entity, Object id, Object[] state, String[] propertyNames, IType[] types) { // Implementation } Boolean OnFlushDirty(Object entity, Object id, Object[] currentState, Object[] previousState, String[] propertyNames, IType[] types) { // Implementation } }

The mistake we made is that we manipulated the audit information of the entity directly in the OnSave and OnFlushDirty methods. This only became apparent when saving an aggregate root with his child entities. Everything worked fine until we wanted to persist one of the child entities that should have been a piece of cake but turned out to be the messenger of trouble. NHibernate refused to perform an INSERT statement, but instead threw an UPDATE statement at us.

After some debugging we found out that the audit information should have been manipulated through the state and currentState arguments of the OnSave and OnFlushDirty methods respectively.

The documentation of the OnSave method clearly states:

The interceptor may modify the state, which will be used for the SQL INSERT
and propagated to the persistent object

Lesson relearned.

I'm off, reading some docs. Until next time.

No comments: