Jest wiele do powiedzenia na ten temat. Pozwól mi skupić się na AsEnumerable
i AsQueryable
i wzmianka ToList()
po drodze.
Co robią te metody?
AsEnumerable
i odpowiednio AsQueryable
obsadzić lub przekonwertować na IEnumerable
lub IQueryable
. Mówię obsadę lub konwersję bez podania przyczyny:
Gdy obiekt źródłowy już implementuje interfejs docelowy, sam obiekt źródłowy jest zwracany, ale rzutowany na interfejs docelowy. Innymi słowy: typ nie jest zmieniany, ale typem kompilacji jest.
Gdy obiekt źródłowy nie implementuje interfejsu docelowego, obiekt źródłowy jest konwertowany na obiekt, który implementuje interfejs docelowy. Tak więc zarówno typ, jak i typ kompilacji są zmieniane.
Pokażę to na kilku przykładach. Mam tę małą metodę, która zgłasza typ czasu kompilacji i rzeczywisty typ obiektu ( dzięki uprzejmości Jona Skeeta ):
void ReportTypeProperties<T>(T obj)
{
Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}
Spróbujmy dowolnego linq-to-sql Table<T>
, który implementuje IQueryable
:
ReportTypeProperties(context.Observations);
ReportTypeProperties(context.Observations.AsEnumerable());
ReportTypeProperties(context.Observations.AsQueryable());
Wynik:
Compile-time type: Table`1
Actual type: Table`1
Compile-time type: IEnumerable`1
Actual type: Table`1
Compile-time type: IQueryable`1
Actual type: Table`1
Widzisz, że sama klasa tabeli jest zawsze zwracana, ale jej reprezentacja zmienia się.
Teraz obiekt, który implementuje IEnumerable
, a nie IQueryable
:
var ints = new[] { 1, 2 };
ReportTypeProperties(ints);
ReportTypeProperties(ints.AsEnumerable());
ReportTypeProperties(ints.AsQueryable());
Wyniki:
Compile-time type: Int32[]
Actual type: Int32[]
Compile-time type: IEnumerable`1
Actual type: Int32[]
Compile-time type: IQueryable`1
Actual type: EnumerableQuery`1
Tu jest. AsQueryable()
przekształcił tablicę w obiekt EnumerableQuery
, który „reprezentuje IEnumerable<T>
kolekcję jako IQueryable<T>
źródło danych”. (MSDN).
Jakie jest zastosowanie?
AsEnumerable
jest często używany do przełączania z dowolnej IQueryable
implementacji na LINQ na obiekty (L2O), głównie dlatego, że ta pierwsza nie obsługuje funkcji, które ma L2O. Aby uzyskać więcej informacji, zobacz Jaki jest wpływ AsEnumerable () na jednostkę LINQ? .
Na przykład w zapytaniu Entity Framework możemy użyć tylko ograniczonej liczby metod. Więc jeśli na przykład musimy użyć jednej z naszych własnych metod w zapytaniu, zwykle napisalibyśmy coś takiego
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => MySuperSmartMethod(x))
ToList
- który zamienia an IEnumerable<T>
na a List<T>
- jest również często używany do tego celu. Zaletą używania AsEnumerable
vs. ToList
jest to, że AsEnumerable
nie wykonuje zapytania. AsEnumerable
zachowuje odroczone wykonanie i nie tworzy często bezużytecznej listy pośredniej.
Z drugiej strony, gdy pożądane jest wymuszone wykonanie zapytania LINQ, ToList
można to zrobić.
AsQueryable
może służyć do tego, aby kolekcja wymienna akceptowała wyrażenia w instrukcjach LINQ. Zobacz więcej szczegółów: Czy naprawdę potrzebuję używać AsQueryable () w kolekcji? .
Uwaga na temat nadużywania substancji!
AsEnumerable
działa jak narkotyk. Jest to szybka poprawka, ale kosztuje i nie rozwiązuje podstawowego problemu.
W wielu odpowiedziach na przepełnienie stosu widzę, że ludzie starają AsEnumerable
się naprawić prawie każdy problem z nieobsługiwanymi metodami w wyrażeniach LINQ. Ale cena nie zawsze jest jasna. Na przykład, jeśli to zrobisz:
context.MyLongWideTable // A table with many records and columns
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate })
... wszystko jest starannie przetłumaczone na instrukcję SQL, która filtruje ( Where
) i projekty ( Select
). Oznacza to, że zarówno długość, jak i szerokość zestawu wyników SQL są odpowiednio zmniejszone.
Załóżmy teraz, że użytkownicy chcą widzieć tylko datę CreateDate
. W Entity Framework szybko odkryjesz, że ...
.Select(x => new { x.Name, x.CreateDate.Date })
... nie jest obsługiwany (w momencie pisania). Ach, na szczęście jest AsEnumerable
poprawka:
context.MyLongWideTable.AsEnumerable()
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate.Date })
Pewnie, pewnie działa. Ale wciąga całą tabelę do pamięci, a następnie stosuje filtr i projekcje. Cóż, większość ludzi jest wystarczająco inteligentna, aby zrobić Where
pierwsze:
context.MyLongWideTable
.Where(x => x.Type == "type").AsEnumerable()
.Select(x => new { x.Name, x.CreateDate.Date })
Ale nadal wszystkie kolumny są pobierane jako pierwsze, a projekcja odbywa się w pamięci.
Prawdziwa poprawka to:
context.MyLongWideTable
.Where(x => x.Type == "type")
.Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) })
(Ale to wymaga trochę więcej wiedzy ...)
Czego NIE robią te metody?
Przywróć możliwości IQueryable
Teraz ważne zastrzeżenie. Kiedy to zrobisz
context.Observations.AsEnumerable()
.AsQueryable()
skończysz z obiektem źródłowym reprezentowanym jako IQueryable
. (Ponieważ obie metody tylko rzutują i nie konwertują).
Ale kiedy to zrobisz
context.Observations.AsEnumerable().Select(x => x)
.AsQueryable()
jaki będzie wynik?
Select
Produkuje WhereSelectEnumerableIterator
. Jest to wewnętrzna klasa .Net, która implementuje IEnumerable
, a nieIQueryable
. Tak więc nastąpiła konwersja na inny typ i kolejne AsQueryable
nigdy nie mogą już zwrócić oryginalnego źródła.
Konsekwencją tego jest, że używanie AsQueryable
to nie sposób magicznie wstrzyknąć dostawca zapytania z jego specyficznymi cechami w produkt przeliczalny. Załóżmy, że tak
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => x.ToString())
.AsQueryable()
.Where(...)
Miejsce, w którym warunek nigdy nie zostanie przetłumaczony na SQL. AsEnumerable()
następnie instrukcje LINQ definitywnie przerywają połączenie z dostawcą zapytań w ramach encji.
Celowo pokazuję ten przykład, ponieważ widziałem tutaj pytania, w których ludzie na przykład próbują „wprowadzić” Include
funkcje do kolekcji, dzwoniąc AsQueryable
. Kompiluje się i działa, ale nic nie robi, ponieważ obiekt bazowy nie ma Include
już implementacji.
Wykonać
Zarówno AsQueryable
i AsEnumerable
nie wykonują (lub enumerate ) obiekt źródłowy. Zmieniają tylko swój typ lub reprezentację. Oba dotyczyły interfejsów IQueryable
i IEnumerable
są niczym innym jak „wyliczeniem, które się czeka”. Nie są one wykonywane, dopóki nie zostaną do tego zmuszone, na przykład, jak wspomniano powyżej, przez telefon ToList()
.
Oznacza to, że Wykonywanie IEnumerable
uzyskać dzwoniąc AsEnumerable
na IQueryable
obiekcie, wykona instrumentu bazowego IQueryable
. Kolejne wykonanie IEnumerable
testamentu ponownie wykona IQueryable
. Co może być bardzo drogie.
Konkretne implementacje
Do tej pory było to tylko o Queryable.AsQueryable
i Enumerable.AsEnumerable
metod rozszerzenie. Ale oczywiście każdy może pisać metody instancji lub metody rozszerzeń o tych samych nazwach (i funkcjach).
W rzeczywistości powszechnym przykładem konkretnej AsEnumerable
metody rozszerzenia jest DataTableExtensions.AsEnumerable
. DataTable
nie implementuje IQueryable
lub IEnumerable
, więc standardowe metody rozszerzenia nie mają zastosowania.