Many-to-many auditing at NHibernate

I used listeners to audit table changes in my application using IPreUpdateEventListener and IPreInsertEventListener , and everything works, except for my many-to-many relationships that don't have additional data in the join table (i.e. I have POCO for join tables).

Each object being scanned implements the IAuditable interface, so the event listener checks whether the POCO type is IAuditable and if it records any changes to the object. The lookup tables implement the inteface IAuditableProperty , so if the IAuditable POCO property points to the lookup table, the changes are logged for the main POCO.

So the question is, how should I determine that I am working with a many-to-many collection and recording changes to my audit table?

Edit: I am using 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(); } 
+6
c # nhibernate audit many-to-many
source share
2 answers

The reason this does not work is because the collections have not changed, i.e. they are still the same instance of ICollection that was there before, but the contents of the collections have changed.

I searched for it myself, and event listeners cannot cope with this situation. Perhaps this has been fixed for v3.0 (but don't quote me on this). There are several non-standard workarounds:

1) Put an object property that creates a string representation of the collection for audit purposes.

2) Make the elements in the collection implement the interface so that they are inspected individually.

Edit: There is a third option:

“Instead of many-to-many, I have many-to-one going to the connection table, and then from one-to-many going from it to the property table. I hide the POCO connection table the logic of each end many-to-many associations, but still must implement the object and all the interfaces on it. "

+3
source share

It turns out that there is actually a way to do this through event listeners without opening the connection tables. You just need to get the event listener to implement the IPostCollectionRecreateEventListener or IPreCollectionRecreateEventListener. Based on my testing, these events are fired for modified collections whenever a session is cleared. Here is my event listener code for the PostRecreateCollection method.

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

The hardest part is getting the name of the updated collection. You need to bind your path through PersistenceContext in order to get Persister for a collection that gives you access to this metadata.

Since none of these events or listeners is documented, I don’t know if this event gets into situations other than a flash, so there is a potential possibility of creating false audit records. I plan to conduct further research in this area.

+3
source share

All Articles