Who is Ali Özgür?

RecentComments

Comment RSS
aliozgur posted on February 6, 2008 14:28

Audit logging is an important issue while building enterprise systems. Simplest form of audit logging is recording
who and when created/updated an object, or a record in the database respectively. We perform four basic operations
on a domain object. These are

Load
Update
Save
Delete


Data access layer is a good place to perform automated audit logging
whenever one of the operations above is executed. NHibernate provides us with
IInterceptor interface plus ILifecylce interface. You can read this article for more
information about Nhibernate entity lifecyle management.

In this article we will try to perform simple logging that meets the
following minimal requirements

1- We will log who performed insert/update and when this operation was performed
2- Log data will be written to the same database and table as our domain object

Class Model

We have three interfaces directly related to audit logging and IInterceptor implementation, one interface
(IVersionedEntity) which is in the model just for conceptual completiness and is used
to indicate that we want to utilize NHibernate managed versioning, one base DomainObject
from which all our domain objects inherit and finally three implementation classes that
implement IInsertLoggable, IModifyLoggable and IUpdateLoggable interfaces.

 


public abstract class InsertLoggableDomainObject : 

            DomainObject,IVersionedEntity, IInsertLoggable

{

  #region IVersionedEntity Members 


  private long _version = 0;

  public long Version

  {

    get { return _version; }

  } 


  #endregion 


  #region IInsertLogable Members 


  private DateTime? _sysCreatedOn = null;

  public DateTime? SysCreatedOn

  {

    get { return _sysCreatedOn; }

    set { _sysCreatedOn = value; }

  } 


  private int? _sysCreatedBy = null;

  public int? SysCreatedBy

  {

    get { return _sysCreatedBy; }

    set { _sysCreatedBy = value; }

  }

  #endregion

} 


public abstract class UpdateLoggableDomainObject : 

            DomainObject, IVersionedEntity, IUpdateLoggable

{

  #region IVersionedEntity Members 


  private long _version = 0;

  public long Version

  {

    get { return _version; }

  } 


  #endregion 


  #region IUpdateLogable Members 


  private DateTime? _sysLastUpdatedOn = null;

  public DateTime? SysLastUpdatedOn

  {

    get { return _sysLastUpdatedOn; }

    set { _sysLastUpdatedOn = value; }

  } 


  private int? _sysLastUpdatedBy = null;

  public int? SysLastUpdatedBy

  {

    get { return _sysLastUpdatedBy; }

    set { _sysLastUpdatedBy = value; }

  }

  #endregion

} 


public abstract class ModifyLoggableDomainObject : 

            DomainObject, IVersionedEntity, IModifyLogable

{

  #region IVersionedEntity Members 


  private long _version = 0;

  public long Version

  {

    get { return _version; }

  } 


  #endregion 


  #region IModifyLogable Members 


  private DateTime? _sysCreatedOn = null;

  public DateTime? SysCreatedOn

  {

    get { return _sysCreatedOn; }

    set { _sysCreatedOn = value; }

  } 


  private int? _sysCreatedBy = null;

  public int? SysCreatedBy

  {

    get { return _sysCreatedBy; }

    set { _sysCreatedBy = value; }

  } 


  private DateTime? _sysLastUpdatedOn = null;

  public DateTime? SysLastUpdatedOn

  {

    get { return _sysLastUpdatedOn; }

    set { _sysLastUpdatedOn = value; }

  } 


  private int? _sysLastUpdatedBy = null;

  public int? SysLastUpdatedBy

  {

    get { return _sysLastUpdatedBy; }

    set { _sysLastUpdatedBy = value; }

  }

  #endregion

} 


 

If we want our domain objects to be audit logged by our NHibernate IInterceptor instead of solely inheriting from
DomainObject we may inherit our domain objects from one of the loggable domain object implementations
(InsertLoggableDomainObject, UpdateLoggableDomainObject, ModifyLoggableDomainObject)

NHibernate Setup

Inheriting our domain objects from one of the base loggable classes does not provide us
full logging support.

NHibernate Mappings

As any other NHibernate utilization needs some mapping work this sample also needs some NHibernate mapping.
All our loggable domain objects must include the following mapping information

 


<?xml version='1.0' encoding='utf-8'?>

