it-swarm.com.de

Rückgabe von IEnumerable <T> vs. IQueryable <T>

Was ist der Unterschied zwischen der Rückgabe von IQueryable<T> und IEnumerable<T>?

IQueryable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

IEnumerable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

Wird sowohl die Ausführung aufgeschoben, als auch wann sollte eine Ausführung der anderen vorgezogen werden?

1029

Ja, beide geben Ihnen verzögerte Ausführung .

Der Unterschied besteht darin, dass IQueryable<T> die Schnittstelle ist, über die LINQ-to-SQL (LINQ.-to-anything) funktioniert. Wenn Sie also Ihre Abfrage auf einem IQueryable<T> weiter verfeinern, wird diese Abfrage nach Möglichkeit in der Datenbank ausgeführt.

Für den Fall IEnumerable<T> ist es LINQ-zu-Objekt, dh alle Objekte, die der ursprünglichen Abfrage entsprechen, müssen aus der Datenbank in den Speicher geladen werden.

In Code:

_IQueryable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);
_

Dieser Code führt SQL aus, um nur Goldkunden auszuwählen. Der folgende Code führt andererseits die ursprüngliche Abfrage in der Datenbank aus und filtert dann die Nicht-Gold-Kunden im Speicher heraus:

_IEnumerable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);
_

Dies ist ein wichtiger Unterschied. Wenn Sie an IQueryable<T> arbeiten, können Sie in vielen Fällen verhindern, dass zu viele Zeilen aus der Datenbank zurückgegeben werden. Ein weiteres Paradebeispiel ist das Blättern: Wenn Sie Take und Skip am IQueryable verwenden, erhalten Sie nur die Anzahl der angeforderten Zeilen. Wenn Sie dies bei einem IEnumerable<T> tun, werden alle Ihre Zeilen in den Speicher geladen.

1704
driis

Die beste Antwort ist gut, aber es werden keine Ausdrucksbäume erwähnt, die erklären, wie sich die beiden Schnittstellen unterscheiden. Grundsätzlich gibt es zwei identische Sätze von LINQ-Erweiterungen. Where(), Sum(), Count(), FirstOrDefault() usw. haben alle zwei Versionen: eine, die Funktionen akzeptiert, und eine, die Ausdrücke akzeptiert.

  • Die IEnumerable -Versionssignatur lautet: Where(Func<Customer, bool> predicate)

  • Die IQueryable -Versionssignatur lautet: Where(Expression<Func<Customer, bool>> predicate)

Sie haben wahrscheinlich beide verwendet, ohne es zu bemerken, da beide mit identischer Syntax aufgerufen werden:

z.B. Where(x => x.City == "<City>") funktioniert sowohl mit IEnumerable als auch mit IQueryable

  • Bei Verwendung von Where() für eine IEnumerable-Auflistung übergibt der Compiler eine kompilierte Funktion an Where().

  • Wenn Sie Where() für eine IQueryable -Auflistung verwenden, übergibt der Compiler einen Ausdrucksbaum an Where(). Ein Ausdrucksbaum ist wie das Reflexionssystem, nur für Code. Der Compiler konvertiert Ihren Code in eine Datenstruktur, die beschreibt, was Ihr Code in einem Format tut, das leicht verdaulich ist.

Warum sollte ich mich mit dieser Ausdrucksbaum-Sache beschäftigen? Ich möchte nur, dass Where() meine Daten filtert. Der Hauptgrund ist, dass sowohl EF- als auch Linq2SQL-ORMs Ausdrucksbäume direkt in SQL konvertieren können, wo Ihr Code es will viel schneller ausführen.

Oh, das klingt nach einer kostenlosen Leistungssteigerung, sollte ich in diesem Fall AsQueryable() überall verwenden? Nein, IQueryable ist nur nützlich, wenn der zugrunde liegende Datenprovider dies kann etwas damit. Wenn Sie so etwas wie ein reguläres List in IQueryable konvertieren, erhalten Sie keine Vorteile.

280
Jacob

