Ja, beide führen zu einer verzögerten Ausführung .
Der Unterschied besteht darin, dass IQueryable<T>
die Schnittstelle LINQ-to-SQL (LINQ.-to-irgendetwas wirklich) funktioniert. Wenn Sie also Ihre Abfrage auf einem weiter verfeinernIQueryable<T>
, wird diese Abfrage nach Möglichkeit in der Datenbank ausgeführt.
In diesem IEnumerable<T>
Fall handelt es sich um LINQ-to-Object. Dies 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 heraus:
IEnumerable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);
Dies ist ein ziemlich wichtiger Unterschied, und IQueryable<T>
wenn Sie daran arbeiten, können Sie in vielen Fällen nicht zu viele Zeilen aus der Datenbank zurückgeben. Ein weiteres gutes Beispiel tut Paging: Wenn Sie Take
und Skip
auf IQueryable
, werden Sie nur die Anzahl der angeforderten Zeilen erhalten; Wenn Sie dies tun, IEnumerable<T>
werden alle Ihre Zeilen in den Speicher geladen.
IEnumerable
,IQueryable
ist, dass nicht alle LINQ-Vorgänge von allen LINQ-Anbietern unterstützt werden. Solange Sie wissen, was Sie tun, können SieIQueryable
so viele Anfragen wie möglich an den LINQ-Anbieter (LINQ2SQL, EF, NHibernate, MongoDB usw.) senden. Wenn Sie jedoch anderen Code mit Ihrem Code tun lassen, was immer er will, werdenIQueryable
Sie möglicherweise in Schwierigkeiten geraten, da ein Client-Code irgendwo eine nicht unterstützte Operation verwendet. Ich bin mit der Empfehlung einverstanden,IQueryable
s nicht "in the wild" hinter dem Repository oder einer gleichwertigen Schicht freizugeben .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()
, Etc alle zwei Versionen: eine , die Funktionen und eine , die Ausdrücke akzeptiert 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 merken, da beide mit identischer Syntax aufgerufen werden:
zB
Where(x => x.City == "<City>")
funktioniert auf beidenIEnumerable
undIQueryable
Bei Verwendung
Where()
einerIEnumerable
Sammlung übergibt der Compiler eine kompilierte Funktion anWhere()
Bei Verwendung
Where()
einerIQueryable
Sammlung übergibt der Compiler einen Ausdrucksbaum anWhere()
. Ein Ausdrucksbaum ist wie das Reflexionssystem, jedoch für Code. Der Compiler konvertiert Ihren Code in eine Datenstruktur, die beschreibt, was Ihr Code in einem leicht verdaulichen Format tut.Warum sich mit diesem Ausdrucksbaum beschäftigen? Ich möchte nur
Where()
meine Daten filtern. Der Hauptgrund ist, dass sowohl die EF- als auch die Linq2SQL-ORMs Ausdrucksbäume direkt in SQL konvertieren können, wobei Ihr Code viel schneller ausgeführt wird.Oh, das klingt nach einem kostenlosen Leistungsschub. Sollte ich
AsQueryable()
in diesem Fall überall verwenden? Nein,IQueryable
ist nur dann sinnvoll, wenn der zugrunde liegende Datenanbieter etwas damit anfangen kann. So etwas wie ein StammgastList
umwandelnIQueryable
konvertieren, haben Sie keinen Vorteil.quelle
IQueryable
Funktionen nicht so gut analysiert werdenIEnumerable
: Wenn Sie beispielsweise wissen möchten, welche Elemente von aDBSet<BookEntity>
nicht in a enthalten sindList<BookObject>
, wirddbSetObject.Where(e => !listObject.Any(o => o.bookEntitySource == e))
eine Ausnahme ausgelöst :Expression of type 'BookEntity' cannot be used for parameter of type 'BookObject' of method 'Boolean Contains[BookObject] (IEnumerable[BookObject], BookObject)'
. Ich musste hinzufügen.ToList()
nachdbSetObject
.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:
Im SQL Server-Profiler finden wir einen Befehl gleich:
Es dauert ungefähr 90 Sekunden, um diesen Codeblock für eine WebLog-Tabelle mit 1 Million Datensätzen auszuführen.
Alle Tabellendatensätze werden also als Objekte in den Speicher geladen, und dann wird mit jedem .Where () ein weiterer Filter im Speicher für diese Objekte erstellt.
Wenn wir
IQueryable
anstelle vonIEnumerable
im obigen Beispiel (zweite Zeile) verwenden:Im SQL Server-Profiler finden wir einen Befehl gleich:
Es dauert ungefähr vier Sekunden, um diesen Codeblock mit auszuführen
IQueryable
.IQueryable hat eine Eigenschaft namens,
Expression
die einen Baumausdruck speichert, der erstellt wird, wenn wir denresult
in unserem Beispiel verwendeten verwenden (der als verzögerte Ausführung bezeichnet wird). Am Ende wird dieser Ausdruck in eine SQL-Abfrage konvertiert, die auf dem Datenbankmodul ausgeführt wird.quelle
Beides führt zu einer verzögerten Ausführung, ja.
Welche der anderen vorgezogen wird, hängt davon ab, welche Datenquelle Ihnen zugrunde liegt.
Wenn Sie ein zurückgeben,
IEnumerable
wird die Laufzeit automatisch gezwungen, LINQ to Objects zum Abfragen Ihrer Sammlung zu verwenden.Die Rückgabe von
IQueryable
(dieIEnumerable
übrigens implementiert wird) bietet die zusätzliche Funktionalität, um Ihre Abfrage in etwas zu übersetzen, das für die zugrunde liegende Quelle möglicherweise eine bessere Leistung erbringt (LINQ zu SQL, LINQ zu XML usw.).quelle
Im Allgemeinen würde ich Folgendes empfehlen:
Zurückgeben,
IQueryable<T>
wenn Sie dem Entwickler ermöglichen möchten, die von Ihnen zurückgegebene Abfrage vor der Ausführung mithilfe Ihrer Methode zu verfeinern.Geben
IEnumerable
Sie zurück, wenn Sie eine Reihe von Objekten transportieren möchten, die aufgelistet werden sollen.Stellen Sie sich vor
IQueryable
, was es ist - eine "Abfrage" nach Daten (die Sie verfeinern können, wenn Sie möchten). AnIEnumerable
ist eine Reihe von Objekten (die bereits empfangen oder erstellt wurden), über die Sie aufzählen können.quelle
Es wurde bereits viel gesagt, aber auf technischere Weise zurück zu den Wurzeln:
IEnumerable
ist eine Sammlung von Objekten im Speicher, die Sie aufzählen können - eine In-Memory-Sequenz, die das Durchlaufen von Objekten ermöglicht (erleichtert das In-foreach
Loop- Verfahren , obwohl SieIEnumerator
nur damit arbeiten können). Sie befinden sich in der Erinnerung wie sie ist.IQueryable
ist ein Ausdrucksbaum , der irgendwann in etwas anderes übersetzt wird und über das Endergebnis aufzählen kann . Ich denke, das ist es, was die meisten Menschen verwirrt.Sie haben offensichtlich unterschiedliche Konnotationen.
IQueryable
stellt einen Ausdrucksbaum (einfach eine Abfrage) dar, der vom zugrunde liegenden Abfrageanbieter in etwas anderes übersetzt wird, sobald Release-APIs aufgerufen werden, z. B. LINQ-Aggregatfunktionen (Summe, Anzahl usw.) oder ToList [Array, Dictionary,. ..]. UndIQueryable
auch Objekte implementierenIEnumerable
,IEnumerable<T>
so dass , wenn sie eine Abfrage repräsentiert das Ergebnis dieser Abfrage könnte wiederholt werden. Dies bedeutet, dass IQueryable nicht nur Abfragen sein müssen. Der richtige Begriff ist, dass sie Ausdrucksbäume sind .Wie diese Ausdrücke ausgeführt werden und woran sie sich wenden, hängt von den sogenannten Abfrageanbietern ab (Ausdrucksausführende, an die wir sie denken können).
In der Entity Framework- Welt ( dh dem mystischen zugrunde liegenden Datenquellenanbieter oder dem Abfrageanbieter) werden
IQueryable
Ausdrücke in native T-SQL- Abfragen übersetzt.Nhibernate
macht ähnliche Dinge mit ihnen. Sie können Ihr eigenes schreiben, indem Sie den in LINQ ziemlich gut beschriebenen Konzepten folgen : Erstellen eines IQueryable Provider- Links, und Sie möchten möglicherweise eine benutzerdefinierte Abfrage-API für Ihren Product Store Provider-Service.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 Weiterverarbeitung herunterzusenden.Wie bei einer verzögerten Ausführung ist es eine
LINQ
Funktion, das Ausdrucksbaumschema im Speicher zu halten und es nur bei Bedarf in die Ausführung zu senden, wenn bestimmte APIs für die Sequenz aufgerufen werden (dieselbe Anzahl, ToList usw.).Die ordnungsgemäße Verwendung von beiden hängt stark von den Aufgaben ab, denen Sie für den jeweiligen Fall gegenüberstehen. Für das bekannte Repository-Muster entscheide ich mich persönlich für die Rückgabe
IList
,IEnumerable
dh über Listen (Indexer und dergleichen). Daher ist es mein Rat,IQueryable
nur innerhalb von Repositorys und IEnumerable irgendwo anders im Code zu verwenden. Nicht über die Bedenken hinsichtlich der Testbarkeit zu sprechen,IQueryable
die zusammenbrechen und das Prinzip der Trennung von Bedenken ruinieren. Wenn Sie einen Ausdruck aus Repositorys zurückgeben, können Verbraucher mit der Persistenzschicht spielen, wie sie es wünschen.Eine kleine Ergänzung zum Durcheinander :) (aus einer Diskussion in den Kommentaren)) Keines von ihnen ist ein Objekt im Speicher, da es sich nicht um echte Typen an sich handelt, sondern um Marker eines Typs - wenn Sie so tief gehen möchten. Aber es ist sinnvoll (und deshalb hat es sogar MSDN so ausgedrückt), IEnumerables als In-Memory-Sammlungen und IQueryables als Ausdrucksbäume zu betrachten. Der Punkt ist, dass die IQueryable-Schnittstelle die IEnumerable-Schnittstelle erbt, sodass die Ergebnisse dieser Abfrage aufgelistet werden können, wenn sie eine Abfrage darstellen. Durch die Aufzählung wird der einem IQueryable-Objekt zugeordnete Ausdrucksbaum ausgeführt. Tatsächlich können Sie also kein IEnumerable-Mitglied aufrufen, ohne das Objekt im Speicher zu haben. Es wird dort hineinkommen, wenn Sie es tun, wenn es nicht leer ist. IQueryables sind nur Abfragen, nicht die Daten.
quelle
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 entweder
IQueryable<>
oder definieren,IEnumerable<>
und Sie werden wissen, dass Sie den Typ nicht ändern.Wenn Sie mit einem beginnen
IQueryable<>
, möchten Sie es normalerweise so lange beibehalten,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. Wenn Sie beispielsweise nur 10 Ergebnisse verwendenTake(10)
möchten (Sie haben angerufen ), 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 verwenden.Ein zwingender Grund, den Typ von
IQueryable<>
in zu ändern ,IEnumerable<>
könnte sein, dass Sie eine Erweiterungsfunktion aufrufen, die die ImplementierungIQueryable<>
in Ihrem bestimmten Objekt entweder nicht oder ineffizient verarbeiten kann. In diesem Fall möchten Sie den Typ möglicherweise in konvertierenIEnumerable<>
(indem Sie ihn einer Variablen des Typs zuweisenIEnumerable<>
oderAsEnumerable
beispielsweise die Erweiterungsmethode verwenden), damit die von Ihnen aufgerufenen Erweiterungsfunktionen diejenigen in derEnumerable
Klasse anstelle derQueryable
Klasse sind.quelle
Es gibt einen Blog-Beitrag mit einem kurzen Beispiel für den Quellcode darüber, wie sich ein Missbrauch von
IEnumerable<T>
dramatisch auf die Leistung von LINQ-Abfragen auswirken kann: Entity Framework: IQueryable vs. IEnumerable .Wenn wir tiefer graben und in die Quellen schauen, können wir sehen, dass es offensichtlich verschiedene Erweiterungsmethoden gibt, für die
IEnumerable<T>
:und
IQueryable<T>
:Der erste gibt einen aufzählbaren Iterator zurück, und der zweite erstellt eine Abfrage über den in der
IQueryable
Quelle angegebenen Abfrageanbieter .quelle
Ich bin kürzlich auf ein Problem mit
IEnumerable
v gestoßenIQueryable
. Der verwendete Algorithmus führte zuerst eineIQueryable
Abfrage durch, um eine Reihe von Ergebnissen zu erhalten. Diese wurden dann an eineforeach
Schleife übergeben, wobei die Elemente als Entity Framework (EF) -Klasse instanziiert wurden. Diese EF-Klasse wurde dann in derfrom
Klausel einer Linq to Entity-Abfrage verwendet, wodurch das Ergebnis erhalten wurdeIEnumerable
.Ich bin ziemlich neu bei EF und Linq for Entities, daher 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ührungIEnumerable
dauerte 15 Sekunden und dieIQueryable
Ausführung 0,5 Sekunden. Es waren drei Tabellen beteiligt, und nachdem ich dies gelesen habe, glaube ich, dass dieIEnumerable
Abfrage tatsächlich ein Kreuzprodukt 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.
quelle
IQueryable
s bleiben auch im Speicher stecken, sobald Sie eine dieser APIs aufgerufen haben. Andernfalls können Sie den Ausdruck über den Stapel der Ebenen weitergeben und bis zum API-Aufruf mit den Filtern herumspielen. Gut gestaltetes DAL als gut gestaltetes Repository wird diese Art von Problemen lösen;)Ich möchte einige Dinge aufgrund scheinbar widersprüchlicher Antworten (hauptsächlich in Bezug auf IEnumerable) klarstellen.
(1)
IQueryable
erweitert dieIEnumerable
Schnittstelle. (Sie können eineIQueryable
an etwas senden , dasIEnumerable
ohne Fehler erwartet .)(2) Sowohl
IQueryable
als auchIEnumerable
LINQ versuchen, beim Durchlaufen der Ergebnismenge verzögert zu laden. (Beachten Sie, dass die Implementierung in Schnittstellenerweiterungsmethoden für jeden Typ angezeigt wird.)Mit anderen Worten,
IEnumerables
sind nicht ausschließlich "in-memory".IQueryables
werden nicht immer in der Datenbank ausgeführt.IEnumerable
muss Dinge in den Speicher laden (einmal abgerufen, möglicherweise träge), da es keinen abstrakten Datenprovider gibt.IQueryables
Verlassen Sie sich auf einen abstrakten Anbieter (wie LINQ-to-SQL), obwohl dies auch der In-Memory-Anbieter von .NET sein kann.Beispiel für einen Anwendungsfall
(a) Rufen Sie die Liste der Datensätze
IQueryable
aus dem EF-Kontext ab. (Es sind keine Datensätze im Speicher.)(b) Übergeben Sie das
IQueryable
an eine Ansicht, deren Modell istIEnumerable
. (Gültig.IQueryable
ErweitertIEnumerable
.)(c) Durchlaufen und über die Ansicht auf die Datensätze, untergeordneten Entitäten und Eigenschaften des Datensatzes zugreifen. (Kann Ausnahmen verursachen!)
Mögliche Probleme
(1) Die
IEnumerable
Versuche zum verzögerten Laden und Ihr Datenkontext ist abgelaufen. Ausnahme ausgelöst, da der Anbieter nicht mehr verfügbar ist.(2) Entity Framework-Entity-Proxys sind aktiviert (Standardeinstellung), und Sie versuchen, auf ein verwandtes (virtuelles) Objekt mit einem abgelaufenen Datenkontext zuzugreifen. Gleich wie (1).
(3) Mehrere aktive Ergebnissätze (MARS). Wenn Sie das
IEnumerable
in einemforeach( var record in resultSet )
Block durchlaufen und gleichzeitig versuchen, darauf zuzugreifenrecord.childEntity.childProperty
, kann es sein, dass Sie MARS erhalten, weil sowohl der Datensatz als auch die relationale Entität verzögert geladen werden. Dies führt zu einer Ausnahme, wenn sie in Ihrer Verbindungszeichenfolge nicht aktiviert ist.Lösung
Führen Sie die Abfrage aus und speichern Sie die Ergebnisse durch Aufrufen.
resultList = resultSet.ToList()
Dies scheint der einfachste Weg zu sein, um sicherzustellen, dass sich Ihre Entitäten im Speicher 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 Entitäts-Proxys und explizit
Include
verwandte Entitäten von Ihrem deaktivierenDbSet
.quelle
Der Hauptunterschied zwischen "IEnumerable" und "IQueryable" besteht darin, wo die Filterlogik ausgeführt wird. Einer wird auf der Clientseite (im Speicher) und der andere auf der Datenbank ausgeführt.
Zum Beispiel können wir 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 also 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 jedoch im selben Fall "IQueryable" verwenden, wird der IsActive-Filter direkt auf die Datenbank angewendet, der direkt von dort die 900 aktiven Benutzer zurückgibt.
Referenz Verbindung
quelle
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 vollständige Auswahlabfrage erstellt wird und nur die zugehörigen Datensätze abgerufen werden.
Zum Beispiel möchten wir die Top 10 Kunden nehmen, deren Name mit 'Nimal' beginnt. In diesem Fall wird die Auswahlabfrage als generiert
select top 10 * from Customer where name like ‘Nimal%’
.Wenn wir jedoch IEnumerable verwenden, sieht die Abfrage wie folgt aus
select * from Customer where name like ‘Nimal%’
und die Top Ten werden auf C # -Codierungsebene gefiltert (alle Kundendatensätze werden aus der Datenbank abgerufen und an C # übergeben).quelle
Zusätzlich zu den ersten 2 wirklich guten Antworten (von driis & von Jacob):
Das IEnumerable-Objekt stellt einen Datensatz im Speicher dar und kann diese Daten nur vorwärts weiterleiten. Die durch das IEnumerable-Objekt dargestellte Abfrage wird sofort und vollständig ausgeführt, sodass die Anwendung Daten schnell 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.
Das IQueryable-Objekt bietet Remotezugriff auf die Datenbank und ermöglicht Ihnen 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. Infolgedessen wird während der Ausführung weniger Speicher verbraucht, weniger Netzwerkbandbreite, aber gleichzeitig kann es etwas langsamer verarbeitet werden als eine Abfrage, die ein IEnumerable-Objekt zurückgibt.
Was auszusuchen?
Wenn Sie den gesamten Satz zurückgegebener Daten benötigen, ist es besser, IEnumerable zu verwenden, das die maximale Geschwindigkeit bietet.
Wenn Sie NICHT den gesamten Satz zurückgegebener Daten benötigen, sondern nur einige gefilterte Daten, ist es besser, IQueryable zu verwenden.
quelle
Darüber hinaus ist es interessant festzustellen, dass Sie Ausnahmen erhalten können, wenn Sie
IQueryable
anstelle vonIEnumerable
:Folgendes funktioniert einwandfrei, wenn
products
es sich um ein handeltIEnumerable
:Wenn
products
es sich jedoch um eine handeltIQueryable
und versucht wird, auf Datensätze aus einer DB-Tabelle zuzugreifen, wird folgende Fehlermeldung angezeigt:Dies liegt daran, dass die folgende Abfrage erstellt wurde:
und OFFSET können keinen negativen Wert haben.
quelle