我有一个事件监听器(用于审核日志),需要将审核日志条目附加到对象的关联中:
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
修改集合,但几乎同样如此。
所以问题是在保存之前修改对象关联的最后机会是什么?
谢谢,
德米特里。
答案 0 :(得分:3)
缺点似乎是通过触发OnPreInsert,NHibernate已经确定了需要更新的内容。如果之后某些东西变脏了,你必须进一步更新“实体状态”,即“脏”对象列表中对象的条目。
您在公司上实施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;
}
答案 1 :(得分:0)
我努力使用不同问题的NH的事件听众
所以我只是决定使用Interceptor(基于EmptyInterceptor
)。
我只需要覆盖SetSession
,OnFlushDirty
,OnSave
方法并加入其中。
他们确实允许创建适当的对象并持久化它们。
到目前为止,这是最可行的解决方案。