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.
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.
0 komentarze:
Prześlij komentarz