Ja, beide verwenden die verzögerte Ausführung. Lassen Sie uns den Unterschied mit dem SQL Server-Profiler veranschaulichen.

Wenn wir den folgenden Code ausführen:

MarketDevEntities db = new MarketDevEntities();

IEnumerable<WebLog> first = db.WebLogs;
var second = first.Where(c => c.DurationSeconds > 10);
var third = second.Where(c => c.WebLogID > 100);
var result = third.Where(c => c.EmailAddress.Length > 11);

Console.Write(result.First().UserName);

In SQL Server Profiler finden wir einen Befehl gleich:

"SELECT * FROM [dbo].[WebLog]"

Es dauert ungefähr 90 Sekunden, um diesen Codeblock für eine WebLog-Tabelle mit 1 Million Datensätzen auszuführen.

Daher werden alle Tabellendatensätze als Objekte in den Speicher geladen, und dann wird mit jedem .Where () ein weiterer Filter im Speicher für diese Objekte erstellt.

Wenn wir im obigen Beispiel IQueryable anstelle von IEnumerable verwenden (zweite Zeile):

In SQL Server Profiler finden wir einen Befehl gleich:

"SELECT TOP 1 * FROM [dbo].[WebLog] WHERE [DurationSeconds] > 10 AND [WebLogID] > 100 AND LEN([EmailAddress]) > 11"

Es dauert ungefähr vier Sekunden, um diesen Codeblock mit IQueryable auszuführen.

IQueryable hat eine Eigenschaft mit dem Namen Expression, die einen Baumausdruck speichert, der erstellt wird, wenn wir in unserem Beispiel result verwenden (was als verzögerte Ausführung bezeichnet wird). Am Ende wird dieser Ausdruck in ein konvertiert Auf dem Datenbankmodul auszuführende SQL-Abfrage.

76
Kasper Roma

Beides verschiebt die Ausführung, ja.

Welches der anderen vorgezogen wird, hängt davon ab, um welche zugrunde liegende Datenquelle es sich handelt.

Wenn Sie IEnumerable zurückgeben, wird die Laufzeit automatisch dazu gezwungen, LINQ to Objects zum Abfragen Ihrer Sammlung zu verwenden.

Die Rückgabe eines IQueryable (das übrigens IEnumerable implementiert) bietet die zusätzliche Funktionalität, um Ihre Abfrage in etwas zu übersetzen, das möglicherweise eine bessere Leistung für die zugrunde liegende Quelle erbringt (LINQ to SQL, LINQ to XML usw.). .

56
Justin Niessner

Generell würde ich folgendes empfehlen:

  • Geben Sie IQueryable<T> zurück, wenn Sie dem Entwickler mithilfe Ihrer Methode die Verfeinerung der Abfrage ermöglichen möchten, die Sie vor der Ausführung zurückgeben.

  • Geben Sie IEnumerable zurück, wenn Sie eine Reihe von Objekten transportieren möchten, über die aufgezählt werden soll.

Stellen Sie sich ein IQueryable als das vor, was es ist - eine "Abfrage" nach Daten (die Sie verfeinern können, wenn Sie möchten). Ein IEnumerable ist eine Gruppe von Objekten (die bereits empfangen oder erstellt wurden), über die Sie eine Aufzählung durchführen können.

29
sebastianmehler

Zuvor wurde viel gesagt, aber auf technischere Weise zurück zu den Wurzeln:

  1. IEnumerable ist eine Sammlung von Objekten im Speicher, die Sie aufzählen können - eine speicherinterne Sequenz, die es ermöglicht, sie zu durchlaufen (macht es möglich) sehr einfach für die foreach Schleife, obwohl Sie nur mit IEnumerator gehen können). Sie bleiben im Gedächtnis wie sie sind.
  2. IQueryable ist ein Ausdrucksbaum , der irgendwann in etwas anderes übersetzt wird und aufgezählt werden kann über das Endergebnis . Ich denke, das ist es, was die meisten Leute verwirrt.

Sie haben offensichtlich unterschiedliche Konnotationen.

