views:

598

answers:

2

Hi, I need to log in to database every user update on any record and need to store old and new value of any record which was changed. This audit is implemented as interceptor in nhibernate so i have method:

 public override bool OnFlushDirty(
            object entity,
            object id,
            object[] currentState,
            object[] previousState,
            string[] propertyNames,
            IType[] types)

and i need to compare current state with previous state of entity and i don't know how to do this (it should be generic solution which i could apply to many types)

A: 

I'm not sure this is exactly what you are asking, b/c it seems like the answer is just loop through the currentState array and compare to the previousState array for the same index value. The property name being changed is the propertyName value at the same index

I would caution you though that if you modify your entities outside of a session (Nhibernate calls this a "detached" model) I dont think the previous state gets populated ( or it causes a costly re-read of the data to do it).

My recommendation would be to use triggers on your database tables for your audit logging. It is much easier IMHO to do it this way and even works when you don't update through NHib

Cheers!

HokieMike
+1  A: 

I asked a similar question awhile back. Audit Logging Strategies also look at this question for a different way how-do-i-implement-changetime-and-changeuser-columns-using-nhibernate

What I ended up implementing the IInterceptor as a class and a generic auditlog poco class that contained the entity type, the entity Id, property name, current state, previous state, the type of change (Insert, Update, Delete) and the datetime the change happened.

Then I created an empty interface IAuditable so that each class that i wanted audited could be identified.

In the OnFlushDirty, OnDelete and another event that I cannot think of right now, added new auditlog classes for each property that had changed.

I will update this question when I get home and have access to the code that I am currently using.

Also look Frederik Gheysels' DevLog - NHibernate IInterceptor: an AuditInterceptor

Edit - Updating answer with implementation
Looking at it again I need to refactor it as it does smell alot, but at the time I just needed something to work, and this is what I came up with.

This is the nhibernate mapping that I have used

    <class name="AuditLog" table="AuditLog" lazy="true" >
        <id name="_persistenceId" column="Id" type="Guid" access="field" unsaved-value="00000000-0000-0000-0000-000000000000" >
          <generator class="guid.comb" />
        </id>
        <version name="_persistenceVersion" column="RowVersion" access="field" type="int" unsaved-value="0"/>
        <property name="CreatedDate" column="CreatedDate" type="DateTime" />
        <property name="UpdatedBy" column="UpdatedBy" type="string" length="100" />
        <property name="EntityID" column="EntityID" type="guid" not-null="true" />
        <property name="EntityName" column="EntityName" type="String" length="100" not-null="true" />
        <property name="PropertyName" column="PropertyName" type="String" length="100" not-null="true"  />
        <property name="ActionType" column="ActionType" type="Char" length="1" not-null="true" />
        <property name="OldValue" column="OldValue" type="String" length="1000"   not-null="false" />
        <property name="NewValue" column="NewValue" type="String" length="1000"   not-null="false" />
    </class>

The class is a general poco class that implements each of these properties.

The Implemetation of the IInterceptor look like this (In VB.Net)

Imports Rhino.Commons
Public Class AuditInterceptor
Inherits NHibernate.EmptyInterceptor
Private _auditLogRepository As IAuditLogRepository
Public Sub New(ByVal [AuditLogRepository] As IAuditLogRepository)
    _auditLogRepository = [AuditLogRepository]
End Sub
Public Overrides Function OnFlushDirty(ByVal entity As Object, ByVal id As Object, ByVal currentState() As Object, ByVal previousState() As Object, ByVal propertyNames() As String, ByVal types() As NHibernate.Type.IType) As Boolean
'Called on an Update

    If TypeOf entity Is IAuditable Then
        Using uow = UnitOfWork.Start(UnitOfWorkNestingOptions.CreateNewOrNestUnitOfWork)
            If TypeOf entity Is [yourObject] Then
                aLog = New AuditLog(Now, My.User.Name)
                With aLog
                    .EntityID = id
                    .EntityName = "[yourObject]"
                    .PropertyName = "[yourProperty]"
                    .ActionType = "U"
                    .OldValue = GetPropertyValue("[yourProperty]", previousState, propertyNames)
                    .NewValue = GetPropertyValue("[yourProperty]", currentState, propertyNames)
                End With
                _auditLogRepository.Save(aLog)    
            End if
            uow.Flush()
        End Using
    End If

    Return MyBase.OnFlushDirty(entity, id, state, propertyNames, types)
End Function
Public Overrides Function OnSave(ByVal entity As Object, ByVal id As Object, ByVal state() As Object, ByVal propertyNames() As String, ByVal types() As NHibernate.Type.IType) As Boolean
'Called on an Insert

    If TypeOf entity Is IAuditable Then
        'create a new audit log class here
    end if
    Return MyBase.OnSave(entity, id, state, propertyNames, types)
End Function
Nathan Fisher
Is saving AuditLog in the same transaction as main entity?
dario-g
To me it should be part of the same transaction, which is why I used UnitOfWork.Start(UnitOfWorkNestingOptions.CreateNewOrNestUnitOfWork) so that it would join the current UOW. I know I had problems in this area when I was trying to get it to work. Using the CreateNewOrNestUnitOfWork option as part of the UOW made the difference.
Nathan Fisher