Prędzej czy później każdy projekt .NET musi "pogadać" z bazą danych. Pytanie tylko - na jakim poziomie abstrakcji chcesz to robić. Na rynku dominują 3 podejścia: Entity Framework Core (pełnoprawny ORM), Dapper (lekki mikro-ORM) oraz czysty ADO.NET (niskopoziomowy dostęp do danych). Różnią się poziomem abstrakcji, prostotą implementacji, wydajnością i nakładem pracy, jakiego wymagają od zespołu.
Jako lider techniczny nie wybierasz "najlepszego" narzędzia w oderwaniu od kontekstu - dobierasz to, które pasuje do wymagań projektu i kompetencji ludzi. W tym artykule porównuję wszystkie 3 podejścia pod kątem prostoty, wydajności, możliwości optymalizacji i nakładu pracy, a na końcu podpowiadam, kiedy sięgnąć po które.
W skrócie:
EF Core = najszybszy development kosztem wydajności.
Dapper = świetna wydajność i prosty kod, ale musisz znać SQL.
ADO.NET = maksymalna kontrola i wydajność za cenę największej ilości kodu.
Entity Framework Core - pełny ORM
EF Core to wysokopoziomowy ORM od Microsoftu, który mapuje bazę danych na obiekty .NET. Daje bogaty zestaw funkcji: automatyczne migracje schematu, tłumaczenie zapytań LINQ na SQL oraz śledzenie zmian w obiektach. Dzięki LINQ piszesz zapytania w C#, a nie w surowym SQL - jest to wygodniejsze i bezpieczniejsze, bo część błędów wychwytuje kompilator jeszcze przed uruchomieniem aplikacji. EF Core obsługuje przy tym wszystkie popularne silniki baz (SQL Server, PostgreSQL, MySQL, SQLite i inne) przez wymienne dostawców, co czyni go rozwiązaniem uniwersalnym.
Pod względem szybkości tworzenia aplikacji EF Core wygrywa bezapelacyjnie. Wysokopoziomowe API ukrywa całą niskopoziomową komunikację z bazą, a dodawanie, pobieranie i modyfikacja danych sprowadzają się do operacji na kolekcjach obiektów. Żeby pobrać klientów z konkretnego kraju, wystarczy:
using var context = new MyDbContext();
var customers = context.Customers
.Where(c => c.Country == "Poland")
.ToList();Wydajność. Za wygodę płacisz narzutem. EF Core jest najwolniejszy z całej trójki, bo dokłada sporo warstw nad ADO.NET - śledzenie zmian, kompleksowe tłumaczenie zapytań i mapowanie. Historyczne benchmarki pokazywały, że potrafił być kilkukrotnie wolniejszy od Dappera czy czystego ADO.NET. Kluczowe słowo to jednak "historyczne": każde kolejne wydanie EF Core przynosi solidne optymalizacje. W nowszych wersjach różnice w typowych operacjach mocno się skurczyły - Dapper wciąż jest szybszy, ale przewaga bywa niewielka, szczególnie przy prostych zapytaniach. EF wciąż zużywa więcej pamięci i w niektórych scenariuszach (np. pobranie pojedynczego rekordu metodą Single) wypada zauważalnie wolniej ze względu na sposób generowania SQL.
Możliwości optymalizacji. Masz ich sporo: wyłączanie śledzenia zmian dla zapytań tylko do odczytu (AsNoTracking), kompilowanie często wykonywanych zapytań, a w krytycznych miejscach zejście do surowego SQL (FromSqlRaw). Pełnej kontroli nad SQL-em jednak nie ma - automatycznie generowane zapytania nie zawsze są idealne, a ich "doszlifowanie" wymaga rozumienia, jak ORM działa pod maską.
Nakład pracy. EF Core minimalizuje ilość kodu do napisania i utrzymania - sam zarządza połączeniami, transakcjami i mapowaniem. To szybszy start i mniej kodu, ale pamiętaj: magia ORM-u wymaga zrozumienia. Niewłaściwe ładowanie powiązań czy zapytania ciągnące zbyt duże ładunki danych potrafią zabić wydajność, mimo że kod wygląda niewinnie.
Dapper - lekki mikro-ORM
Dapper to mikro-ORM stworzony przez zespół Stack Overflow. Działa jako cienka nakładka na ADO.NET i sprowadza się do jednego zadania: mapowania wyników zapytań SQL na obiekty C#. Nie śledzi stanu obiektów i nie generuje zapytań - SQL piszesz sam. W zamian dostajesz bardzo wysoką wydajność przy minimalnym narzucie. To jedno z najszybszych rozwiązań ORM-owych: w testach często dorównuje czystemu ADO.NET, a przy bardziej złożonych zapytaniach potrafi je nawet wyprzedzić. Osiąga to dzięki lekkości i wewnętrznemu cache'owaniu mapowań, świadomie rezygnując z "ciężkich" funkcji pełnego ORM-a.
Prostota i kod. Dapper jest banalnie prosty w użyciu, choć wymaga ręcznego pisania SQL. Eliminuje za to cały boilerplate znany z czystego ADO.NET - nie musisz otwierać czytnika danych ani iterować po rekordach, robi to za Ciebie. Kod pozostaje zwięzły i czytelny:
using var connection = new SqlConnection(connectionString);
var customers = connection.Query<Customer>(
"SELECT * FROM Customers WHERE Country = @Country",
new { Country = "Poland" });Wynik zostanie automatycznie zmapowany na listę obiektów Customer. Cały interfejs sprowadza się w praktyce do 2 metod: Query<T> do zapytań zwracających kolekcje oraz Execute do operacji INSERT/UPDATE/DELETE. Znikają ręczne SqlCommand i pętla po SqlDataReader.
Wydajność. Dapper jest szybki, bo poza mapowaniem wyników nie robi praktycznie nic "w tle". W benchmarkach wyraźnie wyprzedza EF Core i zbliża się do gołego ADO.NET. Ceną jest brak wbudowanych udogodnień - nie ma tu migracji ani śledzenia zmian.
Możliwości optymalizacji. Pełnia kontroli po Twojej stronie. Piszesz dowolny SQL - złożone JOIN-y, CTE, procedury składowane - więc wydajność zależy wprost od Twoich umiejętności pisania zapytań. Dapper niczego nie narzuca. W razie potrzeby integruje się z rozszerzeniami, np. Dapper.FluentMap do niestandardowego mapowania kolumn na właściwości klas POCO.
Nakład pracy i ryzyko. W stosunku do EF Core piszesz więcej kodu (bo sam przygotowujesz zapytania) i potrzebujesz zespołu, który zna SQL. Brak warstwy abstrakcji oznacza brak walidacji zapytań na etapie kompilacji - literówkę w SQL-u wychwycisz dopiero w runtime. Mimo to Dapper jest łatwy do opanowania (API jest maleńkie), a jego zwięzłość sprawia, że kod pozostaje czytelny i prosty w utrzymaniu. To świadomy kompromis: oddajesz trochę wygody ORM-a w zamian za szybkość i pełną kontrolę nad bazą.
ADO.NET - niskopoziomowy dostęp do danych
ADO.NET to fundamentalna technologia dostępu do baz w .NET, obecna od początków platformy. Daje pełną kontrolę: sam otwierasz połączenie, budujesz zapytania lub wywołujesz procedury przez SqlCommand i odbierasz wyniki SqlDataReader-em albo wypełniasz DataSet/DataTable. Ta bezpośredniość przekłada się na maksymalną elastyczność i potencjalnie najwyższą wydajność - nie ma tu żadnej warstwy pośredniej dokładającej narzut. ADO.NET błyszczy tam, gdzie liczy się precyzja na poziomie pojedynczej operacji: ręczne zarządzanie transakcjami, streaming ogromnych zbiorów danych czy integracja z legacy kodem opartym na SQL.
Wydajność. Czyste ADO.NET zwykle zapewnia najlepsze wyniki spośród omawianych podejść. Operując wprost na SqlConnection i SqlDataReader, omijasz wszelkie koszty mapowania i śledzenia zmian. Dobrze napisany kod ADO.NET jest wręcz wzorcem wydajności - często służy za punkt odniesienia dla mikro-ORM-ów. Warto jednak pamiętać, że różnica względem Dappera bywa minimalna, bo Dapper wewnętrznie korzysta z tych samych mechanizmów - po prostu odciąża Cię od powtarzalnej roboty.
Możliwości optymalizacji. Pełne. Skoro sam piszesz i wykonujesz zapytania, możesz wykorzystać każdą funkcję bazy: strumieniowanie wyników wiersz po wierszu, parametry, procedury składowane, hinty optymalizatora - cokolwiek jest potrzebne. Ta swoboda jest nieosiągalna dla żadnego ORM-a, ale wymaga wiedzy i uwagi, żeby nie strzelić sobie w stopę.
Prostota i nakład pracy. Tu ADO.NET wypada najsłabiej - jest najbardziej "ceremonialne". Każda operacja to sporo kodu. Żeby pobrać klientów z Polski, musisz napisać:
var customers = new List<Customer>();
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
const string sql = "SELECT CustomerID, CompanyName, Country FROM Customers WHERE Country = @Country";
using (var command = new SqlCommand(sql, connection))
{
command.Parameters.AddWithValue("@Country", "Poland");
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var customer = new Customer
{
CustomerID = reader.GetInt32(0),
CompanyName = reader.GetString(1),
Country = reader.GetString(2)
/* ...mapowanie pozostałych kolumn... */
};
customers.Add(customer);
}
}
}
}Kod jest dłuższy i łatwo o pomyłkę przy ręcznym mapowaniu. Utrzymanie też bywa trudniejsze - każda zmiana struktury bazy (choćby dodanie kolumny) wymusza aktualizację odpowiednich fragmentów. Z drugiej strony ADO.NET nie ma w sobie żadnej "magii" - kod jest w 100% deterministyczny i pod Twoją kontrolą. Jeśli zespół jest biegły w SQL, a Ty potrzebujesz absolutnej kontroli i wydajności, to podejście ma sens.
Porównanie w pigułce
| Kryterium | EF Core | Dapper | ADO.NET |
| Poziom abstrakcji | Wysoki (pełny ORM) | Średni (mikro-ORM) | Niski |
| Wydajność | Dobra (najniższa z trójki) | Bardzo wysoka | Najwyższa |
| Wymagana znajomość SQL | Niska | Wysoka | Wysoka |
| Szybkość developmentu | Najwyższa | Średnia | Najniższa |
| Kontrola nad SQL | Ograniczona | Pełna | Pełna |
| Migracje, śledzenie zmian | Wbudowane | Brak | Brak |
| Ilość kodu | Najmniej | Umiarkowanie | Najwięcej |
| Błędy w zapytaniach | Częściowo w czasie kompilacji | Dopiero w runtime | Dopiero w runtime |
| Najlepszy do... | Szybki development, aplikacje biznesowe | Wysoka wydajność + prosty kod | Pełna kontrola, legacy, ekstremalne wymagania |
Kiedy wybrać które rozwiązanie?
Nie ma uniwersalnie "najlepszego" narzędzia - każde ma swoje mocne i słabe strony. Decyzja powinna zależeć od wymagań projektu i kompetencji zespołu.
Kilka wskazówek:
• Wybierz Entity Framework Core, jeśli priorytetem jest szybkie dostarczanie funkcjonalności i wygoda, a aplikacja zaakceptuje pewien narzut wydajnościowy. EF Core sprawdzi się tam, gdzie potrzebujesz wielu zaawansowanych funkcji (migracje, relacje między tabelami, złożone zapytania LINQ), a zespół woli C# od ręcznego SQL. To dobry wybór dla aplikacji biznesowych o umiarkowanych wymaganiach wydajnościowych - zwłaszcza gdy zespół nie czuje się mocny w optymalizacji SQL. Do większości codziennych operacji EF Core po prostu wystarcza i wiele zespołów używa go na co dzień.
• Wybierz Dapper, jeśli zależy Ci na wydajności zbliżonej do ADO.NET, ale chcesz utrzymać kod prostszym i szybszym w pisaniu niż surowe ADO.NET. Dapper jest świetny dla aplikacji o krytycznych wymaganiach wydajnościowych (web API, mikroserwisy, gdzie liczy się każda milisekunda) oraz tam, gdzie zespół dobrze zna SQL i chce kontroli nad zapytaniami. Daje czysty, czytelny kod bez dużego narzutu. Pamiętaj tylko, że jakość zapytań i ich testowanie spoczywają na Tobie - błędy wyłapiesz dopiero w runtime.
• Wybierz czysty ADO.NET, jeśli projekt wymaga absolutnie pełnej kontroli, musi integrować się z legacy kodem bazodanowym albo działa w środowisku o bardzo ostrych ograniczeniach wydajnościowych czy zasobowych. ADO.NET bywa niezastąpione tam, gdzie inne narzędzia nie dają rady - nietypowe operacje na danych, streaming ogromnych wyników, własne zarządzanie transakcjami rozproszonymi. To większy nakład pracy, ale też pewność, że nad kodem nie ma żadnej dodatkowej warstwy, która mogłaby coś ukryć lub spowolnić. Podejście dla doświadczonych zespołów, które naprawdę potrzebują tej kontroli.
A może… wszystkie naraz?
W praktyce wiele zespołów łączy te podejścia w jednym projekcie - i słusznie. Nic nie stoi na przeszkodzie, żeby używać EF Core do większości operacji, a w miejscach newralgicznych wydajnościowo sięgać po Dapper lub czyste ADO.NET. Przykład: EF Core do CRUD-a i wygodnego mapowania encji przy tworzeniu produktu, a potem optymalizacja najcięższych zapytań (raporty, agregacje na dużych zbiorach) Dapperem lub ADO.NET, kiedy zajdzie taka potrzeba. Takie hybrydowe podejście łączy szybkość developmentu z wydajnością tam, gdzie faktycznie jej potrzeba.
Nie chodzi o religijną lojalność wobec jednego narzędzia, tylko o dobranie właściwego do konkretnego zadania.
Podsumowanie
EF Core, Dapper i ADO.NET to 3 różne drogi do tego samego celu - komunikacji z bazą danych w aplikacji .NET.
• Pełny ORM (EF Core) - najwyższa abstrakcja i najszybszy start, kosztem pewnego spadku wydajności.
• Mikro-ORM (Dapper) - kompromis: oddajesz część wygody, dostajesz doskonałą wydajność i prosty kod, w zamian za znajomość SQL.
• ADO.NET - maksymalna kontrola i wydajność za cenę większego nakładu pracy i bardziej rozbudowanego kodu.
Kieruj się dwiema rzeczami: wymaganiami projektu (co jest ważniejsze - wydajność czy tempo developmentu? jakich funkcji potrzebujesz?) oraz umiejętnościami zespołu (LINQ czy surowy SQL?). Jednego słusznego wyboru nie ma - każda opcja ma swoje miejsce. A w razie wątpliwości rozważ połączenie kilku podejść i wykorzystanie ich atutów w różnych częściach systemu.
Chcesz iść o krok dalej w .NET?
Niezależnie od tego, którą technologię wybierzesz, o realnej przewadze i tak zdecyduje jedno: jak dobrze Ty i Twój zespół rozumiecie narzędzia, których używacie. Technologię można podmienić w tydzień - nawyki, intuicję i wiedzę buduje się miesiącami.
Jeśli chcesz regularnie podkręcać swój poziom w .NET, dołącz do grona programistów, którzy dostają ode mnie praktyczne materiały prosto na skrzynkę - o C#, ORM-ach, wydajności i architekturze aplikacji. Konkrety, które zastosujesz od razu we własnym kodzie, a nie ogólniki, które i tak znajdziesz w dokumentacji.
To miejsce dla ludzi, którzy nie lubią stać w miejscu. Jeśli to brzmi jak Ty - zapisz się na listę VIP i zobacz, o ile łatwiej podejmuje się dobre decyzje techniczne, kiedy stale poszerzasz swój warsztat.
Powodzenia w wyborze najlepszej technologii dla Twojego projektu.