IQueryable repräsentiert einen Ausdrucksbaum (eine Abfrage, einfach), der vom zugrunde liegenden Abfrageanbieter in etwas anderes übersetzt wird, sobald Release-APIs aufgerufen werden, wie z. B. LINQ-Aggregatfunktionen (Summe, Anzahl usw.) oder ToList [ Array, Dictionary, ...]. Und IQueryable Objekte implementieren auch IEnumerable, IEnumerable<T>, so dass , wenn sie eine Abfrage darstellen Das Ergebnis dieser Abfrage könnte iteriert werden. Dies bedeutet, dass es sich bei IQueryable nicht nur um Abfragen handeln muss. Der richtige Ausdruck ist, dass es sich um Ausdrucksbäume handelt .

Wie diese Ausdrücke ausgeführt werden und woran sie sich wenden, hängt von den sogenannten Abfrageanbietern ab (Ausdrücke, an die wir uns denken können).

In der Welt Entity Framework (das ist der mystische zugrunde liegende Datenquellenanbieter oder der Abfrageanbieter) IQueryable werden Ausdrücke in native T-SQL Abfragen übersetzt. Nhibernate macht ähnliche Dinge mit ihnen. Sie können beispielsweise ein eigenes schreiben, das den in LINQ: Erstellen eines IQueryable-Providers beschriebenen Konzepten folgt, und Sie möchten möglicherweise eine benutzerdefinierte Abfrage-API für den Dienst Ihres Produktgeschäftsanbieters.

Im Grunde genommen werden IQueryable Objekte so lange konstruiert, bis wir sie explizit freigeben und das System anweisen, sie in SQL oder was auch immer umzuschreiben und die Ausführungskette zur weiteren Verarbeitung weiterzuleiten.

Als ob die verzögerte Ausführung eine LINQ Funktion wäre, um das Ausdrucksbaumschema aufrechtzuerhalten im Speicher ab und senden Sie es nur auf Anforderung an die Ausführung, wenn bestimmte APIs gegen die Sequenz aufgerufen werden (derselbe Count, ToList usw.).

Die richtige Verwendung von beiden hängt in hohem Maße von den Aufgaben ab, denen Sie im konkreten Fall gegenüberstehen. Für das bekannte Repository-Muster entscheide ich mich persönlich für die Rückgabe von IList, das heißt IEnumerable über Listen (Indexer und dergleichen). Daher rate ich, IQueryable nur in Repositorys und IEnumerable an einer anderen Stelle im Code zu verwenden. Wenn Sie nicht über die Testbarkeitsbedenken sprechen, die IQueryable auflöst und das Trennung von Bedenken Prinzip ruiniert. Wenn Sie einen Ausdruck aus einem Repository zurückgeben, können die Konsumenten mit der Persistenzebene spielen, wie sie möchten.

Eine kleine Ergänzung zum Durcheinander :) (aus einer Diskussion in den Kommentaren)) Keines von ihnen ist ein Objekt im Gedächtnis, da es sich nicht um echte Typen an sich handelt, sondern um Markierungen eines Typs - wenn Sie so tief gehen wollen. Aber es ist sinnvoll (und deshalb auch MSDN so auszudrücken), sich IEnumerables als speicherinterne Auflistungen und IQueryables als Ausdrucksbäume vorzustellen. Der Punkt ist, dass die IQueryable-Schnittstelle die IEnumerable-Schnittstelle erbt, sodass, wenn sie eine Abfrage darstellt, die Ergebnisse dieser Abfrage aufgelistet werden können. Durch die Aufzählung wird die Ausdrucksbaumstruktur ausgeführt, die einem IQueryable-Objekt zugeordnet ist. Tatsächlich können Sie also kein IEnumerable-Mitglied aufrufen, ohne das Objekt im Speicher zu haben. Es wird sowieso dort hineinkommen, wenn Sie es tun, wenn es nicht leer ist. IQueryables sind nur Abfragen, nicht die Daten.

25

Im Allgemeinen möchten Sie den ursprünglichen statischen Typ der Abfrage beibehalten, bis es darauf ankommt.

