Utilizzo di IQueryable con Linq


257

A cosa serve IQueryablenel contesto di LINQ?

È utilizzato per lo sviluppo di metodi di estensione o altri scopi?

Risposte:


508

La risposta di Marc Gravell è molto completa, ma ho pensato di aggiungere qualcosa al riguardo anche dal punto di vista dell'utente ...


La differenza principale, dal punto di vista dell'utente, è che quando si utilizza IQueryable<T>(con un provider che supporta correttamente le cose), è possibile risparmiare molte risorse.

Ad esempio, se si lavora su un database remoto, con molti sistemi ORM, è possibile recuperare i dati da una tabella in due modi, uno che restituisce IEnumerable<T>e uno che restituisce un IQueryable<T>. Supponiamo, ad esempio, che tu abbia una tabella Prodotti e desideri ottenere tutti i prodotti il ​​cui costo è> $ 25.

Se fate:

 IEnumerable<Product> products = myORM.GetProducts();
 var productsOver25 = products.Where(p => p.Cost >= 25.00);

Quello che succede qui è che il database carica tutti i prodotti e li passa attraverso il cavo al programma. Il programma filtra quindi i dati. In sostanza, il database fa un SELECT * FROM Productse restituisce OGNI prodotto.

Con il IQueryable<T>fornitore giusto , d'altra parte, puoi fare:

 IQueryable<Product> products = myORM.GetQueryableProducts();
 var productsOver25 = products.Where(p => p.Cost >= 25.00);

Il codice ha lo stesso aspetto, ma la differenza è che lo SQL eseguito sarà SELECT * FROM Products WHERE Cost >= 25.

Dal tuo POV come sviluppatore, questo sembra lo stesso. Tuttavia, dal punto di vista delle prestazioni, è possibile restituire solo 2 record attraverso la rete anziché 20.000 ....


9
Dov'è la definizione di "GetQueryableProducts ();"?
Pankaj,

12
@StackOverflowUser È destinato a essere qualsiasi metodo che restituisce un IQueryable<Product>- sarebbe specifico per il tuo ORM o repository, ecc.
Reed Copsey

giusto. ma hai menzionato la clausola where dopo questa chiamata di funzione. Quindi il sistema non è ancora a conoscenza del filtro. Voglio dire, continua a recuperare tutti i record di prodotti. giusto?
Pankaj,

@StackOverflowUser No - questa è la bellezza di IQueryable <T> - può essere configurato per valutare quando si ottengono i risultati - il che significa che la clausola Where, utilizzata dopo il fatto, verrà comunque tradotta in un'istruzione SQL eseguita sul server e tira solo gli elementi richiesti attraverso il filo ...
Reed Copsey

40
@Testing In realtà non stai ancora andando al DB. Fino a quando non vengono effettivamente enumerati i risultati (ovvero: utilizzare a foreacho call ToList()), non si raggiunge effettivamente il DB.
Reed Copsey,

188

In sostanza, il suo lavoro è molto simile a IEnumerable<T>- rappresentare un'origine dati interrogabile - la differenza è che i vari metodi LINQ (on Queryable) possono essere più specifici, per costruire la query usando Expressionalberi invece che delegati (che è ciò che Enumerableusa).

Gli alberi delle espressioni possono essere controllati dal provider LINQ scelto e trasformati in una query effettiva , sebbene si tratti di un'arte nera in sé.

Questo è davvero dovuto al ElementType, Expressione Provider- ma in realtà raramente devi preoccuparti di questo come utente . Solo un implementatore LINQ deve conoscere i dettagli cruenti.


Ri commenti; Non sono del tutto sicuro di ciò che vuoi a titolo di esempio, ma considera LINQ-to-SQL; l'oggetto centrale qui è a DataContext, che rappresenta il nostro wrapper di database. In genere ha una proprietà per tabella (ad esempio, Customers) e una tabella implementa IQueryable<Customer>. Ma non usiamo così tanto direttamente; tener conto di:

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);
    }
}

