Qual è la differenza (s) tra .ToList (), .AsEnumerable (), AsQueryable ()?


182

Conosco alcune differenze di LINQ to Entities e LINQ to Objects che la prima implementa IQueryablee la seconda implementa IEnumerablee la mia portata di domanda rientra in EF 5.

La mia domanda è qual è la differenza tecnica di questi 3 metodi? Vedo che in molte situazioni funzionano tutti. Vedo anche l'uso di combinazioni di questi come .ToList().AsQueryable().

  1. Cosa significano esattamente questi metodi?

  2. C'è qualche problema di prestazioni o qualcosa che porterebbe all'uso dell'uno rispetto all'altro?

  3. Perché uno dovrebbe usare, ad esempio, .ToList().AsQueryable()invece di .AsQueryable()?


Risposte:


354

C'è molto da dire al riguardo. Mi permetta di concentrarsi su AsEnumerablee AsQueryablee menzione ToList()lungo la strada.

Cosa fanno questi metodi?

AsEnumerablee AsQueryablecast o convertire rispettivamente a IEnumerableo IQueryable. Dico cast o convertire con un motivo:

  • Quando l'oggetto di origine implementa già l'interfaccia di destinazione, l'oggetto di origine stesso viene restituito ma trasmesso all'interfaccia di destinazione. In altre parole: il tipo non è cambiato, ma il tipo di compilazione è.

  • Quando l'oggetto di origine non implementa l'interfaccia di destinazione, l'oggetto di origine viene convertito in un oggetto che implementa l'interfaccia di destinazione. Quindi vengono cambiati sia il tipo che il tipo di tempo di compilazione.

Lascia che lo mostri con alcuni esempi. Ho questo piccolo metodo che riporta il tipo di tempo di compilazione e il tipo effettivo di un oggetto (per gentile concessione di Jon Skeet ):

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

Proviamo un linq-to-sql arbitrario Table<T>, che implementa IQueryable:

ReportTypeProperties(context.Observations);
ReportTypeProperties(context.Observations.AsEnumerable());
ReportTypeProperties(context.Observations.AsQueryable());

Il risultato:

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

Si vede che la stessa classe di tabella viene sempre restituita, ma la sua rappresentazione cambia.

Ora un oggetto che implementa IEnumerable, non IQueryable:

var ints = new[] { 1, 2 };
ReportTypeProperties(ints);
ReportTypeProperties(ints.AsEnumerable());
ReportTypeProperties(ints.AsQueryable());

I risultati:

Compile-time type: Int32[]
Actual type: Int32[]