Aus diesem Grund können Sie Ihre Variable als 'var' anstelle von IQueryable<> oder IEnumerable<> definieren und wissen, dass Sie den Typ nicht ändern.

Wenn Sie mit einem IQueryable<> beginnen, möchten Sie diesen normalerweise als IQueryable<> beibehalten, bis ein zwingender Grund vorliegt, ihn zu ändern. Der Grund dafür ist, dass Sie dem Abfrageprozessor so viele Informationen wie möglich geben möchten. Wenn Sie beispielsweise nur 10 Ergebnisse verwenden möchten (Sie haben Take(10) aufgerufen), möchten Sie, dass SQL Server darüber informiert wird, damit es seine Abfragepläne optimieren und Ihnen nur die Daten senden kann, die Sie benötigen verwenden.

Ein zwingender Grund, den Typ von IQueryable<> in IEnumerable<> zu ändern, könnte sein, dass Sie eine Erweiterungsfunktion aufrufen, die die Implementierung von IQueryable<> in Ihrem bestimmten Objekt entweder nicht oder nicht effizient verarbeiten kann. In diesem Fall möchten Sie den Typ möglicherweise in IEnumerable<> konvertieren (indem Sie ihn einer Variablen vom Typ IEnumerable<> zuweisen oder beispielsweise die Erweiterungsmethode AsEnumerable verwenden), damit die Erweiterung für Sie funktioniert Rufen Sie am Ende auf, die in der Klasse Enumerable anstelle der Klasse Queryable zu sein.

24
AJS

Es gibt einen Blogeintrag mit einem kurzen Quellcodebeispiel darüber, wie sich der Missbrauch von IEnumerable<T> dramatisch auf die Leistung von LINQ-Abfragen auswirken kann: Entity Framework: IQueryable vs. IEnumerable .

Wenn wir tiefer in die Quellen schauen, können wir sehen, dass es offensichtlich verschiedene Erweiterungsmethoden gibt, die für IEnumerable<T> durchgeführt werden:

// Type: System.Linq.Enumerable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Enumerable
{
    public static IEnumerable<TSource> Where<TSource>(
        this IEnumerable<TSource> source, 
        Func<TSource, bool> predicate)
    {
        return (IEnumerable<TSource>) 
            new Enumerable.WhereEnumerableIterator<TSource>(source, predicate);
    }
}

und IQueryable<T>:

// Type: System.Linq.Queryable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(
        this IQueryable<TSource> source, 
        Expression<Func<TSource, bool>> predicate)
    {
        return source.Provider.CreateQuery<TSource>(
            Expression.Call(
                null, 
                ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(
                    new Type[] { typeof(TSource) }), 
                    new Expression[] 
                        { source.Expression, Expression.Quote(predicate) }));
    }
}

Der erste gibt einen zählbaren Iterator zurück, und der zweite erstellt eine Abfrage über den in der Quelle IQueryable angegebenen Abfrageanbieter.

18

Ich bin kürzlich auf ein Problem mit IEnumerable v. IQueryable gestoßen. Der zuerst verwendete Algorithmus führte eine IQueryable -Anfrage durch, um eine Reihe von Ergebnissen zu erhalten. Diese wurden dann an eine foreach -Schleife übergeben, wobei die Elemente als Entity Framework (EF) -Klasse instanziiert wurden. Diese EF-Klasse wurde dann in der from -Klausel einer Linq to Entity-Abfrage verwendet, wodurch das Ergebnis IEnumerable lautete.

Ich bin ziemlich neu in EF und Linq für Entities, also hat es eine Weile gedauert, um herauszufinden, was der Engpass war. Mit MiniProfiling habe ich die Abfrage gefunden und dann alle einzelnen Vorgänge in eine einzelne IQueryable Linq for Entities-Abfrage konvertiert. Die Ausführung von IEnumerable dauerte 15 Sekunden und von IQueryable 0,5 Sekunden. Es handelte sich um drei Tabellen, und nachdem ich dies gelesen hatte, war die Abfrage IEnumerable meines Erachtens tatsächlich ein Kreuzprodukt aus drei Tabellen und filterte die Ergebnisse.

