Wprowadzenie

Jednym z największych problemów związanych z przygotowaniem aplikacji opartej na połączeniu z bazą danych jest zmniejszenie różnic pomiędzy relacyjnym przechowywaniem danych a ich obiektowym odpowiednikiem. To są dwa odmienne podejścia, które niestety nie łączą się bezbłędnie.

Mapowanie obiektowo-relacyjne (ORM, tj. Object-relational mapping) próbuje rozwiązać te niedopasowania poprzez zapewnie warstwy translacji pomiędzy relacjami a obiektami. Nie zawsze jest to lekiem na wszystkie problemy.

Nie ma idealnego rozwiązania mapowania obiektowo-relacyjnego. Istnieje kilka rozwiązań – mogą one być bardzo przydatne – jednakże użycie każdego z nich wiąże się z pewnymi kompromisami, które nie są do końca jasne aż do późniejszego cyklu rozwojowego aplikacji.

Programiści i architekci systemów mogą w większości wypadków podzielić się informacją na temat swojego ulubionego sposobu dostępu do danych. Niektórzy z nich jednak rezygnują z ORM na rzecz tradycyjnego podejścia programistycznego nieopartego na modelach obiektowych (w kontekście baz danych). Inni z kolei kompletnie porzucają relacyjny model przechowywania danych na rzecz jego obiektowego odpowiednika.

Pozostali z nas tkwią gdzieś po środku wprowadzając relacyjne odzwierciedlenie bazy danych w postaci modelu obiektowego. Odwzorowanie takie może zostać napisane własnoręcznie, ale zdecydowanie łatwiej skorzystać z jednej z wielu dostępnych technologii, które zadanie to wykonają za nas. I dochodzimy do miejsca, w którym ciężko natrafić na subiektywną informację mówiącą o przewadze jednej technologii nad drugą. Celem tego artykułu jest analiza kompromisów, na które decydujemy się wybierając wskazane rozwiązanie. Skupimy się na analizie 4 z nich – są one dobrze znane i powszechnie używane:

  • ADO.NET – stare i dobrze znane rozwiązanie pozwalające na "ręczny" dostęp do danych (nie jest to ORM - nie możemy jednak zapominać o tej technologii);
  • Tworzenie warstwy ORM przy użyciu technologii NHibernate;
  • Użycie LINQ to SQL pozwalające na wyciąganie danych do modelu obiektowego;
  • Użycie Entity Framework.


Kontrola i zarządzanie

ADO.NET jako narzędzie pozwalające na "ręczny" dostęp do danych daje największa kontrolę w szczególności jeżeli chodzi o zapytanie SQL, które jest aktualnie wykonywane. Takie podejście pozwala na przygotowanie odpowiednio dopasowanych, wydajnych zapytań, które są ściśle powiązane z systemem, który projektujemy. Minusem takiego rozwiązania jest ogromna ilość kodu niezbędnego do napisania. Każde takie zapytanie może być kryjówką dla wielu małych błędów, ich poprawa pozwalająca na doprowadzenie do stabilnej wersji może być bardziej czasochłonna niż się wydaje.

ADO.NET jest doskonałym kandydatem, gdy potrzebujemy bardzo szybkiego dostępu do stosunkowo niewielkiego modelu bazy danych. Problem jaki się pojawia związany jest z założeniami. Zwykle małe i proste modele bazy danych szybko się rozrastają do dużych i bardzo skomplikowanych modeli, a Ty, jako programista, staniesz przed wyzwaniem wymagającym wiele wysiłku, aby utrzymać kontrolę nad taką bazą danych.

Pozostałe rozwiązania zajmują się wykonaniem "brudnej roboty" za Ciebie, jednakże robią to w odmienny sposób. NHibernate używa struktury XML do mapowania tabel na ich obiektowy odpowiednik a dostęp do nich jest zapewniony przez centralny obiekt Session. Rozwiązanie takie ukrywa szczegóły SQL przed Tobą, dzięki czemu jest znacznie mniej kodu, którym trzeba zarządzać. Z drugiej jednak strony pojawia się problem związany z tworzeniem definicji XML dla większych systemów – krok ten może być wykonany przy pomocy narzędzi do zarządzania – jednak NHibernate opiera się luźno pisanej składni zapytań, która może być częstym źródłem wielu błędów.

LINQ to SQL jest niekoniecznie pełnoprawnym narzędziem ORM jednakże pozwala na bardzo szybki dostęp do bazy danych. Automatycznie generuje zestaw obiektów, który bezpośrednio mapuje na tabele danych a wielu programistów używa LINQ to SQL w połączeniu z LINQ to Object aby odwzorować te obiekty na tzw. encje (w kontekście baz danych jest to pojedyncza osoba, miejsce lub rzecz o której możemy przechowywać informacje). Podejście takie ułatwia prawidłowe odwzorowanie tabel do obiektów, jednakże z tendencją do tworzenia bardzo dużej ilości kodu, która może być trudna w utrzymaniu. Programiści używający LINQ to SQL myślą o tej technologii jako narzędziu, które pozwala na bardzo szybkie tworzenie obiektów oraz jest łatwe w rozpoczęciu pracy – zapewnia również dobre i przejrzyste narzędzia graficzne w Visual Studio.

Entity Framework jest pełnoprawnym narzędziem ORM, które bazuje na Entity Data Model a jest reprezentowany jako XML. Konfiguracja oparta o dokument XML nie jest czymś z czym musisz mieć bezpośrednio doczynienia ponieważ Entity Framework dostarcza bogaty zestaw narzędzi do zarządzania mapowaniem. Jedną z najważniejszych zalet Entity Framework jest silne typowanie (strong typing – w najprostszych słowach termin ten można wyjaśnić jako: środowisko wygeneruje błąd albo nie pozwoli na kompilację jeżeli argument przekazany do metody nie będzie odpowiadał typowi oczekiwanemu) oraz wsparcie dla sprawdzania modelu w trakcie kompilacji. Takie rozwiązanie sprawia, że zarządzanie tą platformą dostępu do danych wymaga najmniej uwagi spośród innych platform tutaj opisywanych.


Wydajność

ADO.NET gwarantuje najwyższy poziom kontroli na wywoływanymi zapytaniami SQL i jest bardzo dobrym kandydatem dla mniejszych modeli bazy danych w których wymagamy szybkiego dostępu do danych. Problemem jest fakt, że małe modele bazy danych mają tendencję co ciągłego rozrostu, przez co może okazać się, że zarządzanie taką bazą danych będzie dużo trudniejsze niż na wczesnych etapach rozwoju. Możesz potrzebować szybkiego dostępu do danych, jednak warto się zastanowić czy warto przedkładać potencjalny wzrost wydajności nad fakt dużo wyższych kosztów związanych z utrzymaniem takiego modelu bazy danych.

Można zrobić wiele, aby poprawić wydajność w przypadku pozostałych technologii, należy jednak pamiętać o bardzo dobrej wiedzy odnośnie danej technologii. Nie można również zapominać o planowaniu do przodu. Wszystkie z nich wspierają użycie procedur składowanych do wykonywania operacji na danych co jest znacznie lepsze niż formatowanie zapytań SQL w locie. Zapewniają również różne funkcje pozwalające na poprawę wydajności w szczególnych przypadkach. Dla przykładu, kompilowanie zapytań w LINQ to SQL pozwala na poprawę wydajności regularnie używanych operacji. Z kolei NHibernate wspiera wsadowe (batching) wykonywanie operacji odczytu oraz zapisu, co stanowi kolejny poziom kontroli nad operacjami w bazie danych. Batching jest procesem, który pozwala na grupowanie zapytań w określone grupy. Wcześniej, jeżeli chcieliśmy wykonać operację Update była ona wykonywana pojedynczo dla danego wiersza. Właściwość batching pozwala na grupowanie tych operacji, tzn. ustawiając jej wartość np. na 10 dojdzie do złączenia 10 osobnych operacji w jedną operację, która zostanie wysłana do bazy danych i wykonana.

Najważniejszą rzeczą do zapamiętania jest informacja związana z działaniem każdej z powyższych technologii. Jeżeli będziemy posiadali wiedzę na temat operacji wykonywanych "w tle" nie dojdzie do sytuacji, w której naszej rozwiązanie spowoduje drastyczny spadek wydajności wykonywanych operacji. Bez względu na wybór technologii należy pamiętać o tym, żeby nie pracować z większą ilością danych niż rzeczywiście jest potrzebna oraz żeby ograniczyć ilość zapytań do bazy danych do absolutnego minimum.


Spójność z platformą .NET

Jedną z głównych zalet ORM jest fakt, że pozwalają programiście pracować we wspólnym, zorientowanym obiektowo środowisku przez co praca taka jest bardziej komfortowa niż bezpośrednia praca z SQL i relacyjną bazą danych. Aby w pełni wykorzystać możliwości takich rozwiązań powinny one być jak najbardziej zgodne z technologią .NET.

