many-to-one,all-delete orphan,将property设置为null但实体未删除

时间:2011-03-10 20:47:09

标签: nhibernate

使用NHibernate v3.0。我有一个类似的类:

class Foo
{
  bool barActive;
  Bar bar;
}

Bar实例完全由内部管理到Foo:

  1. 当“barActive”为true时,“bar”设置为Bar实例。
  2. 当“barActive”设置为false时,“bar”字段设置为null。
  3. Foo.bar的映射如下:

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

    但是,当“bar”设置为null时,它不会删除数据库中的Bar记录。 Bar是一个在其他地方使用的继承类,所以我不能只将这个字段作为一个组件。

    我原本期望“独特”约束+“删除孤儿”来处理这个问题。我错过了什么,或NHibernate不能透明地处理这个问题?如果它不能,似乎我唯一的选择是引发事件,以便更高级别的范围可以调用ISession.Delete(bar)。

1 个答案:

答案 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可能有bar1bar2!),因此,您需要在_barOrphan中执行删除操作的队列,而不是单个PostFlush()成员。您需要使用泛型和属性选择器(例如bar,而不是硬编码所涉及的类的类型和属性PropertySelector.GetPropertyName<Foo>(foo => foo.bar)的名称,请参阅this link。数据库约束可能有问题,将删除操作移到Interceptor.PreFlush()可能有帮助,但我没有测试它。不要忘记性能影响(例如,为每个更新的实体调用OnFlushDirty(),所以不要使它成为瓶颈)。