我有两个实体处于双向一对多关系中:
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
才能将其删除?
答案 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的集合>是孤儿。