Dlaczego Entity Framework nie jest dobrym wyborem

Wiele razy rożni ludzie dziwili mi się, że “nie lubię Entity Frameworka”. Takie stwierdzenie nie tylko bardzo mija się z prawdą, ale wręcz jest zupełnie pozbawione sensu. Lubić można rosół z makaronem, komplementy i kolor niebieski, ale nie ORMa. To samo zresztą dotyczy innych bibliotek czy języków - technologia to jest coś, czego się używa albo i nie (w zależności od potrzeb, zastosowań i możliwości), nie obiekt uczuć. Co do EF zaś - mam po prostu bardzo wiele argumentów za tym, żeby go nie używać. Postaram sie je wymienić w tym krótkim wpisie.

Po co w ogóle używać ORM?

Tutaj są dwie szkoły:

  1. Aby automatycznie mapować obiekty na relacje (jak sama nazwa zresztą wskazuje) oraz odpytywać bazę danych przy użyciu obiektowego API.
  2. To samo, ale wydajnie, elastycznie, stabilnie, z dużą dozą automatyzacji, ale jednocześnie mając wpływ na to, co się dzieje, np. jakie zapytania są generowane.

Jak nietrudno się domyślić, ja należę do tej drugiej szkoły. Dlatego też wymagam od ORMa nieco więcej niż tylko wygenerowania kodu zapytań SQL i skonwertowania ich wyników na obiekty C#. Owszem, Entity Framework potrafi co prawda generować SQL (oj, naprawdę dużo tego SQL potrafi wygenerować!) i daje jakieś minimalne możliwości rozszerzania, ale jest naprawdę ubogi funkcjonalnie.

Podejście

To truizm, ale programowanie w języku obiektowym polega na utworzeniu obiektowego modelu jakiegoś zagadnienia. Nawet jeśli aplikacja ma korzystać z jakiegoś źródła danych, to i tak najpierw projektuje się i implementuje model biznesowy oraz struktury danych w języku, w którym powstaje aplikacja. Celem istnienia aplikacji jest przede wszystkim realizacja jakiejś logiki biznesowej, nie łączenie interfejsu użytkownika z bazą danych. A baza danych nie jest centrum aplikacji, lecz szczegółem implementacji. Oczywiście - trzeba mądrze wybrać RDBMS, dobrze go skonfigurować, świadomie używać i dbać o bazę, ale to nadal jest tylko miejsce przechowywania danych. Najważniejsza jest logika biznesowa zapisana w kodzie aplikacji.

Dawno temu takie podejście nie było możliwe. Nie było odpowiedniej technologii ani tak ekspresywnych języków obiektowych, jakie mamty teraz. Logika biznesowa była implementowana w bazach danych. To nie jest dobre podejście - kod SQL trudno refaktoryzować, trudno testować, słabo wspiera modularyzację, a przede wszystkim trzeba go napisać bardzo dużo w porównaniu z językami obiektowymi. Niestety, to podejście z dawnych czasów w wielu miejscach trwa do dzisiaj. Co gorsza, nowe pokolenia programistów są kształcone w przekonaniu, że tworzenie aplikacji zaczyna się od narysowania relacji i powiązań między nimi, a następnie napisania kodu DDL albo wyklikania w desigerze struktury bazy. Potem dopiero można zacząć pisać aplikację. Ten strukturalny sposób tworzenia oprogramowania jest nie tylko staromodny, ale też bardzo nieefektywny. Stosując go tracimy bardzo dużo czasu, chociażby przez utrudnione wprowadzanie zmian.

Na tym podejściu, zwanym database first od początku swojego istnienia oparty był Entity Framework. Visual Studio 2008 zawierało designer, który pozwalał na przeciągnięcie tabelek z bazy MSSQL na obszar designera, co powodowało automatyczne wygenerowanie klas odpowiadających tabelom oraz ObjectContextu.

