使用NHibernate v3.0。我有一个类似的类:
class Foo
{
bool barActive;
Bar bar;
}
Bar实例完全由内部管理到Foo:
Foo.bar的映射如下:
<many-to-one name="bar" column="BarId" cascade="all-delete-orphan" unique="true" />
但是,当“bar”设置为null时,它不会删除数据库中的Bar记录。 Bar是一个在其他地方使用的继承类,所以我不能只将这个字段作为一个组件。
我原本期望“独特”约束+“删除孤儿”来处理这个问题。我错过了什么,或NHibernate不能透明地处理这个问题?如果它不能,似乎我唯一的选择是引发事件,以便更高级别的范围可以调用ISession.Delete(bar)。
答案 0 :(得分:0)
我有一个解决方法,会自动删除孤儿。我相信它应该适用于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)
的名称,请参阅this link。数据库约束可能有问题,将删除操作移到Interceptor.PreFlush()
可能有帮助,但我没有测试它。不要忘记性能影响(例如,为每个更新的实体调用OnFlushDirty()
,所以不要使它成为瓶颈)。