Много-к-одному, все-удалить-сироту, установить свойство равным нулю, но объект не удален
Использование NHibernate v3.0. У меня есть класс, похожий на этот:
class Foo
{
bool barActive;
Bar bar;
}
Экземпляр Bar полностью управляется Foo:
- когда "barActive" имеет значение true, для "bar" устанавливается экземпляр Bar.
- когда "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()
вызывается для каждой обновленной сущности, поэтому не делайте это узким местом).