Obiektowe podejście code first zostało wprowadzone do EF z wersją 4.1, gdzieś w okolicach 2011 roku. Niemniej jednak, wciąż dla wielu programistów .NET to podejście jawi się jako nowe, niektórzy się go obawiają, i nawet korzystając z nowszych wersji EF stosują database first. No cóż, lata promowania aberracji zbierają swoje pokłosie. Promowane przez EF database first jest ogromną stratą czasu. Aby wprowadzić jakąkolwiek zmianę w srukturze danych, trzeba zmienić kod SQL (albo użyć designera), następnie odświeżyć model, modlić się, aby nic się nie posypało, wprowadzić poprawki, a potem spróbować skompilować kod.

Tymczasem NHibernate od początku był ORMem dla programistów. Jedynym dostępnym podejściem jest i było code first. Nie ma designerów, ani żadnych innych zaśmiecaczy. Idea jest prosta - piszemy kod modelu biznesowego, piszemy mapowania obiektów na relacje, bazę danych tworzy i aktualizuje ORM. Po to przecież używamy ORMów, żeby nie dłubać ręcznie w bazie danych!

Architektura

Każdy chyba wie (a przynajmniej słyszał), że jedną z podstawowych zasad tworzenia oprogramowania jest SRP. Zasada ta oznacza, że klasa musi mieć jedną odpowiedzialność. Np. klasa, której zadaniem jest odczyt danych z pliku, nie powinna wyświetlać tych danych na ekranie. Klasa, której celem jest konwersja z formatu wav na mp3, nie powinna jednocześnie wysyłać danych w sieć. A klasa, która służy do operowania na bazie danych, nie powinna być swoją fabryką i konfiguracją. Ups… Chyba jednak nie wszyscy słyszeli o SRP.

Klasa DbContext w Entity Framework jest jednocześnie konfiguracją mapowań, jak i operującym na bazie generycznym repozytorium. Dla porównania, w NHibernate mamy:

  1. Configuration - obiekt konfiguracji mapowań obiektowo relacyjnych, połączenia do bazy danych, wszelkich bajerów rozszerzających zachowanie ORMa.
  2. ISession - główny obiekt, “tani” w tworzeniu, który daje generyczny dostęp do bazy danych i przeprowadzania na niej operacji CRUD.
  3. ISessionFactory - fabryka sesji, twór, który wraca obiekt ISession na podstawie ustalonej konfiguracji.

Oczywiście Configuration tworzymy raz, przy starcie aplikacji, pojedyncza instancja ISessionFactory żyje przez cały okres trwania aplikacji, a ISession stanowi unit of work żyjący tak długo, jak wymaga tego aplikacja. Np. w przypadku aplikacji webowej będzie to w praktyce pojedynczy web request.

Co to wszystko daje? Dzięki dobrej architekturze, w NH możliwe są rzeczy, które są niewykonalne w EF, takie jak:

  1. Cache drugiego poziomu - czyli współdzielone dla wszystkich obiektów ISession.
  2. Wydajność działania (chociaż EF ponoć juz naprawili, i nie odtwarza całej konfiguracji przy tworzeniu DbContext).
  3. Łatwość testowania - interfejsy łatwo mockować w unit testach. W EF początkowo nie było w ogóle takiej możliwości, bo nie było interfejsów.
  4. Najczęściej używany przez nas obiekt, czyl ISession nie jest z zaśmiecony zbędnymi metodami jak to ma miejsce w przypadku DbContext.
  5. Intuicyjnie wiadomo, co gdzie jest. Np. oczywiste jest, że metadane mapowania jakiejś klasy znajdziemy w ISessionFactory, a nie w jednej z miliona właściwości DbContext.

Czego brakuje w Entity Frameworku

Tu już nie będzie opowiadania, lecz prosta wyliczanka:

Cache drugiego poziomu

