I am trying to write audit tracing for Nhibernate that catches the PreUpdate event. I have an AuditLogEntry class (when, who, etc.) that contains a list of AuditLogEntryDetails (i.e. Modified individual properties). If I isolate the AuditLogEntry class from the entity being verified, then my code works without errors. However, if I add the AuditLogEntry list to the entity being checked, then my code will produce
the collection [DomainObjects.AuditTracking.AuditLogEntry.Details] was not processed flush ()
An approval error occurred while trying to save a modified list inside an event listener. This only happens when the checked item already has one (or more) instance of AuditLogEntry in the list. If there are no entries, a new list is created and added to the object being checked, and this is normal.
I think that by isolating the problem above, it turned out that around (lazy) loads the existing list to add a new instance of AuditLogEntry. However, I could not move forward. Adding "Lazy =" False "to the list display does not help. I really did in the early days of using NHibernate, borrowing concepts from both Cookbook HN 3.0 and a blog post . My code is very similar to this, but trying to add an audit history to the verifiable an element in the list (and, as such, it seems to me that I need to do this also in the pre event, and not after the update).
Capturing interfaces / feature classes:
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; } }
For mappings, we have:
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>
The PreUpdate EventListener event handler calls the following code: The lines that cause the problem are commented out at the end of the code
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,
As mentioned earlier, in this configuration I get a rejection of the statement " collection [] is not processed by flush () ". If I delete the three lines above and match the list in lookupcode.hmb.xml, then everything will work as expected, except for the object being checked, it no longer contains a link to its own checked elements.