Takie też jest założenie Entity Framework. Technologia ta jest ściśle zintegrowana z innymi częściami platformy .NET, tj. ADO.NET włączając w to nawet ADO.NET Data Services. Z kolei LINQ to SQL nie korzysta z takiego poziomu intergrancji z platformą .NET a NHibernate ma swoje korzenie w świecie Javy. Obecnie wspiera już rozwiązania takie jak LINQ czy interfejs IQueryable co sprawia, że programiści równie często sięgają po to rozwiązanie.


Testowalność

Wczesne wersje Entity Framework poddane zostały krytyce, gdyż zmuszały do użycia konkretnego wzorca projektowego i nie zapewniały wsparcia dla obiektów POCO. Ponadto warstwa danych generowana przez tą technologię wymuszała zależności w każdej części warstwy danych przez co testy jednostkowe były prawie niemożliwe. LINQ to SQL ma podobny problem ponieważ jest zbyt ściśle powiązany z platformą .NET co sprawia, że pisanie testów jednostkowych jest również utrudnione.

NHibernate i ADO.NET zawsze zapewniał dobre wsparcie dla testów jednostkowych ponieważ pozwala na pełną kontrolę nad projektowanym kodem. Obecna wersja Entity Framework rozwiązuje wspomniany wyżej problem przez zastosowanie mechanizmu generowania kodu opartego na szablonach T4. Szablony są teraz dostępne dla różnych podejść projektowych, zapewniają również wsparcie dla testów klas POCO.


Podejście projektowe i możliwości modelowania

Pierwsze pytanie, które nasuwa się na język: czy na pierwszym miejscu powinien być projekt relacyjny czy obiektowy? W wielu wypadkach nie mamy wyboru – baza danych będzie istniała a my będziemy musieli z nią współpracować. Podejście oparte jest na dostępnych danych a Ty będziesz potrzebował platformy, która dokona mapowania na model obiektowy. Wielu architektów systemów preferuje podejście modelowania obiektowego, który następnie zostanie zmapowany na model relacyjny.

NHibernate pozwala na wybór każdego z powyższych podejść, jednakże wydaje się być przeważnie używane przez osoby preferujące podejście obiektowe. Z kolei Entity Framework dostarcza dobre narzędzia zapewniające wsparcie dla obu podejść projektowych. Odwzorowanie bazy danych jest proste i automatyczne, podczas gdy, "code first" pozwala na rozpoczęcie projektu od modelu obiektowego a następnie użycie inżynierii wstecznej (reverse engineering) do odwzorowania relacyjnego. Pod względem elastyczności projektowania i dostępnych narzędzi rozwiązanie to ma przewagę nad NHibernate.

ADO.NET nie zapewnia bezpośredniego wsparcia w żadnym kierunku. Podobnie wygląda sytuacja z LINQ to SQL, które pozwala jedynie na odwzorowanie schematu bazy danych.


Funkcjonalność

NHibernate jest na rynku od dawna i zdążył już ewoluować w celu zapewniania bardzo bogatego zestawu funkcji. Zapewnia wsparcie dla wielu ważnych funkcjonalności, takich jak skalowalność dostępu do danych, których nie odnajdziemy w innych technologiach. Przykładem może być tzw. batching, który pozwala na ograniczenie zapytań do bazy danych przy wykonywaniu operacji odczytu i zapisu. Posiada również szereg rozszerzeń takich jak: NHibernate Search, NHibernate Validator i NHibernate Shards – jest mało prawdopodobne aby takie rozszerzenia pojawiały się w Entity Framework.
Cechą wartą wspomnienia w Entity Framework jest wsparcie Lazy Loading – pozwala kontrolować ilość danych ładowanych z każdego obiektu.


Wsparcie i przyszłość technologii

ADO.NET wydaje się dobrym wyborem ponieważ jest ciągle wspierany przez Microsoft. LINQ to SQL wydaje się być zastąpiony przez Entity Framework a Microsoft nie ma planów na przyszłe wsparcie tej technologii. Ponadto wiele osób ze społeczności LINQ to SQL postanowiło zrezygnować ze wsparcia tej technologii po ogłoszeniu, że Microsoft nie planuje wsparcia dla tej technologii.

Entity Framework wygląda na dość bezpieczną decyzję ponieważ jest ciągle ulepszany. NHibernate ma dużą społeczność stojącą za tą technologią jednakże wydaje się, że Entity Framework przyciąga do siebie coraz szersze grono programistów.