Czyli cache wspólnego dla wszystkich kontenerów unit of work. Można do niego wczytać np. wszystkie słowniki, które nie zmieniają się przecież często podczas życia aplikacji. Pozwala to odciążyć bazę i zmniejszyć ruch między nią a aplikacją. W EF nie jest to chyba w ogóle możliwe do zaimplementowania (przez brak obiektu świadomego istnienia wszystkich kontekstów, odpowiednika ISessionFactory).

Generowanie identyfikatorów…

Oczywiście to jest coś, co świetnie robi baza danych. Jasnym jest, że baza danych po prostu musi być w stanie nadać identyfikator nowemu rekordowi. Ale czemu mamy przerzucać do zadanie na bazę danych, kiedy korzystamy z ORMa?! Dlaczego nie mielibyśmy sobie wygenerować ID naszego rekordu po stronie aplikacji i nie zapisać całego, gotowego rekordu do bazy? Jeśli zapisujemy np. trzy powiązane ze sobą obiekty w ramach jednego UoW, to NHibernate wygeneruje trzy polecenia insert, a EF musi dodatkowo po każdym z nich zapytać bazę o ID nowo utworzonego obiektu, żeby móc je przypisać do klucza obcego następnie zapisywanego obiektu. To strata czasu i zbędny narzut wydajnościowy.

…nawet Guidów

Jak wiadomo, Guid nie jest zbyt dobrym typem dla kluczy głównych. Losowość kolejnych jego wartości sprawia, że wstawianie nowych wierszy powoduje przebudowywanie indeksu klastrowanego tabeli (najczęściej zakładanego na kolumnie z kluczem głównym), a więc jest niewydajne. NHibernate rozwiązuje ten problem, gdyż posiada sekwencyjny generator ID typu Guid.

Batch insert

To wiąże się z poprzednim punktem. Ponieważ Entity Framework sam nie potrafi generować kluczy głównych, to i nie może generować wielu poleceń insert i wysłać je w pakiecie np. 100 na raz.

Różne typy kolekcji

W EF właściwości kolekcji deklaruje się jako ICollection<T>. Nie można mieć listy, nie można mieć baga, nie można mieć tablicy, nie można mieć słownika. Tymczasem NHibernate pozwala na kolekcje będące implementacjami takich interfejsów:

  • System.Collections.Generic.ICollection<T>
  • System.Collections.Generic.IList<T>
  • System.Collections.Generic.IDictionary
  • System.Collections.Generic.ISet<T>
  • lub własnego implementującego: NHibernate.UserType.IUserCollectionType.

Dynamiczne API zapytań

Jedyną możliwością odpytywania w Entity Framework jest LINQ. Nie ma co przeczyć, ten sposób jest bardzo przyjemny, każdy z nas zna to API i lubi lambdy, ale to nie znaczy, że LINQ rozwiązuje wszystkie problemy. W przypadku ORMa wręcz rodzi nowe. Prosty przykład - budowanie dynamicznych zapytań. Z webowego interejsu użytkownika dostajemy nazwy właściwości do pobrania/sortowania/filtrowania najczęściej w postaci ich nazw w zmiennych typu string. Jak zbudować z nich zapytanie w Entity Frameworku? Nie da się. Tymczasem NHibernate ma Criteria API, które pozwala na budowanie zapytań na podstawie nazw właściwości. Przykład z oficjalnej dokumentacji:

