2009-11-11 20 views
8

Piszę silnik bloga jako ćwiczenie uczenia się. Wiem, że istnieje wiele silników blogów, ale proszę o mnie ...Jak uniknąć NHibernate.NonUniqueObjectException

Mam podmiot BlogPost, który ma właściwość Znaczniki, które są ilistą tagów z nim związanych. Metoda BlogPost.SetTags (ciąg) dzieli ciąg znaków, tworzy nowe obiekty znaczników o określonej nazwie znacznika i dodaje je do listy. To samo dotyczy BlogPost.AddTag (string tagName).

To, co chciałbym mieć, to to, że kiedy zadzwonię do BlogPost.AddTag ("foo"), gdzie jednostka tagu o nazwie "foo" już istnieje i jest przechowywana w bazie danych, nHibernate właśnie to rozumie i drąży wpis z istniejącym tagiem.

W metodzie BlogRepository.Save() sprawdzam, czy każdy znacznik na liście znaczników już istnieje. Jeśli nie, zapisz go, wywołując TagRepository.Save (tag);

Problem polega na tym, że w poniższym przykładowym kodzie otrzymuję komunikat o błędzie "NHibernate.NonUniqueObjectException: inny obiekt o tej samej wartości identyfikatora był już powiązany z sesją: tag 1, entity: CMS.Core. Model.Tag ", gdy próbuję utrzymać obiekt BlogPost przy użyciu istniejącego tagu. Kiedy utrzymuję obiekt BlogPost, który używa tylko nowych tagów, są one tworzone i wszystko jest w porządku.

Uwaga Używam również nazwy znacznika jako klucza podstawowego w bazie danych dla tabeli bp_Tags. Wydawało się zbędne używanie liczby całkowitej lub GUID PK, gdy tabela przechowuje tylko unikalne nazwy znaczników.

konfiguracja My nHibernate wygląda następująco:

<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: inny obiekt o tej samej wartości identyfikatora był już związany z sesją tagu: 1, jednostki: 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 

...

Wszelkie myśli o tym, co robię źle i jak to naprawić?

+2

Wierzę, że problem ma charakter wewnętrzny i pochodzi od sposobu, można uzyskać listę istniejących tagów do porównaj nowy z i jak przypisujesz istniejący do nowego obiektu BlogPost. Dobrym pomysłem byłoby opublikowanie kodu BlogRepository.Save(); metoda, w której to wszystko się dzieje, abyśmy mogli wykryć problem. – tolism7

Odpowiedz

8

Ponieważ TagName jest identyfikatorem, natrafiasz na mapę tożsamości NHibernate. Jego mapa tożsamości jest już świadoma obiektu o tym samym ID, więc daje ci ten wyjątek.

Być może zechcesz wypróbować coś tam, gdzie patrzysz, czy ten Tag już istnieje w tej sesji, a jeśli tak, to powiąż poprzedni Tag z drugim wpisem.

przykład Psuedo-code:

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

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

Ten blog wpis daje szczegółowe wyjaśnienie: NHibernate - Cross session operations

+0

Dzięki. Skończyło się na zrobieniu czegoś podobnego. Zamiast wykonywać sprawdzanie wywołującego przed dodaniem znacznika lub sprawić, by podmioty treści uzależniały się od repozytorium znaczników w celu wykonania sprawdzenia, sprawdzam w klasie bazowej repozytorium treści. Jeśli tag już istnieje, usuwam "nowy" i dodaje "istniejący". Działa jak urok i nie łamie moich celów zależności. –

4

Sposób, w jaki to robisz, nie jest taki, jakbym to zrobił, ale oto jak rozwiązać problem. Normalnie w Object Oriented Programming kolejne 2 obiekty nie są równe:

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

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

Zrobiłeś 2 oddzielne obiekty z identycznym stanie, ale są to dwa różne obiekty, więc jeśli porównać je do równości to nie są takie same . Oczywiście, jeśli chodzi o NHibernate, te obiekty są w rzeczywistości tym samym bytem.

Rozwiązujemy to dla NHibernate przez przesłonięcie 2 metod klasy Object. GetHashCode() i Equals()

Funkcja GetHashCode() w zasadzie zwraca unikatowy kod skrótu oparty na stanie obiektu. equals() porównuje dwa obiekty dla równości

tak:

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

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

Zasadniczo GetHashCode Łączy typ obiektu oraz nazwę znacznika jako ciąg tj App.Domain.Tag|nameoftag i generuje hashcode dla tego łańcucha

Equals() następnie porównuje wynik GetHashCode() dla pierwszego obiektu z wynikiem GetHashCode() dla drugiego obiektu, aby przetestować pod kątem równości. Jeśli zrobisz to z dwoma obiektami, które zdefiniowaliśmy powyżej, dwa hashcode będą takie same, a zatem porównanie dla Equals() będzie prawdziwe. Kiedy NHibernate przetestuje dwa obiekty pod kątem równości w swoich wewnętrznych działaniach, określi, że są one takie same i powinno rozwiązać twój problem.

+0

Dzięki. Te zmiany zostały przeze mnie nadpisane, ale bardziej podoba mi się twoje implementacje i zostały zaadoptowane. –

+0

Przy okazji, chciałbym usłyszeć twój wkład na temat tego, jak poradzisz sobie z tym scenariuszem w inny sposób. –

+3

@Joe: należy pamiętać, że implementacja 'Equals' w ten sposób nie jest zalecana, ponieważ' GetHashCode' nie zwraca unikatowych wartości mieszania dla różnych instancji. W ten sposób 'Equals' może zwrócić' true' dla obiektów, które są ** nie ** w rzeczywistości równe. Spowoduje to rozwiązanie problemu związanego z wyjątkami, ale możesz na przykład uzyskać błędną instancję zwróconą przez 'Hashtable'. – Groo