2009-11-11 17 views
8

Ich schreibe eine Blog-Engine als Lernübung. Ich weiß, es gibt viele Blog-Engines da draußen, aber ertragen Sie mit mir ...Wie zu vermeiden NHibernate.NonUniqueObjectException

Ich habe eine BlogPost-Entität, die eine Eigenschaft Tags hat, die eine IList der Tags zugeordnet ist. Die Methode BlogPost.SetTags (string) teilt die Zeichenfolge auf, erstellt neue Tag-Objekte mit dem angegebenen Tag-Namen und fügt sie der Liste hinzu. Gleiches für BlogPost.AddTag (string tagName). Wenn ich BlogPost.AddTag ("foo") anrufe, wo eine Tag-Entität mit dem Namen "foo" bereits existiert und in der Datenbank persistiert ist, realisiert nHibernate das und leitet den Post weiter.

Was würde ich gerne geschehen? mit dem vorhandenen Tag.

In der BlogRepository.Save() -Methode überprüfe ich, ob jedes Tag in der Liste Tags bereits existiert. Wenn nicht, speichere ich es mit einem Aufruf von TagRepository.Save (tag);

Das Problem ist, in dem folgenden Beispielcode erhalte ich einen Fehler "NHibernate.NonUniqueObjectException: ein anderes Objekt mit dem gleichen Bezeichner Wert wurde bereits mit der Sitzung verknüpft: Tag 1, der Einheit: CMS.Core. Model.Tag ", wenn ich versuche, ein BlogPost-Objekt mit einem vorhandenen Tag zu speichern. Wenn ich ein BlogPost-Objekt behalte, das nur neue Tags verwendet, werden diese erstellt, und alles ist in Ordnung.

Hinweis: Ich verwende auch den TagName als Primärschlüssel in der Datenbank für die Tabelle bp_Tags. Es erschien überflüssig, eine Ganzzahl oder eine GUID-PK zu verwenden, wenn die Tabelle nur eindeutige Tag-Namen speichert.

Mein nHibernate Konfiguration wie folgt aussieht:

<class name="CMS.Core.Model.Tag,CMS.Core" table="bp_Tags"> 
    <id column="TagName" name="TagName" type="String" unsaved-value=""> 
     <generator class="assigned" /> 
    </id> 
    </class> 

    <class name="CMS.Core.Model.BlogPost,CMS.Core" table="bp_Content"> 
    <id name="Id" column="Id" type="Int32" unsaved-value="0"> 
     <generator class="native"></generator> 
    </id> 
    <property name="SubmittedBy" column="SubmittedBy" type="string" length="256" not-null="true" /> 
    <property name="SubmittedDate" column="SubmittedDate" type="datetime" not-null="true" /> 
    <property name="PublishDate" column="PublishDate" type="datetime" not-null="true" /> 
    ...  
    <bag name="_tagsList" table="bp_Tags_Mappings" lazy="false" cascade="all"> 
     <key column="Target_Id" /> 
     <many-to-many class="CMS.Core.Model.Tag,CMS.Core" column="TagName" lazy="false" /> 
    </bag> 

NHibernate.NonUniqueObjectException: ein anderes Objekt mit dem gleichen Kennungswert wurde bereits mit der Sitzung verknüpft ist: Tag 1 der Einheit: Bariliant.CMS.Core.Model. Tag

BlogPost post, post2; 

    using (UnitOfWork.Start()) 
    { 
     post = BlogPostFactory.CreateBlogPost("test post", "test body"); 
     post.Publish(); 
     BlogRepository.Save(post); 
     UnitOfWork.Current.Flush(); 

     post.SetTags("tag 1, tag 2"); 
     BlogRepository.Save(post); 
     UnitOfWork.Current.Flush(); 
    } 

    using (UnitOfWork.Start()) 
    { 
     post2 = BlogPostFactory.CreateBlogPost("test post2", "test body"); 
     post2.Publish(); 
     BlogRepository.Save(post2); 
     UnitOfWork.Current.Flush(); 

     post2.AddTag("tag 1"); 
     BlogRepository.Save(post2); // throws 

...

Irgendwelche Gedanken auf, was ich falsch mache und wie man es beheben?

+2

Ich glaube, das Problem intern ist und kommt von der Art und Weise Sie die Liste der vorhandenen Tags erhalten zu Vergleichen Sie den neuen mit und wie Sie dem neuen BlogPost-Objekt einen vorhandenen zuweisen. Es wäre eine gute Idee, den Code des BlogRepository.Save(); Methode, bei der all dies geschieht, damit wir das Problem erkennen können. – tolism7

Antwort

8

Da TagName die ID ist, wird die Identitätskarte von NHibernate angezeigt. Seine Identitätskarte kennt bereits ein Objekt mit derselben ID, daher gibt es diese Ausnahme.

Sie können versuchen, etwas, wo Sie schauen, ob dieses Tag bereits in dieser Sitzung vorhanden ist, und wenn ja, dann verknüpfen Sie das bestehende Tag mit dem 2. Post.

Psuedo-Codebeispiel:

var tag = session.Get<Tag>("Tag 1"); 

if (tag != null) 
{ 
    post.AddTag(tag); 
} 
else 
{ 
    post.AddTag(new Tag("Tag 1")); 
} 

Dieser Blog-Posting wird Ihnen eine detaillierte Erklärung: NHibernate - Cross session operations

+0

Danke. Ich habe etwas ähnliches gemacht. Anstatt den Aufrufer zu überprüfen, bevor das Tag hinzugefügt wird, oder die Content-Entities für die Überprüfung eine Abhängigkeit vom Tag-Repository festlegen zu lassen, führe ich die Überprüfung in der Content-Repository-Basisklasse durch. Wenn das Tag bereits existiert, entferne ich das "neue" und füge das "existierende" zurück. Funktioniert wie ein Zauber und bricht meine Abhängigkeitsziele nicht. –

4

Die Art, wie Sie vorgehen, ist nicht die Art, wie ich es tun würde, aber hier ist, wie Sie Ihr Problem lösen. Normalerweise in der objektorientierten Programmierung die folgenden 2-Objekte sind nicht gleich:

var object1 = new Tag("hello"); 
var object2 = new Tag("hello"); 

var areSame = (object1 == object2); // false 

Sie gemacht haben 2 getrennte Objekte mit gleichem Zustand, aber sie sind zwei verschiedene Objekte so, wenn man sich auf Gleichheit zu vergleichen, dann ist sie nicht gleich sind . Wenn es um NHibernate geht, sind diese Objekte eindeutig die gleiche Entität.

Wir lösen dies für NHibernate durch Überschreiben von 2 Methoden der Objektklasse. GetHashCode() und Equals()

GetHashCode() gibt grundsätzlich einen eindeutigen Hashcode basierend auf dem Status eines Objekts zurück. equals() vergleicht zwei Objekte auf Gleichheit

wie folgt aus:

public override int GetHashCode() 
{ 
    return (this.GetType() + "|" + _tagName).GetHashCode(); 
} 

public override bool Equals(object obj) 
{ 
    return this.GetHashCode() == obj.GetHashCode(); 
} 

Grundsätzlich GetHashCode verkettet den Objekttyp und den Namen des Tags als String dh App.Domain.Tag|nameoftag und erzeugt einen Hash-Code für diese Saite

Equals() vergleicht dann das GetHashCode() - Ergebnis für das erste Objekt mit dem GetHashCode() - Ergebnis, damit das zweite Objekt auf Gleichheit testet. Wenn Sie dies mit den beiden oben definierten Objekten tun, sind die beiden Hashcodes identisch und daher ist der Vergleich für Equals() wahr. Wenn NHibernate die beiden Objekte auf Gleichheit in ihren Innenfunktionen testet, stellt es fest, dass sie identisch sind, und es sollte Ihr Problem lösen.

+0

Danke. Ich habe diese übersteuert, aber ich mag Ihre Implementierungen besser und habe sie übernommen. –

+0

Übrigens, ich würde gerne Ihre Eingabe darüber hören, wie Sie dieses Szenario anders handhaben würden. –

+3

@Joe: Beachten Sie, dass die Implementierung von 'Equals' auf diese Weise nicht empfohlen wird, da' GetHashCode' keine eindeutigen Hashwerte für verschiedene Instanzen zurückgibt. Auf diese Weise kann "Equals" für Objekte, die ** nicht ** tatsächlich gleich sind, "true" zurückgeben. Dadurch wird das Problem mit der Ausnahme behoben, aber Sie können beispielsweise leicht eine falsche Instanz von einer Hashtable erhalten. – Groo

Verwandte Themen