1 IList<Cat> cats = session.CreateCriteria<Cat>()
2     .Add(Expression.Like("Name", "F%")
3     .AddOrder(Order.Asc("Name"))
4     .AddOrder(Order.Desc("Age"))
5     .List<Cat>();

Generowane zapytania

Entity Framework konwertuje wyrażenia LINQ na kod SQL w całkowicie automagiczny sposób. Programista nie ma żadnego wpływu na to, jaki kształt będzie miało to zapytanie, jakie złączenia zostaną użyte, czy będą podzapytania, czy zostaną użyte funkcje agregujące… Po prostu nic. EF próbuje udawać, że coś takiego jak baza danych i język SQL nie istnieje, że nie istnieje algebra relacji, że nie trzeba myśleć o tym, jak się pobiera dane. Niestety, korzystając z bazy danych, nawet przez ORM trzeba myśleć, głównie o wydajności.

API NHibernate pozwalają na definiowanie typów joinów, podzapytań, korzystanie z funkcji agregujących, nawet stosowanie having. Pisząc kod NHibernate można się spodziewać, jak będzie wyglądał wyjściowy SQL. Źródło poniższych przykładów.

Np. taki kod:

 1 var results = session.QueryOver<Product>()
 2     .JoinQueryOver(pr => pr.TransactionHistory, () => transactionHistoryAlias)
 3     .SelectList(list => list
 4         .SelectGroup(pr => pr.Id)
 5         .SelectGroup(pr => pr.Name)
 6         .SelectCount(() => transactionHistoryAlias.Id)
 7     )
 8     /* Generates a HAVING clause: */
 9     .Where(Restrictions.Gt(
10         Projections.Count(
11             Projections.Property(() => transactionHistoryAlias.Id)), 5))
12     .List<object[]>();

wygeneruje taki SQL:

 1 SELECT
 2     this_.ProductID as y0_,
 3     this_.Name as y1_,
 4     count(transactio1_.TransactionID) as y2_
 5 FROM
 6     Production.Product this_
 7 inner join
 8     Production.TransactionHistory transactio1_
 9         on this_.ProductID = transactio1_.ProductID
10 GROUP BY
11     this_.ProductID,
12     this_.Name
13 HAVING
14     count(transactio1_.TransactionID) > 5;

A taki:

 1 var results = session.QueryOver<Product>(() => productAlias)
 2     .SelectList(list => list
 3         .Select(pr => pr.Id)
 4         .SelectSubQuery(
 5             QueryOver.Of<TransactionHistory>()
 6                 // Creates a correlated subquery
 7                 .Where(tx => tx.Product.Id == productAlias.Id)
 8                 .OrderBy(tx => tx.TransactionDate).Asc
 9                 .Select(tx => tx.TransactionDate)
10                 .Take(1)
11             )
12     )
13     .List<object[]>();

da w efekcie:

 1 SELECT
 2    this_.ProductID as y0_,
 3    (SELECT
 4        TOP (1)  this_0_.TransactionDate as y0_
 5    FROM
 6        Production.TransactionHistory this_0_
 7    WHERE
 8        this_0_.ProductID = this_.ProductID
 9    ORDER BY
10        this_0_.TransactionDate asc) as y1_
11 FROM
12    Production.Product this_;

Zdarzenia

NHibernate ofertuje ponad 20 zdarzeń: insert, update, load, delete, flush w wersji przed i po wykonaniu operacji, do tego zdarzenia dla kolekcji i właściwie wszystkich metod, które udostępnia ISession (np. SaveOrUpdate albo Merge). Dzięki temu wystarczy się podpiąć i wykonać jakąś logikę przed lub po operacji wykonanej przez biliotekę. Za pomocą zdarzeń pre* można też zastąpić domyślne zachowanie NHibernate.. Daje to ogromne możliwości rozszerzalności i automatyzacji pewnych zadań. Nie trzeba w tym celu budować ogromnych hierarchii klas opakowujących wywołania metod ORMa, aby osiągnać podobny efekt bez pomocy zdarzeń. Dzięki temu implementacja np. mechanizmu soft delete jest banalna. EF dla porównania oferuje całe dwa zdarzenia… Bieda. :(

Globalne filtry

Czyli warunek automatycznie dodawany do każdego zapytania o obiekt danego typu. Dla NHibernate nic niesamowitego, co już pokazywałem w poście o soft delete. O dziwo, w Entity Framework jest coś podobnego (oczywiście znacznie ograniczone, gdyż można jedynie stosować porównanie albo sprawdzenie czy dana wartość jest null czy not null), ale chyba nikt tego nie używa. Pracowałem w kilku firmach, nad różnymi projektami korzystającymi z EF, niemniej jednak nigdzie o tym nie słyszano. Obstawiam, że to przez nazwę - conditional mapping, która kompletnie nic nie mówi odnośnie możliwości tego mechanizmu.

Optimistic concurrency

Entity Framework wspiera optimistic concurrency tylko na kolmnach typu rowversion albo porównując wartość wszystkich kolumn rekordu. NHibernate oprócz tego pozwala na dosć intuicyjne rozwiązania takie jak użycie kolumny z kolejnym numerem wersji albo datą i czasem ostatniej modyfikacji.

Future queries

Ten mechanizm pozwala na wysłanie kilku oddzielnie zdefiniowanych zapytań do bazy jednocześnie. Użycie wygląda tak:

1 var totalItemsCount = executableQueryOver
2     .ToRowCountQuery()
3     .FutureValue<int>();
4 
5 var viewModels = executableQueryOver
6     .ProjectToViewModel<TPersistenceModel, TGridModel>(projectionsList)
7     .Future<TGridModel>()
8     .ToList();

W tym przykładzie, oba zapytania zostaną wykonane dopiero w momencie wywołania ToList().

Przy okazji widzimy tu całkiem fajną metodę, której brak w EF - ToRowCountQuery. Usuwa ona sortowanie i paginację z zapytania, a zamiast jego wyniku zwraca count. Całkiem przydatna rzecz.

Query by example

Dzięki temu mechanizmowi możemy utworzyć wzorcowy obiekt (poprzez ustawienie właściwości na interesujące nas wartości), a następnie ORM wygeneruje na jego podtawie odpowiednie warunki where. Na przykład:

1 var example = new Customer();
2 example.Type = CustomerType.Individual;
3 
4 var customers = session.CreateCriteria(typeof(Customer))
5     .Add(Example.Create(example))
6     .List<Customer>();  // zwróci tych, którzy są typu Individual 

Wyliczane właściwości

Ten mechanizm pozwala na definiowanie wyliczanych właściwości, ale nie na poziomie kodu C#, lecz poprzez odpowiednie wyrażenie SQL. Dzięki temu można np. dla pozycji faktury zdefiniować sobie wartość TotalPrice jako iloczyn ItemCount oraz ItemPrice, i używać jej w zapytaniach: .Where(x => x.TotalPrice > 5.0).

Lazy properties

Nie chodzi tu o powiązane obiekty, lecz właściwości BLOB/CLOB np. przechowujące jakiś duży tekst, dokument XML albo grafikę. W EF brak takiego mechanizmu.

Usuwanie kaskadowe

EF jak i NH pozwalają na usuwanie kaskadowe, ale NHibernate dodatkowo pozwala na ustawienie wartości null dla klucza obcego u dzieci usuwanego rodzica.

Decydowanie o tym, kiedy zostaną zapisane zmiany w bazie danych

Entity Framework robi to po wywołaniu metody SubmitChanges(). W NHibernate można skonfigurować, czy chcemy, aby działo się to:

  1. Automatycznie - np. gdy istnieją w sesji niezapisane obiekty danego typu, na którym jest wykonywane zapytanie.
  2. Przy każdym commicie aktywnej transakcji.
  3. Wyłącznie ręcznie, przy wywołaniu metody Flush().

Rozszerzone możliwości transformowania wyników zapytania

W Entity Frameworku możemy za pomocą zapytania wybrać jedynie gotowe obiekty (albo ich projekcję za pomocą metody Select). To jest niewątpliwie najbardziej potrzebny przypadek. Niemniej jednak czasem może zdarzyć się inna potrzeba, np. zwrócenia po prostu tablicy object albo odrębnych obiektów z głównej tabeli, na której uruchomione zostało zapytanie, mimo że warunki zapytania dotyczyły tabeli dołączanej. Albo na użycie jakiegoś niestandardowego konstruktora danego typu. Więcej tutaj.

Pozostałe wady

Entity Framework wymaga nadmiarowych właściwości

Na przykład, aby zrealizować połączenie między klasami Invoice a Customer oprócz właściwości typu Customer trzeba dodać także CustomerId. To łamie DRY, SRP, i pozwala na idiotyczne błędy, gdy do Customer przypisze się obiekt o innym ID niż wstawi do CustomerId. Do tego, jak widać, EF nie wspiera programowania obiektowego - trzeba operować na konceptach bazodanowych w rodzaju klucz obcy. Trochę się to kłóci z ideą całkowitego ukrycia operacji bazodanowych przed programistą.

Konieczne ręczne zarządzanie stanem obiektu

W tysiącach odpowiedzi na pytania dotyczące EF pojawia się taki kod:

1 context.Entry(customer).State == EntityState.Modified/Delted/Whatever...

Skoro często występuje taka konieczność, świadczy to o tym, że Entity Framework nie bardzo sobie radzi ze śledzeniem zmian.

EF nie jest w stanie określić pochodzenia obiektu.

Obiekt nieistniejący poza kontekstem nie moze zostać przypisany do obiektu w kontekście. W takim przypadku nastąpi próba zapisania go do bazy. Oczywiście rozwiązaniem jest:

1 context.Entry(someObject).State = EntityState.Unchanged;

Usuwanie “dzieci”

Jeśli usuniemy jakiś mapowany przez EF obiekt z kolekcji swojego rodzica, np. InvoiceItem z Invoice.Items i spróbujemy zapisać zmiany, dostaniemy za karę wyjątek:

“The relationship could not be changed because one or more of the foreign-key properties is non-nullable”

Po prostu, EF nie potrafi śledzić obiektów, więc nie wystarczy usunać obiektu z kolekcji, trzeba jeszcze to zrobić z kontekstu.

Powolny rozwój

Drugiego kwietnia 2012 r. Microsoft wypuścił MSSQL 2012. Dodano w nim m.in. nową konstrukcję offset i fetch, która znacząco ułatwia konstruowanie zapytań ze stronicowaniem. Entity Framework obsługuje tę konstrukcję od wersji 6.1.2, czyli od 22 grudnia 2014 roku. Ponad dwa i pół roku zajęło Microsoftowi dostosowanie jednego swojego produktu do drugiego…

Własna matematyka

To moja absolutnie ulubiona rzecz w EF. :)

Klasa ma właściwość typu decimal a kolumna w bazie jest typu decimal(18,2). Jeśli ustawimy właściwości wartość np. 20.4777, co się znajdzie w bazie po insercie? Oczywiście 20.47. Dlaczego nie 20.48, jak nakazuje matematyka? Tego nie wiadomo. Żeby było śmieszniej, w czystym ADO.NET działa to dobrze. A EF przecież z niego ponoć korzysta… Niestety nie mam pojęcia jak twórcy EF zhackowali działanie ADO.NET, że to przestało działać, ani kto to testował. ( Może ktoś z ZUS? ;)

Żeby było jeszcze śmieszniej, to nie zostało naprawione - ale w wersji 6.ileś dodano flagę włączającą prawidłowe zaokrąglanie.

Jakby nie patrzeć, żaden inny ORM nie daje możliwości wyboru między stosowaniem matematyki a jej ignorowaniem. Miłośnicy trójkątków o czeterech bokach i dzielenia przez zero powinni być szczęśliwi. ;)

