Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NHibernate - how to audit all fields of entity?

I'm using NHibernate and looking for a solution that will allow me to audit changes to all fields in entity. I want to be able to create a history table for every entity i.e. Users -> UsersHistory that will have same structure as Users table and additional fields such as operation type (update, delete), userid of user that made change, etc. I don't want to define such class for every entity. I'm looking for something like History<T> (i.e. History<User>) because these entries don't belong to my domain and will only be used to prepare list of changes made to the entity. I also think that it would be better to create inserts to these tables in code rather than creating sql triggers. Basically, I just need to create a copy of record in history table on update or delete and I want the insert to be generated by NHibernate. I will also need to read records from history tables - as I said these tables will consist of entity fields and some common history fields.

I cannot find guidance on how to create such solution. All I can find is adding UserModified, UpdatedTimestamp etc. if I already have such fields on entity. However, I need full history of entity not just the information who last changed the entry.

Thanks in advance for help.

like image 551
empi Avatar asked Aug 16 '11 20:08

empi


2 Answers

There is cool, open source audit trail for NHibernate called nhibernate.envers https://bitbucket.org/RogerKratz/nhibernate.envers , so you do not have to reinvent the wheel.

It integrates transparently into NHibernate, no changes to your domain model or mappings.

It's as simple as, adding the reference and call:

var enversConf = new FluentConfiguration(); 
enversConf.Audit<User>();
nhConf.IntegrateWithEnvers(enversConf);

whereas nhConf is your NHibernate config object.

For every change on your object a new revision is created, you can ask Envers to retrieve a revision by calling:

var reader = AuditReaderFactory.Get(session);
var userInRevOne = reader.Find<User>(user.Id, 1);

or list all revisions etc. The revision data itself can be enriched with a username, userid, timestamp etc. (whatever you can think off).

EDIT: And it is available at NuGet: http://nuget.org/packages/NHibernate.Envers

like image 118
Andreas Avatar answered Oct 18 '22 20:10

Andreas


I think the best solution is using Event Listeners:

http://darrell.mozingo.net/2009/08/31/auditing-with-nhibernate-listeners/

I wrote something similar to above (modified after finding that blog) except I store the result in XML.

e.g:

public void OnPostUpdate(PostUpdateEvent updateEvent)
{
    if (updateEvent.Entity is AuditItem)
        return;

    var dirtyFieldIndexes = updateEvent.Persister.FindDirty(updateEvent.State, updateEvent.OldState, updateEvent.Entity, updateEvent.Session);

    var data = new XElement("AuditedData");

    foreach (var dirtyFieldIndex in dirtyFieldIndexes)
    {
        var oldValue = GetStringValueFromStateArray(updateEvent.OldState, dirtyFieldIndex);
        var newValue = GetStringValueFromStateArray(updateEvent.State, dirtyFieldIndex);

        if (oldValue == newValue)
        {
            continue;
        }

        data.Add(new XElement("Item",
                              new XAttribute("Property", updateEvent.Persister.PropertyNames[dirtyFieldIndex]),
                              new XElement("OldValue", oldValue),
                              new XElement("NewValue", newValue)
                             ));
    }

    AuditService.Record(data, updateEvent.Entity, AuditType.Update);
}

Audit Service just builds add some additional data such as IP Address, User (if any), was it a system/service update or actioned via a website or user, etc.

Then in my DB i Store the XML like:

<AuditedData>
  <Item Property="Awesomeness">
    <OldValue>above average</OldValue>
    <NewValue>godly</NewValue>
  </Item>
  <Item Property="Name">
    <OldValue>Phill</OldValue>
    <NewValue>Phillip</NewValue>
  </Item>
</AuditedData>

I also have Insert/Delete listeners.

like image 32
Phill Avatar answered Oct 18 '22 20:10

Phill