Dazu gibt es viel zu sagen. Lassen Sie mich auf AsEnumerable
und konzentrieren AsQueryable
und ToList()
auf dem Weg erwähnen .
Was machen diese Methoden?
AsEnumerable
und AsQueryable
gegossen oder konvertiert zu IEnumerable
oder IQueryable
. Ich sage Besetzung oder Konvertierung mit einem Grund:
Wenn das Quellobjekt bereits den Ziel - Schnittstelle implementiert, wird das Quellobjekt selbst zurückgegeben , sondern warf auf die Zieloberfläche. Mit anderen Worten: Der Typ wird nicht geändert, der Typ zur Kompilierungszeit jedoch.
Wenn das Quellobjekt die Zielschnittstelle nicht implementiert, wird das Quellobjekt in ein Objekt konvertiert , das die Zielschnittstelle implementiert. Daher werden sowohl der Typ als auch der Typ zur Kompilierungszeit geändert.
Lassen Sie mich dies anhand einiger Beispiele zeigen. Ich habe diese kleine Methode, die den Typ der Kompilierungszeit und den tatsächlichen Typ eines Objekts angibt (mit freundlicher Genehmigung von Jon Skeet ):
void ReportTypeProperties<T>(T obj)
{
Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}
Versuchen wir es mit einer beliebigen Linq-to-SQL-Methode Table<T>
, die Folgendes implementiert IQueryable
:
ReportTypeProperties(context.Observations);
ReportTypeProperties(context.Observations.AsEnumerable());
ReportTypeProperties(context.Observations.AsQueryable());
Das Ergebnis:
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
Sie sehen, dass die Tabellenklasse selbst immer zurückgegeben wird, ihre Darstellung sich jedoch ändert.
Nun ein Objekt, das implementiert IEnumerable
, nicht IQueryable
:
var ints = new[] { 1, 2 };
ReportTypeProperties(ints);
ReportTypeProperties(ints.AsEnumerable());
ReportTypeProperties(ints.AsQueryable());
Die Ergebnisse:
Compile-time type: Int32[]
Actual type: Int32[]
Compile-time type: IEnumerable`1
Actual type: Int32[]
Compile-time type: IQueryable`1
Actual type: EnumerableQuery`1
Da ist es. AsQueryable()
hat das Array in ein Array konvertiert EnumerableQuery
, das "eine IEnumerable<T>
Sammlung als IQueryable<T>
Datenquelle darstellt". (MSDN).
Was ist der Nutzen?
AsEnumerable
wird häufig verwendet, um von einer IQueryable
Implementierung zu LINQ zu Objekten (L2O) zu wechseln , hauptsächlich weil ersteres keine Funktionen unterstützt, über die L2O verfügt. Weitere Informationen finden Sie unter Welche Auswirkungen hat AsEnumerable () auf eine LINQ-Entität? .
In einer Entity Framework-Abfrage können wir beispielsweise nur eine begrenzte Anzahl von Methoden verwenden. Wenn wir zum Beispiel eine unserer eigenen Methoden in einer Abfrage verwenden müssen, schreiben wir normalerweise so etwas wie
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => MySuperSmartMethod(x))
ToList
- was ein IEnumerable<T>
in ein umwandelt List<T>
- wird häufig auch für diesen Zweck verwendet. Der Vorteil der Verwendung von AsEnumerable
vs. ToList
besteht darin, dass AsEnumerable
die Abfrage nicht ausgeführt wird. AsEnumerable
Bewahrt die verzögerte Ausführung und erstellt keine häufig nutzlose Zwischenliste.
Wenn andererseits eine erzwungene Ausführung einer LINQ-Abfrage gewünscht wird, ToList
kann dies eine Möglichkeit sein.
AsQueryable
kann verwendet werden, um eine aufzählbare Sammlung Ausdrücke in LINQ-Anweisungen akzeptieren zu lassen. Weitere Informationen finden Sie hier: Muss ich AsQueryable () wirklich für die Sammlung verwenden? .
Hinweis zum Drogenmissbrauch!
AsEnumerable
wirkt wie eine Droge. Es ist eine schnelle Lösung, aber kostenpflichtig und behebt das zugrunde liegende Problem nicht.
In vielen Antworten zum Stapelüberlauf sehe ich Leute, die sich bewerben AsEnumerable
, um fast jedes Problem mit nicht unterstützten Methoden in LINQ-Ausdrücken zu beheben. Der Preis ist aber nicht immer klar. Wenn Sie dies beispielsweise tun:
context.MyLongWideTable // A table with many records and columns
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate })
... alles wird ordentlich in eine SQL-Anweisung übersetzt, die filter ( Where
) und projects ( Select
). Das heißt, sowohl die Länge als auch die Breite der SQL-Ergebnismenge werden reduziert.
Angenommen, Benutzer möchten nur den Datumsteil von sehen CreateDate
. In Entity Framework werden Sie schnell feststellen, dass ...
.Select(x => new { x.Name, x.CreateDate.Date })
... wird nicht unterstützt (zum Zeitpunkt des Schreibens). Ah, zum Glück gibt es die AsEnumerable
Lösung:
context.MyLongWideTable.AsEnumerable()
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate.Date })
Sicher, es läuft wahrscheinlich. Es zieht jedoch die gesamte Tabelle in den Speicher und wendet dann den Filter und die Projektionen an. Nun, die meisten Leute sind klug genug, um das Where
erste zu tun :
context.MyLongWideTable
.Where(x => x.Type == "type").AsEnumerable()
.Select(x => new { x.Name, x.CreateDate.Date })
Trotzdem werden zuerst alle Spalten abgerufen und die Projektion erfolgt im Speicher.
Die eigentliche Lösung ist:
context.MyLongWideTable
.Where(x => x.Type == "type")
.Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) })
(Aber das erfordert nur ein bisschen mehr Wissen ...)
Was machen diese Methoden NICHT?
Stellen Sie die IQueryable-Funktionen wieder her
Nun eine wichtige Einschränkung. Wenn Sie das tun
context.Observations.AsEnumerable()
.AsQueryable()
Sie erhalten das Quellobjekt, das als dargestellt wird IQueryable
. (Weil beide Methoden nur umwandeln und nicht konvertieren).
Aber wenn du es tust
context.Observations.AsEnumerable().Select(x => x)
.AsQueryable()
Was wird das Ergebnis sein?
Das Select
produziert a WhereSelectEnumerableIterator
. Dies ist eine interne .NET - Klasse, die implementiert IEnumerable
, nichtIQueryable
. Es wurde also eine Konvertierung in einen anderen Typ durchgeführt, und die nachfolgende AsQueryable
kann die ursprüngliche Quelle nie mehr zurückgeben.
Die Folge davon ist , dass die Verwendung AsQueryable
ist nicht ein Weg , um auf magische Weise inject ein Abfrage - Provider mit seinem spezifischen Funktionen in eine zählbaren. Angenommen, Sie tun es
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => x.ToString())
.AsQueryable()
.Where(...)
Die where-Bedingung wird niemals in SQL übersetzt. AsEnumerable()
gefolgt von LINQ-Anweisungen wird die Verbindung zum Entity Framework-Abfrageanbieter endgültig unterbrochen.
Ich zeige dieses Beispiel absichtlich, weil ich hier Fragen gesehen habe, bei denen beispielsweise versucht wird, Include
Funktionen durch Aufrufen in eine Sammlung einzufügen AsQueryable
. Es wird kompiliert und ausgeführt, tut aber nichts, da das zugrunde liegende Objekt keine Include
Implementierung mehr hat.
Ausführen
Sowohl AsQueryable
und AsEnumerable
nicht ausführen (oder enumerate ) das Quellobjekt. Sie ändern nur ihren Typ oder ihre Darstellung. Beide beteiligten Schnittstellen IQueryable
und IEnumerable
sind nichts anderes als "eine Aufzählung, die darauf wartet, passiert zu werden". Sie werden nicht ausgeführt, bevor sie dazu gezwungen werden, beispielsweise wie oben erwähnt, durch Aufrufen ToList()
.
Das bedeutet, dass beim Ausführen eines IEnumerable
durch Aufrufen AsEnumerable
eines IQueryable
Objekts erhaltenen Objekts der Basiswert ausgeführt wird IQueryable
. Eine nachfolgende Ausführung des IEnumerable
Testaments führt das erneut aus IQueryable
. Welches kann sehr teuer sein.
Spezifische Implementierungen
Bisher ging es nur um die Queryable.AsQueryable
und Enumerable.AsEnumerable
Erweiterungsmethoden. Aber natürlich kann jeder Instanzmethoden oder Erweiterungsmethoden mit denselben Namen (und Funktionen) schreiben.
In der Tat ist ein häufiges Beispiel für eine bestimmte AsEnumerable
Erweiterungsmethode DataTableExtensions.AsEnumerable
. DataTable
implementiert nicht IQueryable
oder IEnumerable
, daher gelten die regulären Erweiterungsmethoden nicht.