在 OnPreInsert、OnPreUpdate 中将对象添加到关联

发布于 2024-09-01 08:52:48 字数 3553 浏览 4 评论 0原文

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

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()

因为 级联设置为 All我希望 NH 能够处理这个问题。我还尝试使用 state 修改集合,但发生的情况几乎相同。

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

谢谢,
德米特里。

I have an event listener (for Audit Logs) which needs to append audit log entries to the association of the object:

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 }
    }
}

The AuditLogs collection is mapped with cascading:

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

And the listener just asks the auditable object to create log entries so it can update them:

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
    }
}

The problem with it though is that it throws TransientObjectException when commiting the transaction:

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()

As the cascading is set to All I expected NH to handle this. I also tried to modify the collection using state but pretty much the same happens.

So the question is what is the last chance to modify object's associations before it gets saved?

Thanks,
Dmitriy.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

无敌元气妹 2024-09-08 08:52:48

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

缺点似乎是,通过触发 OnPreInsert,NHibernate 已经确定了需要更新的内容。如果此后某些内容变脏,则必须进一步更新“实体状态”,即“脏”对象列表中该对象的条目。

您在 Company 上实施 IAuditable 会对该对象进行更改;即,向集合中添加一个新对象。在创建全新对象的情况下,最佳实践(正如 Revin Hart 在博客文章的评论中提到的)似乎是创建一个子 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;
}

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

The short of it seems to be that by the firing of OnPreInsert, NHibernate has already determined what needs to be updated. If something becomes dirty after that, you have to further update the "entity state", the entry for the object in the list of "dirty" objects.

Your implementation of IAuditable on Company makes a change to that object; namely, adding a new object to a collection. In the case of creating entire new objects, the best practice (as mentioned by Revin Hart in the comments of the blog post) seems to be to create a child Session and save the new object there. That sounds much easier than adding all the necessary entries to the entity state with Set() calls. Try grabbing the IAuditLog from your IAuditable.CreateEntry() call, and saving it using code similar to the following:

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;
}
⊕婉儿 2024-09-08 08:52:48

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

我只需要重写 SetSession、OnFlushDirty、OnSave 方法并连接到这些方法。

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

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

I tried hard to use NH's event listeners with different issue.
So I just decied to use Interceptor (based on EmptyInterceptor).

I only need to override SetSession, OnFlushDirty, OnSave methods and hook up into those.

They do allow creating proper objects and persisting them.

So this is the most viable solution so far.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文