NHibernate nebo Entity Framework? (díl č. 1)

NHibernate byl dlouhou dobu hlavní volbou při výběru ORM pro .NET platformu. Je to vyspělý a prověřený framework s širokou uživatelskou základnou. V poslední době však výrazně roste jeho hlavní konkurent, Entity Framework, zvláště od roku 2012, kdy byl uvolněn jako open source.

Při volbě ORM frameworku pro nové .NET projekty v naší společnosti mezi sebou často „bojují“ zastánci NHibernate a přívrženci Entity Frameworku. V diskuzích se vždy ukáže, že oba frameworky mají svoje výhody i nevýhody a jasný vítěz není patrný na první pohled. Rozhodli jsme se proto pro detailnější porovnání aktuálních verzí frameworků k dubnu 2014, se zaměřením na konkrétní problémy, se kterými jsme se v minulosti setkali při implementaci našich projektů.

Při porovnávání jsme se soustředili na vlastnosti, které jsou důležité jak při vývoji velkých enterprise aplikací pracujících s velkými daty, tak pro vývoj miniaplikací a prototypů, kde je prvořadým požadavkem rychlost a nízká cena vývoje. Je jasné, že z takového porovnání nelze vyvodit jediný jednoznačný závěr, neboť jde o typy aplikací s navzájem do velké míry protichůdnými požadavky.

S ohledem na rozsah článku jsme ho rozdělili na dvě části. První díl, který právě čtete, se zaměřuje na porovnání základních vlastností. Ve druhém díle se dočtete o dalších vlastnostech, a také se můžete těšit na závěrečné shrnutí.

Linq

Zásadní součást frameworku je způsob dotazování do databáze. To má vliv zejména na rychlost vývoje. Pojďme se podívat na podporu LINQ, což je pro .NET programátora elegantní a hlavně rychlý přístup pro psaní dotazů nad objekty, nejen entitními (databázovými). Proto jeho podporu ve spojení s DB frameworkem vidíme jako důležitou. Na druhou stranu přepokládá, aby si programátor dokázal dobře uvědomit, jak bude v principu takto zapsaný dotaz přeložen do SQL, a tedy co to bude znamenat pro DB server z pohledu výkonnosti a  velikosti přenášených dat mezi databází a aplikací.

Zatímco NHibernate se vyvíjel dávno před dobami jazyka LINQ a má proto několik jiných způsobů, jak vytvářet dotazy, EF stojí a padá na LINQu jako na primárním dotazovacím mechanismu. Z toho je pochopitelné, že podpora LINQ v EF je ukázková, zatímco NHibernate má v podpoře LINQ slabiny (jeho provider LINQ to NHibernate nepodporuje např. klauzuli order by,LEFT JOIN apod).

Vlastní dotazovací jazyky – Entity SQL a HQL

Jak NHibernate tak EF definují vlastní dotazovací jazyky, které je možné použít v situacích, kdy LINQ nebo jiné přístupy nestačí. Syntakticky jde velmi zjednodušeně o dialekty jazyka SQL, které navíc umožňují pracovat s ORM objekty (entitami) daného frameworku.

NHibernate má dotazovací jazyk zvaný HQL, který použijeme zejm. pro psaní složitých dotazů, např. s kombinacemi inner, outer a cross joinů, při potřebě komplikovaných podmínek, SQL funkcí, atp. Neumožňuje ho však na rozdíl od EF mapovat na použití LINQ, je tedy určený jen pro explicitní dotazy.

EF definuje jazyk Entity SQL. Podobně jako v případě HQL u NHibernate jej lze použít pro složitější dotazy, které by pomocí standardního LINQ  byly složité či neoptimální z pohledu výkonnosti nebo nešly napsat vůbec. Navíc ale EF umožňuje namapovat Entity SQL kód na vlastní .NET metodu. Můžeme tak doplnit do EF např. SQL funkci LIKE nebo jakoukoli další vlastní specifickou funkci. Takto definovanou funkci pak můžeme volat v LINQ dotazech, což NHibernate neumí. EF se při serializaci LINQ dotazu postará o přeložení z .NET přes Entity SQL až do vlastního SQL kódu, který je následně interpretován na databázi. Tuto vlastnost EF jsme několikrát použili právě pro implementaci LIKE a jeho alternativ (LIKE pro integer sloupce, kde je potřeba navíc provést SQL CAST, náhrada „*“ za „%“ v LIKE, atp.). Na druhou stranu, bez Entity SQL funkcí se lze obejít, buď o něco složitějším .NET kódem, nebo použitím uložených procedur. To představuje malé zvýšení pracnosti.

