Ayende有一篇关于如何使用事件处理程序为NHibernate(here)实现简单审计跟踪的文章。
不幸的是,正如在评论中可以看到的那样,他的实现导致抛出以下异常:集合xxx未由flush()
处理问题似乎是对脏属性的 ToString 的隐式调用,如果dirty属性也是映射实体,这会导致麻烦。
我尽最大努力建立一个有效的实施但没有运气。
有人知道有效的解决方案吗?
答案 0 :(得分:9)
我能够使用以下解决方法解决同样的问题:在侦听器中当前持久化上下文中的所有集合上将processed标记设置为true
public void OnPostUpdate(PostUpdateEvent postEvent)
{
if (IsAuditable(postEvent.Entity))
{
//skip application specific code
foreach (var collection in postEvent.Session.PersistenceContext.CollectionEntries.Values)
{
var collectionEntry = collection as CollectionEntry;
collectionEntry.IsProcessed = true;
}
//var session = postEvent.Session.GetSession(EntityMode.Poco);
//session.Save(auditTrailEntry);
//session.Flush();
}
}
希望这有帮助。
答案 1 :(得分:8)
修复应该如下。创建一个新的事件监听器类,并从NHibernate.Event.Default.DefaultFlushEventListener派生它:
[Serializable]
public class FixedDefaultFlushEventListener: DefaultFlushEventListener
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
protected override void PerformExecutions(IEventSource session)
{
if (log.IsDebugEnabled)
{
log.Debug("executing flush");
}
try
{
session.ConnectionManager.FlushBeginning();
session.PersistenceContext.Flushing = true;
session.ActionQueue.PrepareActions();
session.ActionQueue.ExecuteActions();
}
catch (HibernateException exception)
{
if (log.IsErrorEnabled)
{
log.Error("Could not synchronize database state with session", exception);
}
throw;
}
finally
{
session.PersistenceContext.Flushing = false;
session.ConnectionManager.FlushEnding();
}
}
}
在NHibernate configuraiton期间注册它:
cfg.EventListeners.FlushEventListeners = new IFlushEventListener[] { new FixedDefaultFlushEventListener() };
您可以在Hibernate JIRA中阅读有关此错误的更多信息: https://hibernate.onjira.com/browse/HHH-2763
NHibernate的下一个版本也应包含该修复。
答案 2 :(得分:4)
这根本不容易。我写了这样的东西,但它非常特定于我们的需求而不是微不足道。
一些额外的提示:
您可以使用
测试是否加载引用NHibernateUtil.IsInitialized(entity)
或
NHibernateUtil.IsPropertyInitialized(entity, propertyName)
您可以将集合转换为IPersistentCollection
。我实现了一个IInterceptor
,我得到了每个属性的NHibernate类型,我不知道在使用事件时你可以在哪里得到它:
if (nhtype.IsCollectionType)
{
var collection = previousValue as NHibernate.Collection.IPersistentCollection;
if (collection != null)
{
// just skip uninitialized collections
if (!collection.WasInitialized)
{
// skip
}
else
{
// read collections previous values
previousValue = collection.StoredSnapshot;
}
}
}
当您从NHibernate获取更新事件时,将初始化该实例。您可以安全地访问基本类型的属性。如果您想使用ToString
,请确保您的ToString
实施不会访问任何引用的实体或任何集合。
您可以使用NHibernate元数据来确定某个类型是否被映射为实体。这可能对您在对象模型中导航很有用。当您引用另一个实体时,您将在更改时获得有关此内容的其他更新事件。
答案 3 :(得分:0)
当应用程序代码加载实体有集合的Lazy Propery时,我能够确定抛出此错误。
我的第一次尝试开始观看新的CollectionEntries
(我从来没有想要处理,因为实际上不应该有任何更改)。然后将它们标记为IsProcessed = true
,这样它们就不会出现问题。
var collections = args.Session.PersistenceContext.CollectionEntries;
var collectionKeys = args.Session.PersistenceContext.CollectionEntries.Keys;
var roundCollectionKeys = collectionKeys.Cast<object>().ToList();
var collectionValuesClount = collectionKeys.Count;
// Application code that that loads a Lazy propery where the Entity has a collection
var postCollectionKeys = collectionKeys.Cast<object>().ToList();
var newLength = postCollectionKeys.Count;
if (newLength != collectionValuesClount) {
foreach (var newKey in postCollectionKeys.Except(roundCollectionKeys)) {
var collectionEntry = (CollectionEntry)collections[newKey];
collectionEntry.IsProcessed = true;
}
}
然而,这并未彻底解决问题。在某些情况下,我仍然会得到例外。
调用OnPostUpdate
时,CollectionEntries
字典中的值应全部设置为IsProcessed = true
。所以我决定做一个额外的检查,看看未处理的集合是否符合我的预期。
var valuesNotProcessed = collections.Values.Cast<CollectionEntry>().Where(x => !x.IsProcessed).ToList();
if (valuesNotProcessed.Any()) {
// Assert: valuesNotProcessed.Count() == (newLength - collectionValuesClount)
}
在我第一次尝试修复的情况下,这些数字将完全匹配。然而,在它不起作用的情况下,字典中还有额外的项目。在我,我可以肯定这些额外的项目也不会导致更新,所以我可以为所有IsProcessed = true
设置valuesNotProcessed
。