Co ma Entity Framework, czego nie ma NHibernate

Entity Framework wspiera asynchronicznych operacje na bazie danych.

Oczywiście tylko na MSSQL. Wiele osób myśli, że magiczne słówko async rozwiązuje automatycznie wszytkie problemy z wydajnością w systemie. Mnie zaś szczególnie zastanawia zysk z użycia tego w komunikacji z bazą danych. Operacje na bazie są zazwyczaj synchroniczne - pytamy o coś i oczekujemy danych, dopóki ich nie mamy, nasza aplikacja raczej nie ma co robić (w większości przypadków). Dużo więcej w kwestii wydajności może dać raczej batch insert czy future query niż asynchroniczność.

Lepsza dokumentacja i więcej tutoriali

Entity Framework jest dość dobrze opisany na znanym wszystkim programistom .NET MSDNie, jest też masa tutoriali z tym, co programiści lubią najbardziej - kolorowymi obrazkami. Źródeł wiedzy na temat NHibernate jest niestety znacznie mniej.

Więcej commitów niż NHibernate

Serio, niektórzy fanboje Entity Frameworka używają takiego argumentu! To tak jakby powiedzieć, że lepiej mieszkać w dopiero budowanym domu, bez prądu i wody, bo więcej pustaków się muruje. No ok, można i tak.