Versuchen Sie, IQueryables als Faustregel zu verwenden und Ihre Arbeit zu profilieren, um Ihre Änderungen messbar zu machen.

11
sscheider

dies sind einige Unterschiede zwischen IQueryable<T> und IEnumerable<T>

difference between returning IQueryable<T> vs. IEnumerable<T>

11

Ich möchte ein paar Dinge klären, weil die Antworten scheinbar widersprüchlich sind (meistens um IEnumerable herum).

(1) IQueryable erweitert die Schnittstelle IEnumerable. (Sie können ein IQueryable ohne Fehler an etwas senden, das IEnumerable erwartet.)

(2) Sowohl IQueryable als auch IEnumerable LINQ versuchen, beim Durchlaufen der Ergebnismenge verzögert zu laden. (Beachten Sie, dass die Implementierung in den Schnittstellenerweiterungsmethoden für jeden Typ zu sehen ist.)

Mit anderen Worten, IEnumerables sind nicht ausschließlich "im Speicher". IQueryables werden nicht immer in der Datenbank ausgeführt. IEnumerable muss Dinge in den Speicher laden (einmal abgerufen, möglicherweise träge), weil es keinen abstrakten Datenprovider gibt. IQueryables stützen sich auf einen abstrakten Anbieter (wie LINQ-to-SQL), obwohl dies auch der speicherinterne .NET-Anbieter sein kann.

Anwendungsbeispiel

(a) Liste der Datensätze als IQueryable aus dem EF-Kontext abrufen. (Es befinden sich keine Datensätze im Speicher.)

(b) Übergeben Sie das IQueryable an eine Ansicht, deren Modell IEnumerable ist. (Gültig. IQueryable erweitert IEnumerable.)

(c) Durchlaufen Sie die Ansicht und greifen Sie auf die Datensätze, untergeordneten Entitäten und Eigenschaften des Datensatzes zu. (Kann Ausnahmen verursachen!)

Mögliche Probleme

(1) Das IEnumerable versucht das verzögerte Laden und Ihr Datenkontext ist abgelaufen. Ausnahme ausgelöst, da der Anbieter nicht mehr verfügbar ist.

(2) Entity Framework-Entitätsproxys sind aktiviert (Standardeinstellung), und Sie versuchen, auf ein zugehöriges (virtuelles) Objekt mit einem abgelaufenen Datenkontext zuzugreifen. Gleich wie (1).

(3) Mehrere aktive Ergebnismengen (MARS). Wenn Sie den IEnumerable in einem foreach( var record in resultSet ) -Block durchlaufen und gleichzeitig versuchen, auf record.childEntity.childProperty zuzugreifen, führt dies möglicherweise zu MARS, da sowohl der Datensatz als auch die relationale Entität nur schleppend geladen werden. Dies führt zu einer Ausnahme, wenn diese in Ihrer Verbindungszeichenfolge nicht aktiviert ist.

Lösung

  • Ich habe festgestellt, dass das Aktivieren von MARS in der Verbindungszeichenfolge unzuverlässig funktioniert. Ich schlage vor, Sie vermeiden MARS, es sei denn, es ist gut verstanden und ausdrücklich gewünscht.

Führen Sie die Abfrage aus und speichern Sie die Ergebnisse, indem Sie resultList = resultSet.ToList() aufrufen. Dies scheint die einfachste Methode zu sein, um sicherzustellen, dass sich Ihre Entitäten im Arbeitsspeicher befinden.

In Fällen, in denen Sie auf verwandte Entitäten zugreifen, benötigen Sie möglicherweise noch einen Datenkontext. Entweder das, oder Sie können Entity-Proxys deaktivieren und explizit Include zugehörige Entities aus Ihrem DbSet entfernen.

10

Der Hauptunterschied zwischen "IEnumerable" und "IQueryable" besteht darin, wo die Filterlogik ausgeführt wird. Einer wird auf der Client-Seite (im Speicher) und der andere auf der Datenbank ausgeführt.

