Czytałem kilka artykułów na temat zalet tworzenia Ogólnych repozytoriów dla nowej aplikacji ( przykład ). Pomysł wydaje się miły, ponieważ pozwala mi używać tego samego repozytorium do robienia kilku rzeczy dla kilku różnych typów jednostek jednocześnie:
IRepository repo = new EfRepository(); // Would normally pass through IOC into constructor
var c1 = new Country() { Name = "United States", CountryCode = "US" };
var c2 = new Country() { Name = "Canada", CountryCode = "CA" };
var c3 = new Country() { Name = "Mexico", CountryCode = "MX" };
var p1 = new Province() { Country = c1, Name = "Alabama", Abbreviation = "AL" };
var p2 = new Province() { Country = c1, Name = "Alaska", Abbreviation = "AK" };
var p3 = new Province() { Country = c2, Name = "Alberta", Abbreviation = "AB" };
repo.Add<Country>(c1);
repo.Add<Country>(c2);
repo.Add<Country>(c3);
repo.Add<Province>(p1);
repo.Add<Province>(p2);
repo.Add<Province>(p3);
repo.Save();
Reszta implementacji repozytorium jest jednak w dużym stopniu zależna od Linq:
IQueryable<T> Query();
IList<T> Find(Expression<Func<T,bool>> predicate);
T Get(Expression<Func<T,bool>> predicate);
T First(Expression<Func<T,bool>> predicate);
//... and so on
Ten wzorzec repozytorium działał fantastycznie dla Entity Framework i prawie oferował mapowanie 1 do 1 metod dostępnych w DbContext / DbSet. Ale biorąc pod uwagę powolne wdrażanie Linq w innych technologiach dostępu do danych poza Entity Framework, jaką to daje przewagę nad bezpośrednią pracą z DbContext?
Próbowałem napisać PetaPoco wersję repozytorium, ale PetaPoco nie obsługuje Linq wyrażeń, co sprawia, tworząc ogólną IRepository interfejs prawie bezużyteczne, chyba że tylko używać go do podstawowego GETALL, GetById, dodawać, aktualizować, usuwać i Zapisz metody i wykorzystać jako klasę podstawową. Następnie musisz utworzyć określone repozytoria za pomocą specjalistycznych metod do obsługi wszystkich klauzul „gdzie”, które wcześniej mogłem przekazać jako predykat.
Czy wzorzec Ogólnego repozytorium jest użyteczny dla czegokolwiek poza Entity Framework? Jeśli nie, to dlaczego ktoś miałby go używać zamiast pracować bezpośrednio z Entity Framework?
Oryginalny link nie odzwierciedla wzoru, którego użyłem w moim przykładowym kodzie. Oto ( zaktualizowany link ).
Odpowiedzi:
Ogólne repozytorium jest nawet bezużyteczne (a IMHO również złe) dla Entity Framework. Nie wnosi żadnej dodatkowej wartości do tego, co już zostało dostarczone
IDbSet<T>
(czyli przez zwykłe repozytorium).Jak już odkryłeś, argument, że ogólne repozytorium można zastąpić implementacją innych technologii dostępu do danych jest dość słaby, ponieważ może wymagać napisania własnego dostawcy Linq.
Drugi wspólny argument dotyczący uproszczonego testowania jednostek jest również błędny, ponieważ kpiące repozytorium / zestaw z pamięcią danych w pamięci zastępuje dostawcę Linq innym, który ma różne możliwości. Dostawca Linq do podmiotów obsługuje tylko podzbiór funkcji Linq - nawet nie obsługuje wszystkich metod dostępnych w
IQueryable<T>
interfejsie. Udostępnianie drzew wyrażeń między warstwą dostępu do danych a warstwami logiki biznesowej zapobiega fałszowaniu warstwy dostępu do danych - logikę zapytań należy oddzielić.Jeśli chcesz mieć silną „ogólną” abstrakcję, musisz również zastosować inne wzorce. W takim przypadku należy użyć abstrakcyjnego języka zapytań, który można przetłumaczyć z repozytorium na określony język zapytań obsługiwany przez używaną warstwę dostępu do danych. Jest to obsługiwane przez wzorzec specyfikacji. Linq on
IQueryable
jest specyfikacją (ale tłumaczenie wymaga dostawcy - lub jakiegoś niestandardowego użytkownika tłumaczącego drzewo wyrażeń na zapytanie), ale możesz zdefiniować własną uproszczoną wersję i użyć jej. Na przykład NHibernate używa Criteria API. Nadal najprostszym sposobem jest użycie określonego repozytorium za pomocą określonych metod. Ten sposób jest najprostszy do wdrożenia, najprostszy do przetestowania i najprostszy do sfałszowania w testach jednostkowych, ponieważ logika zapytań jest całkowicie ukryta i oddzielona od abstrakcji.źródło
ISession
Krótkie pytanie, NHibernate ma to, co można łatwo wyśmiewać do ogólnych celów testowania jednostkowego, i chętnie opuściłbym „repozytorium” również podczas pracy z EF - ale czy jest jakiś łatwiejszy, prostszy sposób na odtworzenie tego? Coś podobnego doISession
tegoISessionFactory
, bo nie ma,IDbContext
o ile mogę powiedzieć ...IDbContext
- jeśli chceszIDbContext
, możesz po prostu utworzyć taki i zaimplementować go w kontekście pochodnym.Problemem nie jest wzorzec repozytorium. Dobrze jest mieć abstrakcję między uzyskiwaniem danych a sposobem ich uzyskiwania.
Problemem jest tutaj implementacja. Zakładanie, że do filtrowania zadziała dowolne wyrażenie, jest co najwyżej ryzykowne.
Utworzenie repozytorium dla wszystkich twoich obiektów bezpośrednio mija się z celem. Obiekty danych rzadko, jeśli w ogóle, będą mapowane bezpośrednio na obiekty biznesowe. Przekazywanie T do filtrowania ma w tych sytuacjach znacznie mniej sensu. A zapewnienie tak dużej funkcjonalności gwarantuje, że nie będziesz w stanie obsłużyć wszystkich, gdy pojawi się inny dostawca.
źródło
Wartość ogólnej warstwy danych (repozytorium jest szczególnym typem warstwy danych) pozwala kodowi na zmianę podstawowego mechanizmu pamięci, z niewielkim lub żadnym wpływem na kod wywołujący.
Teoretycznie działa to dobrze. W praktyce, jak zauważyłeś, abstrakcja jest często nieszczelna. Mechanizmy używane do uzyskiwania dostępu do danych w jednym są inne niż mechanizmy w innym. W niektórych przypadkach kończy się to pisaniem kodu dwukrotnie: raz w warstwie biznesowej i powtarzając go w warstwie danych.
Najskuteczniejszym sposobem utworzenia ogólnej warstwy danych jest wcześniejsza znajomość różnych rodzajów źródeł danych, z których będzie korzystać aplikacja. Jak widzieliście, założenie, że LINQ lub SQL jest uniwersalne, może stanowić problem. Próba modernizacji nowych magazynów danych prawdopodobnie spowoduje przepisanie.
[Edycja: Dodano następujące elementy.]
Zależy to również od tego, czego aplikacja potrzebuje od warstwy danych. Jeśli aplikacja ładuje lub przechowuje obiekty, warstwa danych może być bardzo prosta. Gdy rośnie potrzeba wyszukiwania, sortowania i filtrowania, wzrasta złożoność warstwy danych, a abstrakcje stają się nieszczelne (na przykład ujawnianie zapytań LINQ w pytaniu). Jednak gdy użytkownicy mogą dostarczyć własne zapytania, należy dokładnie rozważyć koszty / korzyści warstwy danych.
źródło
Posiadanie warstwy kodu nad bazą danych jest warte zachodu w prawie wszystkich przypadkach. Generalnie wolałbym wzorzec „GetByXXXXX” we wspomnianym kodzie - pozwala on optymalizować zapytania za nim w razie potrzeby, jednocześnie utrzymując interfejs wolny od bałaganu interfejsu danych.
Korzystanie z generycznych jest zdecydowanie uczciwą grą - posiadanie
Load<T>(int id)
metody ma mnóstwo sensu. Ale budowanie repozytoriów wokół LINQ jest równoważne z 2010 r. Porzucaniem zapytań sql wszędzie z niewielkim dodatkowym bezpieczeństwem.źródło
Cóż, z podanym linkiem widzę, że może to być przydatne opakowanie dla
DataServiceContext
, ale nie zmniejsza manipulacji kodem ani nie poprawia czytelności. Poza tym dostęp doDataServiceQuery<T>
jest utrudniony, co ogranicza elastyczność do.Where()
i.Single()
. Nie sąAddRange()
ani alternatywy. NieDelete(Predicate)
podano też, co mogłoby być przydatne (repo.Delete<Person>( p => p.Name=="Joe" );
aby usunąć Joe-ów). Itp.Wniosek: taki interfejs API blokuje natywny interfejs API i ogranicza go do kilku prostych operacji.
źródło
Powiedziałbym tak". W zależności od modelu danych dobrze rozwinięte ogólne repozytorium może uczynić warstwę dostępu do danych bardziej uproszczoną, schludną i znacznie bardziej niezawodną.
Przeczytaj serię artykułów (autor: @ chris-pratt ):
źródło