nHibernate associations introduction
Mapowanie relacji pomiędzy tabelami na asocjacje klas to dusza ORMow.
To przy okazji najbardziej problematyczne zagadnienie.
Zagadnienie relacji jest szerokie jak rzeka i nie sposób opisać wszystkich przypadków. Najważniejsze z mojego punktu widzenia jest jednak zrozumieć sedno sprawy, zrozumienia tego brakowało przy moich pierwszych próbach i przyznam że zjadło mi to wiele godzin.
Dzięki zastosowaniu klas POCO możemy posługiwać się niedostępnymi dla modelu DataSetów kolekcjami i powiązaniami między nimi. Musimy jednak pamiętać o tym że:
asocjacje w nHibernate są z natury skierowane jednokierunkowe.
Powiązanie powyższych klas to dla nHibernate dwa zupełnie osobne powiązania Bid -> Item oraz Item -> Bid.
To dalej oznacza że przypisania bid.Item = item oraz item.Bids.Add(bid) to dwie oddzielne operacje.
Modelując powyższe klasy będziemy mieli taki oto przykład.
Na początek proste jednokierunkowe powiązaniemany-to-one
Do czasu kiedy powiązania pozostają jedno -kierunkowe bardzo prosto korzysta się z nHibernata nie zdając sobie nawet sprawy z tego co może się dziać gdzie głębiej.
Ale czy nie było by użytecznym mieć również powiązanie w drugą stronę? Oj było by, a w aplikacjach opartych na czysto microsoftowych kontrolkach aspowych nie wyobrażam sobie żeby można było nie mieć dwukierunkowych powiązań.
Tak więc stwórzmy powiązanie w drugą stronę one-to-many. jest to kolumna klucza obcego w tabeli BID.
Dla obu asocjacji została określona ta sama kolumna klucza obcego.
Teraz mamy dwie różne jednokierunkowe acjocjacje do tego samego klucza obcego.
I to jest oczywiście problem jeśli spróbujemy zrobić jakąś operację zapewne dostaniemy błąd z serii "Próba modyfikacji obiektu przypisanego do innej sesji". Wynika to z faktu że mając dwie różne asocjacje mamy dwie różne reprezentacje tej samej wartości klucza w pamięci - a co za tym idzie dwie różne zmiany i konflikt.
nHibernate sam z siebie NIE wykrywa faktu że te dwie zmiany dotyczą tej samej kolumny w bazie danych. Musimy powiedzieć nHibernatowi że te dwa powiązania jednokierunkowe to jedno powiązanie dwukierunkowe. A wystarczy do tego atrybut INVERSE
Atrybut CASCADE mówi nHibernatowi aby zapisał do bazy każdy nowy obiekt Bid jeśli Bid jest 'składnikiem' Item.
To przy okazji najbardziej problematyczne zagadnienie.
Zagadnienie relacji jest szerokie jak rzeka i nie sposób opisać wszystkich przypadków. Najważniejsze z mojego punktu widzenia jest jednak zrozumieć sedno sprawy, zrozumienia tego brakowało przy moich pierwszych próbach i przyznam że zjadło mi to wiele godzin.
Dzięki zastosowaniu klas POCO możemy posługiwać się niedostępnymi dla modelu DataSetów kolekcjami i powiązaniami między nimi. Musimy jednak pamiętać o tym że:
asocjacje w nHibernate są z natury skierowane jednokierunkowe.
Powiązanie powyższych klas to dla nHibernate dwa zupełnie osobne powiązania Bid -> Item oraz Item -> Bid.
To dalej oznacza że przypisania bid.Item = item oraz item.Bids.Add(bid) to dwie oddzielne operacje.
Modelując powyższe klasy będziemy mieli taki oto przykład.
Na początek proste jednokierunkowe powiązaniemany-to-one
public class Bid { ... private Item item; public Item Item { get { return item; } set { item = value; } } ... }Oraz plik XML
Kolumna ITEM_ID tabeli BID jest kluczem obcym do klucza głównego tabeli ITEM....
Do czasu kiedy powiązania pozostają jedno -kierunkowe bardzo prosto korzysta się z nHibernata nie zdając sobie nawet sprawy z tego co może się dziać gdzie głębiej.
Ale czy nie było by użytecznym mieć również powiązanie w drugą stronę? Oj było by, a w aplikacjach opartych na czysto microsoftowych kontrolkach aspowych nie wyobrażam sobie żeby można było nie mieć dwukierunkowych powiązań.
Tak więc stwórzmy powiązanie w drugą stronę one-to-many.
public class Item { ... private ISet bids = new HashedSet(); public ISet Bids { get { return bids; } set { bids = value; } } public void AddBid(Bid bid) { bid.Item = this; bids.Add(bid); } ... }
Mapowanie kolumny określone elementem...
Dla obu asocjacji została określona ta sama kolumna klucza obcego.
Teraz mamy dwie różne jednokierunkowe acjocjacje do tego samego klucza obcego.
I to jest oczywiście problem jeśli spróbujemy zrobić jakąś operację zapewne dostaniemy błąd z serii "Próba modyfikacji obiektu przypisanego do innej sesji". Wynika to z faktu że mając dwie różne asocjacje mamy dwie różne reprezentacje tej samej wartości klucza w pamięci - a co za tym idzie dwie różne zmiany i konflikt.
nHibernate sam z siebie NIE wykrywa faktu że te dwie zmiany dotyczą tej samej kolumny w bazie danych. Musimy powiedzieć nHibernatowi że te dwa powiązania jednokierunkowe to jedno powiązanie dwukierunkowe. A wystarczy do tego atrybut INVERSE
Deklarując atrybut INVERS="true" (domyślnie oczywiście false) wyraźnie wskazujemy która końcówka tej asocjacji ma być synchronizowana z bazą danych. W tym przypadku mówimy że do bazy propagowane mają być zmiany w obiekcie Item natomiast zmiany w kolekcji bids mają być ignorowane. Zapis item.Bids.Add(bid) nie będzie miał żadnego efektu w składnicy danych, ponieważ domyślną wartością kolejnego atrybutu jest "none" (chyba że wywołamy sobie na tym obiekcie ISession.Save())....
Aby zmiany dokonane w Bids były propagowane do bazy musimy ustawić atrybut CASCADE="save-update" (możliwe jest również "all-delete-orphan" przydatne przy relacjach parent-child)....
Atrybut CASCADE mówi nHibernatowi aby zapisał do bazy każdy nowy obiekt Bid jeśli Bid jest 'składnikiem' Item.
Komentarze
Prześlij komentarz