OnPreInsert、OnPreUpdate の関連付けにオブジェクトを追加します
-
26-09-2019 - |
質問
オブジェクトの関連付けに監査ログ エントリを追加する必要があるイベント リスナー (監査ログ用) があります。
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
しかし、ほぼ同じことが起こります。
それで質問は オブジェクトの関連付けを保存する前に変更できる最後の機会は何ですか?
ありがとう、
ドミトリー。
解決 2
別の問題でNHのイベントリスナーを使用しようと一生懸命試みました。
そこで、インターセプターを使用することにしました(に基づいて) EmptyInterceptor
).
オーバーライドするだけで済みます SetSession
, OnFlushDirty
, OnSave
メソッドを作成し、それらに接続します。
これらにより、適切なオブジェクトを作成し、それらを永続化することができます。
したがって、これがこれまでのところ最も実行可能な解決策です。
他のヒント
要するに、OnPreInsert の起動によって、NHibernate は更新する必要があるものをすでに決定しているようです。その後、何かがダーティになった場合は、「エンティティ状態」、つまり「ダーティ」オブジェクトのリスト内のオブジェクトのエントリをさらに更新する必要があります。
Company に IAuditable を実装すると、そのオブジェクトが変更されます。つまり、新しいオブジェクトをコレクションに追加します。新しいオブジェクト全体を作成する場合、ベスト プラクティス (ブログ投稿のコメントで Revin Hart が述べているように) は、子セッションを作成し、そこに新しいオブジェクトを保存することのようです。これは、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;
}