questo diventa (dal compilatore C #):

var qry = ctx.Customers.Where(cust => cust.Region == "North")
                .Select(cust => new { cust.Id, cust.Name });

che viene nuovamente interpretato (dal compilatore C #) come:

var qry = Queryable.Select(
              Queryable.Where(
                  ctx.Customers,
                  cust => cust.Region == "North"),
              cust => new { cust.Id, cust.Name });

È importante sottolineare che i metodi statici sugli Queryablealberi delle espressioni prendono, che - anziché il normale IL, vengono compilati in un modello a oggetti. Ad esempio, solo guardando il "Dove", questo ci dà qualcosa di paragonabile a:

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

Il compilatore non ha fatto molto per noi? Questo modello a oggetti può essere distrutto, ispezionato per capire cosa significhi e rimetterlo insieme dal generatore TSQL, dando qualcosa del tipo:

 SELECT c.Id, c.Name
 FROM [dbo].[Customer] c
 WHERE c.Region = 'North'

(la stringa potrebbe finire come parametro; non ricordo)

Niente di tutto ciò sarebbe possibile se avessimo appena usato un delegato. E questo è il punto di Queryable/ IQueryable<T>: fornisce il punto di ingresso per l'utilizzo degli alberi delle espressioni.

Tutto questo è molto complesso, quindi è un buon lavoro che il compilatore lo renda piacevole e facile per noi.

Per ulteriori informazioni, guarda " C # in profondità " o " LINQ in azione ", entrambi i quali forniscono una copertura di questi argomenti.


2
Se non ti dispiace puoi aggiornarmi con un semplice esempio comprensibile (se hai tempo).
user190560,

Puoi spiegare "Dov'è la definizione di" GetQueryableProducts (); "?" nella risposta dell'onorevole Reed Copsey
Pankaj,

Godersi la linea di tradurre espressioni in una query è "l'arte nera in sé" ... molta verità in questo
afreeland

Perché nulla di tutto ciò sarebbe possibile se tu avessi usato un delegato?
David Klempfner,

1
@Backwards_Dave perché un delegato punta (essenzialmente) a IL e IL non è sufficientemente espressivo per rendere ragionevole tentare di decostruire sufficientemente l'intento per costruire SQL. IL permette anche troppe cose - cioè la maggior parte delle cose che possono essere espresse in IL non potrebbero essere espresse nella sintassi limitata che è ragionevole trasformare in cose come SQL
Marc Gravell

17

Sebbene Reed Copsey e Marc Gravell abbiano già descritto IQueryable(e anche IEnumerable) abbastanza, voglio aggiungere altro qui fornendo un piccolo esempio IQueryablee IEnumerablequanti utenti lo hanno chiesto

Esempio : ho creato due tabelle nel database

   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)

La chiave primaria ( PersonId) della tabella Employeeè anche una chiave forgein ( personid) della tabellaPerson

Successivamente ho aggiunto il modello di entità ado.net nella mia applicazione e ho creato sotto la classe di servizio

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;
    }
}

contengono lo stesso linq. Ha chiamato program.cscome definito di seguito

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();
    }
}

L'output è lo stesso per entrambi ovviamente

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

Quindi la domanda è: qual è / dove è la differenza? Non sembra avere alcuna differenza, giusto? Veramente!!

Diamo un'occhiata alle query sql generate ed eseguite da framwork di entità 5 durante questo periodo

Parte di esecuzione IQueryable

--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]

Parte di esecuzione IEnumerable

--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)

Script comune per entrambe le parti dell'esecuzione

/* 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
*/

Quindi hai qualche domanda ora, fammi indovinare e provare a rispondere

Perché vengono generati script diversi per lo stesso risultato?

Scopriamo alcuni punti qui,

tutte le query hanno una parte comune

WHERE [Extent1].[PersonId] IN (0,1,2,3)

perché? Perché sia ​​la funzione IQueryable<Employee> GetEmployeeAndPersonDetailIQueryableche IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerableof SomeServiceClasscontengono una riga comune nelle query linq

where employeesToCollect.Contains(e.PersonId)

Per questo motivo la AND (N'M' = [Extent1].[Gender])parte manca nella IEnumerableparte di esecuzione, mentre in entrambe le funzioni abbiamo usato Where(i => i.Gender == "M") inprogram.cs`

Ora siamo nel punto in cui si è verificata la differenza tra IQueryablee IEnumerable

Cosa fa il framwork di entità quando IQueryableviene chiamato un metodo, prende l'istruzione linq scritta all'interno del metodo e prova a scoprire se sul set di risultati sono definite più espressioni linq, quindi raccoglie tutte le query linq definite fino a quando il risultato non deve recuperare e costruisce sql più appropriato query da eseguire.

Fornisce molti vantaggi come

  • solo quelle righe popolate dal server sql che potrebbero essere valide per l'intera esecuzione della query linq
  • aiuta le prestazioni del server sql non selezionando le righe non necessarie
  • i costi di rete vengono ridotti

come qui nell'esempio sql server è tornato all'applicazione solo due righe dopo l'esecuzione di IQueryable` ma ha restituito TRE righe per la query IEnumerable perché?

Nel caso del IEnumerablemetodo, il framework delle entità ha preso l'istruzione linq scritta all'interno del metodo e costruisce la query sql quando il risultato deve essere recuperato. non include la parte linq restante per costruire la query sql. Come qui, nessun filtro viene eseguito nel server SQL sulla colonna gender.

Ma le uscite sono uguali? Perché 'IEnumerable filtra ulteriormente il risultato a livello di applicazione dopo aver recuperato il risultato dal server sql

Quindi, cosa dovrebbe scegliere qualcuno? Personalmente preferisco definire il risultato della funzione in IQueryable<T>quanto ci sono molti vantaggi rispetto a IEnumerablecome, potresti unire due o più funzioni IQueryable, che generano script più specifici sul server sql.

Qui nell'esempio puoi vedere un IQueryable Query(IQueryableQuery2)genera uno script più specifico di quello IEnumerable query(IEnumerableQuery2)che è molto più accettabile dal mio punto di vista.


2

Consente ulteriori interrogazioni più in basso. Se ciò andasse oltre un limite di servizio, l'utente di questo oggetto IQueryable sarebbe autorizzato a fare di più con esso.

Ad esempio, se si utilizzava il caricamento lento con nhibernate, ciò potrebbe comportare il caricamento del grafico quando / se necessario.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.