我们已经通过NHibernate事件监听器实现了审计系统。在我们的监听器中,我们跟踪所有更改并将其写入审计表。为了尝试最大化性能,我们在审计表中使用了Guid,以便我们可以尽可能地批量处理更新。
我们正在写出“儿童会话”的更新,我们会这样:
protected ISession GetSession(AbstractEvent @event)
{
if (@event == null)
{
throw new ArgumentNullException("event");
}
ISession childSession = @event.Session.GetSession(EntityMode.Poco);
return childSession;
}
从NHibernate文档中,此会话应该是一个“子”会话,它继承了它的父级的所有属性 - 包括事务。
创建实体后,我们将其保存到会话中:
childSession.Save(auditLogEntry);
所有这些都在一个事务中调用,我希望一旦提交了事务,对childSession所做的更改就会刷新。不幸的是,没有任何事情发生,变化也没有发生。
应该注意的是,我可以在保存后立即使用手动刷新,但这对我们不起作用,因为更改将不再进行批处理(这会产生不可接受的性能)。
起初我认为这种行为仅限于事件,但我能够将其抽象为单元测试以复制行为。
public void When_Saving_Audit_Log_Records_To_Child_Session_Flushes_When_Transaction_Committed()
{
ISession session = GetSession();
session.FlushMode = FlushMode.Commit;
ITransaction transaction = session.BeginTransaction();
ISession childSession = session.GetSession(EntityMode.Poco);
AuditLogEntry entry = CreateAuditLogEntry();
entry.AddAuditLogEntryDetail(CreateAuditLogEntryDetail());
entry.AddAuditLogEntryDetail(CreateAuditLogEntryDetail());
childSession.Save(entry);
transaction.Commit();
}
protected ISession GetSession()
{
return _sessionFactory.OpenSession();
}
我知道这不是你的工厂NHibernate问题,但如果有人有任何经验或建议分享,我很乐意听到。
我距离将审计记录写入队列只有2秒钟,但我想在放弃之前尽力消除所有可能性。
提前致谢,
史蒂夫
答案 0 :(得分:0)
问题来自FlushMode.Commit
:在这种模式下,NHibernate只会在提交事务时刷新一次会话。因此,在冲洗后,它不会冲洗另一次,冲洗后的任何更改都不会被冲洗。
要解决此问题,您可以手动刷新会话,也可以更改为FlushMode.Auto
。但是,如果您使用Auto,请注意带有事件侦听器和/或拦截器的StackOverflowException,因为使用Auto,NHibernate会在查询之前刷新,因此调用OnFlushDirty
,所以如果您在OnFlushDirty
中查询某些内容,它会触发另一次刷新,然后在永无止境的循环中再次调用OnFlushDirty
。要防止出现这种情况,您必须暂时将FlushMode更改为Never
,或者实施系统以确定已处理了哪些更改,以避免反复处理相同的更改。
答案 1 :(得分:0)
我们以相同的方式处理审计(使用GUID PK)。使用Identity PK生成器时,每次调用Save都会立即发出INSERT,但是当使用GUID时,INSERT仅在Flush期间执行。我们在几年前通过修补NHibernate源解决了这个问题。在Flush方法的SessionImpl.cs中(第1467行)我添加了以下内容:
// Flush children when parent is flushed.
if (childSessionsByEntityMode != null) {
foreach (var childSession in childSessionsByEntityMode) {
childSession.Value.Flush();
}
}