2010-12-24 17 views
6

Ich habe Listener implementiert, um Änderungen an Tabellen in meiner Anwendung mit IPreUpdateEventListener und IPreInsertEventListener zu überwachen, und alles funktioniert außer für meine Viele-zu-viele-Beziehungen, die keine zusätzlichen Daten in der Verknüpfung haben Tabelle (dh ich habe kein POCO für die Verbindungstabelle).Viele-zu-viele-Beziehung in NHibernate überprüfen

Jedes auditierbare Objekt implementiert eine IAuditable-Schnittstelle, sodass der Ereignis-Listener überprüft, ob ein POCO vom Typ IAuditable ist, und falls dies der Fall ist, werden Änderungen am Objekt aufgezeichnet. Nachschlagetabellen implementieren eine IAuditableProperty Schnittstelle, wenn also eine Eigenschaft der IAuditable POCO auf eine Nachschlagetabelle zeigt, werden die Änderungen im Protokoll für die Haupt-POCO aufgezeichnet.

Die Frage ist also, wie soll ich feststellen, dass ich mit einer Viele-zu-Viele-Sammlung arbeite und die Änderungen in meiner Audit-Tabelle aufzeichne?

Edit: Ich verwende NHibernate 2.1.2.4000

//first two checks for LastUpdated and LastUpdatedBy ommitted for brevity 
else if (newState[i] is IAuditable) 
{ 
    //Do nothing, these will record themselves separately 
} 
else if (!(newState[i] is IAuditableProperty) && (newState[i] is IList<object> || newState[i] is ISet)) 
{ 
    //Do nothing, this is a collection and individual items will update themselves if they are auditable 
    //I believe this is where my many-to-many values are being lost 
} 
else if (!isUpdateEvent || !Equals(oldState[i], newState[i]))//Record only modified fields when updating 
{ 
    changes.Append(preDatabaseEvent.Persister.PropertyNames[i]) 
     .Append(": "); 
    if (newState[i] is IAuditableProperty) 
    { 
     //Record changes to values in lookup tables 
     if (isUpdateEvent) 
     { 
      changes.Append(((IAuditableProperty)oldState[i]).AuditPropertyValue) 
       .Append(" => "); 
     } 
     changes.Append(((IAuditableProperty)newState[i]).AuditPropertyValue); 
    } 
    else 
    { 
     //Record changes for primitive values 
     if(isUpdateEvent) 
     { 
      changes.Append(oldState[i]) 
       .Append(" => "); 
     } 
     changes.Append(newState[i]); 
    } 
    changes.AppendLine(); 
} 
+0

Wenn ich weiter in dieses Thema eindringe, scheinen meine 'OnPreUpdate' und' OnPreInsert' Ereignisse nicht einmal zu schießen, wenn ich die Viele-zu-Viele-Sammlungen modifiziere, aber die Änderungen werden in der Datenbank gespeichert. Dies ist wahrscheinlich aufgrund einiger tieferer Magie von NHibernate zu erwarten, aber es fühlt sich wie ein Bug/Ommision zu den ungewaschenen Massen ... – Kendrick

Antwort

3

Der Grund, dass dies nicht Feuer, weil die Kollektionen nicht geändert haben, dh sie sind immer noch die gleiche Instanz von ICollection sind, die war Davor hat sich jedoch der Inhalt der Sammlungen verändert.

Ich habe selbst gesucht, und die Ereignis-Listener behandeln diese Situation nicht. Dies wurde möglicherweise für v3.0 behoben (aber zitieren Sie mich nicht dazu). Es gibt ein paar nicht optimale Problemumgehungen:

1) Legen Sie eine Eigenschaft auf das Objekt, die eine Zeichenfolgendarstellung der Auflistung für die Zwecke der Überwachung erstellt.

2) Sorgen Sie dafür, dass die Elemente in der Sammlung die Schnittstelle implementieren, sodass sie einzeln geprüft werden.

