Jaka jest różnica (s) między.ToList (),AsEnumerable (), AsQueryable ()?

Znam pewne różnice w LINQ do encji i LINQ do obiektów, które pierwszy implementuje IQueryable a drugi implementuje IEnumerable i moje pytanie dotyczy zakresu EF 5.

Moje pytanie brzmi, jaka jest techniczna różnica(S) tych 3 metod? Widzę, że w wielu sytuacjach wszystkie działają. Widzę też używanie ich kombinacji jak .ToList().AsQueryable().

  1. Co dokładnie oznaczają te metody?

  2. Czy Jest jakiś problem z wydajnością Czy coś to prowadziłoby do użycia jednego nad drugim?

  3. Dlaczego warto użyć na przykład .ToList().AsQueryable() zamiast .AsQueryable()?

Author: Ry-, 2013-07-31

4 answers

Jest wiele do powiedzenia na ten temat. Pozwól mi skupić się na AsEnumerable i AsQueryable i wspomnieć ToList() po drodze.

Do czego służą te metody?

AsEnumerable i AsQueryable rzucać lub konwertować na IEnumerable lub IQueryable, odpowiednio. Mówię rzucać lub konwertować z powodu:

  • Gdy obiekt źródłowy już implementuje interfejs docelowy, sam obiekt źródłowy jest zwracany, ale rzuca do interfejsu docelowego. Innymi słowy: Typ nie ulega zmianie, ale typu compile-time.

  • Gdy obiekt źródłowy nie implementuje interfejsu docelowego, obiekt źródłowy jest przekształcany w obiekt implementujący interfejs docelowy. Tak więc zarówno typ, jak i typ kompilacji są zmieniane.

Pozwól, że pokażę to na kilku przykładach. Mam małą metodę, która raportuje typ kompilacji i rzeczywisty typ obiektu (dzięki uprzejmości Jon Skeet):

void ReportTypeProperties<T>(T obj)
{
    Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
    Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}

Spróbujmy arbitrary 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 table 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
Jest. AsQueryable() konwertuje tablicę na EnumerableQuery, który " reprezentuje zbiór IEnumerable<T> jako źródło danych IQueryable<T>."(MSDN).

What ' s the użyć?

AsEnumerable jest często używany do przełączania się z dowolnej implementacji IQueryable do LINQ to objects (L2O), głównie dlatego, że ta pierwsza nie obsługuje funkcji, które posiada L2O. Po więcej szczegółów zobacz jaki jest wpływ funkcji AsEnumerable () na encję LINQ?.

Na przykład, w zapytaniu Entity Framework możemy używać tylko ograniczonej liczby metod. Więc jeśli, na przykład, musimy użyć jednej z naszych własnych metod w zapytaniu typowo piszemy coś jak

var query = context.Observations.Select(o => o.Id)
                   .AsEnumerable().Select(x => MySuperSmartMethod(x))

ToList – który zamienia an {[27] } na List<T> - jest często używany również w tym celu. Zaletą użycia AsEnumerable vs. ToList jest to, że AsEnumerable nie wykonuje zapytania. AsEnumerable zachowuje odroczone wykonanie i nie buduje często bezużytecznej listy pośredniej.

Z drugiej strony, gdy wymagane jest wymuszone wykonanie zapytania LINQ, ToList może być na to sposobem.

AsQueryable może być użyty do zaakceptowania zbioru enumerable wyrażenia w wyrażeniach LINQ. Zobacz tutaj po więcej szczegółów: czy naprawdę muszę używać asqueryable () na kolekcji?.

Uwaga na nadużywanie substancji!

AsEnumerable działa jak narkotyk. Jest to szybkie rozwiązanie, ale kosztem i nie rozwiązuje podstawowego problemu.

W wielu odpowiedziach dotyczących przepełnienia stosu widzę ludzi stosujących AsEnumerable, aby 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:

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 polecenie SQL, które filtruje (Where) I projekty (Select). Oznacza to, że zarówno długość, jak i Szerokość zestawu wyników SQL są zmniejszone.

Teraz Załóżmy, że użytkownicy chcą zobaczyć tylko część daty CreateDate. W Entity Framework szybko to odkryjesz...

.Select(x => new { x.Name, x.CreateDate.Date })

... nie jest obsługiwane (w momencie pisania). Na szczęście jestAsEnumerable poprawka:

context.MyLongWideTable.AsEnumerable()
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, x.CreateDate.Date })

Jasne, że działa, prawdopodobnie. Ale wciąga całą tabelę do pamięci, a następnie nakłada filtr i projekcje. Cóż, większość ludzi jest na tyle inteligentna, aby zrobić Where pierwszy:

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 jest wykonywana 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 te metody nie robią?

Przywracanie możliwości IQueryable

Teraz ważne zastrzeżenie. Kiedy ty do
context.Observations.AsEnumerable()
                    .AsQueryable()

