【问题标题】:Add objects to association in OnPreInsert, OnPreUpdate在 OnPreInsert、OnPreUpdate 中将对象添加到关联
【发布时间】:2011-02-19 10:29:52
【问题描述】:

我有一个事件监听器(用于审计日志),它需要将审计日志条目附加到对象的关联中:

public Company : IAuditable {
    // Other stuff removed for bravety
    IAuditLog IAuditable.CreateEntry() {
        var entry = new CompanyAudit();
        this.auditLogs.Add(entry);
        return entry;
    }
    public virtual IEnumerable<CompanyAudit> AuditLogs {
        get { return this.auditLogs }
    }
}

AuditLogs 集合使用级联进行映射:

public class CompanyMap : ClassMap<Company> {
    public CompanyMap() {
        // Id and others removed fro bravety
        HasMany(x => x.AuditLogs).AsSet()
            .LazyLoad()
            .Access.ReadOnlyPropertyThroughCamelCaseField()
            .Cascade.All();
    }
}

监听器只是要求可审计对象创建日志条目,以便更新它们:

internal class AuditEventListener : IPreInsertEventListener, IPreUpdateEventListener {
    public bool OnPreUpdate(PreUpdateEvent ev) {
        var audit = ev.Entity as IAuditable;
        if (audit == null)
            return false;
        Log(audit);
        return false;
    }


    public bool OnPreInsert(PreInsertEvent ev) {
        var audit = ev.Entity as IAuditable;
        if (audit == null)
            return false;

        Log(audit);
        return false;
    }
    private static void Log(IAuditable auditable) {
        var entry = auditable.CreateAuditEntry();  // Doing this for every auditable property
        entry.CreatedAt = DateTime.Now;
        entry.Who = GetCurrentUser(); // Might potentially execute a query as it links current user with log entry
        // Also other information is set for entry here
    }
}

但它的问题是它在提交事务时抛出TransientObjectException

NHibernate.TransientObjectException : object references an unsaved transient instance - save the transient instance before flushing. Type: CompanyAudit, Entity: CompanyAudit
    at NHibernate.Engine.ForeignKeys.GetEntityIdentifierIfNotUnsaved(String entityName, Object entity, ISessionImplementor session)
    at NHibernate.Type.EntityType.GetIdentifier(Object value, ISessionImplementor session)
    at NHibernate.Type.ManyToOneType.NullSafeSet(IDbCommand st, Object value, Int32 index, Boolean[] settable, ISessionImplementor session)
    at NHibernate.Persister.Collection.AbstractCollectionPersister.WriteElement(IDbCommand st, Object elt, Int32 i, ISessionImplementor session)
    at NHibernate.Persister.Collection.AbstractCollectionPersister.PerformInsert(Object ownerId, IPersistentCollection collection, IExpectation expectation, Object entry, Int32 index, Boolean useBatch, Boolean callable, ISessionImplementor session)
    at NHibernate.Persister.Collection.AbstractCollectionPersister.Recreate(IPersistentCollection collection, Object id, ISessionImplementor session)
    at NHibernate.Action.CollectionRecreateAction.Execute()
    at NHibernate.Engine.ActionQueue.Execute(IExecutable executable)
    at NHibernate.Engine.ActionQueue.ExecuteActions(IList list)
    at NHibernate.Engine.ActionQueue.ExecuteActions()
    at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session)
    at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)
    at NHibernate.Impl.SessionImpl.Flush()
    at NHibernate.Transaction.AdoTransaction.Commit()

由于级联设置为全部,我希望 NH 能够处理此问题。我还尝试使用state 修改集合,但几乎相同。

所以问题是在保存之前修改对象关联的最后机会是什么

谢谢,
德米特里。

【问题讨论】:

    标签: .net nhibernate audit interceptor event-listener


    【解决方案1】:

    http://ayende.com/Blog/archive/2009/04/29/nhibernate-ipreupdateeventlistener-amp-ipreinserteventlistener.aspx

    短处似乎是通过 OnPreInsert 的触发,NHibernate 已经确定了需要更新的内容。如果在那之后有什么东西变脏了,你必须进一步更新“实体状态”,即“脏”对象列表中对象的条目。

    您在 Company 上的 IAuditable 实现对该对象进行了更改;即,将新对象添加到集合中。在创建全新对象的情况下,最佳实践(如 Revin Hart 在博客文章的 cmets 中提到的)似乎是创建一个子 Session 并将新对象保存在那里。这听起来比使用 Set() 调用将所有必要的条目添加到实体状态要容易得多。尝试从您的 IAuditable.CreateEntry() 调用中获取 IAuditLog,并使用类似于以下的代码保存它:

    public bool OnPreInsert(PreInsertEvent ev) {
        var audit = ev.Entity as IAuditable;
        if (audit == null)
            return false;
    
        var log = Log(audit);
    
        ISession newSession = ev.Source.PersistenceContext.Session.GetSession();
        newSession.Save(log);
        return false;
    }
    
    
    private static IAuditLog Log(IAuditable auditable) {
        var entry = auditable.CreateAuditEntry();  // Doing this for every auditable property
        entry.CreatedAt = DateTime.Now;
        entry.Who = GetCurrentUser(); // Might potentially execute a query as it links current user with log entry
    
        return entry;
    }
    

    【讨论】:

    • 我试过没有成功。我的解决方案是使用旧式拦截器。有了它,我可以轻松实现OnFlushDirtyOnSave
    • 我认为@KeithS 是正确的,您需要将审核对象保存在事件中。不过,我在该解决方案中遇到了一些问题,返回的子会话被标记为已关闭。检查这种情况并在这种情况下使用主会话似乎可以解决它(而且似乎非常错误),所以我仍在寻找更好的解决方案来解决这个问题......
    • 这是 Ayende 博客的另一个链接,可能会有所帮助:ayende.com/Blog/archive/2009/08/30/avoid-soft-deletes.aspx
    【解决方案2】:

    我努力使用 NH 的事件侦听器解决不同的问题。
    所以我决定使用拦截器(基于EmptyInterceptor)。

    我只需要覆盖 SetSessionOnFlushDirtyOnSave 方法并连接到这些方法。

    它们确实允许创建适当的对象并将它们持久化。

    所以这是迄今为止最可行的解决方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多