<hibernate-mapping

    xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'

    xmlns:xsd='http://www.w3.org/2001/XMLSchema' 

                xmlns='urn:nhibernate-mapping-2.0'>

  <class

      name='Fully Qualified Class name comes here'

      table='Table name'>

    <id name='Identity field name'

        column='Identity column to which identity field maps'

        unsaved-value='0'>

      <generator class='identity'/>

    </id>

    <!--Managed versioning support. Defined in IVersionedEntity interface -->

    <version name='_version' column='Version' access='field' 

                    unsaved-value='0' type='Int64'/> 


    <!--Log field mappings -->

    <property name='SysCreatedOn' column='SysCreatedOn' 

            type='NHibernate.Nullables2.NullableDateTimeType, 

            NHibernate.Nullables2'/>

    <property name='SysCreatedBy' column='SysCreatedBy' 

            type='NHibernate.Nullables2.NullableInt32Type, 

            NHibernate.Nullables2'/>

    <property name='SysLastUpdatedOn' column='SysLastUpdatedOn' 

            type='NHibernate.Nullables2.NullableDateTimeType, 

            NHibernate.Nullables2'/>

    <property name='SysLastUpdatedBy' column='SysLastUpdatedBy' 

            type='NHibernate.Nullables2.NullableInt32Type, 

            NHibernate.Nullables2'/>

    <!-- Some other field, relation, subclass and other kind of mappings 

                        from this point on-->

  </class>

</hibernate-mapping> 


 

We use NHibernate.Nullables2 (Found in NHibernate Contributions) for type definitions of our log data fields.
We can simply ommit this reference and usage of nullables by simply changing interface field definitions
and their implementations from int? to int and DateTime? to DateTime. But be warned that SysCreatedBy
and SysLastUpdatedBy columns are defined as foreign keys to another table in the database, say Person.
Thus making these fields non-nullable may cause constraint violations on the db side.

NHibernate Session Initialization

This step is simple.Either you directly manage NHibernate sessions from your own code
or you use simple but yet very powerfull NHSessionManager singleton
you have to use ISession ISessionFactory.OpenSession(IInterceptor interceptor)
overload from NHibernate assembly to open a session. IInterceptor interceptor parameter here
is an instance of our IInterceptor implementation.

IInterceptor Implementation

You can thing of an interceptor as a hook utility to data access layer. IInterceptor implementation
will look like this

 


public class MyAuditLogger:IInterceptor

{

  #region IInterceptor Members 


  public int[] FindDirty(object entity, object id, 

            object[] currentState, object[] previousState, 

            string[] propertyNames, 

            global::NHibernate.Type.IType[] types)

  {

      return null;

  } 


  public object Instantiate(Type type, object id)

  {

      return null;                

  } 


  public object IsUnsaved(object entity)

  {

      return null;

  } 


  public void OnDelete(object entity, object id, object[] state, 

    string[] propertyNames, global::NHibernate.Type.IType[] types)

  {

  } 


  public bool OnFlushDirty(object entity, object id, object[] currentState, 

        object[] previousState, string[] propertyNames, 

        global::NHibernate.Type.IType[] types)

  {

      if (entity is IUpdateLogable)

      {

          SetUpdateLoggableValues(currentState, propertyNames);

          return true;

      }

      else if (entity is IModifyLogable)

      {

          SetModifyLoggableValues_OnUpdate(currentState, propertyNames);

          return true;

      }

      else

      {

          return true;

      }

  } 


  public bool OnLoad(object entity, object id, object[] state, 

    string[] propertyNames, global::NHibernate.Type.IType[] types)

  {

      return true;

  } 


  public bool OnSave(object entity, object id, object[] state, 

    string[] propertyNames, global::NHibernate.Type.IType[] types)

  {

      if (entity is IInsertLogable)

      {

          SetInsertLoggableValues(state, propertyNames);

          return true;

      }

      else if (entity is IModifyLogable)

      {

          SetModifyLoggableValues_OnInsert(state, propertyNames);

          return true;

      }

      else

      {

          return true;

      }

  } 


  public void PostFlush(System.Collections.ICollection entities)

  { 


  } 


  public void PreFlush(System.Collections.ICollection entities)

  { 


  } 


