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

Dan*_* T. 13 nhibernate cascade nhibernate-mapping

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

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

public class Box
{
    public Storage CurrentStorage { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

和映射:

<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>
Run Code Online (Sandbox Code Playgroud)

A 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);
Run Code Online (Sandbox Code Playgroud)

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

// 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
Run Code Online (Sandbox Code Playgroud)

我得到以下异常:

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

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

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

Dan*_*ing 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>
Run Code Online (Sandbox Code Playgroud)

定义一个名为的新类型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;
    }
}
Run Code Online (Sandbox Code Playgroud)

......以及一个名为的新类型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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这种GetOrphans方法是魔术发生的地方.我们向NHibernate询问Box认为是孤儿的es 列表,然后将其过滤到只有实际上是孤儿的Boxes 集合.