Obecně použití HQL stejně jako použití Entity SQL má nevýhody, jak při programování, tak zejména při refaktoringu. Dotazy jsou totiž zapsány v podobě řetězcového literálu, a nejsou tak syntakticky kontrolovány při překladu.

Lazy fetching

Lazy fetching (v EF pod termínem Lazy loading) – V NHibernate je výrazně konfigurovatelný, včetně načítání jednotlivých sloupců (typu BLOB). V EF je možné jej zapnout/vypnout globálně na celý model, kdy jsou načítány vždy celé kolekce entit, tj. join do referencované tabulky. Možnost lazy fetchingu na úrovni properties (sloupců) je přislíbena pro EF7.

Bezvýhradné spoléhání na lazy fetching však může přinášet problémy. Při nesprávném použití má lazy fetching velké problémy s výkonem. Například při načítání mnoha prvků z podřízené kolekce, kde načtení každého prvku kolekce může způsobit jeden dotaz do databáze (toto chování lze u NHibernate ovlivnit nastavením tzv. fetching strategy a načítat např. dávkově více záznamů z kolekce naráz). U správně navržené aplikace bychom měli dopředu vědět, které všechny vztahy nebo sloupce potřebujeme do aplikace načíst, a načítat je pokud možno najednou, v jednom dotazu do DB. Tento přístup se nazývá eager fetching a oba frameworky jej podporují.
Toto platí především pro webové aplikace, kdy bychom měli vědět, jaká data pro obsluhu daného requestu potřebujeme načíst. U desktopových aplikací se lazy fetching může hodit, je-li aplikace navržená tak, že DB session je otevřená např. pro každý formulář, a na základě akcí uživatele je potřeba načítat další data ve stromu entitních objektů. Lépe je však využít návrhový vzor session-per-conversation, a tak i zde se bez lazy fetchingu bez problémů obejdeme.

Chování NHibernate a EF se značně liší v případě vypnutí lazy fetchingu (oba frameworky to dovolují). V takovém případě NHibernate načítá podřízené kolekce ihned (rekurzivně), což může nejhůře skončit načítáním celé databáze. Proto se v případě NHibernate důrazně nedoporučuje lazy fetching globálně vypínat (lze ho vypnout v odůvodněných případech na některých entitách). V EF vypnutí lazy fetchingu nevadí, protože při načtení entity nechá reference na podřízené kolekce na null, a umožňuje později donačíst jejich obsah, což se nazývá Explicit loading.

Co se týče lazy fetchingu, je zde o něco mocnější NHibernate díky jemnější konfigurovatelnosti.

Nároky frameworků na entitní vrstvu

Zde se podíváme na to, jak moc framework ovlivňuje strukturu entitní vrstvy. Při hodnocení jsou pro nás zajímavé následující otázky.

Je nutné dědit od bázové třídy frameworku? Často totiž chceme funkcionalitu společnou všem entitám, například obecné metody pro načtení podle primárního klíče nebo společné properties mapované do databáze – auditační sloupec s datem poslední modifikace záznamu apod. To se většinou přirozeně řeší dědičností od bázové třídy. C# je jazyk bez vícenásobné dědičnosti, povinnost dědit od bázové třídy frameworku by nám mohla tedy hodně zkomplikovat situaci. Takto to bylo v raných verzích EF.

Musí být entitní assembly závislá na assembly frameworku? To může být důležité v situaci, kdy chcete třídy entitní vrstvy sdílet mezi několika aplikacemi, které všechny nemusí používat databázi. Z jiného pohledu, pokud vyvíjíte podle metodiky domain driven design a použijete entitní vrstvu jako doménový model, budete chtít ji mít nezávislou na ORM frameworku (tato vlastnost se nazývá persistence ignorance).

Umožňují frameworky mapování do private properties? Tato vlastnost je opět důležitá pro domain driven design, umožňuje vytvořit zapouzdřený doménový model.

V této oblasti jsou si oba frameworky rovnocenné:

  • Oba frameworky umožnují mít entity jako klasické POCO třídy, ani jeden frameworků tedy nevynucuje dědění od určité bázové třídy z frameworku. Lze tedy dědit od vlastní třídy.
  • Jak v NHibernate tak v EF lze dosáhnout toho, že entitní vrstva nemá závislost na knihovně daného frameworku.
  • Oba frameworky podporují mapování do private properties (včetně auto-properties s rozdílnou viditelností pro getter a setter).

