Много-к-одному, все-удалить-сироту, установить свойство равным нулю, но объект не удален

Использование NHibernate v3.0. У меня есть класс, похожий на этот:

class Foo
{
  bool barActive;
  Bar bar;
}

Экземпляр Bar полностью управляется Foo:

  1. когда "barActive" имеет значение true, для "bar" устанавливается экземпляр Bar.
  2. когда "barActive" имеет значение false, поле "bar" имеет значение null.

Foo.bar отображается так:

<many-to-one name="bar" column="BarId" cascade="all-delete-orphan" unique="true" />

Однако, когда "bar" имеет значение null, он не удаляет запись Bar в базе данных. Bar - это унаследованный класс, который также используется в других местах, поэтому я не могу просто сделать это поле компонентом.

Я ожидал, что "уникальное" ограничение + "удалить-сирота" справится с этим. Я что-то упустил, или NHibernate не может справиться с этим прозрачно? Если это невозможно, кажется, что мой единственный вариант - вызвать событие, чтобы область более высокого уровня могла вызвать ISession.Delete(bar).

1 ответ

У меня есть обходной путь, который автоматически удалит сироту. Я считаю, что это должно работать для NHibernate версии 3 и выше. Он использует перехватчик - в основном объект, который обрабатывает различные связанные с сеансом события. Когда он обнаруживает операцию обновления на Foo, это добавит явное удаление для осиротевших Bar,

using System;
using System.Collections;
using NHibernate;
using NHibernate.Type;

class Interceptor : EmptyInterceptor
{
    private ISession _session;
    private Bar _barOrphan;

    public override void SetSession(ISession session)
    {
        base.SetSession(session);
        _session = session;
    }

    public override bool OnFlushDirty(object entity, object id, object[] currentStates, object[] previousStates, string[] propertyNames, IType[] types)
    {
        if (entity.GetType() != typeof(Foo)) return;

        for (var i = 0; i < propertyNames.Length; i++)
        {
            if (!StringComparer.Ordinal.Equals(propertyNames[i], "bar")) continue;

            object previousState = previousStates[i];
            if (currentStates[i] != previousState)
            {
                _barOrphan = (Bar) previousState;
            }
            break;
        }
    }

    public override void PostFlush(ICollection entities)
    {
        if (_barOrphan == null) return;

        _session.Delete(_barOrphan);
        _barOrphan = null;

        _session.Flush();
    }
}

Теперь, когда вы открываете сеанс NHibernate, вы должны использовать одну из перегрузок, которая принимает экземпляр перехватчика в качестве аргумента, например

using (ISession session = YourSessionFactoryGoesHere.OpenSession(new Interceptor()))
{
    ...
}

Обратите внимание, что это всего лишь черновик, чтобы объяснить концепцию (надеюсь, я не испортил код, так как переписывал его;-). В реальном сценарии использования вам, возможно, придется иметь дело с возможными множественными сиротами, созданными в одной единице работы (событие на том же объекте, например Foo мог бы иметь bar1 а также bar2!) так что вместо одного _barOrphan член вам понадобится очередь действий удаления для выполнения в PostFlush(), Вместо жесткого кодирования типы участвующих классов и имя свойства bar, вы захотите использовать дженерики и селектор свойств (например, PropertySelector.GetPropertyName<Foo>(foo => foo.bar)смотрите по этой ссылке. Проблема с БД может быть связана с перемещением операции удаления в Interceptor.PreFlush() может помочь, но я не проверял это. Не забывайте о влиянии на производительность (например, OnFlushDirty() вызывается для каждой обновленной сущности, поэтому не делайте это узким местом).

Другие вопросы по тегам