Implementing simple change tracking using NHibernate

Some business systems use simple change tracking. User name and timestamp are saved when object is created and modified. In this posting I will show how to do it easily with NHibernate.

The trick is to use interceptor class and plug it to NHibernate when NHibernate session is configured.

Trackable entities

As a first thing we must guarantee that our entities are trackable and we need generalization for this. If code uses base class for entities and all entities are tracked then we can use base class like this:

public abstract class BaseEntity
{
   
public virtual int Id { get; set
; }

   
public virtual DateTime Modified { get; set
; }
   
public virtual string ModifiedBy { get; set
; }
   
public virtual DateTime Created { get; set
; }
   
public virtual string CreatedBy { get; set; }
}

If only some mapped objects in code are trackable then we have to use another base class between base entity and trackable entity. Also we can go with interface and define ITrackable interface. Base class seems better to me as we don’t have to define tracking fields again in classes.

This generalization gives us one more advantage in big picture: when adding new trackable entity to system we have to inherit it from trackable base class. After this tracking works automagically.

Tracking interceptor

NHibernate allows us to define interceptors. Interceptors are plug-ins that provide external functionality for enitites that are about to change some way.

NHibernate provides EmptyInterceptor as base class for other interceptors. It defines dummy methods to override and this way we don’t have to use IInterceptor interface which means implementing all possible tracking methods.

Here is my (simple?) tracking interceptor:

public class TrackingInterceptor : EmptyInterceptor
{
   
public override bool OnSave(object entity, object id, object[] state,
string[] propertyNames,
global::NHibernate.Type.IType
[] types)
    {
       
var
time = DateTime.Now;
       
var userName = // Find user name here

        var typedEntity = (
BaseEntity
)entity;
        typedEntity.Created = time;
        typedEntity.CreatedBy = userName;
        typedEntity.Modified = time;
        typedEntity.ModifiedBy = userName;

       
var indexOfCreated = GetIndex(propertyNames, "Created"
);
       
var indexOfCreatedBy = GetIndex(propertyNames, "CreatedBy"
);
       
var indexOfModified = GetIndex(propertyNames, "Modified"
);
       
var indexOfModifiedBy = GetIndex(propertyNames, "ModifiedBy"
);

        state[indexOfCreated] = time;
        state[indexOfCreatedBy] = userName;
        state[indexOfModified] = time;
        state[indexOfModifiedBy] = userName;

       
return base
.OnSave(entity, id, state, propertyNames, types);
    }

   
public override bool OnFlushDirty(object entity, object id, object[] currentState,
object[] previousState, string[] propertyNames,
global::NHibernate.Type.IType
[] types)
    {
       
var userName = // Find user name here

        var time = DateTime.Now;

       
var indexOfCreated = GetIndex(propertyNames, "Created"
);
       
var indexOfCreatedBy = GetIndex(propertyNames, "CreatedBy"
);
       
var indexOfModified = GetIndex(propertyNames, "Modified"
);
       
var indexOfModifiedBy = GetIndex(propertyNames, "ModifiedBy"
);

       
var typedEntity = (BaseEntity
)entity;
       
if
(typedEntity.Created == DateTime.MinValue)
        {
            currentState[indexOfCreated] = time;
            currentState[indexOfCreatedBy] = userName;
            typedEntity.Created = time;
            typedEntity.CreatedBy = userName;
        }

        currentState[indexOfModified] = time;
        currentState[indexOfModifiedBy] = userName;
        typedEntity.Modified = time;
        typedEntity.ModifiedBy = userName;

       
return base
.OnFlushDirty(entity, id, currentState, previousState, propertyNames, types);
    }

   
private int GetIndex(object[] array, string
property)
    {
       
for (var
i = 0; i < array.Length; i++)
           
if
(array[i].ToString() == property)
               
return
i;

       
return -1;
    }
}

For new entities all tracking fields are valuated. For modified enitities only modification related fields are valuated. This is all we need to make entities trackable.

NB! If youhave also non-trackable objects then make sure control in the beginning of overriden methods if given entity is trackable or not.

Plugging interceptor to session

And here is how to plug it to NHibernate session:

private ISession GetNewSession()
{
   
if (_currentSession == null
)
    {
        _currentSession = _sessionFactory.OpenSession(
new TrackingInterceptor
());
    }
   
return _currentSession;
}

Conclusion

We used NHibernate interceptor to make enitities trackable. Interceptor was built by extending EmptyInterceptor and overriding methods we need for entity tracking. Also it was easy to plug our interceptor to NHibernate session. After getting tracking work we don’t have to return back there – all new trackable classes will be tracked automatically.

Gunnar Peipman

Gunnar Peipman is ASP.NET, Azure and SharePoint fan, Estonian Microsoft user group leader, blogger, conference speaker, teacher, and tech maniac. Since 2008 he is Microsoft MVP specialized on ASP.NET.

    3 thoughts on “Implementing simple change tracking using NHibernate

    • Pingback:The Morning Brew - Chris Alcock » The Morning Brew #1401

    • July 18, 2013 at 10:34 am
      Permalink

      I have used IPreUpdateEventListener and IPreInsertEventListener for the same purposes and registered them in NHibernate configuration section. Any specific reason why you use interceptors?

      Also, when defining interface like ITrackable, it’s more convenient to encapsulate these four properties in it’s own class (ie. TrackingInfo).

    • June 26, 2014 at 4:46 am
      Permalink

      If you’re using some kind of NHibernate facility (eg Transaction facility, as we are) you may not have access to the SessionFactory OpenSession() method to set the interceptor as in the example here… So we set the interceptor via NHibernate’s fluent configuration like this:


      return new NHibernate.Cfg.Configuration().DataBaseIntegration(db =>
      {
      db.ConnectionString = "aConnectionString";
      db.Dialect();
      db.IsolationLevel = IsolationLevel.ReadCommitted;
      })
      .SetInterceptor(new ObjectInterceptor())
      ...

    Leave a Reply

    Your email address will not be published. Required fields are marked *