Nowy Entity Framework

Microsoft sam zdaje sobie sprawę, że Entity Framework to produkt, który trudno rozwijać. Zapewne narosło w nim już tyle warstw kodu spaghetti, że w pewnym momencie stwierdzono, że pora zacząć od nowa. Co ma swoje dobre i złe strony. Dobre są takie, że może będzie lepszy - chociaż po tym, co widzę, to niekoniecznie. Większość wad, które wymieniłem w tym poście zapewne będzie aktualna w nowej wersji. Złe strony są takie, że będą zapewne nowe błędy, i jeszcze mniejsza funkcjonalność, przynajmniej na początku. Poza tym, Microft zdecydował się na usunięcie designera do modelowania EF, co powoduje blady strach u jego miłośników.

Podsumowanie

To zapewne nie jest wszystko, tę listę można byłoby ciągnąć, pokazać więcej przykładów kodu… A jeszcze jakby wkejać przykłady absurdalnego kodu SQL generowanego przez EF, to miejsca na hostingu by zabrakło. Patrząc czasem w profilerze na wygenerowany przez EF kod SQL, nie sposób nie dojść do wniosku, że został on wygenerowany metodą Monte Carlo - zapytania były losowane tak długo, aż zwrócona lista kolumn przypomina strukturą obiekt żądany przez użytkownika. ;)

