简体   繁体   中英

NHibernate - Intercept changes of an entity

I have a very simple table in an SQL server that is mapped in a C# POCO object, with the following code:

public class DataTable : Entity<Guid>
{
   public virtual int itemId { get; set; }
   public virtual string data { get; set; }
   public virtual DateTime lastDate { get; set; }
   public virtual string lastUser { get; set; }

}

public class DataTableMap : ClassMap<DataTable>
{
   public DataTableMap()
    {
      Map(x => x.itemId);
      Map(x => x.data);
      Map(x => x.lastDate);
      Map(x => x.lastUser);
    }
}

I would like to track changes, using an Hibernate interceptor, so that I can add some info in another SQL server table is some of the value occurs (Eg data is empty or lastUser is Admin).

I have implemented a simple interceptor:

public class TrackingEntityInterceptor : EmptyInterceptor
  {
    public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, IType[] types)
      {
        var persistentObject = entity as IPersistentObject;
        if (persistentObject != null)
         {
           persistentObject.OnSave();
         }
     return false;
    }

   public override SqlString OnPrepareStatement(SqlString sql)
    {
     ApplicationBlocks.Logging.Log.Debug($"Executing sql: {sql.ToString()}");
     return sql;
    }
 }

But I am not able to access the values of the Entity that is saved, nor I am able to active the interceptor only when that specific Entity is saved.

Is there a way to have a function called when that Entity is being saved, that allows me to inspect the values that are about to be saved and eventually apply some other changes to the DB ?

Yeah. Like David said, an EventListener is your best bet here. Here's an example of one I use to stamp all entities with audit field values.

namespace NHibernate.Extensions.EventListeners
{
    public class EventListener : IPreInsertEventListener, IPreUpdateEventListener
    {
        private readonly IStamper _stamper;

        public EventListener() : this(new Stamper())
        {
        }

        public EventListener(IStamper stamper)
        {
            _stamper = stamper;
        }

        public bool OnPreInsert(PreInsertEvent @event)
        {
            _stamper.Insert(@event.Entity as IStampedEntity, @event.State, @event.Persister);
            return false;
        }

        public Task<bool> OnPreInsertAsync(PreInsertEvent @event, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        public bool OnPreUpdate(PreUpdateEvent @event)
        {
            _stamper.Update(@event.Entity as IStampedEntity, @event.OldState, @event.State, @event.Persister);
            return false;
        }

        public Task<bool> OnPreUpdateAsync(PreUpdateEvent @event, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }
    }
}

and then the stamper

public class Stamper : IStamper
{
    private const string CreateUser = "CreateUser";
    private const string CreateDate = "CreateDate";
    private const string LastUpdateUser = "LastUpdateUser";
    private const string LastUpdateDate = "LastUpdateDate";

    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.CreateUser = GetUserName();
        SetState(persister, state, CreateUser, entity.CreateUser);
        entity.CreateDate = DateTime.UtcNow;
        SetState(persister, state, CreateDate, entity.CreateDate);
    }

    private void SetChange(IStampedEntity entity, object[] state, IEntityPersister persister)
    {
        entity.LastUpdateUser = GetUserName();
        SetState(persister, state, LastUpdateUser, entity.LastUpdateUser);
        entity.LastUpdateDate = DateTime.UtcNow;
        SetState(persister, state, LastUpdateDate, entity.LastUpdateDate);
    }

    private void SetState(IEntityPersister persister, IList<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 HttpContext.Current != null
            ? HttpContext.Current.User.Identity.Name
            : WindowsIdentity.GetCurrent().Name;
    }
}

PreInsert is going to run before the changes are flushed to the DB and within the transaction. You probably want to use the PostInsert/PostUpdate to send your updates to a different database.

Probably a better scheme for updating a different database would be using a service bus to notify a subscriber that a change was made and then have the subscriber make the change to the external database.

From the documentation , it looks like you cannot apply interception on a type basis, but you must filter in the implemented methods:

if (entity is IAuditable) {
            
}

From the signature of the FlushDirty() method you can access the previous and current state of the entity:

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

The other thing to consider here is that event listeners are, arguably, the preferred way to handle this aspect of behaviour in NHibernate:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM