Fluent Nhibernate Rocks!!!
Marzec 18, 2009
W ramach wolnych chwil w drodze do pracy staram się zapoznać z nowymi frameworkami. Jedną z tych nowinek jest open sourcowy projekt Fluent NHibernate. Jak sama nazwa wskazuje jest to projekt powiązany z bardzo znanym mapperem obiektowo – relacyjnym NHibernate. Hasło fluent z kolei tyczy się ostatnimi czasy bardzo popularnego wzorca Fluent Interface. Wzorzec ten oparty jest przede wszystkim na łańcuchowym wywoływaniu metod API, poprzez które możliwe jest stworzenie składni języka zwanego powszechnie DSL (Domain Specific Language). Język DSL pozwala na pisanie bardziej „wyrazistego” kodu źródłowego programu, koncentrującego się na konkretnej dziedzinie, przez co osoba znająca daną domenę jest w stanie bardzo szybko pojąc jaka logika się kryje za tym kodem.
W takim razie warto sobie zadać na wstępie pytanie jakiej dziedziny dotyczy Fluent NHibernate? Wydaje mi się, że dla tych osób, które miały do czynienia z mapperem NHibernate pytanie to nie jest trudne dlatego, że zapewne nieraz musiały pokonywać problemy wywołane poprzez błędnie zdefiniowane metadane mapowanie obiektów domenowych. W NHibernacie metadane domyślnie definiowane są w postaci XML’a, jedyną alternatywą do tej pory było stosowanie atrybutów, którymi należy dekorować klasy domenowe.
Jedno i drugie rozwiązanie ma swoje wady i zalety. Zaletą XML’a w porównaniu do atrybutów jest przede wszystkim izolacja modelu domenowego od wszelkiego rodzaju kodu infrastrukturalnego, którym są właśnie metadane mapowania. Zaś atrybuty eliminują ciężkie do definiowania i modyfikacji pliki XML, które są piętą achillesową nie tylko NHibernate’a. Oba rozwiązania mają również pewne wspólne wady. Jedną z najpoważniejszych moim zdaniem, jest zbyt duży nakład pracy dla osób początkujących w celu zdefiniowania podstawowego mapowania. Wiąże się to przede wszystkim z dużą liczbą prób i błędów w trakcie doboru odpowiednich opcji mapowania. Oczywiście z czasem, gdy już poznamy, które opcje idą ze sobą w parze okazuje się, że ich duża dostępna liczba jest wielką zaletą NHibernate’a. Nie oznacza to jednak, że nie popełnia się czasami trywialnych błędów w definicji XML’a lub atrybutach.
Kolejnym ważnym problemem jest brak lub ograniczona możliwości refaktoryzacji modelu domenowego bez wymuszania zmian w metadanych mapowania. W przypadku użycia plików XML, każda zmiana nazwy klasy lub jednej z jej właściwości, wymusza również modyfikację zawartości pliku, co łatwo prowadzi do błędów pozwalających „położyć” całą aplikację. Ten problem jak i możliwość prostego automatycznego testowania mapowania, był jednym z podstawowych powodów wskrzeszenia projektu Fluent NHibernate. Co najlepsze, z czasem okazało się, że można usprawnić jeszcze wiele innych funkcjonalności.
Rozpoczynając moją przygodę z tym projektem znałem już pewne jego możliwości dlatego, że dużo informacji pojawiło się w międzyczasie na blogach, które czytam na co dzień. Mimo to zostałem bardzo pozytywnie zaskoczony funkcjonalnością pozwalającą na wyeliminowanie pliku XML konfigurującego mapper. Obecnie w celu stworzenia obiektu SessionFactory komunikującego się z bazą danych MS SQL 2005 wystarczy napisać następujący kod.
var factory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005.ConnectionString(s=>s.FromConnectionStringWithKey(“Default”)))
.BuildSessionFactory();
Najpiękniejsze w tym rozwiązaniu jest to, że connection string potrzebny do połączenia się z bazą danych jest pobierany z sekcji connectionStrings pliku konfiguracyjnego aplikacji. Taka prosta rzecz a jak cieszy
, a administratora jeszcze bardziej ucieszy dlatego, że nie będzie musiał przedzierać się przez Überconifgi w celu wprowadzenia prostych zmian.
Domyślnie w ramach Fluent NHibernate zostały zdefiniowane klasy reprezentujące konfigurację większości obsługiwanych przez NHibernate baz danych. Odziwo nie można znaleźć na dzień dzisiejszy klasy odpowiedzialnej za konfigurację Oracla. To jednak nie jest żaden problem dlatego, że napisanie tego typu klasy wymaga od nas jedynie dziedziczenia po klasie PersistenceConfiguration i ustawienia odpowiedniego drivera oraz innych koniecznych wartości ,takich jak choćby dialect. Poza tym należy zwrócić uwagę na to, iż projekt jest cały czas w trakcie developmentu i zapewne dojdzie do niego jeszcze wiele dodatkowych funkcjonalności.
Przejdźmy teraz do największej perełki, czyli do definiowania metadanych mapowania. Dla tych osób, które nie miały do czynienia z definiowaniem mapowania przy użyciu plików XML chętnie przedstawię jak to wygląda.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:NHibernate-mapping-2.2" default-lazy="true" assembly="Examples.FirstProject" namespace=" Examples.FirstProject.Entities">
<class name="PaymentMethod" table="PaymentMethod" xmlns="urn:NHibernate-mapping-2.2">
<id name="Id" column="Id" type="Int32">
<generator class="hilo">
<param name="max_lo">100</param>
</generator>
</id>
<property name="IsActive" type="Boolean"></property>
<many-to-one name="Owner" column="OwnerId" />
<joined-subclass name="Examples.FirstProject.Entities.DirectDebit, Examples.FirstProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<key column="DirectDebitId" />
<property name="MandateCopyIsAvailable" type="Boolean"></property>
<property name="HolderName" type="String"></property>
</joined-subclass>
<joined-subclass name="Examples.FirstProject.Entities.CreditCard, Examples.FirstProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<key column="CreditCardId" />
<property name="CreditCardNumber" type="String"></property>
<property name="Expires" type="DateTime"></property>
<property name="HolderName" type="String"></property>
</joined-subclass>
</class>
</hibernate-mapping>
Okey, wiem, że wyskoczyłem z grubej rury, ale takie jest już życie programisty – przykłady są zawsze proste, a codzienne zmagania nie. Dlatego też przedstawiłem przykładowe mapowanie modelu podobnego do tego, z którym mam do czynienia w pracy. W celu wyjaśnienia, wyżej przedstawione metadane dotyczą dwóch klas abstrachujących formy płatności (CreditCard i DirectDebit) dziedziczących po klasie bazowej (PaymentMethod), która z kolei ma referencję typu wiele do jednego do klasy Customer. Jako, że bazy relacyjne nie obsługują dziedziczenia, zmuszeni jesteśmy do skorzystania z dostępnych strategii obsługi dziedziczenia w NHibernate. W powyższym przypadku użyta została strategia mapowania każdej klasy w hierarchi do osobnej tabeli zwana class table inheritance.

Plik XML nie należy znów do największych, ale warto zobaczyć jak łatwo można zredukować ilość kodu stosując Fluent NHibernate.
public class PaymentMethodMap : ClassMap
{
public PaymentMethodMap()
{
Id(pm => pm.Id).GeneratedBy.HiLo(“100″);
Map(pm => pm.IsActive);
References(pm => pm.Owner).TheColumnNameIs(“OwnerId”);
JoinedSubClass
{
cp.Map(creditCard => creditCard.HolderName);
cp.Map(creditCard => creditCard.Expires);
cp.Map(creditCard => creditCard.CreditCardNumber);
});
JoinedSubClass
{
cp.Map(directDebit => directDebit.ReferenceNumber);
cp.Map(directDebit => directDebit.HolderName);
cp.Map(directDebit => directDebit.MandateCopyIsAvailable);
});
}
}
Definiowanie metadanych mapowania przy wykorzystaniu Fluent NHibernate odbywa się poprzez tworzenie klasy dziedziczącej po klasie szablonowej ClassMap z parametrem klasy, którą mamy zamiar zmapować. Dzięki temu zyskujemy wsparcie intelisensu w trakcie mapowania poszczególnych właściwości mapowanej klasy oraz weryfikację poprawności kodu w trakcie kompilacji. Najczęściej używaną metodą udostępnioną przez ClassMap jest metoda Map, która pozwala na mapowanie właśćwiości klasy do kolumny w tabeli. Ta, jak i pozostała większość metod, wykorzystuje Expression Trees, w celu pozyskiwania nazw mapowanych klas i ich właściwości, dlatego też przedstawiony powyżej kod jest głownie opart o lambda expressions. Ze względu na to, że poszcególne użyte metody opisane są szczegółowo w dokumentacji projektu, nie ma sensu bym się na nich koncentrował.
Jedyne co jeszcze pozostaje do zrobienia, by skorzystać z wyżej przedstawionego mapowania, to zmodyfikowanie kodu tworzącego SessionFactory.
var factory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005.ConnectionString(s=>s.FromConnectionStringWithKey(“Default”))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf
.BuildSessionFactory();
Metoda Mappings pozwala na zdefioniowanie typu metadanych mappingu, którego chcemy użyć. Ze względy na to, że w przykładzie została użyta forma mapowania oparta o kod C#, wybrano opcję FluentMappings. Dostępna również jest opcja HbmMappings, która pozwoli na używanie klasycznych metadanych w pliku XML oraz opcja AutoMappings. AutoMapping wzbudza chyba największy efekt WOW, dlatego że opcja ta eliminuje konieczność definiowania metadanych, dzięki czemu nasza praca jest w pełni DRY(Don’t Repeat Yourself). Rozwiązanie to oparte jest o generację matadanych na podstawie klas domenowych i zdefiniowanych konwencji. Fluent NHibernate wyposażony jest w szeroką gamę domyślnych konwencji, które możemy w prosty sposób nadpisywać.
Ciekawą opcją jest możliwość korzystania z wszystkich metod mapowania jednocześnie. Dzięki temu jesteśmy w stanie sięgnąć po pomoc do pilku XML w przypadku gdy zabraknie nam pewnej opcji w FluentMappings lub AutoMappings. Możliwe to wszystko jest dlatego, że FluentNHibernate działa na zasadzie generowania XML’a z metdanych zdefiniowanych poprzez FluentMappings i AutoMappings. Fakt ten tłumaczy też dlaczego Fluent NHibernate nie wspiera metadanych wyrażonych w postaci atrybutów.
Entry Filed under: Uncategorized. Tagi: NHibernate, ORM, Wzorce.
8 Comments Add your own
Leave a Comment
Some HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>
Trackback this post | Subscribe to the comments via RSS Feed



1. dotnetomaniak.pl | Marzec 21, 2009 at 3:32 pm
Fluent Nhibernate Rocks!!! « !FrAgile Thinking…
Dziękujemy za publikację – Trackback z dotnetomaniak.pl…
2.
dario-g | Marzec 22, 2009 at 10:15 am
Fluent jest super. Niedawno właśnie skończyłem przepisywać bieżący projekt z XMLi na Fluenta.
3.
Bogusław | Marzec 22, 2009 at 8:59 pm
NHibernate zawsze mnie przerażał stosowaniem plików konfiguracyjnych. Obecnie stosuje mało znany mapper CoolStorage http://www.codeplex.com/CoolStorage . Sprawa wygląda podobnie jak w tym przypadku, że najpierw definiuje się schemat bazy danych, a następnie mapuje się klasy na odpowiednie tabele. W przypadku mapowania tabela->klasa trzeba to jawnie określić przy pomocy atrybutu MapTo, to w przypadku kolumn->właściwości wystarczy już zachować nazwę taką samą jak nazwa kolumny a mapper zrobi to automatycznie.
Myślę, że warto będzie się zainteresować Fluent Nhibernate, bo człowiek taki jak ja, który zachłysnął się rozwiązaniami takimi jak ActiveRecord i Og trudno daje się przekonać do klepania dodatkowych linii kodu
4.
dario-g | Marzec 24, 2009 at 7:48 pm
@Bogusław: Tych dodatkowych linii kodu nie ma za wiele, a Unit of Work pattern jest naprawdę fajny
5.
jenrom | Marzec 25, 2009 at 8:58 pm
@dario-g: Zgadzam się całkowicie z tobą. Przy czym należy zwrócić uwagę na to, że korzystanie z wzorca Unit of Work na początku jest bardziej skomplikowane niż zastosowanie wzorca Active Record. Oczywiście po tym jak już się pozna wszystkie zalety wzorca Unit of Work, ciężko sobie wyobrazić inny sposób dostępu do danych. Mimo tego uważam że dla prostych aplikacji warto rozważać stosowanie Active Record’a
@Bogusław
Może zainteresuje cię projekt Castle Active Record.
6.
artur | Maj 22, 2009 at 6:11 pm
Mam pytanie, może nie do końca związane z artykułem.
Co znaczy, ze nHibernate nie obsługuje LINQa?
Chcę sobie zaimplementować wyszukiwanie w List. Z użyciem Linq, bo byłoby najwygodniej.
Jeśli więc taką listę obiektow zaciągnę w sesji z bazy, z użyciem LazyLoading np. to czy coś stoi na przeszkodzie, zebym mógł wykorzystać Linq do wyszukania czegoś w tej liście, wykonania na tym jakisch operacji i zapisania w bazie z uzyciem nHibernate???
Z góry dzięki za podpowiedz:)
7.
jenrom | Maj 25, 2009 at 9:45 am
Na dzień dzisiejszy nie ma oficjalnej wersji pozwalającej na realizowanie zapytań do bazy danych przy pomocy LINQ. Jedynymi opcjami realizowania zapytań w NHibernacie są Criterias, HQL lub Query By Example. Praca nad providerem do LINQ już się rozpoczęła, przy czym z tego co wiem nie ma oficjalnej daty opublikowania wersji NHhibernate’a z providerem. Więcej na ten temat można znaleźć tutaj.
Projekt NHibernate Contrib również udostępnia własną implementację providera LINQ. Z tego co wiem jest to w tym momencie najlepsze rozwiązanie, jeżeli koniecznie zależy ci na możliwości realizowania zapytań przy pomocy LINQ. Kod tego projektu można ściągnąć przy pomocy svn’a z z adresu https://nhcontrib.svn.sourceforge.net/svnroot/nhcontrib/trunk/src/NHibernate.Linq
8.
am | Wrzesień 18, 2009 at 8:34 am
@artur, jenrom nie zrozumiał Twojego pytania – oczywiście po zaciągnięciu z bazy możesz korzystać z LINQ, tak jak na każdej implementacji IEnumerable. To co NHiberante słabo na razie robi, to tłumaczenie wyrażeń LINQ na SQL, ale zdaje się, że nie o to pytałeś.