NHibernate Forge
The official new home for the NHibernate for .NET community

NHibernate Auditing v3 – Poor Man’s Envers

First, let me explain the title of this post. The Hibernate folks – you know, that NHibernate knock off written in the Java (pronounced “ex em el”) programming language – have a project called Envers. Among other things, It audits changes to entities, then allows you to easily retrieve the entity as it was at any previous point in time.

Well, Simon Duduica is porting this over to .NET and NHibernate, and he’s making some AMAZING progress. On June 28th, he shared this news with us on the NH Contrib development group:

Hi everybody,

I have news regarding Envers.NET. I've commited a version that works in basic tests for CUD operations, with entities that have relationships between them, also with entities that are not audited. To make things work I had to make two small modifications of NHibernate, both modifications were tested running all NHibernate unit tests and they all passed. I already sent the first modification to Fabio and the second I will send this evening. I would like to thank Tuna for helping me out with good advices when I was stuck :)

 

So, on to the topic of this post. For NHibernate 3.0 Cookbook, I’ve included a section that explains how to use NHibernate to generate audit triggers. Originally, I had planned to use the code from my previous blog post on the topic, but I didn’t like its structure. I also didn’t want to include all that plumbing code in the printed book. Instead, I’ve rewritten and contributed the “framework” code to uNHAddIns. The “how-to use it” is explained in the book, so I won’t explain it here.

Today, I was writing an integration test for this contribution, and thought the idea was worth sharing. I have a simple Cat class:

ClassDiagram1

When I do anything to this cat, in addition to the normal INSERT, UPDATE, or DELETE, a database trigger records that action in a table called CatAudit:

image

I wanted an easy way to investigate the contents of this table to prove that my audit triggers worked. Here’s what I came up with, along with help from Jose Romaniello (@jfroma). First, I created a class to match this table:

ClassDiagram1

Next, I mapped it, made it readonly and excluded it from hbm2ddl with this mapping:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
				   assembly="uNhAddIns.Test"
				   namespace="uNhAddIns.Test.Audit.TriggerGenerator">
  <typedef class="NHibernate.Type.EnumStringType`1[[uNhAddIns.Audit.TriggerGenerator.TriggerActions, uNhAddIns]], NHibernate"
           name="triggerActions" />
  <class name="CatAudit" 
         mutable="false"
         schema-action="none">
    <composite-id>
      <key-property name="Id" />
      <key-property name="AuditUser" />
      <key-property name="AuditTimestamp" />
    </composite-id>
    <property name="Color"/>
    <property name="AuditOperation" type="triggerActions" />
  </class>
	
</hibernate-mapping>

I made it readonly by setting mutable="false" and excluded it from hbm2ddl with schema-action="none". That’s it!

By the way, the <typedef> along with type="triggerActions" just tells NHibernate I've stored my TriggerActions enum values as strings, not numbers.


Posted jul 05 2010, 11:25 p.m. by Jason Dentler

Comments

Natural wrote re: NHibernate Auditing v3 – Poor Man’s Envers
on 07-06-2010 3:32

Wheres NH3.0

Jason Dentler wrote re: NHibernate Auditing v3 – Poor Man’s Envers
on 07-06-2010 10:10

NHibernate 3.0 is the current trunk. It'll be officially released later this year.

David W Martines wrote re: NHibernate Auditing v3 – Poor Man’s Envers
on 07-18-2010 16:21

Thanks for posting about this - it looks really cool and I am looking forward to trying it out.  I do have a question, though.  Could you explain why you would exclude the generation of the CatAudit table from the schema export?  I would think you would want the create table script executed along with the real Cat table.  At least the way I work, I use hbm2ddl on a "blank" database, and from there use change scripts as I evolve the schema.  Or if I was testing the data access code (maybe using SqlLite) I would want the new database created and ALL the tables generated for the test run.  But I realize there are other ways of working, and so I'm just curious about how you do it.

Jason Dentler wrote re: NHibernate Auditing v3 – Poor Man’s Envers
on 07-18-2010 16:30

Hi David. That's a great question. The answer is simple. The uNhAddIns framework already creates the triggers and the CatAudit table as auxillary database objects. This is necessary since we may want to audit Cats without necessarily doing anything with the audit data.

We exclude our CatAudit class mapping from schema export because we don't want to export the CatAudit table twice.

David W Martines wrote re: NHibernate Auditing v3 – Poor Man’s Envers
on 07-18-2010 16:52

Ah, I see.  I'll have to look into the uNhAddIns framework.  So are these actual "database triggers" on the tables or are they interceptors attached to the session?   I would think that by using triggers as database objects it limits the databases you can use (or do all db engines offer triggers?), whereas with interceptors you get full database portability but without ability to audit changes to data from outside the application.

Jason Dentler wrote re: NHibernate Auditing v3 – Poor Man’s Envers
on 07-18-2010 16:59

These are just simple database triggers. At the moment, I've written "extended dialects" to support triggers in Ms SQL Server and SQLite, since that's what I use. I imagine most databases support some type of trigger, though the syntax is different for each one.

I believe uNhAddIns also has a simple interceptor / event listener for auditing purposes. There's a ton of good stuff in there. Go explore!

Powered by Community Server (Commercial Edition), by Telligent Systems