2010-12-03 22 views
8

Estoy tratando de escribir un seguimiento de auditoría para Nhibernate que engancha en el evento PreUpdate. Tengo una clase AuditLogEntry (cuándo, quién, etc.) que contiene una lista de AuditLogEntryDetails (es decir, propiedades individuales que han cambiado). Si aislo la clase AuditLogEntry de la entidad que se está auditando, mi código se ejecuta sin errores. Sin embargo, si añado una lista de AuditLogEntry de la entidad auditada entonces mi código lanza una colecciónNHibernate: Actualizando colecciones durante EventListener "PreUpdateEvent"

[DomainObjects.AuditTracking.AuditLogEntry.Details] no fue procesado por ras()

error de aserción cuando Intento guardar la lista modificada dentro del detector de eventos. Esto solo ocurre cuando el elemento auditado ya tiene una (o más) instancia AuditLogEntry en la lista. Si no hay entradas, se crea una nueva lista y se agrega a la entidad que se está auditando, y esto está bien.

Creo que al aislar el problema de lo anterior, parecería estar (lazy) cargando la lista existente para agregar también la nueva instancia de AuditLogEntry. Sin embargo, no he podido avanzar más. Agregar 'Lazy = "False"' a la asignación de la lista no parece ayudar. Realmente estoy en los primeros días del uso de NHibernate, habiendo tomado prestados conceptos tanto del libro de cocina HN 3.0 como de este blog post. Mi código es muy similar a esto, pero intenta agregar el historial de auditoría al elemento que se está auditando en una lista (y como tal, creo que también debo hacer eso en el evento pre, en lugar de publicar la actualización).

una instantánea de las interfaces de entidades/clases en cuestión son:

public class AuditLogEntry : Entity 
{ 
    public virtual AuditEntryTypeEnum AuditEntryType { get; set; } 
    public virtual string EntityFullName { get; set; } 
    public virtual string EntityShortName { get; set; } 
    public virtual string Username { get; set; } 
    public virtual DateTime When { get; set; } 
    public virtual IList<AuditLogEntryDetail> Details { get; set; } 
} 

public interface IAuditTrackedEntity 
{ 
    Guid Id { get; } 
    IList<AuditLogEntry> ChangeHistory { get; set; } 
} 

public class AuditTrackedEntity : StampedEntity, IAuditTrackedEntity 
{ 
    public virtual IList<AuditLogEntry> ChangeHistory { get; set; } 
} 

public class LookupValue : AuditTrackedEntity 
{ 
    public virtual string Description { get; set; } 
} 

Para las asignaciones que tengo:

AuditTrackedEntry.hbm.xml:

<?xml version="1.0" encoding="utf-8" ?> 
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DomainObjects" namespace="DomainObjects.AuditTracking"> 
    <class name="AuditLogEntry"> 
    <id name="Id"> 
     <generator class="guid.comb" /> 
    </id> 
    <version name="Version" /> 
    <property name="AuditEntryType"/> 
    <property name="EntityFullName"/> 
    <property name="EntityShortName"/> 
    <property name="Username"/> 
    <property name="When" column="`When`"/> 
    <list name ="Details" cascade="all"> 
     <key column="AuditLogEntryId"/> 
     <list-index column="DetailsIndex" base="1"/> 
     <one-to-many class="AuditLogEntryDetail"/> 
    </list> 
    </class> 
</hibernate-mapping> 

lookupvalue.hbm .xml:

<?xml version="1.0" encoding="utf-8" ?> 
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DomainObjects" namespace="DomainObjects"> 
    <class name="LookupValue"> 
    <id name="Id"> 
     <generator class="guid.comb" /> 
    </id> 
    <discriminator type="string"> 
     <column name="LookupValueType" unique-key="UQ_TypeName" not-null="true" /> 
    </discriminator> 
    <version name="Version" /> 
    <property name="Description" unique-key="UQ_TypeName" not-null="true" /> 
    <property name="CreatedBy" /> 
    <property name="WhenCreated" /> 
    <property name="ChangedBy" /> 
    <property name="WhenChanged" /> 
    <list name ="ChangeHistory"> 
     <key column="EntityId"/> 
     <list-index column="ChangeIndex" base="1"/> 
     <one-to-many class="DomainObjects.AuditTracking.AuditLogEntry"/> 
    </list> 
    </class> 
</hibernate-mapping> 

El EventListener PreUpdate controlador de eventos llama al código siguiente: Las líneas que causan el problema se comentan cerca del final del bloque de código