Wir können zum Beispiel ein Beispiel betrachten, in dem wir 10.000 Datensätze für einen Benutzer in unserer Datenbank haben und sagen wir nur 900, die aktive Benutzer sind. Wenn wir in diesem Fall "IEnumerable" verwenden, werden zuerst alle 10.000 Datensätze in den Speicher und geladen Wendet dann den IsActive-Filter darauf an, der schließlich die 900 aktiven Benutzer zurückgibt.

Wenn wir hingegen "IQueryable" verwenden, wird der IsActive-Filter direkt auf die Datenbank angewendet, wodurch die 900 aktiven Benutzer direkt von dort zurückgegeben werden.

Referenz Link

9
Tabish Usman

Wir können beide auf die gleiche Weise verwenden, und sie unterscheiden sich nur in der Leistung.

IQueryable wird nur auf effiziente Weise für die Datenbank ausgeführt. Dies bedeutet, dass eine gesamte Auswahlabfrage erstellt wird und nur die zugehörigen Datensätze abgerufen werden.

Zum Beispiel wollen wir die Top 1 Kunden nehmen, deren Name mit "Nimal" beginnt. In diesem Fall wird die Auswahlabfrage als select top 10 * from Customer where name like ‘Nimal%’ generiert.

Wenn wir jedoch IEnumerable verwenden, würde die Abfrage select * from Customer where name like ‘Nimal%’ lauten und die Top Ten werden auf der C # -Codierungsebene gefiltert (alle Kundendatensätze werden aus der Datenbank abgerufen und an C # übergeben).

5
user3710357

Zusätzlich zu den ersten 2 wirklich guten Antworten (von driis & von Jacob):

Die IEnumerable-Schnittstelle befindet sich im Namespace System.Collections.

Das IEnumerable-Objekt stellt eine Datenmenge im Speicher dar und kann diese Daten nur vorwärts verschieben. Die vom IEnumerable-Objekt dargestellte Abfrage wird sofort und vollständig ausgeführt, sodass die Anwendung schnell Daten empfängt.

Wenn die Abfrage ausgeführt wird, lädt IEnumerable alle Daten und wenn wir sie filtern müssen, erfolgt die Filterung selbst auf der Clientseite.

Die IQueryable-Schnittstelle befindet sich im System.Linq-Namespace.

Das IQueryable-Objekt bietet Remotezugriff auf die Datenbank und ermöglicht das Navigieren durch die Daten entweder in direkter Reihenfolge von Anfang bis Ende oder in umgekehrter Reihenfolge. Beim Erstellen einer Abfrage ist das zurückgegebene Objekt IQueryable. Die Abfrage wird optimiert. Dies hat zur Folge, dass während der Ausführung weniger Speicher verbraucht wird, weniger Netzwerkbandbreite, aber gleichzeitig etwas langsamer verarbeitet werden kann als bei einer Abfrage, die ein IEnumerable-Objekt zurückgibt.

Was auszusuchen?

Wenn Sie den gesamten Satz der zurückgegebenen Daten benötigen, ist es besser, IEnumerable zu verwenden, das die maximale Geschwindigkeit bietet.

Wenn Sie NICHT die gesamte Menge der zurückgegebenen Daten, sondern nur einige gefilterte Daten benötigen, ist es besser, IQueryable zu verwenden.

5
Gleb B

Darüber hinaus ist es interessant zu wissen, dass Sie Ausnahmen erhalten können, wenn Sie IQueryable anstelle von IEnumerable verwenden:

Das Folgende funktioniert einwandfrei, wenn products ein IEnumerable ist:

products.Skip(-4);

Wenn jedoch products ein IQueryable ist und versucht wird, auf Datensätze aus einer DB-Tabelle zuzugreifen, wird der folgende Fehler ausgegeben:

Der in einer OFFSET-Klausel angegebene Offset darf nicht negativ sein.

Dies liegt daran, dass die folgende Abfrage erstellt wurde:

SELECT [p].[ProductId]
FROM [Products] AS [p]
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS

und OFFSET dürfen keinen negativen Wert haben.

0
Backwards_Dave