Ale do rzeczy… Mamy tu doskonały dowód na to, że ludzie nie używają tego, co jest lepsze, lecz tego, co jest lepiej reklamowane. Entity Framework jest w każdym tutorialu Microsoftu, więc ludzie uczący się dowolnej technologii (np. ASP.NET MVC albo WPF) siłą rzeczy go poznają. A potem używają, nawet nie zastanawiając się, czy są inne alternatywy. To jest właśnie coś, co odróżnia świat .NET od światów innych technologii, np. Javy. U nas ludzie przyspawują się do tego, co daje imperium z Redmont, programiści innych technologii są przyzwyczajeni do wyboru i korzystają z niego.

Entity Framework pozwala zrealizować podstawowe zadania ORMa i odciążyć programistę z najbardziej mozolnej części pracy, niemniej jednak jego nierozszerzalność, słaba wydajność i niemożliwość jej poprawienia sprawiają, że im większy projekt, tym bardziej utrudnia on życie. No, ale wiele osób nawet nie zdaje sobie z tego sprawy, bo są wystarczająco szczęśliwi, że nie muszą pisać SQL ręcznie i to, co daje EF im wystarcza. I w sumie nie ma się czemu dziwić - jeśli ktoś nigdy nie widział na oczy noża i całe życie kroi chleb łyżką, to skąd może wiedzieć, że nożem jest łatwiej?

Opublikowano: