14

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

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我知道一种可能的解决方案是将级联更改为 just all,但随后我失去了让 NHibernate 自动删除 a 的能力Box,只需将其从 a 中删除Storage而不将其与另一个重新关联。或者这是唯一的方法,我必须手动调用Session.Delete盒子才能删除它?

4

1 回答 1

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认为是孤儿的 es 列表,然后将其过滤到仅实际是孤儿的Boxes集合。

于 2011-04-11T15:32:04.730 回答