public void TrackPreUpdate(IAuditTrackedEntity entity, object[] oldState, object[] state, IEntityPersister persister, IEventSource eventSource) 
    { 
     if (entity == null || entity is AuditLogEntry) 
      return; 

     var entityFullName = entity.GetType().FullName; 
     if (oldState == null) 
     { 
      throw new ArgumentNullException("No old state available for entity type '" + entityFullName + 
              "'. Make sure you're loading it into Session before modifying and saving it."); 
     } 

     var dirtyFieldIndexes = persister.FindDirty(state, oldState, entity, eventSource); 
     var session = eventSource.GetSession(EntityMode.Poco); 

     AuditLogEntry auditLogEntry = null; 
     foreach (var dirtyFieldIndex in dirtyFieldIndexes) 
     { 
      if (IsIngoredProperty(persister, dirtyFieldIndex)) 
       continue; 

      var oldValue = GetStringValueFromStateArray(oldState, dirtyFieldIndex); 
      var newValue = GetStringValueFromStateArray(state, dirtyFieldIndex); 

      if (oldValue == newValue) 
      { 
       continue; 
      } 
      if (auditLogEntry == null) 
      { 
       auditLogEntry = new AuditLogEntry 
            { 
             AuditEntryType = AuditEntryTypeEnum.Update, 
             EntityShortName = entity.GetType().Name, 
             EntityFullName = entityFullName, 
             Username = Environment.UserName, 
             //EntityId = entity.Id, 
             When = DateTime.Now, 
             Details = new List<AuditLogEntryDetail>() 
            }; 


       //********************** 
       // The next three lines cause a problem when included, 
       // collection [] was not processed by flush() 
       //********************** 
       if (entity.ChangeHistory == null) 
        entity.ChangeHistory = new List<AuditLogEntry>(); 
       entity.ChangeHistory.Add(auditLogEntry); 

       session.Save(auditLogEntry);  
      } 

      var detail = new AuditLogEntryDetail 
          { 
           //AuditLogEntryId = auditLogEntry.Id, 
           PropertyName = persister.PropertyNames[dirtyFieldIndex], 
           OldValue = oldValue, 
           NewValue = newValue 
          }; 
      session.Save(detail); 
      auditLogEntry.Details.Add(detail); 

     } 

     session.Flush(); 
    } 

Como se ha dicho anteriormente, en esta configuración me sale un error de aserción " colección [] no fue procesada por flush() ". Si elimino las tres líneas de arriba y la asignación de lista en lookupcode.hmb.xml, entonces todo funciona como se espera, aparte de que la entidad que se está auditando ya no contiene una referencia a sus propios elementos auditados.

Respuesta

4

nos enfrentamos a un problema muy similar, exactamente la misma excepción, pero en una situación diferente. No se ha encontrado ninguna solución aún ...

Tenemos NH event listener implementando IPreUpdateEventListener y OnPreUpdate método utilizado para el registro de auditoría. Todo está bien para la actualización de propiedades simples, la verificación sucia funciona bien, pero hay problemas con las colecciones perezosas. Al actualizar un objeto que tiene una recolección diferida y acceder a cualquier campo de objeto en el método OnPreUpdate del detector de eventos, se lanza la misma excepción mencionada anteriormente. Cuando lazy se establece en falso, el problema desaparece.

Parece que hay algún problema con las colecciones diferidas (y sin influencia de la inicialización de la colección antes de guardar). Nuestro problema no está relacionado con la creación de nuevos elementos de colección; solo se lee el objeto existente, solo el acceso al campo desde el detector de eventos causa el problema.

Así que en su caso, tal vez, lazy establecido en falso solo para la asociación podría solucionar el problema, pero por otro lado, probablemente usted realmente desee que la colección sea floja. Tan difícil de decir, si el problema tiene resolución o IInterceptor tiene que ser utilizado en su lugar.

2

Ok, he encontrado su problema, esta línea está causando el problema.

Details = new List<AuditLogEntryDetail>() 

No se puede inicializar una colección vacía antes de guardar porque el EntityPersister no persistirá la colección, pero va a error que la colección no ha sido procesada.

Además, una vez que nHibernate llama a los detectores de eventos, las cascadas no funcionan (no estoy seguro si esto es por diseño o no). Por lo tanto, aunque está agregando el elemento de detalle a la colección más adelante, solo está llamando guardar en los detalles, no en el elemento principal, para que el cambio no se propague. Recomiendo volver a factorizar para que los elementos se completen en este orden ...

detalle, a continuación, guardar,

AuditLogEntry, a continuación, guardar,

Entidad, a continuación, actualizar.

+0

Gracias por la actualización, es de esperar que tendrá la oportunidad de volver a examinar el código de mañana y probar su solución. Te dejaré saber lo que encuentro. –

+0

Gracias por su ayuda, pero me temo que sigo teniendo problemas y cambiar el orden según lo recomendado no ha hecho ninguna diferencia. En diferentes configuraciones, puede obtener diferentes errores, pero comete errores de todos modos. Estoy de acuerdo en que parece ser la colección y me preguntaba si fue porque el código no estaba actualizando el nuevo estado para reflejar la adición de la colección a la entidad, sino que agrega que luego crea otro error ya sea de estado obsoleto o al actualizar una objeto en dos sesiones. –

2

Tuve exactamente el mismo problema al usar EventListener. Estaba recorriendo las propiedades una a una para detectar cambios, que incluían la enumeración de colecciones. Sin embargo, cuando agregué un cheque para la colección usando NHibernateUtil.IsInitialized(collection), el problema desapareció. No captaría e ignoraré la excepción de AssertionFailure ya que podría tener efectos secundarios desconocidos.

+0

¿podría explicar con más detalle cómo utilizó NHibernateUtil.IsInitialized (colección)? – Giedrius

Cuestiones relacionadas