Tak, obie dadzą ci odroczoną egzekucję .
Różnica polega na tym, że IQueryable<T>
jest to interfejs, który umożliwia działanie LINQ-to-SQL (LINQ.-to-cokolwiek naprawdę). Jeśli więc dopracujesz swoje zapytanie naIQueryable<T>
, zapytanie to zostanie wykonane w bazie danych, jeśli to możliwe.
W tym IEnumerable<T>
przypadku będzie to LINQ-to-object, co oznacza, że wszystkie obiekty pasujące do pierwotnego zapytania będą musiały zostać załadowane do pamięci z bazy danych.
W kodzie:
IQueryable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);
Ten kod wykona SQL, aby wybrać tylko złotych klientów. Z kolei następujący kod wykona oryginalne zapytanie w bazie danych, a następnie odfiltruje klientów niebędących złotymi w pamięci:
IEnumerable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);
Jest to dość ważna różnica, a praca nad nią IQueryable<T>
może w wielu przypadkach uchronić Cię przed zwróceniem zbyt wielu wierszy z bazy danych. Innym doskonałym przykładem robi stronicowania: Jeśli używasz Take
i Skip
na IQueryable
, dostaniesz liczba wierszy żądać tylko; wykonanie tego IEnumerable<T>
spowoduje, że wszystkie wiersze zostaną załadowane do pamięci.
IEnumerable
zrobić,IQueryable
jest to, że nie wszystkie operacje LINQ są obsługiwane przez wszystkich dostawców LINQ. Tak długo, jak wiesz, co robisz, możesz użyć,IQueryable
aby przekazać jak najwięcej zapytania dostawcy LINQ (LINQ2SQL, EF, NHibernate, MongoDB itp.). Ale jeśli pozwolisz, aby inny kod robił zIQueryable
tobą co tylko zechce , w końcu wpadniesz w kłopoty, ponieważ jakiś kod klienta użył nieobsługiwanej operacji. Zgadzam się z zaleceniem, aby nie wypuszczaćIQueryable
s „na wolności” poza repozytorium lub równoważną warstwę.Najlepsza odpowiedź jest dobra, ale nie wspomina o drzewach wyrażeń, które wyjaśniają „czym” różnią się oba interfejsy. Zasadniczo istnieją dwa identyczne zestawy rozszerzeń LINQ.
Where()
,Sum()
,Count()
,FirstOrDefault()
, Etc wszyscy mają dwie wersje: jedną, która akceptuje funkcje i taki, który akceptuje wyrażeń.IEnumerable
Podpis wersja jest:Where(Func<Customer, bool> predicate)
IQueryable
Podpis wersja jest:Where(Expression<Func<Customer, bool>> predicate)
Prawdopodobnie korzystałeś z obu, nie zdając sobie z tego sprawy, ponieważ oba są wywoływane przy użyciu identycznej składni:
np.
Where(x => x.City == "<City>")
działa zarówno na, jakIEnumerable
i naIQueryable
Podczas korzystania
Where()
zIEnumerable
kolekcji kompilator przekazuje skompilowaną funkcję doWhere()
Podczas korzystania
Where()
zIQueryable
kolekcji kompilator przekazuje drzewo wyrażeń doWhere()
. Drzewo wyrażeń jest jak system odbicia, ale dla kodu. Kompilator przekształca kod w strukturę danych, która opisuje, co robi Twój kod, w formacie łatwym do strawienia.Po co zawracać sobie głowę tym drzewkiem wyrażeń? Chcę tylko
Where()
filtrować moje dane. Głównym powodem jest to, że zarówno EF, jak i Linq2SQL ORM mogą konwertować drzewa wyrażeń bezpośrednio na SQL, gdzie kod będzie wykonywany znacznie szybciej.Och, to brzmi jak darmowy wzrost wydajności, czy powinienem
AsQueryable()
w tym przypadku używać wszędzie? Nie,IQueryable
jest użyteczny tylko wtedy, gdy bazowy dostawca danych może coś z tym zrobić. Konwersja czegoś takiego jak zwykłyList
naIQueryable
nie przyniesie żadnych korzyści.źródło
IQueryable
nie analizowania funkcje jakIEnumerable
robi: na przykład, jeśli chcesz wiedzieć, które elementyDBSet<BookEntity>
nie są wList<BookObject>
,dbSetObject.Where(e => !listObject.Any(o => o.bookEntitySource == e))
zgłasza wyjątek:Expression of type 'BookEntity' cannot be used for parameter of type 'BookObject' of method 'Boolean Contains[BookObject] (IEnumerable[BookObject], BookObject)'
. Musiałem dodać.ToList()
późniejdbSetObject
.Tak, oba używają odroczonego wykonania. Zilustrujmy różnicę za pomocą profilera SQL Server ....
Kiedy uruchamiamy następujący kod:
W SQL Server profiler znajdujemy polecenie równe:
Uruchomienie tego bloku kodu w tabeli WebLog, która zawiera 1 milion rekordów, zajmuje około 90 sekund.
Tak więc wszystkie rekordy tabeli są ładowane do pamięci jako obiekty, a następnie z każdym .Where () będzie to kolejny filtr w pamięci przeciwko tym obiektom.
Kiedy używamy
IQueryable
zamiastIEnumerable
w powyższym przykładzie (druga linia):W SQL Server profiler znajdujemy polecenie równe:
Uruchomienie tego bloku kodu za pomocą zajmuje około czterech sekund
IQueryable
.IQueryable ma właściwość o nazwie,
Expression
która przechowuje wyrażenie drzewa, które zaczyna się tworzyć, gdy użyliśmyresult
w naszym przykładzie (które nazywa się odroczeniem wykonania), a na końcu to wyrażenie zostanie przekonwertowane na zapytanie SQL, które zostanie uruchomione w silniku bazy danych.źródło
Obie dadzą ci odroczoną egzekucję, tak.
Jeśli chodzi o to, co jest preferowane w stosunku do drugiego, zależy to od tego, jakie jest twoje źródło danych.
Zwrócenie an
IEnumerable
automatycznie zmusi środowisko wykonawcze do użycia LINQ do Objects w celu wykonania zapytania do kolekcji.Zwracanie an
IQueryable
(któryIEnumerable
, nawiasem mówiąc, implementuje ) zapewnia dodatkową funkcjonalność, aby przetłumaczyć zapytanie na coś, co może lepiej działać na źródłowym źródle (LINQ na SQL, LINQ na XML itp.).źródło
Ogólnie rzecz biorąc, zaleciłbym następujące:
Zwróć,
IQueryable<T>
jeśli chcesz włączyć programistę przy użyciu tej metody, aby zawęzić zapytanie zwracane przed wykonaniem.Zwróć,
IEnumerable
jeśli chcesz przetransportować zestaw obiektów do wyliczenia.Wyobraź sobie,
IQueryable
że to, co to jest - „zapytanie” o dane (które możesz zawęzić, jeśli chcesz). AnIEnumerable
to zbiór obiektów (które już zostały otrzymane lub zostały utworzone), nad którymi można wyliczyć.źródło
Wiele już powiedziano, ale wracając do korzeni, w bardziej techniczny sposób:
IEnumerable
to zbiór obiektów w pamięci, które można wyliczyć - sekwencja w pamięci, która umożliwia iterację (ułatwiaforeach
pętlę wewnątrz , chociaż można przejśćIEnumerator
tylko). Pozostają w pamięci jak są.IQueryable
jest drzewem wyrażeń, które w pewnym momencie zostanie przetłumaczone na coś innego, z możliwością wyliczenia końcowego wyniku . Myślę, że właśnie to dezorientuje większość ludzi.Mają oczywiście różne konotacje.
IQueryable
reprezentuje drzewo wyrażeń (zapytanie, po prostu), które zostanie przetłumaczone na coś innego przez podstawowego dostawcę zapytań, gdy tylko zostaną wywołane interfejsy API wydania, takie jak funkcje agregujące LINQ (Sum, Count, itp.) lub ToList [Array, Dictionary ,. ..]. IIQueryable
obiektów również wdrożyćIEnumerable
,IEnumerable<T>
tak że jeśli stanowią one kwerendy wynik tego zapytania może być powtórzyć. Oznacza to, że IQueryable nie muszą być tylko zapytaniami. Właściwy termin to drzewa ekspresyjne .Teraz, w jaki sposób te wyrażenia są wykonywane i do czego się zwracają, wszystko zależy od tak zwanych dostawców zapytań (wykonawców wyrażeń, o których możemy myśleć).
W świecie Entity Framework (czyli mistycznym źródłowym źródle danych lub dostawcy zapytań)
IQueryable
wyrażenia są tłumaczone na natywne zapytania T-SQL .Nhibernate
robi z nimi podobne rzeczy. Możesz napisać własny zgodnie z pojęciami całkiem dobrze opisanymi w LINQ: Na przykład budowanie łącza do IQueryable Provider , a możesz chcieć mieć niestandardowy interfejs API do wysyłania zapytań dla usługi dostawcy magazynu produktów.Zasadniczo
IQueryable
obiekty są konstruowane przez cały czas, dopóki ich jawnie nie zwolnimy i nie powiemy systemowi, aby przepisał je na SQL lub cokolwiek innego i przesłał łańcuch wykonawczy do dalszego przetwarzania.Jakby w celu odroczenia wykonania,
LINQ
funkcją jest zachowanie schematu drzewa wyrażeń w pamięci i wysyłanie go do wykonania tylko na żądanie, za każdym razem, gdy niektóre interfejsy API są wywoływane w sekwencji (ten sam Count, ToList itp.).Właściwe użycie obu zależy w dużej mierze od zadań, które stoją przed konkretnym przypadkiem. W przypadku dobrze znanego wzorca repozytorium osobiście decyduję się na zwrot
IList
, to znaczyIEnumerable
na Listy (indeksatory i tym podobne). Tak więc radzę używaćIQueryable
tylko w repozytoriach i IEnumerable gdziekolwiek indziej w kodzie. Nie mówiąc o obawy testowalności żeIQueryable
załamuje się i niszczy separacji obawy co do zasady. Jeśli zwrócisz wyrażenie z repozytoriów, konsumenci mogą bawić się warstwą trwałości, jak chcieliby.Mały dodatek do bałaganu :) (z dyskusji w komentarzach)) Żadne z nich nie są obiektami w pamięci, ponieważ same w sobie nie są prawdziwymi typami, są markerami typu - jeśli chcesz zagłębić się tak głęboko. Ale ma sens (i dlatego nawet MSDN ujął to w ten sposób) myśleć o IEnumerables jako kolekcjach w pamięci, podczas gdy IQueryables o drzewach wyrażeń. Chodzi o to, że interfejs IQueryable dziedziczy interfejs IEnumerable, więc jeśli reprezentuje zapytanie, wyniki tego zapytania można wyliczyć. Wyliczenie powoduje wykonanie drzewa wyrażeń powiązanego z obiektem IQueryable. Tak więc w rzeczywistości nie można wywołać żadnego elementu IEnumerable bez posiadania obiektu w pamięci. Dostanie się tam, jeśli tak zrobisz, jeśli nie będzie puste. IQueryables to tylko zapytania, a nie dane.
źródło
Ogólnie rzecz biorąc, chcesz zachować oryginalny typ statyczny zapytania, dopóki nie będzie to miało znaczenia.
Z tego powodu możesz zdefiniować zmienną jako „var” zamiast jednego
IQueryable<>
lub,IEnumerable<>
a będziesz wiedział, że nie zmieniasz typu.Jeśli zaczynasz od
IQueryable<>
, zazwyczaj chcesz go zachować,IQueryable<>
dopóki nie będzie istotnych powodów, aby go zmienić. Powodem tego jest to, że chcesz podać procesorowi zapytań jak najwięcej informacji. Na przykład, jeśli zamierzasz użyć tylko 10 wyników (zadzwoniłeśTake(10)
), chcesz, aby SQL Server wiedział o tym, aby mógł zoptymalizować swoje plany zapytań i wysłać ci tylko te dane, których użyjesz.Istotnym powodem do zmiany typu z
IQueryable<>
naIEnumerable<>
może być to, że wywołujesz funkcję rozszerzenia, której implementacjaIQueryable<>
w twoim obiekcie nie może obsłużyć lub nieefektywnie. W takim przypadku możesz przekonwertować typ naIEnumerable<>
(przypisując do zmiennej typu)IEnumerable<>
lubAsEnumerable
na przykład przy użyciu metody rozszerzenia), aby wywoływane funkcje rozszerzeń były tymi, które są wEnumerable
klasie zamiast wQueryable
klasie.źródło
Istnieje post na blogu z krótkim przykładem kodu źródłowego na temat tego, jak niewłaściwe użycie
IEnumerable<T>
może dramatycznie wpłynąć na wydajność zapytania LINQ: Entity Framework: IQueryable vs. IEnumerable .Jeśli zagłębimy się głębiej i zajrzymy do źródeł, zobaczymy, że istnieją oczywiście różne metody rozszerzenia
IEnumerable<T>
:i
IQueryable<T>
:Pierwszy zwraca iterator zliczalny, a drugi tworzy zapytanie za pośrednictwem dostawcy zapytań określonego w
IQueryable
źródle.źródło
Niedawno napotkałem problem z
IEnumerable
vIQueryable
. Algorytm użyty jako pierwszy wykonałIQueryable
zapytanie w celu uzyskania zestawu wyników. Zostały one następnie przekazane doforeach
pętli, z elementami utworzonymi jako klasa Entity Framework (EF). Ta klasa EF została następnie użyta wfrom
klauzuli zapytania Linq to Entity, co spowodowało, że wynik byłIEnumerable
.Jestem dość nowy w EF i Linq dla Entities, więc zajęło mi trochę czasu, aby dowiedzieć się, jakie było wąskie gardło. Korzystając z MiniProfiling, znalazłem zapytanie, a następnie przekonwertowałem wszystkie poszczególne operacje na pojedyncze
IQueryable
zapytanie Linq for Entities. WykonanieIEnumerable
zajęło 15 sekund, a wykonanieIQueryable
zajęło 0,5 sekundy. W grę wchodziły trzy tabele i po przeczytaniu tego uważam, żeIEnumerable
zapytanie faktycznie tworzyło trzy tabele dla wielu produktów i filtrowało wyniki.Spróbuj użyć IQueryables jako ogólnej zasady i profiluj swoją pracę, aby zmiany były mierzalne.
źródło
IQueryable
s również utknęły w pamięci po wywołaniu jednego z tych interfejsów API, ale jeśli nie, możesz przekazać wyrażenie w górę stosu warstw i bawić się filtrami aż do wywołania interfejsu API. Dobrze zaprojektowany DAL jako dobrze zaprojektowane repozytorium rozwiąże tego rodzaju problemy;)Chciałbym wyjaśnić kilka rzeczy ze względu na pozornie sprzeczne odpowiedzi (głównie otaczające IEnumerable).
(1)
IQueryable
rozszerzaIEnumerable
interfejs. (Możesz wysłaćIQueryable
do czegoś, co oczekujeIEnumerable
bezbłędnie.)(2) Zarówno LINQ, jak
IQueryable
iIEnumerable
próba opóźnionego ładowania podczas iteracji nad zestawem wyników. (Należy zauważyć, że implementację można zobaczyć w metodach rozszerzenia interfejsu dla każdego typu).Innymi słowy,
IEnumerables
nie są wyłącznie „w pamięci”.IQueryables
nie zawsze są wykonywane w bazie danych.IEnumerable
musi załadować rzeczy do pamięci (raz pobrane, być może leniwie), ponieważ nie ma abstrakcyjnego dostawcy danych.IQueryables
polegać na abstrakcyjnym dostawcy (takim jak LINQ-to-SQL), chociaż może to być również dostawca in-memory .NET.Przykładowy przypadek użycia
(a) Pobierz listę rekordów
IQueryable
z kontekstu EF. (Brak zapisów w pamięci.)(b) Przekaż
IQueryable
do widoku, którego modelem jestIEnumerable
. (Ważny.IQueryable
RozszerzaIEnumerable
.)(c) Iteruj i uzyskuj dostęp do rekordów, encji potomnych i właściwości zestawu danych z widoku. (Może powodować wyjątki!)
Możliwe problemy
(1)
IEnumerable
Próby opóźnionego ładowania i kontekst danych wygasły. Zgłoszono wyjątek, ponieważ dostawca nie jest już dostępny.(2) Serwery proxy encji Entity Framework są włączone (domyślne), a użytkownik próbuje uzyskać dostęp do pokrewnego (wirtualnego) obiektu z wygasłym kontekstem danych. Taki sam jak (1).
(3) Wiele aktywnych zestawów wyników (MARS). Jeśli iterujesz
IEnumerable
wforeach( var record in resultSet )
bloku i jednocześnierecord.childEntity.childProperty
próbujesz uzyskać dostęp , możesz skończyć z MARS z powodu leniwego ładowania zarówno zestawu danych, jak i jednostki relacyjnej. Spowoduje to wyjątek, jeśli nie zostanie włączony w ciągu połączenia.Rozwiązanie
Wykonaj zapytanie i zapisz wyniki, wywołując
resultList = resultSet.ToList()
To wydaje się być najprostszym sposobem na zapewnienie, że twoje byty są w pamięci.W przypadkach, w których uzyskujesz dostęp do powiązanych podmiotów, nadal możesz wymagać kontekstu danych. Albo to, albo możesz wyłączyć serwery proxy i
Include
podmioty wyraźnie powiązane z twoimDbSet
.źródło
Główna różnica między „IEnumerable” i „IQueryable” polega na tym, gdzie wykonywana jest logika filtru. Jeden wykonuje po stronie klienta (w pamięci), a drugi w bazie danych.
Na przykład możemy rozważyć przykład, w którym mamy 10 000 rekordów dla użytkownika w naszej bazie danych i powiedzmy, że tylko 900 spośród nich jest aktywnymi użytkownikami, więc w tym przypadku, jeśli użyjemy „IEnumerable”, najpierw ładuje wszystkie 10 000 rekordów do pamięci i następnie stosuje na nim filtr IsActive, który ostatecznie zwraca 900 aktywnych użytkowników.
Z drugiej strony w tym samym przypadku, jeśli użyjemy „IQueryable”, zastosuje on bezpośrednio filtr IsActive w bazie danych, który bezpośrednio stamtąd zwróci 900 aktywnych użytkowników.
Link referencyjny
źródło
Możemy używać obu w ten sam sposób, a różnią się one jedynie wydajnością.
IQueryable wykonuje się tylko względem bazy danych w efektywny sposób. Oznacza to, że tworzy całe wybrane zapytanie i pobiera tylko powiązane rekordy.
Na przykład chcemy wziąć 10 najlepszych klientów, których nazwa zaczyna się od „Nimal”. W takim przypadku wybrane zapytanie zostanie wygenerowane jako
select top 10 * from Customer where name like ‘Nimal%’
.Ale jeśli użyjemy IEnumerable, zapytanie będzie podobne,
select * from Customer where name like ‘Nimal%’
a pierwsza dziesiątka zostanie przefiltrowana na poziomie kodowania C # (pobiera wszystkie rekordy klientów z bazy danych i przekazuje je do C #).źródło
Oprócz pierwszych 2 naprawdę dobrych odpowiedzi (autorstwa driis i Jacoba):
Obiekt IEnumerable reprezentuje zestaw danych w pamięci i może poruszać się po tych danych tylko do przodu. Zapytanie reprezentowane przez obiekt IEnumerable jest wykonywane natychmiastowo i całkowicie, dzięki czemu aplikacja szybko otrzymuje dane.
Kiedy zapytanie jest wykonywane, IEnumerable ładuje wszystkie dane, a jeśli musimy je filtrować, samo filtrowanie odbywa się po stronie klienta.
Obiekt IQueryable zapewnia zdalny dostęp do bazy danych i umożliwia nawigację po danych w bezpośredniej kolejności od początku do końca lub w odwrotnej kolejności. Podczas tworzenia zapytania zwracany obiekt jest IQueryable, zapytanie jest optymalizowane. W rezultacie mniej pamięci jest zużywane podczas jego wykonywania, mniejsza przepustowość sieci, ale jednocześnie można ją przetwarzać nieco wolniej niż zapytanie zwracające obiekt IEnumerable.
Co wybrać
Jeśli potrzebujesz całego zestawu zwracanych danych, lepiej użyć IEnumerable, który zapewnia maksymalną prędkość.
Jeśli NIE potrzebujesz całego zestawu zwracanych danych, a tylko niektóre odfiltrowane dane, lepiej użyć IQueryable.
źródło
Oprócz powyższego warto zauważyć, że możesz uzyskać wyjątki, jeśli użyjesz
IQueryable
zamiastIEnumerable
:Następujące działa dobrze, jeśli
products
jestIEnumerable
:Jeśli jednak
products
jestIQueryable
i próbuje uzyskać dostęp do rekordów z tabeli DB, otrzymasz następujący błąd:Jest tak, ponieważ zbudowano następujące zapytanie:
a PRZESUNIĘCIE nie może mieć wartości ujemnej.
źródło