问题描述
我在 sql 服务器中有一个非常简单的表,它映射到 C# POCO 对象中,代码如下:
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);
}
}
我想使用 Hibernate 拦截器跟踪更改,以便我可以在另一个 sql Server 表中添加一些信息,如果出现某些值(例如数据为空或 lastUser 为管理员)。
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;
}
}
但我无法访问保存的实体的值,也无法仅在保存该特定实体时激活拦截器。
有没有办法在保存该实体时调用一个函数,它允许我检查即将保存的值并最终将一些其他更改应用于数据库?
解决方法
是的。就像 David 所说的,EventListener 是您最好的选择。这是我用来标记具有审计字段值的所有实体的示例。
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.Persister);
return false;
}
public Task<bool> OnPreUpdateAsync(PreUpdateEvent @event,CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
}
然后是压模
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,persister);
}
public void Update(IStampedEntity entity,object[] oldState,IEntityPersister persister)
{
if (entity == null)
return;
SetChange(entity,persister);
}
private void SetCreate(IStampedEntity entity,IEntityPersister persister)
{
entity.CreateUser = GetUserName();
SetState(persister,CreateUser,entity.CreateUser);
entity.CreateDate = DateTime.UtcNow;
SetState(persister,CreateDate,entity.CreateDate);
}
private void SetChange(IStampedEntity entity,IEntityPersister persister)
{
entity.LastUpdateUser = GetUserName();
SetState(persister,LastUpdateUser,entity.LastUpdateUser);
entity.LastUpdateDate = DateTime.UtcNow;
SetState(persister,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 将在更改刷新到数据库和事务内之前运行。您可能希望使用 PostInsert/PostUpdate 将更新发送到不同的数据库。
更新不同数据库的更好方案可能是使用服务总线通知订阅者发生了更改,然后让订阅者对外部数据库进行更改。
,从documentation来看,好像不能在类型基础上应用拦截,但必须在实现的方法中过滤:
if (entity is IAuditable) {
}
通过 FlushDirty()
方法的签名,您可以访问实体的先前和当前状态:
public override bool OnFlushDirty(object entity,object id,object[] currentState,object[] previousState,string[] propertyNames,IType[] types)
这里要考虑的另一件事是,事件侦听器可以说是处理 NHibernate 中这方面行为的首选方式: