Aggiungi oggetti all'associazione in OnPreInsert, OnPreUpdate
-
26-09-2019 - |
Domanda
Ho un ascoltatore di eventi (per i registri di controllo) che deve aggiungere le voci del registro di controllo all'associazione dell'oggetto:
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 }
}
}
IL AuditLogs
la raccolta è mappata con a cascata:
public class CompanyMap : ClassMap<Company> {
public CompanyMap() {
// Id and others removed fro bravety
HasMany(x => x.AuditLogs).AsSet()
.LazyLoad()
.Access.ReadOnlyPropertyThroughCamelCaseField()
.Cascade.All();
}
}
E l'ascoltatore chiede semplicemente all'oggetto controllabile di creare voci di registro in modo che possa aggiornarle:
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
}
}
Il problema però è che lancia TransientObjectException
quando si effettua la transazione:
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()
Come il a cascata è impostato su Tutti Mi aspettavo che NH se ne occupasse.Ho anche provato a modificare la raccolta utilizzando state
ma succede più o meno la stessa cosa.
Quindi la domanda è qual è l'ultima possibilità di modificare le associazioni dell'oggetto prima che venga salvato?
Grazie,
Dmitrij.
Soluzione 2
Ho provato a utilizzare gli ascoltatori di eventi di NH con problemi diversi.
Quindi ho deciso di utilizzare Interceptor (basato su EmptyInterceptor
).
Devo solo eseguire l'override SetSession
, OnFlushDirty
, OnSave
metodi e collegarsi a quelli.
Consentono di creare oggetti adeguati e di renderli persistenti.
Quindi questa è la soluzione più praticabile finora.
Altri suggerimenti
In breve sembra che con l'attivazione di OnPreInsert, NHibernate abbia già determinato cosa deve essere aggiornato.Se in seguito qualcosa diventa sporco, è necessario aggiornare ulteriormente lo "stato dell'entità", la voce dell'oggetto nell'elenco degli oggetti "sporchi".
L'implementazione di IAuditable in Company apporta una modifica a tale oggetto;vale a dire, aggiungere un nuovo oggetto a una raccolta.Nel caso di creazione di oggetti completamente nuovi, la pratica migliore (come menzionato da Revin Hart nei commenti del post sul blog) sembra essere quella di creare una sessione figlia e salvare lì il nuovo oggetto.Sembra molto più semplice che aggiungere tutte le voci necessarie allo stato dell'entità con le chiamate Set().Prova a catturare IAuditLog dalla chiamata IAuditable.CreateEntry() e a salvarlo utilizzando un codice simile al seguente:
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;
}