Tworząc standardowe mapowania NHibernate za pomocą plików XML trzeba pamiętać o kilku rzeczach, które bardzo łatwo przeoczyć. Po ich przeoczeniu program nie działa i wywala błędy, a ich treść nie zawsze nakierowuje na przyczynę. Najlepszym tego przykładem jest chyba konieczność oznaczania plików mapowań jako "embedded resource".
Fluent NHibernate znacznie upraszcza sprawę, ale i przy nim trzeba uważać. Czasami dostajemy wyjątek niekoniecznie mówiący prosto z mostu o co chodzi. Jak ciężarna 15-latka, która w jakiś sposób musi oznajmić niczego się niespodziewającym rodzicom, że wkrótce hierarchia dziedziczenia kochanej rodziny zwiększy poziom o 1... a nie bardzo wie jak to zrobić.
Zaczniemy zabawę od takiej klasy:
1: public class Post
2: {
3: public int Id { get; set; }
4: public string Title { get; set; }
5: public User Author { get; set; }
6: }
z takim mapowaniem:
1: class PostMap : ClassMap<Post>
2: {
3: public PostMap()
4: {
5: Map(x => x.Id);
6: Map(x => x.Title);
7: Map(x => x.Author);
8: }
9: }
Jak można się domyślić, powyższy kod jest zepsuty jak zęby Baltazara Gąbki. Taka linijka dowiedzie nam racji w tej kwestii:
1: session.Save(new Post() { Title = "abc", Author = user });
Naprawmy go krok po kroku, walcząc z napotykanymi podczas uruchomienia błędami tak dzielnie jak dzielnie stare baby walczą na pielgrzymkach z TVN24. Beret na łysy czerep, pieluchomajtki pod flanelową kieckę i Allah Akbar!!!
Pierwszy problem:
NHibernate.MappingException: No persister for: Procent.Samples.Entities.Post
Z treści błędu dowiadujemy się, że system nie ma persistera dla klasy posta:). Po chwili namysłu olśnienie: klasa Post jest widoczna, ale nie wiadomo co z nią tak naprawdę trzeba zrobić. Czyli: NHibernate nie potrafi dotrzeć do jej mapowania. Czyli: klasa PostMap w kontekście NH nie istnieje. Dłoń z plaskiem uderza w czoło: oczywiście! Przecież domyślnie klasom nadawany jest modyfikator internal, a żeby NH mógł w ogóle dostrzec PostMap, musi być ona public. Mała zmiana dla człowieka, brak zmiany dla ludzkości:
1: public class PostMap : ClassMap<Post>
2: {
Tip 1: klasy mapowań muszą być PUBLICZNE.
Dumni jednak jeszcze być nie możemy, gdyż oto oczom naszym ukazuje się błąd kolejny:
System.Xml.Schema.XmlSchemaValidationException: The element 'class' in namespace 'urn:nhibernate-mapping-2.2' has invalid child element 'property' in namespace 'urn:nhibernate-mapping-2.2'. List of possible elements expected: 'meta, subselect, cache, synchronize, comment, tuplizer, id, composite-id' in namespace 'urn:nhibernate-mapping-2.2'.
Hę??
Pamiętać należy, że Fluent NH to tylko ładna nakładka na konfigurację NHibernate. Samo NH nic nie wie o takiej bibliotece i bezwzględnie oczekuje pliku XML. Fluent robi więc dokładnie to: generuje odpowiedni plik XML. Tak więc rozwiązania błędów zgłaszanych przez parser XML można spokojnie szukać w dokumentacji NH. W tym konkretnym przypadku odpowiedź zawarta jest w sekcji 5.1.4. id. A dokładniej ten fragment: "Mapped classes must declare the primary key column of the database table".
I oczywiście wszystko jest już jasne. Z rozpędu bądź niewiedzy potraktowaliśmy wszystkie właściwości jak zwykłe kolumny. Po niewielkiej zmianie zmianie idziemy dalej:
1: public PostMap()
2: {
3: Id(x => x.Id);
Tip 2: każda mapowana tabela musi mieć zdefiniowany klucz główny (Id() lub CompositeId())
Dwa błędy to nie tak strasznie. Ruszajmy zatem na trzeci z kolei:
NHibernate.MappingException: Could not determine type for: Procent.Samples.Entities.User, Procent.Samples.NH, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null, for columns: NHibernate.Mapping.Column(Author)
Tym razem coś nie tak z uzytkownikiem... Na wszelki wypadek sprawdzamy poprzednie przypadki w jego mapowaniu, ale wszystko jest OK: modyfikator publiczne i ma Id(). Ale zaraz... tutaj mamy dokładnie wskazane miejsce błędu: kolumna Author w mapowaniu Post. Nie chodzi więc o samą klasę użytkownika, po prostu zrobiliśmy coś nie tak w PostMap:
Ano tak... przecież Author to nie jest zwykła kolumna. To jest relacja z tabelą użytkowników! Ponownie: zmiana jednej linijki:
1: References(x => x.Author);
i błąd znika.
Tip 3: uwaga na Map() i References() - nie można ich stosować zamiennie :) a w szale mapowania łatwo się zagapić.
Wydawać by się mogło, że trzy błędy to już nie tak mało w kodzie o objętości 10 linijek. A jednak...
"NHibernate.InvalidProxyTypeException: The following types may not be used as proxies:
Procent.Samples.Entities.Post: method get_Id should be 'public/protected virtual' or 'protected internal virtual'
Procent.Samples.Entities.Post: method set_Id should be 'public/protected virtual' or 'protected internal virtual'"
Jaka miła niespodzianka, wreszcie dostajemy wyjątek który wyraźnie wskazuje co jest nie tak. Bez ceregieli oznaczamy zatem wszystkie mapowane właściwości jako wirtualne:
1: public class Post
2: {
3: public virtual int Id { get; set; }
4: public virtual string Title { get; set; }
5: public virtual User Author { get; set; }
6: }
Tip 4: Wszystkie właściwości i metody w mapowanych klasach muszą być wirtualne.
Przedstawione pomyłki są bardzo podstawowe, ale pokazują że nawet na najprostszym poziomie trudności na osobę grającą w NHibernate czyha kilka pułapek. Wkrótce więcej wskazówek.