Dlaczego nie powinienem używać wzorca repozytorium z Entity Framework?

203

Podczas rozmowy kwalifikacyjnej poproszono mnie o wyjaśnienie, dlaczego wzorzec repozytorium nie jest dobrym wzorcem do pracy z ORM, takimi jak Entity Framework. Dlaczego tak jest?

StringBuilder
źródło
60
to było podchwytliwe pytanie
Omu,
2
Prawdopodobnie odpowiedziałbym ankieterowi, że Microsoft często używa wzorca repozytorium, podczas gdy demonstrują one strukturę encji: | .
Laurent Bourgault-Roy,
1
Więc jaki był powód ankietera, że ​​nie był to dobry pomysł?
Bob Horn
3
Zabawne jest to, że wyszukiwanie „wzorca repozytorium” w Google daje wyniki, które są głównie związane z Entity Framework i jak używać wzorca z EF.
Arseni Mourzenko
2
sprawdź blog ayende ayende.com/blog . Bazując na tym, co wiem, używał Wzorca Repozytorium, ale ostatecznie zrezygnował z niego na rzecz Wzorca Obiektu Zapytania
Jaime Sangcap

Odpowiedzi:

99

Nie widzę powodu, dla którego wzorzec repozytorium nie działa z Entity Framework. Wzór repozytorium to warstwa abstrakcji, którą nakładasz na warstwę dostępu do danych. Warstwą dostępu do danych może być wszystko, od czysto procedur przechowywanych ADO.NET po Entity Framework lub plik XML.

W dużych systemach, w których masz dane pochodzące z różnych źródeł (baza danych / XML / usługa sieciowa), dobrze jest mieć warstwę abstrakcji. Wzorzec repozytorium działa dobrze w tym scenariuszu. Nie wierzę, że Entity Framework jest wystarczającą abstrakcją, aby ukryć to, co dzieje się za kulisami.

Użyłem wzorca repozytorium z Entity Framework jako moją metodą warstwy dostępu do danych i jeszcze nie mam problemu.

Kolejną zaletą abstrakcji DbContextprzy użyciu repozytorium jest możliwość testowania jednostkowego . Możesz mieć IRepositoryinterfejs, w którym są 2 implementacje, jedna (prawdziwe repozytorium), która używa DbContextdo komunikacji z bazą danych, a druga, FakeRepositoryktóra może zwracać obiekty w pamięci / fałszywe dane. To sprawia, że ​​twoja IRepositoryjednostka może być testowana, a więc inne części kodu, które wykorzystują IRepository.

public interface IRepository
{
  IEnumerable<CustomerDto> GetCustomers();
}
public EFRepository : IRepository
{
  private YourDbContext db;
  private EFRepository()
  {
    db = new YourDbContext();
  }
  public IEnumerable<CustomerDto> GetCustomers()
  {
    return db.Customers.Select(f=>new CustomerDto { Id=f.Id, Name =f.Name}).ToList();
  }
}
public MockRepository : IRepository
{
  public IEnumerable<CustomerDto> GetCustomers()
  {
    // to do : return a mock list of Customers
    // Or you may even use a mocking framework like Moq
  }
}

Teraz za pomocą DI otrzymujesz implementację

public class SomeService
{
  IRepository repo;
  public SomeService(IRepository repo)
  {
     this.repo = repo;
  }  
  public void SomeMethod()
  {
    //use this.repo as needed
  }    
}
Shyju
źródło
3
Nie powiedziałem, że to nie zadziała, pracuję również ze wzorcem repozytorium z EF, ale dzisiaj zapytano mnie, dlaczego NIE JEST DOBRE używanie wzorca z DataBase, aplikacją korzystającą z Database
2
Ok, ponieważ jest to najpopularniejsza odpowiedź, wybrałem ją jako poprawną odpowiedź
65
Kiedy ostatni raz najpopularniejsza == poprawna?
HDave
14
DbContext jest już repozytorium, repozytorium ma być abstrakcją niskiego poziomu. Jeśli chcesz wyodrębnić różne źródła danych, utwórz obiekty do ich reprezentowania.
Daniel Little
7
ColacX. Próbowaliśmy właśnie tego - DBextext bezpośrednio w warstwie kontrolera - i wracamy do wzorca repo. W przypadku wzorca Repo testy jednostkowe przeszły od masowego kpowania z DbContext, które ciągle zawodziły. EF był trudny w użyciu, kruchy i kosztował wiele godzin badań nad niuansami EF. Mamy teraz małe proste makiety repozytorium. Kod jest czystszy. Rozdzielenie pracy jest wyraźniejsze. Nie zgadzam się już z tłumem, że EF jest już wzorem repo i jest już testowany jednostkowo.
Rhyous,
434

