Was nützt es IQueryable
im Kontext von LINQ?
Wird es zur Entwicklung von Erweiterungsmethoden oder für andere Zwecke verwendet?
Was nützt es IQueryable
im Kontext von LINQ?
Wird es zur Entwicklung von Erweiterungsmethoden oder für andere Zwecke verwendet?
Antworten:
Die Antwort von Marc Gravell ist sehr vollständig, aber ich dachte, ich würde auch aus Sicht des Benutzers etwas dazu hinzufügen ...
Der Hauptunterschied aus der Sicht eines Benutzers besteht darin, dass, wenn Sie verwenden IQueryable<T>
(mit einem Anbieter, der die Dinge richtig unterstützt) viele Ressourcen sparen können.
Wenn Sie beispielsweise mit vielen ORM-Systemen gegen eine entfernte Datenbank arbeiten, haben Sie die Möglichkeit, Daten auf zwei Arten aus einer Tabelle abzurufen: eine, die eine zurückgibt IEnumerable<T>
, und eine, die eine zurückgibt IQueryable<T>
. Angenommen, Sie haben eine Produkttabelle und möchten alle Produkte erhalten, deren Kosten> 25 USD betragen.
Wenn Sie tun:
IEnumerable<Product> products = myORM.GetProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
Was hier passiert, ist, dass die Datenbank alle Produkte lädt und sie über die Leitung an Ihr Programm weiterleitet. Ihr Programm filtert dann die Daten. Im Wesentlichen führt die Datenbank ein SELECT * FROM Products
und gibt JEDES Produkt an Sie zurück.
Mit dem richtigen IQueryable<T>
Anbieter können Sie dagegen Folgendes tun:
IQueryable<Product> products = myORM.GetQueryableProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
Der Code sieht gleich aus, aber der Unterschied besteht darin, dass die ausgeführte SQL ausgeführt wird SELECT * FROM Products WHERE Cost >= 25
.
Aus Ihrer Sicht als Entwickler sieht dies genauso aus. Unter Leistungsgesichtspunkten können Sie jedoch nur 2 Datensätze über das Netzwerk zurückgeben, anstatt 20.000 ....
IQueryable<Product>
- spezifisch für Ihr ORM oder Repository usw.
foreach
oder aufrufen ToList()
), treffen Sie die Datenbank nicht wirklich.
Im Wesentlichen ist seine Aufgabe sehr ähnlich IEnumerable<T>
- um eine abfragbare Datenquelle darzustellen - mit dem Unterschied, dass die verschiedenen LINQ-Methoden (on Queryable
) spezifischer sein können, um die Abfrage mithilfe von Expression
Bäumen anstatt mit Delegaten zu erstellen (was auch immer der Fall ist)Enumerable
verwendet wird).
Die Ausdrucksbäume können von Ihrem ausgewählten LINQ-Anbieter überprüft und in eine tatsächliche Abfrage umgewandelt werden - obwohl dies an sich schon eine schwarze Kunst ist.
Dies liegt wirklich am ElementType
, Expression
und Provider
- aber in Wirklichkeit müssen Sie sich als Benutzer selten darum kümmern . Nur ein LINQ- Implementierer muss die wichtigsten Details kennen.
Re Kommentare; Ich bin mir nicht ganz sicher, was Sie als Beispiel wollen, aber betrachten Sie LINQ-to-SQL; Das zentrale Objekt hier ist a DataContext
, das unseren Datenbank-Wrapper darstellt. Dies hat normalerweise eine Eigenschaft pro Tabelle (zum Beispiel Customers
) und eine Tabelle implementiert IQueryable<Customer>
. Aber wir verwenden nicht so viel direkt; Erwägen:
using(var ctx = new MyDataContext()) {
var qry = from cust in ctx.Customers
where cust.Region == "North"
select new { cust.Id, cust.Name };
foreach(var row in qry) {
Console.WriteLine("{0}: {1}", row.Id, row.Name);
}
}
Dies wird (vom C # -Compiler):
var qry = ctx.Customers.Where(cust => cust.Region == "North")
.Select(cust => new { cust.Id, cust.Name });
was wiederum (vom C # -Compiler) wie folgt interpretiert wird:
var qry = Queryable.Select(
Queryable.Where(
ctx.Customers,
cust => cust.Region == "North"),
cust => new { cust.Id, cust.Name });
Wichtig ist, dass die statischen Methoden für Queryable
Ausdrucksbäume verwendet werden, die anstelle der regulären IL zu einem Objektmodell kompiliert werden. Wenn wir zum Beispiel nur das "Wo" betrachten, erhalten wir etwas Vergleichbares zu:
var cust = Expression.Parameter(typeof(Customer), "cust");
var lambda = Expression.Lambda<Func<Customer,bool>>(
Expression.Equal(
Expression.Property(cust, "Region"),
Expression.Constant("North")
), cust);
... Queryable.Where(ctx.Customers, lambda) ...
Hat der Compiler nicht viel für uns getan? Dieses Objektmodell kann auseinandergerissen, auf seine Bedeutung überprüft und vom TSQL-Generator wieder zusammengesetzt werden.
SELECT c.Id, c.Name
FROM [dbo].[Customer] c
WHERE c.Region = 'North'
(Die Zeichenfolge könnte als Parameter enden; ich kann mich nicht erinnern)
Nichts davon wäre möglich, wenn wir nur einen Delegierten eingesetzt hätten. Und dies ist der Punkt von Queryable
/ IQueryable<T>
: Es bietet den Einstiegspunkt für die Verwendung von Ausdrucksbäumen.
All dies ist sehr komplex, daher ist es eine gute Aufgabe, dass der Compiler es uns schön und einfach macht.
Weitere Informationen finden Sie unter " C # in Depth " oder " LINQ in Action ". Beide enthalten Informationen zu diesen Themen.
Obwohl Reed Copsey und Marc Gravell bereits genug IQueryable
(und auch IEnumerable
) genug beschrieben haben, möchte ich hier etwas mehr hinzufügen, indem ich ein kleines Beispiel dazu gebe IQueryable
und IEnumerable
ebenso viele Benutzer danach fragten
Beispiel : Ich habe zwei Tabellen in der Datenbank erstellt
CREATE TABLE [dbo].[Employee]([PersonId] [int] NOT NULL PRIMARY KEY,[Gender] [nchar](1) NOT NULL)
CREATE TABLE [dbo].[Person]([PersonId] [int] NOT NULL PRIMARY KEY,[FirstName] [nvarchar](50) NOT NULL,[LastName] [nvarchar](50) NOT NULL)
Der Primärschlüssel (PersonId
) der Tabelle Employee
ist auch ein Forgein-Schlüssel ( personid
) der TabellePerson
Als nächstes habe ich ado.net Entity Model in meine Anwendung eingefügt und unten eine Serviceklasse erstellt
public class SomeServiceClass
{
public IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable(IEnumerable<int> employeesToCollect)
{
DemoIQueryableEntities db = new DemoIQueryableEntities();
var allDetails = from Employee e in db.Employees
join Person p in db.People on e.PersonId equals p.PersonId
where employeesToCollect.Contains(e.PersonId)
select e;
return allDetails;
}
public IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable(IEnumerable<int> employeesToCollect)
{
DemoIQueryableEntities db = new DemoIQueryableEntities();
var allDetails = from Employee e in db.Employees
join Person p in db.People on e.PersonId equals p.PersonId
where employeesToCollect.Contains(e.PersonId)
select e;
return allDetails;
}
}
sie enthalten die gleiche linq. Es rief anprogram.cs
wie unten definiert
class Program
{
static void Main(string[] args)
{
SomeServiceClass s= new SomeServiceClass();
var employeesToCollect= new []{0,1,2,3};
//IQueryable execution part
var IQueryableList = s.GetEmployeeAndPersonDetailIQueryable(employeesToCollect).Where(i => i.Gender=="M");
foreach (var emp in IQueryableList)
{
System.Console.WriteLine("ID:{0}, EName:{1},Gender:{2}", emp.PersonId, emp.Person.FirstName, emp.Gender);
}
System.Console.WriteLine("IQueryable contain {0} row in result set", IQueryableList.Count());
//IEnumerable execution part
var IEnumerableList = s.GetEmployeeAndPersonDetailIEnumerable(employeesToCollect).Where(i => i.Gender == "M");
foreach (var emp in IEnumerableList)
{
System.Console.WriteLine("ID:{0}, EName:{1},Gender:{2}", emp.PersonId, emp.Person.FirstName, emp.Gender);
}
System.Console.WriteLine("IEnumerable contain {0} row in result set", IEnumerableList.Count());
Console.ReadKey();
}
}
Die Ausgabe ist offensichtlich für beide gleich
ID:1, EName:Ken,Gender:M
ID:3, EName:Roberto,Gender:M
IQueryable contain 2 row in result set
ID:1, EName:Ken,Gender:M
ID:3, EName:Roberto,Gender:M
IEnumerable contain 2 row in result set
Die Frage ist also, was / wo ist der Unterschied? Es scheint keinen Unterschied zu geben, oder? Ja wirklich!!
Werfen wir einen Blick auf SQL-Abfragen, die in diesem Zeitraum von Entity Framwork 5 generiert und ausgeführt wurden
IQueryable Ausführungsteil
--IQueryableQuery1
SELECT
[Extent1].[PersonId] AS [PersonId],
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE ([Extent1].[PersonId] IN (0,1,2,3)) AND (N'M' = [Extent1].[Gender])
--IQueryableQuery2
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM [dbo].[Employee] AS [Extent1]
WHERE ([Extent1].[PersonId] IN (0,1,2,3)) AND (N'M' = [Extent1].[Gender])
) AS [GroupBy1]
IEnumerable Ausführungsteil
--IEnumerableQuery1
SELECT
[Extent1].[PersonId] AS [PersonId],
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE [Extent1].[PersonId] IN (0,1,2,3)
--IEnumerableQuery2
SELECT
[Extent1].[PersonId] AS [PersonId],
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE [Extent1].[PersonId] IN (0,1,2,3)
Gemeinsames Skript für beide Ausführungsteile
/* these two query will execute for both IQueryable or IEnumerable to get details from Person table
Ignore these two queries here because it has nothing to do with IQueryable vs IEnumerable
--ICommonQuery1
exec sp_executesql N'SELECT
[Extent1].[PersonId] AS [PersonId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName]
FROM [dbo].[Person] AS [Extent1]
WHERE [Extent1].[PersonId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1
--ICommonQuery2
exec sp_executesql N'SELECT
[Extent1].[PersonId] AS [PersonId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName]
FROM [dbo].[Person] AS [Extent1]
WHERE [Extent1].[PersonId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=3
*/
Sie haben jetzt einige Fragen, lassen Sie mich diese erraten und versuchen, sie zu beantworten
Warum werden unterschiedliche Skripte für dasselbe Ergebnis generiert?
Lassen Sie uns hier einige Punkte herausfinden,
Alle Abfragen haben einen gemeinsamen Teil
WHERE [Extent1].[PersonId] IN (0,1,2,3)
Warum? Weil sowohl function IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable
als auch
IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable
of SomeServiceClass
eine gemeinsame Zeile in linq-Abfragen enthält
where employeesToCollect.Contains(e.PersonId)
Als warum das
AND (N'M' = [Extent1].[Gender])
Teil in fehlt IEnumerable
Ausführungsteil, während in Funktion wir verwendeten Aufruf Where(i => i.Gender == "M") in
program.cs`
Jetzt sind wir an dem Punkt angelangt, an dem der Unterschied zwischen
IQueryable
und kamIEnumerable
Was Entity Framwork tut, wenn eine IQueryable
Methode aufgerufen wird, nimmt sie eine in die Methode geschriebene linq-Anweisung und versucht herauszufinden, ob mehr linq-Ausdrücke in der Ergebnismenge definiert sind. Anschließend werden alle definierten linq-Abfragen erfasst, bis das Ergebnis abgerufen werden muss, und es wird eine geeignetere SQL erstellt Abfrage auszuführen.
Es bietet viele Vorteile wie:
wie hier im Beispiel SQL Server kehrte nur zwei Zeilen nach der Ausführung von IQueryable zur Anwendung zurück, wurde aber zurückgegeben drei Zeilen für IEnumerable Abfrage warum?
Im Falle von IEnumerable
Methode hat das Entity Framework die in die Methode geschriebene linq-Anweisung verwendet und eine SQL-Abfrage erstellt, wenn das Ergebnis abgerufen werden muss. Der Rest-Linq-Teil zum Erstellen der SQL-Abfrage ist nicht enthalten. Wie hier wird im SQL Server in der Spalte keine Filterung durchgeführt gender
.
Aber die Ausgänge sind gleich? Weil 'IEnumerable das Ergebnis auf Anwendungsebene weiter filtert, nachdem das Ergebnis vom SQL Server abgerufen wurde
Also, was sollte jemand wählen? Ich persönlich bevorzuge es, das Funktionsergebnis so zu definieren, IQueryable<T>
dass es viele Vorteile bietetIEnumerable
wie Sie zwei oder mehr IQueryable Funktionen kommen könnte, die auf SQL Server spezifischere Skript zu generieren.
Hier im Beispiel können Sie sehen IQueryable Query(IQueryableQuery2)
, dass ein spezifischeres Skript generiert wird, als IEnumerable query(IEnumerableQuery2)
es aus meiner Sicht viel akzeptabler ist.
Es ermöglicht weitere Abfragen in der Folge. Wenn dies über eine Servicegrenze hinausgehen würde, könnte der Benutzer dieses IQueryable-Objekts mehr damit tun.
Wenn Sie beispielsweise das verzögerte Laden mit nhibernate verwenden, kann dies dazu führen, dass das Diagramm bei Bedarf geladen wird.