2010-12-24 16 views
6

He implementado oyentes para auditar los cambios en las tablas en mi aplicación usando IPreUpdateEventListener y IPreInsertEventListener y todo funciona, excepto por mis relaciones muchas a muchas que no tienen datos adicionales en la unión tabla (es decir, no tengo un POCO para la tabla de unión).Auditar la relación muchos a muchos en NHibernate

Cada objeto auditable implementa una interfaz IAuditable, por lo que el detector de eventos comprueba si un POCO es del tipo IAuditable y, si lo está, registra cualquier cambio en el objeto. Las tablas de búsqueda implementan una interfaz de IAuditableProperty, por lo que si una propiedad del POCO IAuditable apunta a una tabla de búsqueda, los cambios se registran en el registro para el POCO principal.

Entonces, la pregunta es, ¿cómo debo determinar que estoy trabajando con una colección de varios a muchos y registrar los cambios en mi tabla de auditoría?

Editar: estoy usando 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

Profundizando en esto, parece que mis eventos 'OnPreUpdate' y' OnPreInsert' ni siquiera se activan cuando modifico las colecciones de muchos a muchos, pero los cambios se mantienen en la base de datos. Este es probablemente el comportamiento esperado debido a una magia más profunda de NHibernate, pero se siente como un error/omisión de las masas sucias ... – Kendrick

Respuesta

3

La razón de que esto no es debido a que las colecciones no han cambiado fuego, es decir, siguen siendo la misma instancia de ICollection que era allí antes, sin embargo, el contenido de las colecciones ha cambiado.

He buscado esto yo mismo, y los oyentes del evento no manejan esta situación. Esto puede haber sido arreglado para v3.0 (pero no me cites sobre eso). Hay un par de soluciones no ideales:

1) Ponga una propiedad en el objeto que hace una cadena de representación de la colección con el fin de auditar.

2) Haga que los elementos de la colección implementen la interfaz para que sean auditados individualmente.

Editar: Hay una tercera opción:

"En vez de a-muchos-a-muchos, tengo una relación muchos-a-uno que va a la mesa de unirse, y luego un uno-a-muchos procedentes de en la tabla de propiedades. Oculto la tabla de unión POCO detrás de la lógica de cada uno de los extremos de las combinaciones de muchos a muchos, pero aún tengo que implementar el objeto y todas las interfaces en él ".

+0

Terminé usando una tercera opción: crear un objeto explícito para la tabla de unión. En lugar de ir de muchos a muchos, tengo un montón de uno para ir a la mesa de unión, y luego uno de muchos a la tabla de propiedades. Oculto la tabla de unión POCO detrás de la lógica de cada uno de los extremos de las combinaciones de muchos a muchos, pero todavía tengo que implementar el objeto y todas las interfaces en él. Si esto tiene sentido, es posible que desee agregarlo a su respuesta (por lo que será más fácil de leer que en un comentario) – Kendrick

3

Resulta que realmente hay una manera de hacer esto a través de Event Listeners sin tener que exponer las tablas de unión. Solo tiene que hacer que su detector de eventos implemente IPostCollectionRecreateEventListener o IPreCollectionRecreateEventListener. Basado en mis pruebas, estos eventos son despedidos por colecciones cambiadas cada vez que se vacía la sesión. Aquí está mi código de escucha de eventos para el método PostRecreateCollection.

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

Lo más complicado es obtener el nombre de la colección que se está actualizando. Tiene que abrirse camino a través de PersistenceContext para obtener el Persister para la colección que le da acceso a sus metadatos.

Debido a que ninguno de estos eventos u oyentes están documentados, no sé si este evento se produce en otras situaciones además del enjuague, por lo que existe la posibilidad de que cree falsas entradas de auditoría. Planeo hacer más investigación en esa área.

+0

"Planeo hacer más investigaciones en esa área". - ¿Alguna actualización de esto? – SamuelKDavis

+0

Realmente rodé el proyecto usando este código hace un tiempo. Pero lo usamos durante varios meses sin ningún problema obvio. Sin garantías obviamente – AndrewSwerlick

Cuestiones relacionadas