Widziałem wiele projektów z repozytoriami, które zwracają instancje IQueryable
. Pozwala to na dodatkowe filtry, a sortowanie może odbywać się IQueryable
według innego kodu, co przekłada się na generowanie innego kodu SQL. Jestem ciekawy, skąd wziął się ten wzór i czy to dobry pomysł.
Moją największą obawą jest to, że IQueryable
obietnica trafi do bazy danych jakiś czas później, kiedy zostanie wyliczona. Oznacza to, że błąd zostanie wyrzucony poza repozytorium. Może to oznaczać, że wyjątek Entity Framework zostanie zgłoszony w innej warstwie aplikacji.
W przeszłości miałem również problemy z wieloma aktywnymi zestawami wyników (MARS) (szczególnie przy korzystaniu z transakcji) i takie podejście wydaje się, że częściej by się tak działo.
Zawsze wywoływałem AsEnumerable
lub ToArray
na końcu każdego wyrażenia LINQ, aby upewnić się, że baza danych została trafiona przed opuszczeniem kodu repozytorium.
Zastanawiam się, czy zwracanie IQueryable
może być przydatne jako element składowy warstwy danych. Widziałem dość ekstrawagancki kod z jednym repozytorium wywołującym inne repozytorium w celu zbudowania jeszcze większego IQueryable
.
źródło
where
klauzulę do odroczonegoIQueryable
, wystarczy wysłać te dane przewodowo, a nie cały zestaw wyników.Odpowiedzi:
Zwrócenie IQueryable z pewnością zapewni większą elastyczność konsumentom repozytorium. Odrzuca odpowiedzialność zawężenia wyników do klienta, co oczywiście może być zarówno korzyścią, jak i kulą.
Z drugiej strony nie trzeba tworzyć wielu metod repozytorium (przynajmniej na tej warstwie) - GetAllActiveItems, GetAllNonActiveItems itp. - aby uzyskać potrzebne dane. W zależności od preferencji może to być dobre lub złe. Ci będą (/ powinien) należy zdefiniować umów behawioralne, które twoi implementacje przestrzegania, ale jeżeli idzie to do ciebie.
Dzięki temu możesz umieścić logikę pobierania poza repozytorium i pozwolić, aby była używana tak, jak chce użytkownik. Zatem udostępnienie IQueryable daje największą elastyczność i pozwala na wydajne zapytania w przeciwieństwie do filtrowania w pamięci itp., I może zmniejszyć potrzebę tworzenia mnóstwa określonych metod pobierania danych.
Z drugiej strony, teraz dałeś swoim użytkownikom strzelbę. Mogą robić rzeczy, których nie zamierzałeś (nadużywanie .include (), robienie ciężkich zapytań i filtrowanie w pamięci w ich odpowiednich implementacjach itp.), Które zasadniczo pomogłyby w nakładaniu warstw i kontroli behawioralnej, ponieważ dałeś pełny dostęp.
Więc w zależności od zespołu, ich doświadczenia, wielkości aplikacji, ogólnego nakładania warstw i architektury… to zależy: - \
źródło
Expression<Func<Foo,bool>>
do swojej metody. Parametry te można przekazaćWhere
bezpośrednio do metody, umożliwiając konsumentowi wybór filtra bez potrzeby bezpośredniego dostępu do IQueryable <Foo>.Udostępnianie IQueryable interfejsom publicznym nie jest dobrą praktyką. Powód jest fundamentalny: nie można zapewnić IQueryable realizacji, jak się mówi.
Interfejs publiczny to umowa między dostawcą a klientami. W większości przypadków oczekuje się pełnego wdrożenia. IQueryable to oszustwo:
Wzór repozytorium daje nam wyraźną reprezentację poprzez warstwowanie, a co najważniejsze, niezależność realizacji. Możemy więc zmienić przyszłe źródło danych.
Pytanie brzmi: „ Czy powinna istnieć możliwość zastąpienia źródła danych? ”.
Jeśli jutro część danych można przenieść do usług SAP, bazy danych NoSQL lub po prostu do plików tekstowych, czy możemy zagwarantować prawidłowe wdrożenie interfejsu IQueryable?
( więcej dobrych uwag na temat tego, dlaczego jest to zła praktyka)
źródło
Realistycznie masz trzy możliwości, jeśli chcesz odroczyć wykonanie:
IQueryable
.GetCustomersInAlaskaWithChildren
poniżej)IQueryable
wewnętrznie.Wolę trzeci (chociaż pomogłem kolegom wdrożyć drugi), ale oczywiście wiąże się to z pewną konfiguracją i konserwacją. (T4 na ratunek!)
Edycja : Aby usunąć zamieszanie wokół tego, o czym mówię, rozważ następujący przykład skradziony IQueryable może zabić psa, ukraść żonę, zabić wolę życia itp.
W scenariuszu, w którym ujawniłbyś coś takiego:
API, o którym mówię, pozwoliłoby ci ujawnić metodę, która pozwoliłaby ci wyrazić
GetCustomersInAlaskaWithChildren
(lub dowolną inną kombinację kryteriów) w elastyczny sposób, a repo wykonałoby to jako IQueryable i zwróci ci wyniki. Ale ważne jest to, że działa w warstwie repozytorium, więc nadal korzysta z odroczonego wykonania. Po odzyskaniu wyników nadal możesz LINQ-do-obiektów na nim, aby zadowolić swoje serce.Kolejną zaletą takiego podejścia jest to, że ponieważ są to klasy POCO, to repozytorium może żyć za usługą WWW lub WCF; może być narażony na AJAX lub inne osoby dzwoniące, które nie znają pierwszej rzeczy na temat LINQ.
źródło
IQueryable
bez narażaniaIQueryable
się . Jak zamierzasz to zrobić dzięki wbudowanemu interfejsowi API?Naprawdę jest tylko jedna prawidłowa odpowiedź: zależy to od sposobu użycia repozytorium.
Z jednej strony repozytorium jest bardzo cienkim opakowaniem wokół DBContext, dzięki czemu można wstrzyknąć okleinę testowalności wokół aplikacji sterowanej bazą danych. Naprawdę nie ma rzeczywistych oczekiwań, że można by tego użyć w sposób rozłączony bez przyjaznego DB LINQ, ponieważ twoja aplikacja CRUD nigdy nie będzie tego potrzebować. Więc na pewno, dlaczego nie użyć IQueryable? Prawdopodobnie wolałbym IEnumarable, ponieważ dostajesz większość korzyści [czytaj: opóźnione wykonanie] i nie czuje się tak brudny.
O ile nie siedzisz w tak ekstremalnej sytuacji, starałbym się wykorzystać duch wzorca repozytorium i zwrócić odpowiednie zmaterializowane kolekcje, które nie mają wpływu na podstawowe połączenie z bazą danych.
źródło
IEnumerable
, ważne jest, aby pamiętać, że((IEnumerable<T>)query).LastOrDefault()
różni się bardzo odquery.LastOrDefault()
(zakładając, żequery
jestIQueryable
). Dlatego warto wrócić,IEnumerable
jeśli i tak będziesz przechodzić przez wszystkie elementy.NIE, jeśli chcesz przeprowadzić testowanie / testowanie, ponieważ nie ma łatwego sposobu na utworzenie próbnego repozytorium w celu przetestowania swojego businesslogic (= repozytorium-konsument) w izolacji od bazy danych lub innego dostawcy IQueryable.
źródło
Z czystego punktu widzenia architektura IQueryable jest nieszczelną abstrakcją. Zasadniczo zapewnia użytkownikowi zbyt dużą moc. Biorąc to pod uwagę, są miejsca, w których ma to sens. Jeśli używasz OData, IQueryable sprawia, że niezwykle łatwo jest zapewnić punkt końcowy, który można łatwo filtrować, sortować, grupować ... itd. W rzeczywistości wolę tworzyć DTO, na które odwzorowują moje byty, a IQueryable zwraca DTO. W ten sposób wstępnie filtruję moje dane i naprawdę używam tylko metody IQueryable, aby umożliwić klientom dostosowanie wyników.
źródło
Taki wybór też miałem.
Podsumujmy więc pozytywne i negatywne strony:
Zalety:
Negatywne:
Odradzałbym stosowanie tego podejścia. Zamiast tego rozważ utworzenie kilku Specyfikacji i zaimplementuj metody repozytorium, które mogą wyszukiwać w bazie danych za ich pomocą. Wzorzec specyfikacji to świetny sposób na zawarcie zestawu warunków.
źródło
Myślę, że ujawnienie IQueryable w twoim repozytorium jest całkowicie akceptowalne na początkowych etapach rozwoju. Istnieją narzędzia interfejsu użytkownika, które mogą bezpośrednio współpracować z IQueryable i obsługiwać sortowanie, filtrowanie, grupowanie itp.
Biorąc to pod uwagę, myślę, że samo ujawnienie surowca w repozytorium i nazwanie go dniem jest nieodpowiedzialne. Posiadanie przydatnych operacji i pomocników zapytań dla typowych zapytań pozwala scentralizować logikę zapytania, jednocześnie pokazując mniejszą powierzchnię interfejsu konsumentom repozytorium.
W przypadku pierwszych kilku iteracji projektu publiczne udostępnienie IQueryable w repozytorium pozwala na szybszy rozwój. Przed pierwszą pełną wersją zalecam uczynienie operacji zapytania prywatną lub chronioną i umieszczenie dzikich zapytań pod jednym dachem.
źródło
To, co widziałem (a co sam realizuję), to:
Umożliwia to elastyczność wykonywania
IQueryable.Where(a => a.SomeFilter)
zapytań w repozytoriumconcreteRepo.GetAll(a => a.SomeFilter)
bez ujawniania jakiejkolwiek działalności LINQy.źródło