Edit: Es gibt eine dritte Option:

„Anstatt eine many-to-many, ich habe eine viele-zu-eins auf die Verbindungstabelle gehen und eine dann eine Eins-zu-viele kommen Ich verstecke die Joining-Tabelle POCO hinter der Logik der einzelnen Enden der Viele-zu-Viele-Joins, muss aber das Objekt und alle Interfaces implementieren. "

+0

Ich endete mit einer dritten Option - Erstellen Sie ein explizites Objekt für die Verbindungstabelle. Statt einer number-to-many, habe ich eine Eins-zu-Eins-Verbindung zur Joining-Tabelle und dann eine Eins-zu-Viele-Anweisung, die von ihr in die Eigenschaftstabelle kommt. Ich verstecke die Joining-Tabelle POCO hinter der Logik der einzelnen Enden der Viele-zu-Viele-Joins, muss aber trotzdem das Objekt und alle Interfaces implementieren. Wenn dies sinnvoll ist, können Sie es Ihrer Antwort hinzufügen (so dass es leichter zu lesen ist als in einem Kommentar) – Kendrick

3

Es stellt sich heraus, dass es tatsächlich eine Möglichkeit gibt, dies über Ereignislistener zu tun, ohne die Verbindungstabellen verfügbar zu machen. Sie müssen lediglich Ihren Ereignis-Listener IPostCollectionRecreateEventListener oder IPreCollectionRecreateEventListener implementieren. Basierend auf meinen Tests werden diese Ereignisse für geänderte Sammlungen ausgelöst, wenn die Sitzung geleert wird. Hier ist mein Ereignis-Listener-Code für die PostRecreateCollection-Methode.

public void OnPostRecreateCollection(PostCollectionRecreateEvent @event) 
     { 
      var session = @event.Session.GetSession(EntityMode.Poco); 
      var propertyBeingUpdated = @event.Session.PersistenceContext.GetCollectionEntry(@event.Collection).CurrentPersister.CollectionMetadata.Role; 

      var newCollectionString = @event.Collection.ToString(); 
      var oldCollection = (@event.Collection.StoredSnapshot as IList<object>).Select(o => o.ToString()).ToList(); 
      var oldCollectionString = string.Join(", ",oldCollection.ToArray()); 

      if (newCollectionString == oldCollectionString || (string.IsNullOrEmpty(newCollectionString) && string.IsNullOrEmpty(oldCollectionString))) 
       return; 

      User currentUser = GetLoggedInUser(session); 
      session.Save(new Audit 
      { 
       EntityName = @event.AffectedOwnerOrNull.GetType().Name, 
       EntityId = (int)@event.AffectedOwnerIdOrNull, 
       PropertyName = propertyBeingUpdated, 
       AuditType = "Collection Modified", 
       EventDate = DateTime.Now, 
       NewValue = newCollectionString, 
       OldValue = oldCollectionString, 
       AuditedBy = Environment.UserName, 
       User = currentUser 
      }); 
     } 

Der schwierigste Teil ist, den Namen der Sammlung zu erhalten, die aktualisiert wird. Sie müssen sich durch den PersistenceContext ketten, um den Persister für die Sammlung zu erhalten, der Ihnen Zugriff auf seine Metadaten gibt.

Da keines dieser Ereignisse oder Listener dokumentiert sind, weiß ich nicht, ob dieses Ereignis in anderen Situationen außer Flush geworfen wird, also könnte es möglicherweise falsche Audit-Einträge erstellen. Ich plane, in diesem Bereich weitere Forschung zu betreiben.

+0

"Ich plane, weitere Forschungen in diesem Bereich zu machen." - Irgendwelche Updates dazu? – SamuelKDavis

+0

Ich habe das Projekt mit diesem Code vor einiger Zeit beendet. Aber wir haben es einige Monate ohne offensichtliche Probleme benutzt. Keine Garantien offensichtlich – AndrewSwerlick