nhibernate's IPreUpdateEventListener ... and getting it to work

Just a few days ago I was tasked with a rather interesting and challenging feature -- create a history tracking service that was more or less transparent and easy for other devs to use.  Started to dig into certain parts of nhibernate, I quickly found a few comments about "IPreUpdateEventListener" -- great, cool, that's what I need ... well, almost.

To save others the majority of the pain I ran into, I'll just dive right into it ...

First off, create a class for all your hard work to go into and implement IPreUpdateEventListener 

public class HistoryService : IPreUpdateEventListener

public bool OnPreUpdate(PreUpdateEvent beforeUpdateEvent) { return false; }

Before I move on, let's take a quick look.  PreUpdateEvent gives you a collection of stuff which I'll dive into deeper here in a minute.  The biggest thing you need to know is returning true or false is absolutely critical -- true indicates "abort the save" and false says "no problems, continue your save".  For the life of me I can't find the forum post that explained this, but it seems to be correct.  Ideally, if you have a state where you wanted a save to fail, this is where you COULD do that but I am completely recommend against it (how would someone know to look there?).  Anyway, moving on.  Let's take a look at what is in that PreUpdateEvent object...

At first glance, it looks like just a few things -- but this really gets into the guts of nhibernate and everything you need is here but you probably won't know it until you dig.  Let's go over what's in what.

Entity ( object ) : this is the thing you want to save to the database ... but it isn't.  So for instance if you have a customer object, casting it is completely possible here.  This is also a great place to cast to interfaces that you want to do your dirty pre-update work.  I'll cover that in a bit.

Id ( object ) : exactly what you'd think it is -- the Id of the entity.  I'm sure you can think of a million things you can do with this, but for what I did, I didn't need it.

OldState ( object[] ) : property value bag of what the entity use to be.  Do not confuse this with something you can cast to ... well anything.  It literally is an array of objects.

Persister (IEntityPersister) : I won't go into this because there's a TON of methods on this.  The biggest thing I was interested in was the .FindModified() (returns int[]) and for some reason I can't explain, this would throw an exception on a nested object property.  Oh well.

Session (IEventSource) : Lot's of the database actions are done in here and overall managements of connections, etc.  One stop shop.  Most important action is hidden within this which I'll get to later.

State ( object[] ) : another object property value bag of what the items are -right now-.  Don't confuse this with the entity itself.  You change the entity, it won't be in here.  Wait what?

Before I get any further, let me stop, back up a bit and explain the Persister.FindModified(...) and how you may find it very useful.  The two property value bags State and OldState are completely worthless without an index.  FindModified(...) returns an array of those indexes -- why is that useful?  One, you can compare what it was vs what it is (State vs OldState).  Second, you can find what property State is tied into by running a little reflection and you can find out what property it is.  Unfortunately, I had to use a more manual approach. Moving on...

So, you have your code ready to go and you call ... what?  Off that Session object is Session.SaveOrUpdateCopy(...) and this worked for me.  If you read the description, it does a check and copies over the entity if you changed it during this event.  I've found you MUST call this or otherwise I lost all my changes every time which makes sense -- how would nhibernate know of changes to an entity when it has the proprety value bag and is in mid-flight of a save?  So pass this your updated entity, it'll figure out the rest.

The final step, you can find a ton of examples, most of which are found easily. I went for IHaveHistory interface with an inherited base object.  For example, a CustomerHistory object...

public class CustomerHistory : BaseHistory
{
    
public CustomerHistory(User user, string propertyChangeMessage) : base
(user, propertyChangeMessage) { }
}
 

public class BaseHistory
{
     public BaseHistory(User
user, string propertyChangeMessage) 
    
{
          LastChanged =
DateTime
.UtcNow;
          LastUpdatedBy = user;
          PropertyChangeMessage = propertyChangeMessage;
     }

     public DateTime LastChanged { get; private set; }
    
public User LastUpdatedBy { get; private set
; }
     public String PropertyChangeMessage { get; private set
; }
}

From here, IHaveHistory has a list of CustomerHistory and my PreUpdateEvent adds in another instance.  But when does this happen?  I used an attribute, [TrackHistory] with a string value.  This way it's a bit easier for me to determine what I want, construct a message and limit what is checked.

The last step is telling nhibernate to fire this event ...

.ExposeConfiguration(config => {
                                 
config.EventListeners.PreUpdateEventListeners = 
                                      
new IPreUpdateEventListener[] {new HistoryService()};
                               }

 That's it ... but wait, what about unit testing this?  It's very possible, even simple.  Since this is getting long, I'll finish that in the next post.

Comments are closed