Compile-time type: IEnumerable`1
Actual type: Int32[]

Compile-time type: IQueryable`1
Actual type: EnumerableQuery`1

Eccolo. AsQueryable()ha convertito l'array in un EnumerableQuery, che "rappresenta una IEnumerable<T>raccolta come IQueryable<T>origine dati". (MSDN).

Come si usa?

AsEnumerableviene spesso utilizzato per passare da qualsiasi IQueryableimplementazione a LINQ a oggetti (L2O), principalmente perché il primo non supporta le funzioni di L2O. Per maggiori dettagli, vedi Qual è l'effetto di AsEnumerable () su un'entità LINQ? .

Ad esempio, in una query Entity Framework possiamo usare solo un numero limitato di metodi. Quindi, se, ad esempio, dobbiamo usare uno dei nostri metodi in una query, in genere scriveremmo qualcosa del genere

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

ToList - che converte an IEnumerable<T>in a List<T>- viene spesso utilizzato anche per questo scopo. Il vantaggio di usare AsEnumerablevs. ToListè che AsEnumerablenon esegue la query. AsEnumerableconserva l'esecuzione differita e non crea un elenco intermedio spesso inutile.

D'altra parte, quando si desidera l'esecuzione forzata di una query LINQ, ToListpuò essere un modo per farlo.

AsQueryablepuò essere utilizzato per fare in modo che una raccolta enumerabile accetti espressioni nelle istruzioni LINQ. Vedi qui per maggiori dettagli: Ho davvero bisogno di usare AsQueryable () sulla collezione? .

Nota sull'abuso di sostanze!

AsEnumerablefunziona come una droga. È una soluzione rapida, ma a un costo e non affronta il problema di fondo.

In molte risposte Stack Overflow, vedo persone che chiedono AsEnumerabledi risolvere qualsiasi problema con metodi non supportati nelle espressioni LINQ. Ma il prezzo non è sempre chiaro. Ad esempio, se lo fai:

context.MyLongWideTable // A table with many records and columns
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, x.CreateDate })

... tutto viene tradotto in modo ordinato in un'istruzione SQL che filter ( Where) e projects ( Select). Cioè, sia la lunghezza che la larghezza, rispettivamente, del set di risultati SQL sono ridotte.

Supponiamo ora che gli utenti vogliano vedere solo la data di CreateDate. In Entity Framework scoprirai rapidamente che ...

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

... non è supportato (al momento della scrittura). Ah, per fortuna c'è la AsEnumerablesoluzione:

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

Certo, funziona, probabilmente. Ma tira in memoria l'intera tabella e quindi applica il filtro e le proiezioni. Bene, molte persone sono abbastanza intelligenti da fare il Whereprimo:

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

Ma tutte le colonne vengono comunque recuperate per prime e la proiezione viene eseguita in memoria.

La vera soluzione è:

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

(Ma ciò richiede solo un po 'più di conoscenza ...)

Cosa NON fanno questi metodi?

Ripristina funzionalità IQueryable

Ora un avvertimento importante. Quando lo fai

context.Observations.AsEnumerable()
                    .AsQueryable()

finirai con l'oggetto sorgente rappresentato come IQueryable. (Poiché entrambi i metodi eseguono solo il cast e non convertono).

Ma quando lo fai

context.Observations.AsEnumerable().Select(x => x)
                    .AsQueryable()

quale sarà il risultato?

Il Selectproduce a WhereSelectEnumerableIterator. Questa è una classe .Net interna che implementa IEnumerable, nonIQueryable . Quindi è avvenuta una conversione in un altro tipo e il successivo AsQueryablenon può più restituire la fonte originale.

Ciò implica che l'utilizzo nonAsQueryable è un modo per iniettare magicamente un provider di query con le sue caratteristiche specifiche in un elenco. Supponi di si

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

La condizione in cui non verrà mai tradotta in SQL. AsEnumerable()seguito dalle istruzioni LINQ interrompe definitivamente la connessione con il provider di query del framework di entità.

Mostro deliberatamente questo esempio perché ho visto qui delle domande in cui le persone, ad esempio, cercano di "iniettare" le Includecapacità in una raccolta chiamando AsQueryable. Si compila ed esegue, ma non fa nulla perché l'oggetto sottostante non ha Includepiù un'implementazione.

Eseguire

Entrambi AsQueryablee AsEnumerablenon eseguono (o enumerano ) l'oggetto di origine. Cambiano solo il loro tipo o rappresentazione. Entrambe le interfacce coinvolte, IQueryablee IEnumerable, non sono altro che "un elenco in attesa di accadere". Non vengono eseguiti prima di essere costretti a farlo, ad esempio, come menzionato sopra, chiamando ToList().

Ciò significa che eseguendo un IEnumerableottenuto chiamando AsEnumerableun IQueryableoggetto, verrà eseguito il sottostante IQueryable. Una successiva esecuzione del IEnumerabletest eseguirà nuovamente il IQueryable. Che può essere molto costoso.

Implementazioni specifiche

Finora, si trattava solo dei metodi di estensione Queryable.AsQueryablee Enumerable.AsEnumerable. Ma ovviamente chiunque può scrivere metodi di istanza o metodi di estensione con gli stessi nomi (e funzioni).

In effetti, un esempio comune di un AsEnumerablemetodo di estensione specifico è DataTableExtensions.AsEnumerable. DataTablenon implementa IQueryableo IEnumerable, quindi i metodi di estensione regolari non si applicano.


Grazie per la risposta, puoi condividere la tua risposta alla terza domanda di OP - 3. Perché utilizzare, ad esempio, .ToList (). AsQueryable () invece di .AsQueryable ()? ?
kgzdev,

@ikram Non riesco a pensare a nulla in cui sarebbe utile. Come ho spiegato, l'applicazione AsQueryable()si basa spesso su un'idea sbagliata. Ma lascerò sobbollire nella parte posteriore della mia testa per un po 'e vedere se posso aggiungere un po' più di copertura su quella domanda.
Gert Arnold,

1
Bella risposta. Potresti precisare cosa succede se l'IEnumerable ottenuto chiamando AsEnumerable () su un IQueryable viene enumerato più volte? La query verrà eseguita più volte o i dati già caricati dal database in memoria verranno riutilizzati?
antoninod,

@antoninod Buona idea. Fatto.
Gert Arnold,

46

Elencare()

  • Eseguire immediatamente la query

AsEnumerable ()

  • pigro (eseguire la query in un secondo momento)
  • Parametro: Func<TSource, bool>
  • Caricare OGNI record nella memoria dell'applicazione, quindi gestirli / filtrarli. (es. Where / Take / Skip, selezionerà * dalla tabella 1, nella memoria, quindi selezionerà i primi X elementi) (In questo caso, cosa ha fatto: Linq-to-SQL + Linq-to-Object)

AsQueryable ()

  • pigro (eseguire la query in un secondo momento)
  • Parametro: Expression<Func<TSource, bool>>
  • Converti Expression in T-SQL (con il provider specifico), esegui query in remoto e carica i risultati nella memoria dell'applicazione.
  • Ecco perché DbSet (in Entity Framework) eredita anche IQueryable per ottenere una query efficiente.
  • Non caricare tutti i record, ad esempio se Take (5), genererà selezionare i primi 5 * SQL in background. Ciò significa che questo tipo è più intuitivo per il database SQL ed è per questo motivo che questo tipo di solito ha prestazioni più elevate ed è consigliato quando si ha a che fare con un database.
  • Quindi di AsQueryable()solito funziona molto più velocemente rispetto AsEnumerable()a quando genera T-SQL all'inizio, che include tutte le condizioni in cui si trova Linq.

14

ToList () sarà tutto in memoria e poi ci lavorerai. così, ToList (). dove (applica alcuni filtri) viene eseguito localmente. AsQueryable () eseguirà tutto in remoto, ovvero un filtro su di esso verrà inviato al database per l'applicazione. La query non fa nulla fino a quando non la esegui. ToList, tuttavia viene eseguito immediatamente.

Inoltre, guarda questa risposta Perché usare AsQueryable () invece di List ()? .

EDIT: Inoltre, nel tuo caso una volta che fai ToList (), ogni operazione successiva è locale, incluso AsQueryable (). Non è possibile passare al telecomando una volta avviata l'esecuzione in locale. Spero che questo lo renda un po 'più chiaro.


2
"AsQueryable () eseguirà tutto in remoto" Solo se l'enumerabile è già interrogabile. Altrimenti, ciò non è possibile e tutto funziona ancora localmente. La domanda ha ".... ToList (). AsQueryable ()", che potrebbe utilizzare alcuni chiarimenti nella tua risposta, IMO.

2

Si è verificata una cattiva prestazione sul codice sottostante.

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

Risolto con

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();

}

Per un IQueryable, rimanere in IQueryable quando possibile, provare a non essere usato come IEnumerable.

Aggiornamento . Può essere ulteriormente semplificato in una sola espressione, grazie a Gert Arnold .

T single =  objects is IQueryable<T> q? 
                    q.First(): 
                    objects.First();
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.