wake-up-neo.com

Was ist der Unterschied zwischen .ToList (), .AsEnumerable (), AsQueryable ()?

Ich kenne einige Unterschiede zwischen LINQ to Entities und LINQ to Objects, bei denen das erste IQueryable und das zweite IEnumerable implementiert und mein Fragenbereich innerhalb von EF 5 liegt.

Meine Frage ist, was ist der technische Unterschied dieser 3 Methoden? Ich sehe, dass in vielen Situationen alle funktionieren. Ich sehe auch Kombinationen von ihnen wie .ToList().AsQueryable().

  1. Was bedeuten diese Methoden genau?

  2. Gibt es ein Leistungsproblem oder etwas, das dazu führen würde, dass eines über das andere verwendet wird?

  3. Warum würde man zum Beispiel .ToList().AsQueryable() anstelle von .AsQueryable() verwenden?

160
Amin Saqi

Dazu gibt es viel zu sagen. Ich möchte mich auf AsEnumerable und AsQueryable konzentrieren und dabei ToList() erwähnen.

Was machen diese Methoden?

AsEnumerable und AsQueryable werden in IEnumerable bzw. IQueryable umgewandelt oder konvertiert. Ich sage cast or convert mit einem Grund:

  • Wenn das Quellobjekt die Zielschnittstelle bereits implementiert, wird das Quellobjekt selbst zurückgegeben, jedoch cast an die Zielschnittstelle. Mit anderen Worten: Der Typ wird nicht geändert, der Typ zur Kompilierungszeit jedoch.

  • Wenn das Quellobjekt die Zielschnittstelle nicht implementiert, wird das Quellobjekt konvertiert in ein Objekt umgewandelt, 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 einem beliebigen linq-to-sql Table<T>, Der IQueryable implementiert:

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 IEnumerable und nicht IQueryable implementiert:

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 EnumerableQuery konvertiert, das "eine IEnumerable<T> - Auflistung als IQueryable<T> - Datenquelle darstellt". (MSDN).

Was ist der Nutzen?

AsEnumerable wird häufig verwendet, um von einer IQueryable -Implementierung zu LINQ to Objects (L2O) zu wechseln, hauptsächlich wegen der ersteren unterstützt keine Funktionen, die L2O hat. Weitere Informationen finden Sie unter Wie wirkt sich AsEnumerable () auf eine LINQ-Entität aus? .

Beispielsweise können wir in einer Entity Framework-Abfrage 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 - das einen IEnumerable<T> in einen List<T> umwandelt - wird häufig für diesen Zweck als verwendet Gut. Der Vorteil der Verwendung von AsEnumerable gegenüber ToList besteht darin, dass AsEnumerable die Abfrage nicht ausführt. AsEnumerable behält die verzögerte Ausführung bei und erstellt keine häufig unbrauchbare Zwischenliste.

Wenn andererseits die erzwungene Ausführung einer LINQ-Abfrage gewünscht wird, kann ToList eine Möglichkeit sein, dies zu tun.

AsQueryable kann verwendet werden, um eine Auflistung dazu zu bringen, Ausdrücke in LINQ-Anweisungen zu akzeptieren. Weitere Informationen finden Sie hier: Benötige ich AsQueryable () wirklich für die Sammlung? .

Hinweis zum Drogenmissbrauch!

AsEnumerable wirkt wie eine Droge. Es handelt sich um eine schnelle Lösung, die jedoch teuer ist und das zugrunde liegende Problem nicht behebt.

In vielen Antworten auf Stapelüberläufe sehen wir Leute, die AsEnumerable anwenden, um fast jedes Problem mit nicht unterstützten Methoden in LINQ-Ausdrücken zu beheben. Der Preis ist jedoch nicht immer klar. Zum Beispiel, wenn Sie dies tun:

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

