Frage Rückgabe von IEnumerable vs. IQueryable


Was ist der Unterschied zwischen der Rückkehr? IQueryable<T> gegen 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 beides eine verzögerte Ausführung sein und wann sollte man dem anderen vorgezogen werden?


898
2018-05-20 18:13


Ursprung


Antworten:


Ja, beides wird dir geben verzögerte Ausführung.

Der Unterschied ist der IQueryable<T> ist die Schnittstelle, die LINQ-to-SQL (LINQ.-to-away wirklich) funktioniert. Also, wenn Sie Ihre Abfrage auf einem weiter verfeinern IQueryable<T>, diese Abfrage wird, wenn möglich, in der Datenbank ausgeführt.

Für die IEnumerable<T> Fall, es wird LINQ-to-object sein, was bedeutet, dass alle Objekte, die mit der ursprünglichen Abfrage übereinstimmen, aus der Datenbank in den Speicher geladen werden müssen.

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 aus:

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

Das ist ein ganz wichtiger Unterschied, und daran zu arbeiten IQueryable<T> In vielen Fällen können Sie dadurch verhindern, dass zu viele Zeilen aus der Datenbank zurückgegeben werden. Ein anderes Paradebeispiel ist Paging: Wenn Sie verwenden Take und Skip auf IQueryable, Sie erhalten nur die Anzahl der angeforderten Zeilen; das an einem tun IEnumerable<T> bewirkt, dass alle Zeilen in den Speicher geladen werden.


1522
2018-05-20 18:19



Die Top-Antwort ist gut, erwähnt aber keine Expression-Bäume, die erklären, "wie" sich die beiden Interfaces unterscheiden. Im Grunde 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.

  • Das IEnumerable Versionsunterschrift ist: Where(Func<Customer, bool> predicate)

  • Das IQueryable Versionsunterschrift ist: Where(Expression<Func<Customer, bool>> predicate)

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

z.B. Where(x => x.City == "<City>") arbeitet an beiden IEnumerable und IQueryable

  • Beim Benutzen Where() auf einem IEnumerable Sammlung übergibt der Compiler eine kompilierte Funktion an Where()

  • Beim Benutzen Where() auf einem IQueryable Collection übergibt der Compiler einen Ausdrucksbaum an Where(). Ein Ausdrucksbaum ist wie das Reflexionssystem, aber für Code. Der Compiler konvertiert Ihren Code in eine Datenstruktur, die beschreibt, was Ihr Code in einem leicht verdaulichen Format tut.

Warum sollten Sie sich mit diesem Ausdrucksbaum beschäftigen? Ich will nur Where() um meine Daten zu filtern. Der Hauptgrund ist, dass sowohl die EF- als auch die Linq2SQL-ORMs Expression-Bäume direkt in SQL konvertieren können, wo Ihr Code viel schneller ausgeführt wird.

Oh, das klingt nach einem kostenlosen Leistungsschub, sollte ich verwenden AsQueryable() überall in diesem Fall?  Nein, IQueryable ist nur sinnvoll, wenn der zugrunde liegende Datenanbieter etwas damit machen kann. Etwas wie einen normalen konvertieren List zu IQueryable gibt dir keinen Vorteil.


195
2018-02-14 08:00



Ja, beide verwenden eine 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 gegen eine WebLog-Tabelle mit 1 Million Datensätzen auszuführen.

Daher werden alle Tabellensätze als Objekte in den Speicher geladen, und dann mit jedem .Where () wird ein anderer Filter im Speicher gegen diese Objekte sein.

Wenn wir es benutzen IQueryable Anstatt von IEnumerable im obigen Beispiel (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 zu verwenden IQueryable.

IQueryable hat eine Eigenschaft namens Expression welches einen Baumausdruck speichert, der bei der Verwendung des result In unserem Beispiel (das als verzögerte Ausführung bezeichnet wird) wird dieser Ausdruck am Ende in eine SQL-Abfrage konvertiert, die auf dem Datenbankmodul ausgeführt wird.


56
2018-06-08 01:11



Beide werden Ihnen eine verzögerte Ausführung geben, ja.

Was für das andere bevorzugt wird, hängt davon ab, was die zugrunde liegende Datenquelle ist.

Durch die Rückgabe eines IEnumerable wird die Laufzeit automatisch dazu gezwungen, LINQ to Objects zu verwenden, um Ihre Sammlung abzufragen.

Die Rückgabe eines IQueryable (das übrigens IEnumerable implementiert) bietet die zusätzliche Funktionalität, um Ihre Abfrage in etwas zu überführen, das für die zugrunde liegende Quelle (LINQ to SQL, LINQ to XML usw.) besser funktioniert.


54
2018-05-20 18:18



Im Allgemeinen möchten Sie den ursprünglichen statischen Typ der Abfrage beibehalten, bis sie von Bedeutung ist.

Aus diesem Grund können Sie Ihre Variable auch als 'var' definieren IQueryable<> oder IEnumerable<> und du wirst wissen, dass du den Typ nicht änderst.

Wenn Sie mit einem beginnen IQueryable<>Normalerweise möchten Sie es als IQueryable<> bis es einen zwingenden Grund gibt, es zu ändern. Der Grund dafür ist, dass Sie dem Abfrageprozessor so viele Informationen wie möglich geben möchten. Zum Beispiel, wenn Sie nur 10 Ergebnisse verwenden (Sie haben angerufen Take(10)) Sie möchten, dass SQL Server darüber informiert wird, damit es seine Abfragepläne optimieren und Ihnen nur die Daten senden kann, die Sie verwenden.

Ein zwingender Grund, den Typ zu ändern IQueryable<> zu IEnumerable<> könnte sein, dass Sie eine Erweiterungsfunktion aufrufen, die die Implementierung von IQueryable<> in Ihrem bestimmten Objekt kann entweder nicht effizient handhaben oder behandelt. In diesem Fall können Sie den Typ in konvertieren IEnumerable<> (indem Sie einer Variablen vom Typ zuweisen IEnumerable<> oder mit dem AsEnumerable Englisch: www.doc-o-matic.com/webhelp/TdlgEditEditE.html Die Extension - Funktionen, die Sie aufrufen, enden als solche in der Enumerable Klasse statt der Queryable Klasse.


23
2018-05-15 00:00



Generell würde ich folgendes empfehlen:

Zurückgeben von IQueryable <T>, wenn Sie dem Entwickler, der Ihre Methode verwendet, die Möglichkeit geben möchten, die vor der Ausführung zurückgegebene Abfrage zu verfeinern.

Wenn Sie nur eine Reihe von Objekten transportieren möchten, die Sie aufzählen möchten, nehmen Sie einfach IEnumerable.

Stellen Sie sich ein IQueryable als das vor, was es ist, eine "Abfrage" für Daten (die Sie verfeinern können, wenn Sie möchten)

Ein IEnumerable ist eine Menge von Objekten (die bereits empfangen wurden oder erstellt wurden), über die Sie aufzählen können.


21
2018-04-11 13:09



Es gibt einen Blogbeitrag mit einem kurzen Quelltextbeispiel zum Missbrauch von IEnumerable<T> kann die Leistung der LINQ-Abfrage dramatisch beeinflussen: Entity Framework: IQueryable vs. IEnumerable.

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

// 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 aufzählbaren Iterator zurück und der zweite eine Abfrage über den Abfrageanbieter, der in angegeben ist IQueryable Quelle.


17
2018-04-23 22:45



Viel wurde vorher gesagt, aber zurück zu den Wurzeln, auf eine mehr technische Art:

  1. IEnumerable  ist eine Sammlung von Objekten im Speicher, die Sie aufzählen können - eine In-Memory-Sequenz, die es ermöglicht, iterieren zu können (macht es sehr einfach für das Innere) foreach Schleife, obwohl Sie mit gehen können IEnumerator nur). Sie liegen in der Erinnerung wie sie ist.
  2. IQueryable  ist ein Ausdrucksbaum das wird irgendwann in etwas anderes übersetzt werden mit der Fähigkeit, über das Endergebnis aufzuzählen. Ich denke, das verwirrt die meisten Menschen.

Sie haben offensichtlich unterschiedliche Konnotationen.

IQueryable stellt einen Ausdrucksbaum (eine Abfrage, einfach) dar, der vom zugrunde liegenden Abfrageanbieter in etwas anderes übersetzt wird, sobald Release-APIs aufgerufen werden, wie LINQ-Aggregatfunktionen (Sum, Count usw.) oder ToList [Array, Dictionary ,. ..]. Und IQueryable Objekte auch implementieren IEnumerable, IEnumerable<T> damit wenn sie eine Abfrage darstellen Das Ergebnis dieser Abfrage könnte iteriert werden. Dies bedeutet, dass IQueryable nicht nur Abfragen sein muss. Der richtige Ausdruck sind sie Ausdrucksbäume.

Nun, wie diese Ausdrücke ausgeführt werden und an was sie sich wenden, hängt alles von so genannten Abfrageanbietern ab (Ausdrucksexecutoren, an die wir sie denken können).

In dem Entitätsrahmen world (das ist der mystische zugrunde liegende Datenquellenanbieter oder der Abfrageanbieter) IQueryable Ausdrücke werden in nativ übersetzt T-SQL Abfragen. Nhibernate macht ähnliche Dinge mit ihnen. Sie können Ihre eigenen nach den Konzepten schreiben, die in LINQ: Erstellen eines IQueryable-Anbieters B. eine Verknüpfung herstellen, und Sie möchten möglicherweise eine benutzerdefinierte Abfrage-API für Ihren Produktspeicheranbieter-Dienst haben.

Also im Grunde genommen, IQueryable Objekte werden solange konstruiert, bis wir sie explizit freigeben und dem System mitteilen, sie in SQL oder was auch immer zu schreiben und die Ausführungskette für die weitere Verarbeitung zu senden.

Als ob man aufgeschoben Ausführung ist es ein LINQ Feature, um das Ausdrucksbaumschema im Speicher zu halten und es nur bei Bedarf in die Ausführung zu senden, wann immer bestimmte APIs gegen die Sequenz aufgerufen werden (derselbe Count, ToList, etc.).

Die richtige Verwendung von beiden hängt stark 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 IList, das ist IEnumerable über Listen (Indexer und dergleichen). Also ist es mein Ratschlag zu verwenden IQueryable nur in Repositories und IEnumerable an anderer Stelle im Code. Über die Testbarkeit zu sagen, betrifft das nicht IQueryable bricht zusammen und ruiniert die Trennung von Bedenken Prinzip. Wenn Sie einen Ausdruck aus den Repositories zurückgeben, können die Konsumenten mit der Persistenzschicht spielen, wie sie es wünschen.

Ein kleiner Zusatz zum Chaos :) (aus einer Diskussion in den Kommentaren)) Keine von ihnen sind Objekte im Gedächtnis, da sie keine echten Typen per se sind, sondern Marker eines Typs - wenn du so tief gehen willst. Aber es macht Sinn (und deshalb sogar MSDN Um es so auszudrücken, denken Sie an IEnumerables als In-Memory-Sammlungen, während IQueryables als Ausdrucksbäume betrachtet werden. Der Punkt ist, dass die IQueryable-Schnittstelle die IEnumerable-Schnittstelle erbt, sodass die Ergebnisse dieser Abfrage aufgelistet werden können, wenn sie eine Abfrage darstellt. Die Enumeration bewirkt, dass der mit einem IQueryable-Objekt verknüpfte Ausdrucksbaum ausgeführt wird. Sie können also tatsächlich kein IEnumerable-Element aufrufen, ohne das Objekt im Speicher zu haben. Es wird sowieso dort reinkommen, wenn es nicht leer ist. IQueryables sind nur Abfragen, nicht die Daten.


16
2018-06-05 16:23



Ich bin kürzlich auf ein Problem mit IEnumrable v. IQueryable gestoßen. Der verwendete Algorithmus führte zuerst eine IQueryable-Abfrage durch, um eine Reihe von Ergebnissen zu erhalten. Diese wurden dann an eine foreach-Schleife übergeben, wobei die Elemente als EF-Klasse instanziiert wurden. Diese EF-Klasse wurde dann in der from-Klausel einer Linq to Entity-Abfrage verwendet, wodurch das Ergebnis IEnumerable war. Ich bin ziemlich neu in EF und Linq for Entities, also brauchte ich eine Weile, um herauszufinden, was der Engpass war. Mit MiniProfiling fand ich die Abfrage und konvertierte dann alle einzelnen Operationen in eine einzige Abfrage IQQueryable Linq for Entities. Das IEnumerable dauerte 15 Sekunden und das IQueryable dauerte 0,5 Sekunden, um auszuführen. Es gab drei Tabellen, und nachdem ich das gelesen hatte, glaubte ich, dass die IEnumerable-Abfrage tatsächlich ein Produkt mit drei Tabellen bildete und die Ergebnisse filterte.

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


9
2017-07-12 20:39