NHibernate’s listeners architecture bring with it a lot of power to the game, but understanding how to use it some of the listeners properly may require some additional knowledge. In this post, I want to talk specifically about the pre update hooks that NHibernate provides.
Those allow us to execute our custom logic before the update / insert is sent to the database. On the face of it, it seems like a trivial task, but there are some subtleties that we need to consider when we use them.
Those hooks run awfully late in the processing pipeline, that is part of what make them so useful, but because they run so late, when we use them, we have to be aware to what we are doing with them and how it impacts the rest of the application.
Those two interface define only one method each:
bool OnPreUpdate(PreUpdateEvent @event) and bool OnPreInsert(PreInsertEvent @event), respectively.
Each of those accept an event parameter, which looks like this:
Notice that we have two representations of the entity in the event parameter. One is the entity instance, located in the Entity property, but the second is the dehydrated entity state, which is located in the State property.
In NHibernate, when we talk about the state of an entity we usually mean the values that we loaded or saved from the database, not the entity instance itself. Indeed, the State property is an array that contains the parameters that we will push into the ADO.Net Command that will be executed as soon as the event listener finish running.
Updating the state array is a little bit annoying, since we have to go through the persister to find appropriate index in the state array, but that is easy enough.
Here comes the subtlety, however. We cannot just update the entity state. The reason for that is quite simple, the entity state was extracted from the entity and place in the entity state, any change that we make to the entity state would not be reflected in the entity itself. That may cause the database row and the entity instance to go out of sync, and make cause a whole bunch of really nasty problems that you wouldn’t know where to begin debugging.
You have to update both the entity and the entity state in these two event listeners (this is not necessarily the case in other listeners, by the way). Here is a simple example of using these event listeners:
public class AuditEventListener : IPreUpdateEventListener, IPreInsertEventListener { public bool OnPreUpdate(PreUpdateEvent @event) { var audit = @event.Entity as IHaveAuditInformation; if (audit == null) return false; var time = DateTime.Now; var name = WindowsIdentity.GetCurrent().Name; Set(@event.Persister, @event.State, "UpdatedAt", time); Set(@event.Persister, @event.State, "UpdatedBy", name); audit.UpdatedAt = time; audit.UpdatedBy = name; return false; } public bool OnPreInsert(PreInsertEvent @event) { var audit = @event.Entity as IHaveAuditInformation; if (audit == null) return false; var time = DateTime.Now; var name = WindowsIdentity.GetCurrent().Name; Set(@event.Persister, @event.State, "CreatedAt", time); Set(@event.Persister, @event.State, "UpdatedAt", time); Set(@event.Persister, @event.State, "CreatedBy", name); Set(@event.Persister, @event.State, "UpdatedBy", name); audit.CreatedAt = time; audit.CreatedBy = name; audit.UpdatedAt = time; audit.UpdatedBy = name; return false; } private void Set(IEntityPersister persister, object[] state, string propertyName, object value) { var index = Array.IndexOf(persister.PropertyNames, propertyName); if (index == -1) return; state[index] = value; } }
And the result is pretty neat, I must say.