NHibernate: updating collections during the EventListener "PreUpdateEvent"

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, //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(); } 

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.

+8
nhibernate event-listener
source share
4 answers

we faced a very similar problem, exactly the same exception, but in a different situation. No solution found yet ...

We have an NH event listener that implements the IPreUpdateEventListener and OnPreUpdate method used for the audit log. Everything is good for a simple property update, a dirty check works well, but there are problems with lazy collections. When updating an object that has a lazy collection and access to any field of the object in the OnPreUpdate event listener method, the same exception is thrown, as indicated above. When lazy set to false, the problem disappears.

So it seems that there is some problem with lazy collections (and no effect on initializing the collection before saving). Our problem is not related to the creation of new elements of the collection; only reading an existing object, only its field accessible from the event listener, causes a problem.

So, in your case, maybe lazy set to false only so that the association could fix this problem, but on the other hand, maybe you really want the collection to be lazy. It is so difficult to say that if the problem has a solution or IInterceptor , you should use it instead.

+4
source share

Ok, I found your problem, this line is actually causing the problem.

 Details = new List<AuditLogEntryDetail>() 

You cannot initialize an empty collection before saving, because EntityPersister will not be saved in the collection, but it will be an error that the collection was not processed.

Also, when nHibernate calls event listeners, cascades do not work (not sure if this is by design or not). Therefore, even if you add a detailed item to the collection later, you only cause the details to be saved, not the parent, so the change does not apply. I would recommend re-factoring so that the details are completed in that order ...

Details, then save,

AuditLogEntry then save

Essence and then update.

+2
source share

I had exactly the same problem when using EventListener. I looked through the properties one at a time to discover the changes included in the collection enumeration. However, when I added a check for the collection using NHibernateUtil.IsInitialized(collection) , the problem disappeared. I would not catch and ignore the AssertionFailure exception, as it may have unknown side effects.

+2
source share

There, the problem remains open to solve this problem. There is a patch at the end of the topic that solved it for me.

https://nhibernate.jira.com/browse/NH-3226

-one
source share

Source: https://habr.com/ru/post/649791/


All Articles