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

...
  
    
    
  

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

Popularne posty