NHibernate审计跟踪不会导致“集合未被flush处理”错误

时间:2010-06-22 06:22:20

标签: nhibernate

Ayende有一篇关于如何使用事件处理程序为NHibernate(here)实现简单审计跟踪的文章。

不幸的是,正如在评论中可以看到的那样,他的实现导致抛出以下异常:集合xxx未由flush()

处理

问题似乎是对脏属性的 ToString 的隐式调用,如果dirty属性也是映射实体,这会导致麻烦。

我尽最大努力建立一个有效的实施但没有运气。

有人知道有效的解决方案吗?

4 个答案:

答案 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