2010-05-04 115 views
13

我有一個雙向一個一對多的關係的兩個實體:級聯爲delete-all-orphan時,如何在NHibernate中更改孩子的父級?

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

如果您永遠不會從storage1中刪除該盒子,會發生什麼情況?如果只是將其移入存儲2,CurrentStorage是否會被覆蓋?我不確定在會話中storage1已經加載的情況下是否有效。 – dotjoe 2010-05-04 13:52:49

+0

它的工作原理,但直到我刷新我的實體,我會在這兩個存儲中有一個框的副本。我寧願讓我的數據模型正確,而不是依靠NHib在檢索實體時做正確的事情。 – 2010-05-04 18:26:12

+0

是的,如果你從收藏中刪除,孤兒總是會​​被刪除。我認爲在這種情況下,你最好像你說的那樣去做,你會想'cascade =「all」'並從集合中刪除並調用'session.Delete(box)'來刪除一個方框。我不認爲你可以擁有兩全其美的世界:( – dotjoe 2010-05-04 20:56:20

回答

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,即實際上是是孤兒。

+0

尼斯答案有沒有一種方法來指定自定義集合類型使用流利NHibernate的? – MylesRip 2011-04-12 22:28:35

+0

谷歌帶我到這個答案:http://stackoverflow.com/questions/2899576/automapping-custom-collections-with-fluentnhibernate/2900889#2900889 ...雖然我從來沒有嘗試過。如果所有其他的都失敗了,你可以設置你的NHibernate初始化代碼來首先自動映射某些類,然後ClassMap是那些不能被自動映射的類型,最後爲那些不能被ClassMapped的類型編寫* .hbm.xml文件。 – 2011-04-25 20:11:56

+0

試過了,完美無缺!感謝這篇文章! – Falcon 2011-06-29 11:25:15