2010-05-14 2 views
3

J'ai un écouteur d'événements (pour les journaux d'audit) qui doit ajouter les entrées du journal d'audit à l'association de l'objet:Ajouter des objets à l'association à 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 } 
    } 
} 

La collection AuditLogs est mis en correspondance avec en cascade:

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

Et l'auditeur demande simplement l'objet auditable pour créer les entrées du journal afin qu'il puisse les mettre à jour:

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

Le problème avec c'est bien qu'il jette TransientObjectException quand commiting la 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() 

Comme le en cascade est réglé sur tous Je m'y attendais NH gérer cela. J'ai également essayé de modifier la collection en utilisant state mais à peu près la même chose arrive.

Donc, la question est quelle est la dernière chance de modifier les associations d'objets avant qu'il ne soit sauvegardé?

Merci,
Dmitriy.

Répondre

0

J'ai essayé d'utiliser les écouteurs d'événements de NH avec autre question. J'ai donc décidé d'utiliser Interceptor (basé sur EmptyInterceptor).

J'ai seulement besoin de remplacer les méthodes SetSession, OnFlushDirty, OnSave et de les raccorder.

Ils permettent de créer des objets appropriés et de les conserver.

Donc, c'est la solution la plus viable jusqu'à présent.

3

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

Le court il semble que par la mise à feu de OnPreInsert, NHibernate a déjà déterminé ce qui doit être mis à jour. Si quelque chose devient sale après cela, vous devez mettre à jour l'état de l'entité, l'entrée de l'objet dans la liste des objets "sales".

Votre implémentation de IAuditable sur Company apporte une modification à cet objet; à savoir, ajouter un nouvel objet à une collection. Dans le cas de la création de nouveaux objets entiers, la meilleure pratique (comme mentionné par Revin Hart dans les commentaires de l'article du blog) semble être de créer une session enfant et d'y enregistrer le nouvel objet. Cela semble beaucoup plus facile que d'ajouter toutes les entrées nécessaires à l'état de l'entité avec les appels Set(). Essayez saisir le IAuditLog de votre appel, et l'enregistrer en utilisant le code IAuditable.CreateEntry() semblable au suivant:

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; 
} 
+0

J'ai essayé cela sans succès. Ma solution était d'utiliser l'Interceptor à l'ancienne. Avec cela je pourrais facilement implémenter 'OnFlushDirty' et' OnSave'. –

+0

Je pense @KeithS est correct, que vous devez enregistrer l'objet d'audit dans l'événement. J'ai cependant rencontré quelques problèmes avec cette solution, la session enfant renvoyée étant marquée comme fermée. La vérification de cette condition et l'utilisation de la session principale lorsque cela est le cas semble le réparer (et semble aussi très mauvais), donc je suis toujours à la recherche d'une meilleure solution à ce problème ... – Kendrick

+0

Voici un autre lien vers le blog d'Ayende qui pourrait vous être utile: http://ayende.com/Blog/archive/2009/08/30/avoid-soft-deletes.aspx – Kendrick