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.