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?
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?
Odpowiedzi:
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 DbContext
przy użyciu repozytorium jest możliwość testowania jednostkowego . Możesz mieć IRepository
interfejs, w którym są 2 implementacje, jedna (prawdziwe repozytorium), która używa DbContext
do komunikacji z bazą danych, a druga, FakeRepository
która może zwracać obiekty w pamięci / fałszywe dane. To sprawia, że twoja IRepository
jednostka 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
}
}
Czy jest najlepszy powód, aby nie używać wzorca repozytorium z Entity Framework? Entity Framework już implementuje wzorzec repozytorium. DbContext
jest twoim UoW (Jednostką Pracy), a każde DbSet
jest 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.
DbContext
w EF6 + (patrz: msdn.microsoft.com/en-us/data/dn314429.aspx ). Nawet w mniejszych wersjach, można użyć fałszywego DbContext
klasę -jak z szydzili DbSet
s, ponieważ DbSet
implementuje iterface, IDbSet
.
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.
DbContext
. Niezależnie od tego, zawsze możesz kpić DbSet
, a to jest sedno Entity Framework. DbContext
to niewiele więcej niż klasa do przechowywania twoich DbSet
wł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.
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.
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.
ISessionFactory
i ISession
łatwo można z nich kpić), niestety nie jest to takie proste DbContext
...
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.
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ń.
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.
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!