One of the biggest hurdles I faced when learning NHibernate centered around the requirement that I had to audit any change in state of the domain model. Typically the audit occurs when some entity is created, updated or deleted inside the application. The requirement for my previous project was to insert a record of the previous object state with a timestamp and user id of the person making the change.
At first glance I was looking for NHibernate to facilitate this audit process so I wouldn't have to write a separate mapping file and object just to persist the data. But after almost 3 weeks of thrashing I learned that the best way to approach this problem is to do the insert manually (outside the scope of NHibernate). But how could I tell NHibernate that I wanted to insert this audit data?
This was provided through something called an event listener. Before NHibernate 2.x this was known as an interceptor, but the idea is the same. You need a way to tap into the events that are happening around the NHibernate session. For the purpose of audit I only needed to leverage the 'post-commit-update/insert/delete' event listener.
So to get started you first need to create a class that NHibernate will call after each update/insert/delete. This class must implement each interface for the specific event listener required.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class AuditEventListener : IPostDeleteEventListener, IPostInsertEventListener, IPostUpdateEventListener
{
public void OnPostDelete(NHibernate.Event.PostDeleteEvent @event)
{
var entityToAudit = @event.Entity as IEntityToAudit;
if (entityToAudit != null)
{
entityToAudit.Auditor.AuditDelete();
}
}
public void OnPostInsert(NHibernate.Event.PostInsertEvent @event)
{
var entityToAudit = @event.Entity as IEntityToAudit;
if (entityToAudit != null)
{
entityToAudit.Auditor.AuditInsert();
}
}
public void OnPostUpdate(NHibernate.Event.PostUpdateEvent @event)
{
var entityToAudit = @event.Entity as IEntityToAudit;
if (entityToAudit != null)
{
entityToAudit.Auditor.AuditUpdate();
}
}
}
Each method in the above will first verify that the entity is one that should be audited, and if so call a method specific to the type of audit being done. Notice we have a few dependencies here though. So to get started, create a new interface that will tell NHibernate that the entity is audit friendly. The nice thing about this approach is that it's explicit for the reason that you want some control over what gets audited in your application.
1
2
3
4
5
6
7
8
public interface IEntityToAudit
{
IAuditor Auditor
{
get;
set;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class User : BusinessBase<User>, IEntityToAudit
{
private IAuditor mUserAudit;
public User()
{
mUserAudit = new UserAudit(this);
}
public User(IAuditor UserAudit)
{
mUserAudit = UserAudit;
}
public virtual IAuditor Auditor {
get { return mUserAudit; }
set { mUserAudit = value; }
}
}
1
2
3
4
5
6
public interface IAuditor
{
void AuditInsert();
void AuditUpdate();
void AuditDelete();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class UserAudit : IAuditor
{
private User mUser;
public UserAudit(User User)
{
mUser = User;
}
public void AuditDelete()
{
InsertUserAudit('Delete');
}
public void AuditInsert()
{
InsertUserAudit('Insert');
}
public void AuditUpdate()
{
InsertUserAudit('Update');
}
private void InsertUserAudit(string AuditType)
{
//do the manual insert here, in this example I call a stored procedure passing all the state data required
DatabaseFactory.CreateDatabase().ExecuteNonQuery('usp_Banking_InsertUserAudit', mUser.ID, mUser.FirstName, mUser.LastName, mUser.Address, mUser.City, mUser.State, mUser.Zip, mUser.Phone)
}
}
Now that you have the code needed to do the audit, the next step is to add the configuration to let NHibernate know you have a class that should be called by the event listeners.
1
2
3
4
5
6
7
8
<hibernate-configuration>
<session-factory>
...
<listener class='Persistence.Audit.AuditEventListener, MyFramework' type='post-commit-update'/>
<listener class='Persistence.Audit.AuditEventListener, MyFramework' type='post-commit-insert'/>
<listener class='Persistence.Audit.AuditEventListener, MyFramework' type='post-commit-delete'/>
</session-factory>
</hibernate-configuration>
I couldn't finish this post without first saying a big thank you to both Steve Bohlen and Rob Tennyson. Steve helped push me in the right direction because I spent a lot of time early on trying to tweak my NHibernate mapping files and this was obviously not the best approach. Then I came across a question on stackoverflow that gave me a bit of hope. I asked the author for help, in which he replied with the best blog post to date about this topic. Although he used NHibernate Interceptors in his post, the port to event listeners was a small task.
As always, the source code for this sample is available for download. I also included a separate download for the base class library that contains a few of the reusable classes shown in this post.