2010-07-21 3 views
3

Nous avons mis en place un système d'audit par les auditeurs d'événements NHibernate. Dans notre écouteur, nous suivons toutes les modifications et les écrivons dans nos tables d'audit. Pour essayer de maximiser la performance, nous avons utilisé Guid pour nos tables d'audit afin que nous puissions regrouper nos mises à jour autant que possible.NHibernate session enfant ne chasse correctement

Nous écrivons des mises à jour à une « session d'enfant » que nous obtenons comme ceci:

protected ISession GetSession(AbstractEvent @event) 
{ 
    if (@event == null) 
    { 
     throw new ArgumentNullException("event"); 
    } 

     ISession childSession = @event.Session.GetSession(EntityMode.Poco); 

     return childSession; 
} 

De la documentation NHibernate, cette session devrait être une session « enfant », qui hérite de tous les attributs de c'est parent - y compris la transaction.

Une fois que nous avons créé l'entité, nous l'enregistrer à la session avec:

childSession.Save(auditLogEntry); 

Tout cela est appelé dans une transaction et je voudrais attendre à ce que les modifications apportées à la childSession rougissait une fois la transaction est engagé. Malheureusement, rien ne se passe et les changements ne bougent pas.

Il convient de noter que je peux obtenir ceci pour fonctionner avec un vidage manuel juste après la sauvegarde, mais cela ne fonctionnera pas pour nous parce que les changements ne seront plus lotis (ce qui donnera des performances inacceptables).

Au début, je pensais que ce comportement était limité à des événements, mais j'ai pu faire abstraction dehors dans un test unitaire pour dupliquer le comportement.

public void When_Saving_Audit_Log_Records_To_Child_Session_Flushes_When_Transaction_Committed() 
    { 
     ISession session = GetSession(); 
     session.FlushMode = FlushMode.Commit; 

     ITransaction transaction = session.BeginTransaction(); 

     ISession childSession = session.GetSession(EntityMode.Poco); 

     AuditLogEntry entry = CreateAuditLogEntry(); 
     entry.AddAuditLogEntryDetail(CreateAuditLogEntryDetail()); 
     entry.AddAuditLogEntryDetail(CreateAuditLogEntryDetail()); 

     childSession.Save(entry); 
     transaction.Commit(); 
    } 

protected ISession GetSession() 
    { 
     return _sessionFactory.OpenSession(); 
    } 

Je sais que ce n'est pas votre course de l'usine NHibernate question, mais si quelqu'un a une expérience ou des conseils à partager, j'aimerais entendre.

Je suis 2 secondes de simplement écrire les enregistrements d'audit sur une file d'attente, mais je voulais épuiser toutes les possibilités avant d'abandonner.

Merci à l'avance,

Steve

Répondre

0

Le problème est de FlushMode.Commit: dans ce mode, NHibernate ne rincer la session une fois, sur la validation de la transaction. Donc, après le rinçage, il ne rincera pas une autre fois et tout changement après le rinçage ne sera pas rincé.

Pour résoudre ce problème, vous pouvez rincer la session manuellement ou à changer FlushMode.Auto. Toutefois, si vous utilisez Auto, méfiez-vous des StackOverflowException avec des écouteurs d'événements et/ou des intercepteurs, car avec Auto, NHibernate videra avant d'interroger, d'appeler OnFlushDirty dans la suite, donc si vous interrogez quelque chose dans OnFlushDirty, il déclenche un autre rinçage, qui puis appelez OnFlushDirty à nouveau dans une boucle sans fin. Pour éviter cette situation, vous devez soit modifier temporairement le FlushMode à Never, soit implémenter un système pour déterminer quelles modifications ont été traitées afin d'éviter de traiter la même modification encore et encore.

0

Nous nous occupons d'audit de la même manière (avec un GUID PK). Lors de l'utilisation du générateur Identity PK, chaque appel à Save émettait un INSERT immédiatement, mais lorsque le GUID est utilisé, les INSERT ne sont exécutés que pendant un Flush. Nous avons résolu ce problème il y a quelques années en corrigeant la source de NHibernate. Dans SessionImpl.cs dans le procédé de rinçage (autour de la ligne 1467) I ajouté ce qui suit:

// Flush children when parent is flushed. 
if (childSessionsByEntityMode != null) { 
    foreach (var childSession in childSessionsByEntityMode) { 
     childSession.Value.Flush(); 
    } 
}