Creation and change stamping of entities

Although it does not track the full history of an entity, another option for auditing is to record information about the entity's creation and the most recent change directly in the entity. In this recipe, we will show you how to use NHibernate's events to record creation and change data on your entities.

How to do it…

  1. Create a new class library project named Changestamp.
  2. Install the NHibernate package using the NuGet Package Manager Console by executing the following command:
    Install-Package NHibernate
    
  3. Create an interface named IStampedEntity with the following code:
    public interface IStampedEntity
    {
    
      string CreatedBy { get; set; }
      DateTime CreatedTS { get; set; }
      string ChangedBy { get; set; }
      DateTime ChangedTS { get; set; }
    
    }
  4. Create an interface named IStamper with the following code:
    public interface IStamper
    {
    
      void Insert(IStampedEntity entity, object[] state, 
        IEntityPersister persister);
      void Update(IStampedEntity entity, object[] oldState,
        object[] state, IEntityPersister persister);
    
    }
  5. Create a new EventListener class as follows:
    public class EventListener :
      IPreInsertEventListener,
      IPreUpdateEventListener
    {
    
      private readonly IStamper _stamper;
    
      public EventListener()
        : this(new Stamper())
      { }
    
      public EventListener(IStamper stamper)
      {
        _stamper = stamper;
      }
    
      public bool OnPreInsert(PreInsertEvent e)
      {
        _stamper.Insert(e.Entity as IStampedEntity,
          e.State, e.Persister);
        return false;
      }
    
      public bool OnPreUpdate(PreUpdateEvent e)
      {
        _stamper.Update(e.Entity as IStampedEntity,
          e.OldState, e.State, e.Persister);
        return false;
      }
    
    
    }
  6. Create a base Entity class with the following code:
    public abstract class Entity : IStampedEntity 
    {
    
      public virtual Guid Id { get; protected set; }
    
      public virtual string CreatedBy { get; set; }
      public virtual DateTime CreatedTS { get; set; }
      public virtual string ChangedBy { get; set; }
      public virtual DateTime ChangedTS { get; set; }
    
    }
  7. Create a Product class with the following code:
    public class Product : Entity
    {
    
      public virtual string Name { get; set; }
      public virtual string Description { get; set; }
      public virtual Decimal UnitPrice { get; set; }
    
    }
  8. Create a mapping with the following XML:
    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
        assembly="Changestamp"
        namespace="Changestamp">
      <class name="Product">
        <id name="Id">
          <generator class="guid.comb" />
        </id>
        <discriminator column="ProductType" />
        <natural-id>
          <property name="Name" not-null="true" />
        </natural-id>
        <property name="Description" />
        <property name="UnitPrice" not-null="true" />
        <property name="CreatedBy" />
        <property name="CreatedTS" />
        <property name="ChangedBy" />
        <property name="ChangedTS" />
      </class>
    </hibernate-mapping>
  9. Create an implementation of IStamper with the following code:
    public class Stamper : IStamper 
    {
    
      private const string CREATED_BY = "CreatedBy";
      private const string CREATED_TS = "CreatedTS";
      private const string CHANGED_BY = "ChangedBy";
      private const string CHANGED_TS = "ChangedTS";
    
      public void Insert(IStampedEntity entity, object[] state, 
        IEntityPersister persister)
      {
        if (entity == null)
          return;
        SetCreate(entity, state, persister);
        SetChange(entity, state, persister);
      }
    
      public void Update(IStampedEntity entity, object[] oldState, 
        object[] state, IEntityPersister persister)
      {
        if (entity == null)
          return;
        SetChange(entity, state, persister);
      }
    
      private void SetCreate(IStampedEntity entity, 
        object[] state,
        IEntityPersister persister)
      {
        entity.CreatedBy = GetUserName();
        SetState(persister, state, CREATED_BY, entity.CreatedBy);
        entity.CreatedTS = DateTime.Now;
        SetState(persister, state, CREATED_TS, entity.CreatedTS);
      }
    
      private void SetChange(IStampedEntity entity, 
        object[] state, IEntityPersister persister)
      {
        entity.ChangedBy = GetUserName();
        SetState(persister, state, CHANGED_BY, 
          entity.ChangedBy);
        entity.ChangedTS = DateTime.Now;
        SetState(persister, state, CHANGED_TS, 
          entity.ChangedTS);
      }
    
    
      private void SetState(IEntityPersister persister, 
        object[] state, string propertyName, object value)
      {
        var index = GetIndex(persister, propertyName);
        if (index == -1)
          return;
        state[index] = value;
      }
    
      private int GetIndex(IEntityPersister persister, 
        string propertyName)
      {
        return Array.IndexOf(persister.PropertyNames, 
          propertyName);
      }
    
      private string GetUserName()
      {
        return WindowsIdentity.GetCurrent().Name; 
      }
    
    }
  10. Set the pre-insert and pre-update event listeners in the App.config, just as we did in the previous recipe.

How it works…

In this recipe, we've added four additional properties to our standard entity. Our pre-insert and pre-update event listener is responsible for setting the values of these properties. The task of setting these properties is handed over to our IStamper implementation. The pre- entity listeners happen late in the process of updating the database. NHibernate has already read our entity's property values into the object array state. This object array provides the actual values written to the database. However, failing to keep the object in sync with the state array can lead to a number of strange and unexpected behaviors later, so we must update both the state array and the object properties.

When an object is inserted, we set the create and change properties to the current user and date/time. When an object is updated, we update these change properties with the current user and date or time.

The GetUserName method of Stamper uses WindowsIdentity.GetCurrent(). This may not return a meaningful user identity, but rather the identity of some service account. The correct implementation of the GetUserName method depends on your application's architecture.

See also

  • Creating an audit-event listener
  • Generating trigger-based auditing
  • Implementing a soft-delete pattern
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.195.34