Аудит отношения "многие ко многим" в NHibernate

Я реализовал слушателей для аудита изменений в таблицах в моем приложении, используя IPreUpdateEventListener и IPreInsertEventListener, и все работает, за исключением моих отношений «многие ко многим», которые не имеют дополнительных данных в объединяемой таблице (т.е. у меня нет POCO для соединительный стол).

Каждый проверяемый объект реализует интерфейс IAuditable, поэтому прослушиватель событий проверяет, имеет ли POCO тип IAuditable, и если это так, он записывает любые изменения объекта. Таблицы поиска реализуют IAuditableProperty интерфейс, поэтому, если свойство IAuditable POCO указывает на таблицу поиска, изменения записываются в журнал для основного POCO.

Итак, вопрос в том, как мне определить, что я работаю с коллекцией «многие ко многим», и записать изменения в свою таблицу аудита?

Изменить: я использую 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();
}

person Kendrick    schedule 24.12.2010    source источник
comment
Углубляясь в это, выясняется, что мои события OnPreUpdate и OnPreInsert даже не срабатывают, когда я изменяю коллекции "многие ко многим", но изменения сохраняются в базе данных. Это, вероятно, ожидаемое поведение из-за некоторой более глубокой магии NHibernate, но это похоже на ошибку / упущение для немытых масс ...   -  person Kendrick    schedule 24.12.2010


Ответы (2)


Причина, по которой это не сработает, заключается в том, что коллекции не изменились, то есть они все еще тот же экземпляр ICollection, что и раньше, однако содержимое коллекций изменилось.

Я сам искал это, и слушатели событий не справляются с этой ситуацией. Возможно, это было исправлено в версии 3.0 (но не цитируйте меня по этому поводу). Есть несколько неидеальных обходных путей:

1) Поместите свойство в объект, который создает строковое представление коллекции для целей аудита.

2) Сделайте так, чтобы элементы в коллекции реализуют интерфейс, чтобы они проверялись индивидуально.

Изменить: есть третий вариант:

"Вместо" многие-ко-многим "у меня есть соединение" многие-ко-многим ", идущее к объединяемой таблице, а затем" одно-ко-многим ", исходящее от него в таблицу свойств. Я скрываю объединяющую таблицу POCO за логикой каждый из концов объединений "многие ко многим", но все же необходимо реализовать объект и все интерфейсы на нем ".

person Gunner    schedule 24.02.2011
comment
В итоге я использовал третий вариант - создать явный объект для соединяющейся таблицы. Вместо «многие-ко-многим» у меня есть «многие-ко-многим», идущие к объединяющейся таблице, а затем «один-ко-многим», исходящие от нее в таблицу свойств. Я скрываю таблицу соединений POCO за логикой каждого из концов соединений многие ко многим, но все же должен реализовать объект и все интерфейсы на нем. Если это имеет смысл, вы можете добавить его к своему ответу (чтобы его было легче читать, чем в комментарии) - person Kendrick; 24.02.2011

Оказывается, на самом деле есть способ сделать это с помощью прослушивателей событий, не открывая соединяемые таблицы. Вам просто нужно сделать так, чтобы ваш прослушиватель событий реализовал IPostCollectionRecreateEventListener или IPreCollectionRecreateEventListener. Основываясь на моем тестировании, эти события запускаются для измененных коллекций всякий раз, когда сеанс сбрасывается. Вот мой код прослушивателя событий для метода 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
            });
        }

Самая сложная часть - узнать название обновляемой коллекции. Вы должны пройти свой путь через PersistenceContext, чтобы получить Persister для коллекции, который дает вам доступ к его метаданным.

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

person AndrewSwerlick    schedule 24.08.2012
comment
Я планирую провести дальнейшие исследования в этой области. - какие-нибудь обновления к этому? - person SamuelKDavis; 18.06.2014
comment
Я фактически свернул проект, используя этот код некоторое время назад. Но мы использовали его несколько месяцев без каких-либо явных проблем. Очевидно, никаких гарантий - person AndrewSwerlick; 18.06.2014