Určitá pravidla pro entity je však potřeba dodržet, nejsou však příliš omezující:

  • Oba frameworky vyžadují, aby každá entitní třída obsahovala bezparametrický konstruktor (naštěstí nemusí být veřejný).
  • Pro podporu lazy loadingu je u obou frameworků nutné, aby properties byly virtuální.
  • Pro entity se zapnutým lazy fetchingem NHibernate vytváří proxy, ale v případě vypnutého lazy fetchingu je nevytváří (jde rovnou o cílový typ). V implementaci Equals tedy nelze použít porovnání na shodnost typů (pomocí typeof), protože se může stát že porovnáváte tutéž entitu a její proxy, ale jedná se o dva různé typy. To se může stát např. při deserializaci entit (např. při volání WCF). Doporučuje se implementovat Equals jako rovnost přirozeného klíče. Toto téma je v kontextu NHibernate rozebráno naStackOverflow. Entity framework vytváří proxy vždy, bez ohledu na lazy fetching. To situaci zjednodušuje, nicméně v případě deserializace také nelze spoléhat na výchozí implementaci Equals, což je rovnost referencí.

Typy ORM mapování

Zde uvádíme různé možnosti, jak definovat ORM mapování – mapování objektů resp. properties na tabulky resp. sloupce v databázi.

Mapování pomocí XML
NHibernate Původní metoda mapování pro NHibernate. Mapování je uložené v samostatných XML souborech. Nevýhodou je, že správnost mapování není kontrolovaná při kompilaci. 
EF EDMX Model EF je reprezentován v jediném XML souboru, který obsahuje část popisující databázové schema, část objektového modelu, a nakonec mapování mezi nimi. Model je možné editovat modellerem ve Visual Studiu, third-party nástroji, nebo i ručně. Ve Visual Studiu je možné:

  • vytvořit EDMX model a z něj vygenerovat skripty pro vytvoření nebo změnu DB schematu (model first).
  • Model vygenerovat přímo z databáze (DB first).

 

Atribute mapping
Dekorování tříd a properties entity atributy určujícími tabulku, sloupec a další informace. Nevýhodou je zanášení závislosti na frameworku do entitní vrstvy/doménového modelu.
NHibernate Podporováno v externím projektu NHibernate.Mapping.Attributes. Výhodou oproti XML je lepší kontrola při kompilaci.
EF Podporováno, jako součást Code First přístupu k vývoji, viz. další kapitola.

 

Mapping by code
Mapování specifikované v kódu, voláním metod API, které framework poskytuje. Výhodami jsou kontrola při překladu a možnost mapování oddělit od projektu entitní vrstvy/doménového modelu.
NHibernate Podporováno od verze 3.2, dříve pomocí externí knihovny Fluent NHibernate.
EF Podporováno, jako součást Code First přístupu k vývoji (Fluent API), viz. další kapitola.

 

Convention based mapping (Automapping)
Namísto explicitního mapování každé property na sloupec stanovíme tzv. konvence, což je např. „každá property končící na Id je je primární klíč“. Obvyklou konvencí je, že sloupce tabulky se jmenují stejně jako properties a tabulky stejně jako třídy.
NHibernate Podporuje od verze 3.2, dříve pomocí externí knihovny Fluent NHibernate.
EF Je podporováno od verze 6 jako součást Code First.

Oba frameworky mají v základních principech podobné možnosti, co se týká mapování, i když v detailu je realizace značně odlišná. Neshledáváme zde ale žádný podstatný nedostatek, který by stál při porovnání frameworků za zmínku.

Ve prospěch EF může mluvit snad jen editor EDMX mapování, dodávaný jako součást Visual Studia. Není tedy třeba spoléhat se na 3rd party nástroj, pokud bychom jej pro zvolený typ mapování potřebovali.

Dokumentace

Dokumentace NHibernate má své slabé stránky a není zcela vyčerpávající, může se stát, že podrobný popis určité funkcionality tam nenajdete a budete muset hledat porůznu na webu. Na druhou stranu, komunita uživatelů je velká a na webu lze nalézt řešení většiny problémů, se kterými se vývojář setká.

EF se může pyšnit kvalitní dokumentací, silné programátorské komunitě, a tedy i velkému množství informací, problémů a řešení na Internetu.

V této kategorii si EF vede v porovnání s NHibernate mnohem lépe. To hodnotíme jako velmi důležitý prvek ve vzájemném porovnání, zejm. pro velké aplikace, které musí žít mnoho (možná i desítek) let a setká se s nimi velký počet vývojářů s různými zkušenostmi.

Na daší důležité vlastnosti ORM frameworků a závěrečné shrnutí se můžete těšit příští týden.