  private Hashtable GetInsertLoggablePropertyIndexes(string[] Properties)

  {

      Hashtable result = new Hashtable();

      int propCounter = 0;

      for (int i = 0; i < Properties.Length; i++)

      {

          if (Properties[i].ToLower() == "syscreatedby")

          {

              propCounter++;

              result.Add("syscreatedby", i);

          }

          else if (Properties[i].ToLower() == "syscreatedon")

          {

              propCounter++;

              result.Add("syscreatedon", i);

          } 


          if (propCounter == 2)

          {

              break;

          }

      }

      return result;  

  } 


  private Hashtable GetUpdateLoggablePropertyIndexes(string[] Properties)

  {

      Hashtable result = new Hashtable();

      int propCounter = 0;

      for (int i = 0; i < Properties.Length ; i++)

      {

          if (Properties[i].ToLower() == "syslastupdatedby")

          {

              propCounter++;

              result.Add("syslastupdatedby", i);

          }

          else if (Properties[i].ToLower() == "syslastupdatedon")

          {

              propCounter++;

              result.Add("syslastupdatedon", i);

          } 


          if (propCounter == 2)

          {

              break;

          }

      }

      return result;

  } 


  private Hashtable GetModifyLoggablePropertyIndexes(string[] Properties)

  {

      Hashtable result = new Hashtable();

      int propCounter = 0;

      for (int i = 0; i < Properties.Length; i++)

      {

          if (Properties[i].ToLower() == "syscreatedby")

          {

              propCounter++;

              result.Add("syscreatedby", i);

          }

          else if (Properties[i].ToLower() == "syscreatedon")

          {

              propCounter++;

              result.Add("syscreatedon", i);

          }

          else if (Properties[i].ToLower() == "syslastupdatedby")

          {

              propCounter++;

              result.Add("syslastupdatedby", i);

          }

          else if (Properties[i].ToLower() == "syslastupdatedon")

          {

              propCounter++;

              result.Add("syslastupdatedon", i);

          } 


          if (propCounter == 4)

          {

              break;

          }

      }

      return result;

  } 


  private void SetInsertLoggableValues(object[] state, string[] Properties)

