Verwenden von IQueryable mit Linq

256

Was nützt es IQueryableim Kontext von LINQ?

Wird es zur Entwicklung von Erweiterungsmethoden oder für andere Zwecke verwendet?

user190560
quelle

Antworten:

507

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 Productsund 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 ....

Reed Copsey
quelle
9
Wo ist die Definition von "GetQueryableProducts ();"?
Pankaj
12
@StackOverflowUser Es ist beabsichtigt, eine Methode zu sein, die eine IQueryable<Product>- spezifisch für Ihr ORM oder Repository usw.
Reed Copsey
richtig. Sie haben jedoch die where-Klausel nach diesem Funktionsaufruf erwähnt. Das System kennt den Filter also immer noch nicht. Ich meine, es werden immer noch alle Aufzeichnungen von Produkten abgerufen. richtig?
Pankaj
@StackOverflowUser Nein - das ist das Schöne an IQueryable <T> - es kann so eingerichtet werden, dass es ausgewertet wird, wann Sie die Ergebnisse erhalten. Dies bedeutet, dass die nachträglich verwendete Where-Klausel weiterhin in eine auf dem Server ausgeführte SQL-Anweisung übersetzt wird Ziehen Sie nur die erforderlichen Elemente über den Draht ...
Reed Copsey
40
@Testing Du gehst eigentlich immer noch nicht in die DB. Bis Sie die Ergebnisse tatsächlich aufzählen (dh: a verwenden foreachoder aufrufen ToList()), treffen Sie die Datenbank nicht wirklich.
Reed Copsey
188

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 ExpressionBä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, Expressionund 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 QueryableAusdrucksbä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.

Marc Gravell
quelle
2
Wenn es Ihnen nichts ausmacht, können Sie mich mit einem einfachen verständlichen Beispiel aktualisieren (wenn Sie Zeit haben).
user190560
Können Sie erklären, wo die Definition von GetQueryableProducts () ist? "?" in der Antwort von Herrn Reed Copsey
Pankaj
Genossen die Linie der Übersetzung von Ausdrücken in eine Abfrage ist "schwarze Kunst an sich" ... eine Menge Wahrheit dazu
afreeland
Warum wäre nichts davon möglich, wenn Sie einen Delegierten eingesetzt hätten?
David Klempfner
1
@Backwards_Dave, da ein Delegat (im Wesentlichen) auf IL verweist und IL nicht aussagekräftig genug ist, um zu versuchen, die Absicht zum Erstellen von SQL ausreichend zu dekonstruieren. IL erlaubt auch zu viele Dinge - dh die meisten Dinge, die in IL ausgedrückt werden können, konnten nicht in der eingeschränkten Syntax ausgedrückt werden, dass es vernünftig ist, sich in Dinge wie SQL zu verwandeln
Marc Gravell
17

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 IQueryableund IEnumerableebenso 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 Employeeist 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> GetEmployeeAndPersonDetailIQueryableals auch IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerableof SomeServiceClasseine gemeinsame Zeile in linq-Abfragen enthält

where employeesToCollect.Contains(e.PersonId)

Als warum das AND (N'M' = [Extent1].[Gender])Teil in fehlt IEnumerableAusführungsteil, während in Funktion wir verwendeten Aufruf Where(i => i.Gender == "M") inprogram.cs`

Jetzt sind wir an dem Punkt angelangt, an dem der Unterschied zwischen IQueryableund kam IEnumerable

Was Entity Framwork tut, wenn eine IQueryableMethode 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:

  • Nur die vom SQL Server aufgefüllten Zeilen, die für die gesamte Ausführung der Linq-Abfrage gültig sein könnten
  • Verbessert die Leistung von SQL Servern, indem keine unnötigen Zeilen ausgewählt werden
  • Netzwerkkosten werden reduziert

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.

Moumit
quelle
2

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.

Taube
quelle