tags:

views:

132

answers:

2

Ayende has an article about how to implement a simple audit trail for NHibernate (here) using event handlers.

Unfortunately, as can be seen in the comments, his implementation causes the following exception to be thrown: collection xxx was not processed by flush()

The problem appears to be the implicit call to ToString on the dirty properties, which can cause trouble if the dirty property is also a mapped entity.

I have tried my hardest to build a working implementation but with no luck.

Does anyone know of a working solution?

+1  A: 

This is not easy at all. I wrote something like this, but it is very specific to our needs and not trivial.

Some additional hints:

You can test if references are loaded using

NHibernateUtil.IsInitialized(entity)

or

NHibernateUtil.IsPropertyInitialized(entity, propertyName)

You can cast collections to the IPersistentCollection. I implemented an IInterceptor where I get the NHibernate Type of each property, I don't know where you can get this when using events:

if (nhtype.IsCollectionType)
{
    var collection = previousValue as NHibernate.Collection.IPersistentCollection;
    if (collection != null)
    {
        // just skip uninitialized collections
        if (!collection.WasInitialized)
        {
            // skip
        }
        else
        {
            // read collections previous values
            previousValue = collection.StoredSnapshot;
        }
    }
}

When you get the update event from NHibernate, the instance is initialized. You can safely access properties of primitive types. When you want to use ToString, make sure that your ToString implementation doesn't access any referenced entities nor any collections.

You may use NHibernate meta-data to find out if a type is mapped as an entity or not. This could be useful to navigate in your object model. When you reference another entity, you will get additional update events on this when it changed.

Stefan Steinegger
A: 

I was able to solve the same problem using following workaround: set the processed flag to true on all collections in the current persistence context within the listener

public void OnPostUpdate(PostUpdateEvent postEvent)
{
    if (IsAuditable(postEvent.Entity))
    {
       //skip application specific code

        foreach (var collection in postEvent.Session.PersistenceContext.CollectionEntries.Values)
        {
            var collectionEntry = collection as CollectionEntry;
            collectionEntry.IsProcessed = true;
        }

        //var session = postEvent.Session.GetSession(EntityMode.Poco);
        //session.Save(auditTrailEntry);
        //session.Flush();
    }
}

Hope this helps.

Yura Ageev