  {

      Hashtable indexes = GetInsertLoggablePropertyIndexes(Properties);

      if (indexes.Count != 2)

      {

          throw new Exception("Can't log IInsertLoggable entity. 


                        Indexes not found!");


      } 


      int index = -1;

      

      if(indexes["syscreatedby"] == null)

      {

          throw new Exception("Can't log IInsertLoggable entity. 


            Index value for SysCreatedBy does not exist!");            


      }

      index = (int)indexes["syscreatedby"];

      state[index] = ContextManager.Instance.ActivePersonID; 


      if (indexes["syscreatedon"] == null)

      {

          throw new Exception("Can't log IInsertLoggable entity. 


            Index value for SysCreatedOn does not exist!");


      }

      index = (int)indexes["syscreatedon"];

      state[index] = DateTime.Now; 


  } 


  private void SetUpdateLoggableValues(object[] state, string[] Properties)

  {

      Hashtable indexes = GetUpdateLoggablePropertyIndexes(Properties);

      if (indexes.Count != 2)

      {

          throw new Exception("Can't log IUpdateLoggable entity. 


                            Indexes not found!");


      } 


      int index = -1; 


      if (indexes["syslastupdatedby"] == null)

      {

          throw new Exception("Can't log IUpdateLoggable entity. 


            Index value for SysLastUpdatedBy does not exist!");


      }

      index = (int)indexes["syslastupdatedby"];

      state[index] = ContextManager.Instance.ActivePersonID; 


      if (indexes["syslastupdatedon"] == null)

      {

          throw new Exception("Can't log IUpdateLoggable entity. 


            Index value for SysLastUpdatedOn does not exist!");


      }

      index = (int)indexes["syslastupdatedon"];

      state[index] = DateTime.Now; 


  } 


  private void SetModifyLoggableValues_OnInsert(object[] state, 

                            string[] Properties)

  {

      Hashtable indexes = GetModifyLoggablePropertyIndexes(Properties);

      if (indexes.Count != 4)

      {

          throw new Exception("Can't log IInsertLoggable entity. 


                            Indexes not found!");


      } 


      int index = -1; 


      if (indexes["syscreatedby"] == null)

      {

          throw new Exception("Can't log IInsertLoggable entity. 


            Index value for SysCreatedBy does not exist!");


      }

      index = (int)indexes["syscreatedby"];

      state[index] = ContextManager.Instance.ActivePersonID; 


      if (indexes["syscreatedon"] == null)

      {

          throw new Exception("Can't log IInsertLoggable entity. 


            Index value for SysCreatedOn does not exist!");


      }

      index = (int)indexes["syscreatedon"];

      state[index] = DateTime.Now; 


  } 


  private void SetModifyLoggableValues_OnUpdate(object[] state, 

                            string[] Properties)

  {

      Hashtable indexes = GetModifyLoggablePropertyIndexes(Properties);

      if (indexes.Count != 4)

      {

          throw new Exception("Can't log IModifyLogable entity. 


                            Indexes not found!");


      } 


      int index = -1; 


      if (indexes["syslastupdatedby"] == null)

      {

          throw new Exception("Can't log IModifyLogable entity. 


            Index value for SysLastUpdatedBy does not exist!");


      }

      index = (int)indexes["syslastupdatedby"];

      state[index] = ContextManager.Instance.ActivePersonID; 


      if (indexes["syslastupdatedon"] == null)

      {

          throw new Exception("Can't log IModifyLogable entity. 


            Index value for SysLastUpdatedOn does not exist!");


      }

      index = (int)indexes["syslastupdatedon"];

      state[index] = DateTime.Now;

  }

  

  #endregion

} 


 

Actually we only implemented OnFlushDirty and OnSave methods of the IInterceptor
and left other interface methods to return null or empty. Following methods are utility methods
that help us to find out domain object type and fill the appropriate log data

Property validation methods: These methods simply loop through domain object properties
,already supported by NHibernate, to determine if log fields (SysCreatedBy, SysLastUpdatedBy,SysCreatedOn,SysLastUpdatedOn)
has NHibernate mapping definitions. If NHibernate mapping definitions does not exist for these fields
exception is thrown by the interceptor.
- GetInsertLoggablePropertyIndexes
- GetUpdateLoggablePropertyIndexes
- GetModifyLoggablePropertyIndexes

Log data setters: These methods simply set log data (SysCreatedBy, SysLastUpdatedBy,SysCreatedOn,SysLastUpdatedOn)
based on the loıggable interface implemented by the domain object.
-SetInsertLoggableValues
-SetUpdateLoggableValues
-SetModifyLoggableValues_OnInsert
-SetModifyLoggableValues_OnUpdate

Remarks and Future Work Suggestions

The sample implementation was one of the my first experiences with NHibernate.So it may not sound
very efficient to you. But i beleive this sample will be a good point to start

I've read about some difficulties and tips about performing interceptor operations and logging
using the NHibernate session interceptor is already attached to

Further work can focus on logging to different database by defining loggable domain object
implementations as reusable domain objects with their own mappings


Posted in: .NET Development , CodeProject , NHibernate  Tags:

Comments


March 24. 2009 06:34
Slim Fit Blog
Thank you for sharing your ideas webmaster! This theme will really help me to do something better next time. Thanks again webmaster for having me in in your site! What a gorgeous post!

   http://www.slimfitblog.com/


May 8. 2009 23:31
sulumits retsambew
great code, thank you for sahring.

   http://phreakaholic.com/sulumits-retsambew/


July 14. 2009 02:55
Marc Ecko
Please let me know if you are interested to work as article writer for me? I can offer $10/article.

   http://thewatchshop.biz/


July 21. 2009 03:40
Slankepiller
Great post - keep it up man Smile

   http://blog.tv2.dk/hoodia_danmark/


July 21. 2009 03:40
Hoodia
Thanks.. Funny, I actually had this on my mind a few days ago..

   http://blog.tv2.dk/hoodia_danmark/


July 22. 2009 03:05
Betsey Johnson
Please let me know if you are interested to work as article writer for me? I can offer $10/article.

   http://luxurywatchstore.biz/


July 25. 2010 17:24
pingback
Pingback from aarpweb.info

AAPR webs » NHibernate Sample

   http://www.aarpweb.info/?p=292

Comments are closed