Skończysz z obiektem źródłowym reprezentowanym jako IQueryable. (Ponieważ obie metody tylko rzucają 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, Nie IQueryable. Tak więc konwersja na inny typ miała miejsce, a następny {[15] } nie może już zwrócić oryginalnego źródła.

Implikacją tego jest to, że używając Nie jest to sposób na magiczne wstrzyknięcie dostawcy zapytań z jego specyficznymi cechami do wyliczenia. Załóżmy, że robisz

var query = context.Observations.Select(o => o.Id)
                   .AsEnumerable().Select(x => x.ToString())
                   .AsQueryable()
                   .Where(...)

Warunek where nigdy nie zostanie przetłumaczony na język SQL. AsEnumerable() po których następuje LINQ definitywnie odcina połączenie z entity framework query provider.

Celowo pokazuję ten przykład, ponieważ widziałem tutaj pytania, w których ludzie na przykład próbują "wstrzyknąć" Include możliwości do kolekcji, wywołując AsQueryable. Informatyka kompiluje i uruchamia, ale nic nie robi, ponieważ obiekt bazowy nie ma już implementacji Include.

Wykonać

Zarówno AsQueryable jak i AsEnumerable nie wykonują (lub wyliczają ) obiektu źródłowego. Zmieniają tylko swój typ lub reprezentację. Oba zaangażowane interfejsy, IQueryable i IEnumerable, są niczym innym jak"wyliczeniem czekającym na nadejście". Nie są one wykonywane, zanim zostaną do tego zmuszone, na przykład, jak wspomniano powyżej, poprzez wywołanie ToList().

Oznacza to, że wykonanie IEnumerable uzyskanego przez wywołanie AsEnumerable na obiekcie IQueryable, spowoduje wykonanie bazowego IQueryable. Kolejne wykonanie {[19] } spowoduje ponowne wykonanie IQueryable. Co może być bardzo drogie.

Konkretne Wdrożenia

Do tej pory chodziło tylko o Queryable.AsQueryable oraz Enumerable.AsEnumerable metody rozszerzenia. Ale oczywiście każdy może napisać metody instancji lub metody rozszerzeń o tych samych nazwach (i funkcje).

W rzeczywistości powszechnym przykładem specyficznej metody rozszerzenia AsEnumerable jest DataTableExtensions.AsEnumerable. DataTable nie implementuje IQueryable ani IEnumerable, więc zwykłe metody rozszerzenia nie mają zastosowania.

 367
Author: Gert Arnold,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

ToList()

  • wykonaj zapytanie natychmiast

AsEnumerable()

  • lazy (wykonaj zapytanie później)
  • parametr: Func<TSource, bool>
  • załaduj co rekord do pamięci aplikacji,a następnie obsłuż / filtruj je. (np. Where / Take / Skip, wybierze * z table1, do pamięci, a następnie wybierze pierwsze elementy X) (w tym przypadku co zrobił: Linq-to-SQL + Linq-to-Object)

AsQueryable()

  • lazy (wykonaj zapytanie później)
  • Parametr: Expression<Func<TSource, bool>>
  • Konwertuj wyrażenie na T-SQL( z konkretnym dostawcą), odpytywaj zdalnie i ładuj wynik do pamięci aplikacji.
  • dlatego DbSet (w ramach encji) dziedziczy również IQueryable, aby uzyskać wydajne zapytanie.
  • nie ładuje każdego rekordu, np. jeśli Take(5), wygeneruje select top 5 * SQL w tle. Oznacza to, że ten typ jest bardziej przyjazny dla bazy danych SQL, dlatego ten typ zwykle ma wyższą wydajność i jest zalecany w przypadku bazy danych.
  • więc AsQueryable() zwykle działa znacznie szybciej niż AsEnumerable(), ponieważ generuje najpierw T-SQL, który zawiera wszystkie warunki where w Linq.
 50
Author: Xin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-22 01:02:12

ToList () będzie wszystko w pamięci, a potem będziesz nad tym pracował. tolist ().gdzie (zastosuj jakiś filtr ) jest wykonywane lokalnie. AsQueryable() wykona wszystko zdalnie, tzn. filtr na nim jest wysyłany do bazy danych w celu zastosowania. Queryable nic nie robi, dopóki go nie wykonasz. ToList, jednak wykonuje natychmiast.

Spójrz również na tę odpowiedź Dlaczego używać asqueryable () zamiast List ()?.

Edytuj : Również, w Twoim przypadku, gdy zrobisz ToList () wtedy każdy kolejna operacja jest lokalna włączając asqueryable (). Nie można przełączyć się na zdalne po uruchomieniu lokalnie. Mam nadzieję, że to będzie jaśniejsze.

 14
Author: ashutosh raina,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:30

Napotkał złą wydajność na poniższym kodzie.

void DoSomething<T>(IEnumerable<T> objects){
    var single = objects.First(); //load everything into memory before .First()
    ...
}

Fixed with

void DoSomething<T>(IEnumerable<T> objects){
    T single;
    if (objects is IQueryable<T>)
        single = objects.AsQueryable().First(); // SELECT TOP (1) ... is used
    else
        single = objects.First();

}

Dla IQueryable, pozostań w IQueryable, jeśli to możliwe, staraj się nie być używany jak IEnumerable.

Update . Można go dodatkowo uprościć w jednym wyrażeniu, dzięki Gert Arnold .

T single =  objects is IQueryable<T> q? 
                    q.First(): 
                    objects.First();
 2
Author: Rm558,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-07-28 01:02:19