... alles ist ordentlich in eine SQL-Anweisung übersetzt, die Filter (Where) und Projekte (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 CreateDate sehen. In Entity Framework werden Sie schnell feststellen, dass ...

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

... wird derzeit nicht unterstützt. Ah, zum Glück gibt es das AsEnumerable Update:

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

Sicher, es läuft wahrscheinlich. Aber es zieht die gesamte Tabelle in den Speicher und wendet dann den Filter und die Projektionen an. Nun, die meisten Leute sind klug genug, zuerst Where zu machen:

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 im Speicher durchgeführt.

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?

Nun eine wichtige Einschränkung. Wenn Sie das tun

context.Observations.AsEnumerable()
                    .AsQueryable()

am Ende wird das Quellobjekt als IQueryable dargestellt. (Da beide Methoden nur casten und nicht konvertieren).

Aber wenn du es tust

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

was wird das Ergebnis sein?

Das Select erzeugt ein WhereSelectEnumerableIterator. Dies ist eine interne .Net-Klasse, die IEnumerable, not IQueryable implementiert. Es hat also eine Konvertierung in einen anderen Typ stattgefunden und das nachfolgende AsQueryable kann nie mehr die ursprüngliche Quelle zurückgeben.

Dies impliziert, dass die Verwendung von AsQueryable keine Möglichkeit darstellt, einen Abfrageanbieter mit seinen spezifischen Funktionen auf magische Weise in eine Aufzählung einzufügen. 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 unterbricht definitiv die Verbindung zum Entity Framework-Abfrageanbieter.

Ich zeige dieses Beispiel absichtlich, weil ich hier Fragen gesehen habe, bei denen beispielsweise versucht wird, Include -Funktionen durch Aufrufen von AsQueryable in eine Sammlung zu "injizieren". Es wird kompiliert und ausgeführt, aber es führt nichts aus, da das zugrunde liegende Objekt keine Include -Implementierung mehr hat.

Spezifische Implementierungen

Bisher ging es nur um die Erweiterungsmethoden Queryable.AsQueryable und Enumerable.AsEnumerable . Aber natürlich kann jeder Instanzmethoden oder Erweiterungsmethoden mit den gleichen Namen (und Funktionen) schreiben.

Tatsächlich ist DataTableExtensions.AsEnumerable ein häufiges Beispiel für eine bestimmte AsEnumerable -Erweiterungsmethode. DataTable implementiert IQueryable oder IEnumerable nicht, daher gelten die regulären Erweiterungsmethoden nicht.

318
Gert Arnold

ToList ()

  • Führen Sie die Abfrage sofort aus

AsEnumerable ()

  • faul (Abfrage später ausführen)
  • Parameter: Func<TSource, bool>
  • Laden Sie ALLE Datensätze in den Anwendungsspeicher und verarbeiten/filtern Sie sie dann. (z. B. Where/Take/Skip wählt * aus Tabelle1 in den Speicher aus und wählt dann die ersten X-Elemente aus.) (In diesem Fall: Linq-to-SQL + Linq-to-Object)

AsQueryable ()

  • faul (Abfrage später ausführen)
  • Parameter: Expression<Func<TSource, bool>>
  • Konvertieren Sie Expression in T-SQL (mit dem jeweiligen Anbieter), fragen Sie remote ab und laden Sie das Ergebnis in Ihren Anwendungsspeicher.
  • Aus diesem Grund erbt DbSet (in Entity Framework) auch IQueryable, um die effiziente Abfrage zu erhalten.
  • Laden Sie nicht jeden Datensatz, z. Wenn Take (5), wird im Hintergrund Select Top 5 * SQL generiert. Dies bedeutet, dass dieser Typ SQL-Datenbankfreundlicher ist. Aus diesem Grund weist dieser Typ normalerweise eine höhere Leistung auf und wird beim Umgang mit einer Datenbank empfohlen.
  • Daher arbeitet AsQueryable() normalerweise viel schneller als AsEnumerable(), da es zuerst T-SQL generiert, das alle Ihre where-Bedingungen in Ihrem Linq enthält.
39
Xin

ToList () wird alles im Speicher sein und dann werden Sie daran arbeiten. also ToList (). where (Filter anwenden) wird lokal ausgeführt. AsQueryable () führt alles aus der Ferne aus, d. H. Ein Filter darauf wird zur Anwendung an die Datenbank gesendet. Queryable tut nichts, bis Sie es ausführen. ToList wird jedoch sofort ausgeführt.

Schauen Sie sich auch diese Antwort an Warum sollte AsQueryable () anstelle von List () verwendet werden? .

BEARBEITEN: In Ihrem Fall ist jede nachfolgende Operation, einschließlich AsQueryable (), lokal, sobald Sie ToList () ausführen. Sie können nicht zu Remote wechseln, sobald Sie mit der lokalen Ausführung beginnen. Hoffe, das macht es ein bisschen klarer.

12
ashutosh raina

Bei unten stehendem Code wurde eine schlechte Leistung festgestellt.

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

Fest mit

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

}

Bleiben Sie für ein IQueryable in IQueryable, wenn dies möglich ist, und versuchen Sie, nicht wie IEnumerable verwendet zu werden.

Update . Es kann in einem Ausdruck weiter vereinfacht werden, danke Gert Arnold .

T single =  objects is IQueryable<T> q? 
                    q.First(): 
                    objects.First();
2
Rm558