Czy jest najlepszy powód, aby nie używać wzorca repozytorium z Entity Framework? Entity Framework już implementuje wzorzec repozytorium. DbContextjest twoim UoW (Jednostką Pracy), a każde DbSetjest repozytorium. Wdrożenie kolejnej warstwy jest nie tylko zbędne, ale utrudnia konserwację.

Ludzie podążają za wzorami, nie zdając sobie sprawy z celu wzorca. W przypadku wzorca repozytorium celem jest wyodrębnienie logiki zapytań do bazy danych niskiego poziomu. W dawnych czasach pisania instrukcji SQL w kodzie wzorzec repozytorium był sposobem na przeniesienie SQL z poszczególnych metod rozproszonych po całej bazie kodu i zlokalizowanie go w jednym miejscu. Posiadanie ORM, takiego jak Entity Framework, NHibernate itp., Zastępuje tę abstrakcję kodu i jako takie neguje potrzebę wzorca.

Jednak nie jest złym pomysłem, aby stworzyć abstrakcję na swojej ORM, po prostu nie tak złożoną jak UoW / repostitory. Wybrałbym wzorzec usługi, w którym konstruujesz interfejs API, którego może używać Twoja aplikacja, nie wiedząc ani nie dbając o to, czy dane pochodzą z Entity Framework, NHibernate czy interfejsu API sieci Web. Jest to o wiele prostsze, ponieważ dodajesz metody do klasy usługi, aby zwrócić dane, których potrzebuje Twoja aplikacja. Jeśli piszesz na przykład aplikację do zrobienia, być może masz zgłoszenie serwisowe, aby zwrócić przedmioty, które są w tym tygodniu i jeszcze nie zostały ukończone. Twoja aplikacja wie tylko, że jeśli chce tych informacji, wywołuje tę metodę. Wewnątrz tej metody i ogólnie w twoich usługach wchodzisz w interakcję z Entity Framework lub czymkolwiek innym, czego używasz. Następnie, jeśli później zdecydujesz się zmienić ORM lub pobrać informacje z interfejsu API sieci Web,

Może się to wydawać potencjalnym argumentem przemawiającym za wykorzystaniem wzorca repozytorium, ale kluczową różnicą jest to, że usługa jest cieńszą warstwą i jest nastawiona na zwracanie w pełni upieczonych danych, a nie na coś, do czego nadal pytasz, na przykład magazyn.

Chris Pratt
źródło
68
To wydaje się być jedyną prawidłową odpowiedzią.
Mike Chamberlain
10
Państwo może kpić DbContextw EF6 + (patrz: msdn.microsoft.com/en-us/data/dn314429.aspx ). Nawet w mniejszych wersjach, można użyć fałszywego DbContextklasę -jak z szydzili DbSets, ponieważ DbSetimplementuje iterface, IDbSet.
Chris Pratt
14
@TheZenker, być może nie przestrzegałeś dokładnie wzorca repozytorium. Najostrzejszą różnicą jest wartość zwracana. Repozytoria zwracają kwerendy, podczas gdy usługi powinny zwracać wyliczenia. Nawet to nie jest tak czarno-białe, ponieważ są tam pewne nakładki. To bardziej w sposobie korzystania z niego. Repozytorium powinno po prostu zwrócić zestaw wszystkich obiektów, do których następnie należy wykonać zapytanie, a usługa powinna zwrócić ostateczny zestaw danych i nie powinna obsługiwać dalszego zapytania.
Chris Pratt
10
Ryzykując, że zabrzmi to egoistycznie: są w błędzie. Teraz, jeśli chodzi o oficjalne samouczki, Microsoft wycofał się z używania repozytoriów z tego, co widziałem od EF6. Jeśli chodzi o książkę, nie mogę powiedzieć, dlaczego autor postanowił korzystać z repozytoriów. Jako ktoś w okopach budujących aplikacje na dużą skalę mogę powiedzieć, że używanie wzorca repozytorium w Entity Framework to koszmar konserwacji. Po przejściu do czegoś bardziej złożonego niż garść repozytoriów, spędzasz nadmiernie dużo czasu na zarządzaniu repozytoriami / jednostkami pracy.
Chris Pratt
6
Zwykle mam tylko jedną usługę na bazę danych lub metodę dostępu. Korzystam z metod ogólnych, aby wysyłać zapytania do wielu typów jednostek z tego samego zestawu metod. Używam Ninject, aby wprowadzić mój kontekst do mojej usługi, a następnie moją usługę do moich kontrolerów, aby wszystko było uporządkowane.
Chris Pratt
45

