当cascade是delete-all-orphan时,如何更改NHibernate中孩子的父级?

时间:2010-05-04 08:59:25

标签: nhibernate nhibernate-mapping cascade

我有两个实体处于双向一对多关系中:

public class Storage
{
    public IList<Box> Boxes { get; set; }
}

public class Box
{
    public Storage CurrentStorage { get; set; }
}

映射:

<class name="Storage">
    <bag name="Boxes" cascade="all-delete-orphan" inverse="true">
        <key column="Storage_Id" />
        <one-to-many class="Box" />
    </bag>
</class>

<class name="Box">
    <many-to-one name="CurrentStorage" column="Storage_Id" />
</class>

Storage可以有多个Boxes,但Box只能属于一个Storage。 我对它们进行了映射,以便一对多具有all-delete-orphan的级联。

当我尝试更改Box Storage时出现问题。假设我已经运行了这段代码:

var storage1 = new Storage();
var storage2 = new Storage();
storage1.Boxes.Add(new Box());

Session.Create(storage1);
Session.Create(storage2);

以下代码会给我一个例外:

// get the first and only box in the DB
var existingBox = Database.GetBox().First();

// remove the box from storage1
existingBox.CurrentStorage.Boxes.Remove(existingBox);

// add the box to storage2 after it's been removed from storage1
var storage2 = Database.GetStorage().Second();
storage2.Boxes.Add(existingBox);

Session.Flush(); // commit changes to DB

我得到以下异常:

  

NHibernate.ObjectDeletedException:已删除的对象将通过级联重新保存(从关联中删除已删除的对象)

发生此异常是因为我将级联设置为all-delete-orphan。第一个Storage检测到我从其集合中删除了Box并将其标记为删除。但是,当我将其添加到第二个Storage(在同一会话中)时,它会再次尝试保存该框并抛出ObjectDeletedException

我的问题是,如何在不遇到此异常的情况下让Box更改其父Storage?我知道一种可能的解决方案是将级联更改为all,但是我失去了让NHibernate自动删除Box的能力,只需将其从Storage删除即可将它与另一个相关联。或者这是唯一的方法,我必须在框中手动调用Session.Delete才能将其删除?

1 个答案:

答案 0 :(得分:9)

请参阅http://fabiomaulo.blogspot.com/2009/09/nhibernate-tree-re-parenting.html

基本上,它可以归结为...你需要为NHibernate定义一个自定义集合类型,重新定义它是一个孤儿的含义。 NHibernate的默认行为就像你发现的一样 - 考虑一个孩子如果已从父母中删除而成为孤儿。相反,您需要NHibernate来测试子节点以查看它是否已分配给新父节点。 NHibernate默认不会这样做,因为它需要有关一对多映射的其他信息 - 它需要知道子对应的多对一属性的名称。

Storage映射更改为如下所示:

<class name="Storage">
    <bag name="Boxes" cascade="all-delete-orphan" inverse="true" collection-type="StorageBoxBag">
        <key column="Storage_Id" />
        <one-to-many class="Box" />
    </bag>
</class>

定义一个名为StorageBoxBag的新类型(注意 - 此代码是针对NHibernate 2.1编写的 - 如果您使用的是NH3,则可能需要稍微调整一下):

public class StorageBoxBag : IUserCollectionType
{
    public object Instantiate(int anticipatedSize)
    {
        return new List<Box>();
    }

    public IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister)
    {
        return new PersistentStorageBoxBag(session);
    }

    public IPersistentCollection Wrap(ISessionImplementor session, object collection)
    {
        return new PersistentStorageBoxBag(session, (IList<Box>)collection);
    }

    public IEnumerable GetElements(object collection)
    {
        return (IEnumerable)collection;
    }

    public bool Contains(object collection, object entity)
    {
        return ((IList<Box>)collection).Contains((Box)entity);
    }

    public object IndexOf(object collection, object entity)
    {
        return ((IList<Box>) collection).IndexOf((Box) entity);
    }

    public object ReplaceElements(object original, object target, ICollectionPersister persister, object owner, IDictionary copyCache, ISessionImplementor session)
    {
        var result = (IList<Box>)target;
        result.Clear();

        foreach (var box in (IEnumerable)original)
            result.Add((Box)box);

        return result;
    }
}

...还有一个名为PersistentStorageBoxBag的新类型:

public class PersistentStorageBoxBag: PersistentGenericBag<Box>
{
    public PersistentStorageBoxBag(ISessionImplementor session)
        : base(session)
    {
    }

    public PersistentStorageBoxBag(ISessionImplementor session, ICollection<Box> original)
        : base(session, original)
    {
    }

    public override ICollection GetOrphans(object snapshot, string entityName)
    {
        var orphans = base.GetOrphans(snapshot, entityName)
            .Cast<Box>()
            .Where(b => ReferenceEquals(null, b.CurrentStorage))
            .ToArray();

        return orphans;
    }
}

GetOrphans方法是神奇发生的地方。我们向NHibernate询问认为Box列表是否是孤儿,然后将其过滤到只有实际Box es的集合>是孤儿。