当级联删除所有孤儿时,如何在 NHibernate 中更改孩子的父级? [英] How do I change a child's parent in NHibernate when cascade is delete-all-orphan?

查看:18
本文介绍了当级联删除所有孤儿时,如何在 NHibernate 中更改孩子的父级?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have two entities in a bi-directional one-to-many relationship:

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 级联.

A Storage can have many Boxes, but a Box can only belong to one Storage. I have them mapped so that the one-to-many has a cascade of all-delete-orphan.

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

My problem arises when I try to change a Box's Storage. Assuming I already ran this code:

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

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

下面的代码会给我一个例外:

The following code will give me an exception:

// 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 : 已删除的对象将通过级联重新保存(从关联中删除已删除的对象)

NHibernate.ObjectDeletedException : deleted object would be re-saved by cascade (remove deleted object from associations)

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

This exception occurs because I have the cascade set to all-delete-orphan. The first Storage detected that I removed the Box from its collection and marks it for deletion. However, when I added it to the second Storage (in the same session), it attempts to save the box again and the ObjectDeletedException is thrown.

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

My question is, how do I get the Box to change its parent Storage without encountering this exception? I know one possible solution is to change the cascade to just all, but then I lose the ability to have NHibernate automatically delete a Box by simply removing it from a Storage and not re-associating it with another one. Or is this the only way to do it and I have to manually call Session.Delete on the box in order to remove it?

推荐答案

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

基本上,它归结为...您需要为 NHibernate 定义一个自定义集合类型,重新定义成为孤儿意味着什么.NHibernate 的默认行为是按照您发现的方式进行操作 - 如果从父级中删除了子级,则将其视为孤儿.相反,您需要 NHibernate 来测试子项以查看它是否已分配给新的父项.NHibernate 默认不这样做,因为它需要关于一对多映射的额外信息——它需要知道子节点上对应的多对一属性的名称.

Basically, it boils down to this... You need to define a custom collection type for NHibernate that re-defines what it means to be an orphan. NHibernate's default behavior is to do just as you discovered - to consider a child to be orphaned if it has been removed from the parent. Instead, you need NHibernate to test the child to see if it has been assigned to a new parent. NHibernate does not do this by default because it would require additional information on the one-to-many mapping - it would need to know the name of the corresponding many-to-one property on the child.

将您的 Storage 映射更改为如下所示:

Change your Storage mapping to look like this:

<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,则可能需要稍微调整一下):

Define a new type named StorageBoxBag (note - this code is written against NHibernate 2.1 - if you are using NH3 you may have to tweak this a bit):

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 的新类型:

... and a new type named 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 提供它认为是孤儿的 Boxes 列表,然后将其过滤到仅 Boxes 的集合 实际上是孤儿.

The GetOrphans method is where the magic happens. We ask NHibernate for the list of Boxes that it thinks are orphans, and then filter that down to only the set of Boxes that actually are orphans.

这篇关于当级联删除所有孤儿时,如何在 NHibernate 中更改孩子的父级?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