Oto jedno ujęcie Ayende Rahien: Architektury w otchłani zagłady: Zło warstwy abstrakcji repozytorium

Nie jestem jeszcze pewien, czy zgadzam się z jego wnioskiem. Jest to catch-22 - z jednej strony, jeśli opakowuję swój kontekst EF w repozytoria specyficzne dla typu metodami pobierania danych specyficznymi dla zapytania, faktycznie jestem w stanie przetestować mój kod (tak jakby), co jest prawie niemożliwe z Entity Sam szkielet. Z drugiej strony tracę możliwość wykonywania bogatych zapytań i utrzymywania relacji semantycznych (ale nawet gdy mam pełny dostęp do tych funkcji, zawsze mam wrażenie, że chodzę po skorupkach jaj wokół EF lub innej ORM, którą wybiorę , ponieważ nigdy nie wiem, jakie metody jego implementacja IQueryable może obsługiwać, czy nie, czy interpretuje moje dodawanie do kolekcji właściwości nawigacji jako kreację, czy tylko skojarzenie, niezależnie od tego, czy będzie leniwe, chętne, czy też nie zostanie załadowane wcale domyślne itp., więc może to na lepsze. „Mapowanie” obiektów o zerowej impedancji jest czymś w rodzaju mitologicznego stworzenia - może dlatego najnowsza wersja Entity Framework nosiła nazwę „Magic Unicorn”).

Jednak wyszukiwanie jednostek za pomocą metod wyszukiwania danych specyficznych dla zapytania oznacza, że ​​testy jednostkowe są teraz zasadniczo białymi skrzynkami i nie masz wyboru w tej sprawie, ponieważ musisz z góry dokładnie wiedzieć, jaką metodą repozytorium będzie testowana jednostka zadzwoń, aby go wyśmiewać. Wciąż nie testujesz samych zapytań, chyba że napiszesz również testy integracyjne.

Są to złożone problemy wymagające kompleksowego rozwiązania. Nie możesz tego naprawić, udając, że wszystkie twoje istoty są osobnymi typami bez żadnych relacji między nimi i rozpylają je na swoje własne repozytorium. Cóż, możesz , ale to jest do bani.

Aktualizacja: Odniosłem pewien sukces, korzystając z dostawcy Effort dla Entity Framework. Effort jest dostawcą w pamięci (open source), który pozwala używać EF w testach dokładnie tak, jak byś go używał na prawdziwej bazie danych. Zastanawiam się nad przełączeniem wszystkich testów w tym projekcie. Pracuję nad użyciem tego dostawcy, ponieważ wydaje się, że znacznie to ułatwia. Jest to jedyne rozwiązanie, jakie do tej pory znalazłem, które rozwiązuje wszystkie problemy, o których wcześniej mówiłem. Jedyną rzeczą jest niewielkie opóźnienie podczas uruchamiania moich testów, ponieważ tworzy bazę danych w pamięci (używa do tego innego pakietu o nazwie NMemory), ale nie uważam tego za prawdziwy problem. Istnieje artykuł Code Project, który mówi o używaniu Effort (w porównaniu z SQL CE) do testowania.

Luksan
źródło
3
Każdy artykuł o architekturze bez wzmianki o teście jednostkowym jest dla mnie automatycznie wysyłany do kosza. Jednym z punktów wzorca repozytorium jest zdobycie pewnej zdolności testowej.
Sleeper Smith
3
Nadal można przeprowadzać testy jednostkowe bez zawijania kontekstu EF (który jest już repozytorium). Powinieneś być jednostką testującą domenę / usługi, a nie zapytania do bazy danych (są to testy integracyjne).
Daniel Little
2
Testowalność EF znacznie się poprawiła w wersji 6. Możesz teraz w pełni kpić DbContext. Niezależnie od tego, zawsze możesz kpić DbSet, a to jest sedno Entity Framework. DbContextto niewiele więcej niż klasa do przechowywania twoich DbSetwłaściwości (repozytoriów) w jednym miejscu (jednostce pracy), szczególnie w kontekście testów jednostkowych, gdzie inicjalizacja bazy danych i połączenia nie są i tak potrzebne.
Chris Pratt
Utrata nawigacji powiązanej jednostki jest zła i jest przeciwna OOP, ale będziesz mieć większą kontrolę nad tym, o co pytasz.
Alireza
Do momentu przetestowania, EF Core przeszedł długą drogę, po wyjęciu z pudełka In-Memory i In-Memory z dostawcami Sqlite, aby umożliwić testowanie jednostkowe. Wprowadź dokera, gdy potrzebujesz testów integracji, aby uruchomić testy w kontenerowej bazie danych.
Sudhanshu Mishra
16

