NHibernate: Обновление коллекций во время EventListener "PreUpdateEvent" - PullRequest
8 голосов
/ 03 декабря 2010

Я пытаюсь написать отслеживание аудита для Nhibernate, которое подключается к событию PreUpdate. У меня есть класс AuditLogEntry (когда, кто и т. Д.), Который содержит список AuditLogEntryDetails (т. Е. Отдельные свойства, которые изменились). Если я изолирую класс AuditLogEntry от проверяемой сущности, тогда мой код выполняется без ошибок. Тем не менее, если я добавлю список AuditLogEntry в проверяемую сущность, мой код выдаст

коллекция [DomainObjects.AuditTracking.AuditLogEntry.Details] не была обработана заподлицо ()

ошибка подтверждения при попытке сохранить измененный список в приемнике событий. Это происходит только тогда, когда проверяемый элемент уже имеет один (или более) экземпляр AuditLogEntry в списке. Если записей нет, то создается новый список, который добавляется к объекту аудита, и это нормально.

Я думаю, что если изолировать проблему от вышеприведенной, то это может показаться (ленивой) загрузкой существующего списка, чтобы добавить новый экземпляр AuditLogEntry. Однако я не смог продвинуться дальше. Добавление 'Lazy = "False"' к отображению списка не помогает. Я действительно в первые дни использования NHibernate, позаимствовав концепции из поваренной книги HN 3.0 и этого сообщения в блоге . Мой код очень похож на этот, но я пытаюсь добавить историю аудита к проверяемому элементу в списке (и поэтому я думаю, что мне нужно сделать это также до, а не после обновления).

Снимок рассматриваемых интерфейсов / классов сущностей:

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

Для отображений у меня есть:

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>

Обработчик события EventListener PreUpdate вызывает следующий код: Строки, вызывающие проблему, комментируются ближе к концу блока кода

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

Как указывалось ранее, в этой конфигурации я получаю ошибку подтверждения " collection [] не было обработано flush () ". Если я удалю три вышеприведенные строки и отображение списка в lookupcode.hmb.xml, то все будет работать так, как ожидается, за исключением того, что проверяемая сущность больше не содержит ссылки на свои проверенные элементы.

Ответы [ 4 ]

5 голосов
/ 02 марта 2011

мы столкнулись с очень похожей проблемой, точно таким же исключением, но в другой ситуации. Решение пока не найдено ...

У нас есть прослушиватель событий NH, реализующий метод IPreUpdateEventListener и OnPreUpdate, используемый для журнала аудита. Все хорошо для простого обновления свойств, грязная проверка работает хорошо, но есть проблемы с отложенными коллекциями. При обновлении некоторого объекта, который имеет отложенную коллекцию, и доступе к любому полю объекта в методе прослушивателя событий OnPreUpdate, выдается то же исключение, что упомянуто выше. Если для lazy установлено значение false, проблема исчезает.

Так что, похоже, есть проблема с отложенными коллекциями (и без влияния инициализации коллекции перед сохранением). Наша проблема не связана с созданием новых предметов коллекции; только чтение существующего объекта, только доступ к его полю из прослушивателя событий вызывает проблему.

Так что в вашем случае, возможно, lazy установлен в false только для того, чтобы ассоциация могла решить проблему, но с другой стороны, вероятно, вы действительно хотите, чтобы коллекция была ленивой. Сложно сказать, если проблема имеет разрешение или вместо нее следует использовать IInterceptor.

2 голосов
/ 06 июля 2012

У меня была точно такая же проблема при использовании EventListener.Я просматривал свойства один за другим, чтобы обнаружить изменения, которые включали перечисление коллекций.Однако, когда я добавил проверку коллекции, используя NHibernateUtil.IsInitialized(collection), проблема исчезла.Я бы не стал замечать и игнорировать исключение AssertionFailure, поскольку оно может иметь неизвестные побочные эффекты.

2 голосов
/ 14 декабря 2010

Хорошо, я нашел вашу проблему, эта строка на самом деле вызывает проблему.

Details =  new List<AuditLogEntryDetail>()

Вы не можете инициализировать пустую коллекцию перед сохранением, поскольку EntityPersister не сохранит коллекцию, но при этом возникнет ошибка, что коллекция не была обработана.

Кроме того, когда nHibernate вызывает прослушиватели событий, каскады не работают (не уверен, что это дизайн или нет). Таким образом, даже если вы добавляете элемент сведений в коллекцию позже, вы вызываете функцию сохранения только для сведений, а не для родительского элемента, поэтому изменение не распространяется. Я бы порекомендовал перефакторинг, чтобы элементы были завершены в этом порядке ...

Детализируйте, затем сохраните,

AuditLogEntry, затем сохранить,

Сущность, затем обновите.

0 голосов
/ 27 ноября 2017

Есть еще проблема, чтобы решить эту проблему. В конце темы есть патч, который решил его для меня.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...