NHibernate.Envers - a quick introduction

Print Content | More

Recently I had the need to introduce a sort of ‘Entity Versioning’ in a portion of my applications, the whole data access strategy was realized using NHibernate + XML / ConfORM mappings.

I started the thing introducing an poor auditing system using a listener and the pre-insert and pre-update events. This will allow me to track who created a record and the last one that modified it (and when) in an easy way (you can find some examples on this technique in a lot of blog posts around, just look for NHibernate + auditing in any web search engine).

The afore mentioned strategy is enough for the great part of my domain, but then I had to move a step forward and not only log who make the action, but also I needed to store the state of the entity at the time it was modified (being it updated or deleted), in short I need the whole history of the changes on that entity. My first thought was to extend my original approach and save the entity in another version of the previous listener...huge work to do!

Then I reminded Fabio Maulo talking about the release of NHibernate.Envers at one of the last UgiAlt workshops I attended to...Gotcha! Let’s take a look at it and see if it fits my needs.

Download the whole source code at https://bitbucket.org/RogerKratz/nhibernate.envers, you need to if you use the latest bits of NHibernate 3.2 because you will need to update the referenced libraries and recompile the project.

Let’s start considering this very simple domain:

public class Person : Entity<int>
{
	protected Person()
	{
	}

	public Person(string name, string surname)
	{
		Games = new List<Game>();
		Name = name;
		Surname = surname;
	}

	public string Name { get; set; }

	public string Surname { get; set; }

	public string Note { get; set; }
	
	public IList<Game> Games { get; set; }

	public override string ToString()
	{
		StringBuilder sb = new StringBuilder();
		sb.AppendFormat("Id: {2}, Name: {0}, Surname: {1}, Note: {3}\n", Name, Surname, Id, Note);
		sb.AppendLine("Games:");
		foreach (var g in Games)
			sb.AppendLine(g.ToString());
		sb.AppendLine("---");
		return sb.ToString();
	}
}

public class Game : Entity<int>
{
	public string Name { get; set; }

	public string Type { get; set; }

	public int Rating { get; set; }

	public string Note { get; set; }

	public override string ToString()
	{
		return string.Format("Id: {3}, Name: {0}, Type: {1}, Rating: {2}, Note: {4}", Name, Type, Rating, Id, Note);
	}
}

here we have a person and the games he plays; both the classes are mapped as individual entities using ConfORM.

To keep the post short I’ll skip the mapping initialization, so we can move to the interesting bits: how to setup NHibernate.Envers. You have several ways to set it up, the easiest (and more flexible) one is configuring it by code:

  • Declare FluentConfiguration object (which is nothing more than an IMetaDataProvider).
  • Call its Audit() or Audit<>() methods to decide which entities you want to track.
  • Attach it to the NHibernate Configuration object calling its IntegrateWithEnvers extension method.

Here’s how it looks like:

var enversConf = new NHibernate.Envers.Configuration.Fluent.FluentConfiguration();

// audit the whole domain
// enversConf.Audit(GetDomainEntities());

enversConf.Audit<Person>();
enversConf.Audit<Game>();

Configure.IntegrateWithEnvers(enversConf);

If we ask NHibernate to generate the schema for us this is a picture of what we get:

NHibernateEnversSampleSchema

Figure 1 - domain model + auditing tables database schema

As you can see the one-to-many relation between Person and Games has been correctly transformed in a many-to-many when it comes to track objects history.

By default we’ll have a main table named REVINFO that will assign a unique revision number to every version of the object we track, we’ll have an history table for every entity and for every relation between objects (these tables use the suffix _AUD). all those tables maintain a relation with the main REVINFO that provide the timestamp of the operation; moreover another column is added to every history table (named REVTYPE) that states the type of operation performed on the object (update, delete, etc...).

NHibernate.Envers allows you to change the default naming of tables and suffixes with a series of new properties you can specify in the NHibernate configuration section, here are some:

  • nhibernate.envers.audit_table_prefix - defaults to String.Empty
  • nhibernate.envers.audit_table_suffix - defaults to “_AUD”
  • nhibernate.envers.revision_field_name - defaults to “REV”
  • nhibernate.envers.revision_type_field_name - default to “REVTYPE”

Other interesting options are:

  • nhibernate.envers.default_catalog - defaults to String.Empty, allows you to set a specific catalog for the auditing tables.
  • nhibernate.envers.default_schema - defaults to String.Empty, allows you to set a specific schema for the auditing tables.
  • nhibernate.envers.store_data_at_delete - defaults to false, if set to true all the data of the removed entity will be saved when you remove it (instead of having just a marker with all null values).

Enough for today, the next time we’ll see some methods to retrieve the history of the changes related to the tracked entities.

You can grab the actual sample code here:

I’m always amazed to see that every time I have a problem related to the data access strategy and what’s around it, NHibernate always seems to something that, if not fully solve your problems, can point you in right direction to customize it and have your job done.



Nhibernate, Nhibernate.Envers

3 comments

Related Post

  1. #1 da Phillip Haydon - Tuesday August 2011 alle 03:50

    Hey, I just came across this trying to figure out what 'NHibernate.Envers' was, you say:

    "My first thought was to extend my original approach and save the entity in another version of the previous listener...huge work to do!"

    It's like 3 lines of code to get the changes made to an entity? Am I missing something? Auditing changes via event listeners is really simple...

  2. #2 da alessandro giorgetti - Thursday August 2011 alle 06:29

    For a simple and 'plain' entity yes, but what about a complex tree of objects that have your entity as root ? If you wanna do that for every entity in your tree it can become quite complex and that was my scenario. Envers actually handle the whole thing for me and offer enough flexibility (in my case) to avoid using a custom made solution.

  3. #3 da Phillip Haydon - Friday August 2011 alle 04:25

    Ahh, you're right, I just ran into this issue, so I'll give the Envers another look :)

    Cheers.

All fields are required and you must provide valid data in order to be able to comment on this post.


(will not be published)
(es: http://www.mysite.com)


  1. #1 da http://www.alvinashcraft.com/2011/06/07/dew-drop-june-7-2011/

    Dew Drop &ndash; June 7, 2011 | Alvin Ashcraft&#039;s Morning Dew

  2. #2 da http://blog.cwa.me.uk/2011/06/08/the-morning-brew-868/

    The Morning Brew - Chris Alcock &raquo; The Morning Brew #868

  3. #3 da http://www.alvinashcraft.com/2011/06/08/dew-drop-june-8-2011/

    Dew Drop &ndash; June 8, 2011 | Alvin Ashcraft&#039;s Morning Dew

  4. #4 da http://www.primordialcode.com/blog/post/nhibernate-envers-querying-part-1

    www.primordialcode.com - NHibernate.Envers - Querying - part 1

  5. #5 da http://www.syngu.com/development/2859402865/nhibernate.envers-a-quick-introduction/

    NHibernate.Envers - a quick introduction | SQL, .NET and NHibernate | Syngu