Powodem, dla którego prawdopodobnie to zrobiłbyś, jest to, że jest trochę zbędny. Entity Framework zapewnia bogactwo kodowania i korzyści funkcjonalnych, dlatego go używasz, jeśli następnie weźmiesz go i zapakujesz we wzór repozytorium, wyrzucając te zalety, równie dobrze możesz użyć dowolnej innej warstwy dostępu do danych.

Dziewięc ogonów
źródło
czy mógłbyś opowiedzieć o niektórych zaletach „Entity Framework zapewnia bogactwo zalet kodowania i funkcjonalności”?
ManirajSS
2
o to mu chodziło. var id = Entity.Where (i => i.Id == 1337) .Single () enkapsuluje i zawija to w repozytorium, w zasadzie nie można wykonać takiej logiki zapytań z zewnątrz, co zmusza do dodania dodatkowego kodu repozytorium i interfejs do pobierania identyfikatora. B zwróć kontekst encji z repozytorium, abyś mógł napisać logikę zapytania (co jest po prostu nonsensem)
ColacX 21.01.16
14

Teoretycznie myślę, że sensowne jest enkapsulowanie logiki połączenia z bazą danych, aby ułatwić jej wielokrotne użycie, ale jak dowodzi poniższy link, nasze współczesne frameworki zasadniczo zajmują się tym teraz.

Ponowne rozpatrzenie wzoru repozytorium

Splendor
źródło
Podobał mi się ten artykuł, ale IMHO dla aplikacji korporacyjnych, warstwa abstrakcji między DAL i Bl MUSI mieć funkcję, ponieważ nie mogłeś wiedzieć, co dokładnie zostanie użyte jutro. Ale dziękuję za udostępnienie linku
1
Chociaż osobiście uważam, że tak jest np. W przypadku NHibernate ( ISessionFactoryi ISessionłatwo można z nich kpić), niestety nie jest to takie proste DbContext...
Patryk Ćwiek
6

Bardzo dobrym powodem do użycia wzorca repozytorium jest umożliwienie oddzielenia logiki biznesowej i / lub interfejsu użytkownika od System.Data.Entity. Ma to wiele zalet, w tym realne korzyści z testów jednostkowych, pozwalając na użycie podróbek lub prób.

James Culshaw
źródło
Zgadzam się z tą odpowiedzią. Moje repozytoria to w zasadzie tylko metody rozszerzeń, które robią tylko drzewa ekspresji. Ponad BARDZO prostą abstrakcją, która po prostu zapewnia ogólną funkcjonalność bezpośrednio nad dbcontext. Jedynym prawdziwym celem abstrakcji jest ułatwienie IoC. Myślę, że ludzie próbują robić w swoich repozytoriach rzeczy, których nie powinni robić. Dokonują repo na jednostkę lub wprowadzają tam logikę biznesową, która powinna znajdować się w warstwie usług. W rzeczywistości potrzebujesz tylko jednego prostego ogólnego repozytorium. To nie jest konieczne, po prostu zapewnia spójny interfejs.
Brandon
Jeszcze jedną rzecz, którą chciałem dodać. Tak CQRS to zdecydowanie lepsza metodologia w większości przypadków. Dla niektórych klientów, nad którymi pracowałem, gdy faceci z bazy danych nie współpracują dobrze z programistami (co zdarza się częściej niż by się to wydawało szczególnie w bankach), EF zamiast SQL jest najlepszą opcją. W tym konkretnym scenariuszu, gdy absolutnie nie masz kontroli nad bazą danych, wzorzec repozytorium ma sens. Ponieważ bardzo przypomina strukturę danych i jest łatwy do przetłumaczenia, co dzieje się w bazie danych i odwrotnie. Moim zdaniem to naprawdę polityczna i logistyczna decyzja. Aby ułagodzić bogów DB.
Brandon
1
Właściwie zaczynam kwestionować moje wcześniejsze opinie na ten temat. EF to połączony wzorzec pracy i repozytorium. Jak wspomniano powyżej Chris Pratt z EF6, możesz łatwo wyśmiewać obiekty Context i DbSet. Nadal uważam, że dostęp do danych powinien być zawinięty w klasy, aby zabezpieczyć klasy logiki biznesowej przed rzeczywistym mechanizmem dostępu do danych, ale pójść na całość i owinąć EF innym repozytorium, a abstrakcja Jednostki Pracy wydaje się przesadą.
James Culshaw
Nie sądzę, żeby to była dobra odpowiedź, ponieważ twoje stwierdzenie wspierające jest takie, że istnieje wiele zalet, jednocześnie wymieniając tylko jedną. Lista, którą robisz, nie jest dobrym powodem, ponieważ możesz użyć bazy danych w pamięci dla encji testów jednostkowych.
Joel McBeth
@ jcmcbeth, jeśli spojrzysz na mój komentarz bezpośrednio nad swoim, zobaczysz, że zmieniłem moją oryginalną opinię w odniesieniu do wzorca repozytorium i EF.
James Culshaw,
0

