dinsdag 20 mei 2008

NHibernate's Optimistic Locking: Where did we go wrong?

I was trying to get optimistic locking to work properly by applying the following articles (this and this) from Ray Houston.  I got it up and running in a couple of minutes and my audit information was automatically saved whenever I inserted or updated an object.  If you think you can let NHibernate handle the version in this case you are wrong.  The version (or timestamp) attribute needs to map to a single database column if you want to use it for optimistic locking.

So in order to have a working locking strategy I removed the LastModified Timestamp from my CompositeUserType and added a specific timestamp property in my mapping file.  

Piece of cake, .... however  ..... what happens when you start working disconnected?  You have two possible solutions.  Keeping your Session open (which will kill your application's scalability) or you can re-attach the objects into a new session (prefer this solution if you can). 

Now consider my business use case where I am loading an entire object graph in my server session, these objects are transformed to DTO's and send to the client.  The client then makes some changes and sends me only the updated values.  In other words I cannot attach my objects into a new session because I simply do not have all the information at my disposal.  There is simple solution for this problem, do it yourself (check 10.4.4 application version checking of the Nhibernate online documentation). 

I decided to load the entire object graph again and set the LastModified whenever I apply the changes from the client.  My version check will happen in the interceptor in the OnFlushDirty method, there I will check the original value of the date with the version I got back from the client (and applied in the object).

Int32 lastModificationPropertyIndex = Array.IndexOf(propertyNames, lastModificationProperty); if (-1 != lastModificationPropertyIndex) { DateTime originalDateTime = (DateTime)(previousState[lastModificationPropertyIndex]); DateTime currentDateTime = domainObject.LastModification; if (originalDateTime.Ticks != currentDateTime.Ticks) { if(originalDateTime != DateTime.MinValue) { throw new StaleObjectException(id); } } }

The OnFlushDirty method is called only for dirty objects, the OnSave is called for new objects (although I added the check that the originalDateTime must be different than the MinValue if it should happen that this method gets called for new objects).  75% of the time this seems to work but sometimes the StaleObjectException is thrown when and object is not out-of-date.  The LastModified date is updated whenever the data is flushed to the database.  This flushing can be set on the session using the enumeration type FlushMode. Default is set to Automatic meaning that it can happen that the session is flushed before a query.  The session now gets flushed before the interceptor gets called (strange, but this seems to be the reason).  Setting the FlushMode to Commit or Never seems to resolve the above issue because then the flush has to be called manually or by committing a transaction.

Till next time

Geen opmerkingen: