2010-05-04 13 views
13

Ich habe zwei Entitäten in einer bidirektionalen Eins-zu-viele-Beziehung:Wie ändere ich das übergeordnete Element eines Kindes in NHibernate, wenn cascade delete-all-orphan ist?

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

public class Box 
{ 
    public Storage CurrentStorage { get; set; } 
} 

und die Abbildung:

<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> 

A Storage viele Boxes haben kann, aber ein Box kann nur gehören zu eine Storage. Ich habe sie so zugeordnet, dass die Eins-zu-Viele eine Kaskade von all-delete-orphan hat.

Mein Problem entsteht, wenn ich versuche, eine Box Storage zu ändern. Unter der Annahme, ich lief bereits diesen Code:

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

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

Der folgende Code gibt mir eine Ausnahme:

// 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 

ich die folgende Ausnahme erhalten:

NHibernate.ObjectDeletedException: gelöschtes Objekt wäre erneut gespeichert durch Kaskade (gelöschtes Objekt aus Assoziationen entfernen)

Diese Ausnahme tritt auf, weil ich die Kaskade auf all-delete-orphan eingestellt habe. Die erste Storage erkannte, dass ich die Box aus ihrer Sammlung entfernt und markiert sie zum Löschen. Jedoch, wenn ich es der zweiten Storage (in der gleichen Sitzung) hinzugefügt, versucht es, die Box erneut zu speichern und die ObjectDeletedException wird geworfen.

Meine Frage ist, wie bekomme ich die Box, um ihre Eltern Storage ohne diese Ausnahme zu ändern? Ich weiß, eine mögliche Lösung ist es, die Kaskade zu nur all zu ändern, aber dann verliere ich die Fähigkeit, NHibernate automatisch löschen Box durch einfaches Entfernen von einer Storage und nicht neu assoziieren es mit einem anderen. Oder ist dies der einzige Weg, um es zu tun und ich muss Session.Delete manuell auf der Box anrufen, um es zu entfernen?

+0

Was passiert, wenn Sie die Box nie aus storage1 entfernen? Wenn Sie es nur in storage2 verschieben, wird der CurrentStorage nicht überschrieben? Ich bin nicht sicher, ob das funktioniert, wenn storage1 bereits in der Sitzung geladen ist. – dotjoe

+0

Es funktioniert, aber bis ich meine Entitäten aktualisieren, werde ich eine Kopie der Box in beiden Speichern haben. Ich hätte lieber, dass mein Datenmodell korrekt ist, anstatt darauf zu vertrauen, dass NHib das Richtige tut, wenn ich die Entität abrufe. –

+0

Ah ja, die verwaiste wird immer gelöscht, wenn Sie aus der Sammlung entfernen. Ich denke, in diesem Fall würden Sie tun wollen, was Sie am Ende gesagt haben, Sie würden 'cascade =" all "' löschen und eine Box löschen, indem Sie aus der Sammlung entfernen und 'session.Delete (box)' aufrufen. Ich glaube nicht, dass Sie das Beste aus beiden Welten haben können :( – dotjoe

Antwort

9

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

Im Grunde ist es läuft darauf hinaus, diese nach unten ... Sie müssen eine benutzerdefinierte Sammlung Typ für NHibernate definieren, die neu definiert, was es bedeutet, eine Waise zu sein. Das Standardverhalten von NHibernate besteht darin, genau das zu tun, was Sie entdeckt haben - ein Kind als verwaist zu betrachten, wenn es aus dem Elternteil entfernt wurde. Stattdessen muss NHibernate das untergeordnete Element testen, um festzustellen, ob es einem neuen übergeordneten Element zugewiesen wurde. NHibernate führt dies standardmäßig nicht durch, da es zusätzliche Informationen über das Eins-zu-Viele-Mapping erfordern würde - es müsste den Namen der entsprechenden Viele-zu-Eins-Eigenschaft auf dem Kind kennen.

Ihre Storage Mapping wie folgt aussehen ändern:

<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> 

definieren einen neuen Typ namens StorageBoxBag (Anmerkung - dieser Code geschrieben gegen NHibernate 2.1 - wenn Sie NH3 verwenden Sie dieses Bit haben zwicken können):

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; 
    } 
} 

...und ein neuer Typ namens 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; 
    } 
} 

Die GetOrphans Methode ist, wo die Magie passiert. Wir fragen NHibernate für die Liste von Box es, dass es denkt, sind Waisen, und dann filtern, dass nur die Menge von es eigentlich sind Waisen.

+0

Schöne Antwort, Daniel! Gibt es eine Möglichkeit, den Brauch anzugeben Sammlungstyp mit Fluent NHibernate? – MylesRip

+0

Google führte mich zu dieser Antwort: http://StackOverflow.com/Questions/2899576/Automapping-Custom-Collections-with-Fluenthnibernate/2900889#2900889 ... obwohl ich es noch nie versucht habe Wenn alles andere fehlschlägt, können Sie den Initialisierungscode für NHibernate so einrichten, dass zuerst bestimmte Klassen automatisch zugeordnet werden, dann ClassMap diejenigen, die nicht automatisch zugeordnet werden können, und schließlich * .hbm.xml-Dateien für diejenigen schreiben, die nicht ClassMapped sein können –

+0

Versuchte es, funktioniert perfekt! Danke für diesen Beitrag! – Falcon