Wystąpiły problemy ze zduplikowanymi, ale różnymi instancjami Entity Framework DbContext, gdy kontener IoC, który nowe () tworzy repozytoria według typu (na przykład UserRepository i GroupRepository, z których każde wywołuje własny IDbSet z DBContext), może czasami powodować wiele kontekstów na żądanie (w kontekście MVC / sieci).

Przez większość czasu nadal działa, ale po dodaniu warstwy usługi, a te usługi zakładają, że obiekty utworzone w jednym kontekście zostaną poprawnie dołączone jako kolekcje potomne do nowego obiektu w innym kontekście, czasami zawodzi, a czasem nie t w zależności od prędkości zatwierdzeń.

Mufasa
źródło
Spotkałem ten problem w kilku różnych projektach.
ColacX
0

Po wypróbowaniu wzorca repozytorium w małym projekcie zdecydowanie odradzam jego używanie; nie dlatego, że komplikuje to twój system, a nie dlatego, że szydzenie z danych jest koszmarem, ale dlatego, że testowanie staje się bezużyteczne !!

Wyśmiewanie danych pozwala dodawać szczegóły bez nagłówków, dodawać rekordy naruszające ograniczenia bazy danych i usuwać podmioty, których baza danych odmówiłaby usunięcia. W świecie rzeczywistym pojedyncza aktualizacja może wpływać na wiele tabel, dzienników, historii, podsumowań itp., A także na kolumny, takie jak pole daty ostatniej modyfikacji, automatycznie wygenerowane klucze, pola obliczeniowe.

Krótko mówiąc, test na prawdziwej bazie danych daje rzeczywiste wyniki i możesz przetestować nie tylko swoje usługi i interfejsy, ale także zachowanie bazy danych. Możesz sprawdzić, czy twoje procedury przechowywane prawidłowo wykonują dane, zwrócić oczekiwany wynik lub czy rekord wysłany do usunięcia naprawdę został usunięty! Takie testy mogą również ujawnić problemy, takie jak zapominanie o zgłaszaniu błędów z procedury składowanej i tysiące takich scenariuszy.

Myślę, że środowisko encji implementuje wzorzec repozytorium lepiej niż jakikolwiek artykuł, który przeczytałem do tej pory i wykracza daleko poza to, co próbują osiągnąć.

Repozytorium było najlepszą praktyką w tych dniach, w których korzystaliśmy z XBase, AdoX i Ado.Net, ale z jednostką !! (Repozytorium nad repozytorium)

Wreszcie, myślę, że zbyt wiele osób inwestuje dużo czasu w naukę i wdrażanie wzorca repozytorium i nie chcą go puścić. Głównie po to, by udowodnić sobie, że nie marnują czasu.

Zawer Haroon
źródło
1
Tyle że NIE chcesz testować zachowania bazy danych w testach jednostkowych, ponieważ nie jest to całkowicie poziom testowania.
Mariusz Jamro
Tak, tutaj mówisz o testach integracyjnych i jest to rzeczywiście cenne, ale testy jednostkowe są zupełnie inne. Twoje testy jednostkowe nigdy nie powinny trafić do prawdziwej bazy danych, ale zachęcamy do dodania testów integracyjnych, które to sprawdzają.
Chris Pratt,
-3

Wynika to z migracji: migracja nie jest możliwa, ponieważ parametry połączenia znajdują się w pliku web.config. Ale DbContext znajduje się w warstwie repozytorium. IDbContextFactory musi mieć ciąg konfiguracji do bazy danych. Ale nie ma sposobu, aby migracje pobierały ciąg połączenia z pliku web.config.

Są prace, ale nie znalazłem jeszcze na to czystego rozwiązania!

grzmot
źródło