Witam.
Piszę z kwestią, która już była parę razy poruszana w pomniejszych wątkach. Moje dwa poprzednie projekty korzystają z EF Core i miałem kilka "gwoździ", które rozwiązywałem na różne, szalone sposoby. Teraz stoję przed dylematem, czy dalej brnąć w EF Core, czy może spróbować oprzeć oprogramowanie na Dapperze, bo akurat Dappera znam. Brak elastyczności w przypadku EF trochę mnie boli, ponieważ tak jak zostało to napisane, w którymś z moich wątków - nie ma ogólnych zapytań SQL. Czyste query można sobie puścić z poziomu tabeli, a nie z poziomu całego contextu. Dapper, z drugiej strony, odstrasza mnie "strukturą" - dapper.contrib nie jest w stanie zapisać generycznych obiektów, trzeba gdzieś trzymać wszystkie stringi z zapytaniami. Takie mam doświadczenia z tymi technologiami. Jak żyć? Co wybrać?
My w pracy używamy mixu obu - zapis przez EF Core i odczyt zwykle przez Dappera. Tylko mamy osobne klasy do zapisu i odczytujemy to zupełnie innych klas (dokładnie takich jak w danej chwili potrzeba). Powiedzmy, że jest to CQRS (w jakiejś tam podstawowej wersji).
Tylko mamy to podzielone na niewielkie, podzielone na warstwy klasy i metody więc string z sqlem jest jeden w klasie, który robi dokładnie to co ta klasa potrzebuje, a nie, że wszystkie zapytania w aplikacji są wpakowane do gigantycznej klasy statycznej czy coś tego typu.
To zależy od projektu. Jeżeli masz mały projekt z małą bazą danych to używanie Dappera jest wyciąganiem armaty na wróbla. Przy większych projektach a w zasadzie przy dużych zapytaniach Dapper radzi sobie dużo lepiej niż EF. Najlepiej używać mix tych dwóch. Do małych zapytań EF, do dużych (w sensie obciążających) Dapper.
Pytanie teraz co to jest mały projekt? :-) Ogólnie i krótko pisząc - zarządzanie kontraktami na produkty pomiędzy firmami, z bazą plików (pdf, doc, jpg...) powiązanych z kontraktem lub produktem z uwzględnieniem uprawnień dla użytkowników do tych plików. Duży projekt, czy mały?
AdamWox napisał(a):
Pytanie teraz co to jest mały projekt? :-) Ogólnie i krótko pisząc - zarządzanie kontraktami na produkty pomiędzy firmami, z bazą plików (pdf, doc, jpg...) powiązanych z kontraktem lub produktem z uwzględnieniem uprawnień dla użytkowników do tych plików. Duży projekt, czy mały?
Mały/duży to indywidualna kwestia. Tak samo wielkość bazy też. Aczkolwiek możesz z grubsza oszacować jak duże będziesz miał zapytania w projekcie. Jeżeli znasz schemat bazy to na podstawie use casów możesz z grubsza pokminić ile będzie joinów między tabelami i jak są skomplikowane. Jeżeli schemat jest prosty (Users, Posts, UserRoles, Comments, etc) to EF sobie z tym poradzi. Jeżeli masz pewnie ze 100 tabel i z 10 joinów do zapytania to EF mówi pass.
Jeśli chcesz korzystać z EF i masz problem z jakimś jednym konkretnym zapytaniem to warto się zastanowić nad użyciem procedury składowanej i wykonania jej z poziomu EF. To daje równie mocną elastyczność jak Dapper.
Czy Dapper ma swój model bazy danych i mechanizm do jego aktualizacji?
Istotną częścią większości ORMów jest mechanizm zapewniający pewien poziom spójności pomiędzy kodem a bazą danych. Jeśli używasz podejścia database first i robisz zmiany w bazie, a potem aktualizujesz model bazy, to większość (jeśli nie wszystkie) zmian wyjdzie na etapie kompilacji. Zmienione nazwy tabel, pól, typów pól, parametry sp itp. To pozwala na zmniejszenie ilości błędów w kodzie (mniejsza regresja), więc jest bardzo dużą zaletą. Niestety cała infrastruktura ORM często zjada dużo zasobów i spowalnia komunikację z bazą danych, a generowane zapytania czasem są dalekie od optymalnych.
Pisanie zapytań bezpośrednio do bazy danych likwiduje ten problem, ale też zabiera kontrolę spójności z bazą danych. Dlatego moim zdaniem w dużych projektach najlepsze jest mieszanie obu podejść, czyli używanie dużego ORM wszędzie, gdzie nie powoduje to wąskich gardeł oraz małego mappera w takich miejscach. Przy małych projektach, gdy łatwiej zapanować nad całym kodem naraz, wybór EF/Dapper moim zdaniem powinno zależeć tylko od wymaganej wydajności.
Pamiętajmy, że w tych czasach słaba wydajność to czasem kwestia dokupienia kilku maszyn/dołożenia kilku procesorów, co zwykle jest tańsze niż dodatkowe godziny pracy programistów albo wkurzeni błędami klienci odchodzący do konkurencji.
U siebie też robię miks. Zacząłem od EfCore + nHibernate. Ale z nHibernate było duuużo problemów jak na moje założenia (za dużo robi), więc przesiadłem się na Dappera. EfCore jednak używam tylko do standardowych rzeczy (w stylu ogarnianie użytkowników, ról itd). Problem z Dapperem jest tylko taki, że czasem są potrzebne różne zapytania pod różne DBMS. Akurat w moim projekcie używam jednocześnie MSSQL i SQLite, co stanowi wyzwanie w pewnych momentach :) Ale idzie to opanować.
var napisał(a):
Jeśli chcesz korzystać z EF i masz problem z jakimś jednym konkretnym zapytaniem to warto się zastanowić nad użyciem procedury składowanej i wykonania jej z poziomu EF. To daje równie mocną elastyczność jak Dapper.
A co myślisz o używaniu do tego celu widoków? W ten sposób te skomplikowane joiny trzymamy po stronie bazy, ale wciąż możemy wykorzystać EF np. do filtrowania (co w większości przypadków już powinno generować proste query).
Widzę, że większość korzysta z obu rozwiązań. Teraz pytanie w kwestii Dappera i jego "struktury". Przyznaje się, bez bicia, że do tej pory robiłem klasę statyczną Queries
i pakowałem wszystkie stringi z zapytaniami tam. Rozwiązanie, które zasugerował @mar-ek1 ma sens, tylko nie wiem, czy dobrze rozumiem. Jako statyczna właściwość modelu?
public class Product
{
public int Id {get;set;}
public string Name {get;set;}
public static string GetAllQuery = "select * from Products";
public static string GetByIdQuery = "select * from Products where Id = @Id";
public static string GetByNameQuery = "select * from Products where Name = @Name";
}
Nie trzymam sqla w modelu bo to co jest w bazie może nie mieć nic wspólnego z tym co czytam.
Akurat w naszym przypadku mamy handlery/serwisy, które wyciągają konkretne rzeczy. Jak potrzebuję listę IDków userów z rolą "Tester" to mam klasę, która takie dane wyciąga i mapuje na odpowiednie DTO. Jak potrzebuję z tej samej tabeli wyciagnąć szczegóły aktualnego usera to mam kolejną klasę, która to robi, zwłaszcza, że te dane mogą być np. w 10 tabelach bo na te szczegóły może się składać liczba dodanych do niego projektów, nazwisko przełożonego, data ostatniego logowania i awatar. I w obu przypadkach czytam przypadkiem z tej samej tabeli, ale to są zupełnie inne dane i inne DTO więc i inne query i klasy, które to robią.
Model do zapisu ma tylko to co jest potrzebne do zapisu. Praktycznie nigdy nawet nie zwracamy tego modelu potem.
EDIT/Offtop: Jak widzę "getAll", a potem "getById", "getByName", "getByShoeSize" to mam wrażenie, że coś jest nie tak. Bo kiedy np. potrzebujesz całą tabelę wyciągnać?
Czy to przypadkiem nie jest swego typu overkill robić osobne klasy dla każdego zapytania? Czy znowu czegoś nie rozumiem? Ile masz, w sumie, klas w takim projekcie?
Nie mam tych klas zbyt dużo bo może ze 20? Tyle ile mam endpointów w API. Po prostu każdy endpoint dostaje dokładnie to czego potrzebuje, w takim formacie jaki chcę, bez nadmiarowych danych albo dziwnych zlepionych struktur.
Dzięki temu jak chcę zmienić strukturę danych w endpoincie A to to zmieniam, a nie zastanawiam się czy nie zepsuję endpointów B, C, D przez to.
Jak poszukasz info o takim podstawowym CQRS na jednej bazie to tam pewnie będzie to opisane lepiej.
No ok, myślę, że ma to sens. Mam jeszcze jedno pytanie - bo skoro mam zamiar używać EF + Dapper, to jak to jest w kwestii tworzenia bazy? Czy w takim przypadku też korzystam z code first? Dlaczego ten sposób jest tak popularny? Chodzi o napisanie raz klas i nie trzeba dłubać tabel w Management Studio?
Dapper nie gryzie się z EF. Dapper robi operacje czystym SQL na bazie a entity framework wymaga spójności modelu dbContextu z bazą. Możesz zastosować cose first bo jest po prostu łatwiejszy i spójny między dwoma systemami (apka i baza). Dłubanie tabel ma to ryzyko że zrobisz rename tabeli i EF tego nie załapie jeżeli nie wprowadzisz zmian w kodzie.
Dbcontext nie wymaga spojnosci z cala baza tylko z tym co ma zmapowane.mozesz mieć kilka dbcontextow w aplikacji i każdy mkze sobie malować po swojemu czesci bazy + jeden DbContext dla całej bazy do migracji.
Co ma dapper, poza wydajnością, czego nie można zrobić w EF?