在 OnPreInsert、OnPreUpdate 中将对象添加到关联
我有一个事件侦听器(用于审核日志),需要将审核日志条目附加到对象的关联中:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
http://ayende.com/Blog/archive /2009/04/29/nhibernate-ipreupdateeventlistener-amp-ipreinserteventlistener.aspx
缺点似乎是,通过触发 OnPreInsert,NHibernate 已经确定了需要更新的内容。如果此后某些内容变脏,则必须进一步更新“实体状态”,即“脏”对象列表中该对象的条目。
您在 Company 上实施 IAuditable 会对该对象进行更改;即,向集合中添加一个新对象。在创建全新对象的情况下,最佳实践(正如 Revin Hart 在博客文章的评论中提到的)似乎是创建一个子 Session 并将新对象保存在那里。这听起来比使用 Set() 调用将所有必要的条目添加到实体状态要容易得多。尝试从 IAuditable.CreateEntry() 调用中获取 IAuditLog,并使用类似于以下内容的代码保存它:
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:
我努力尝试使用 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.