A cosa serve IQueryable
nel contesto di LINQ?
È utilizzato per lo sviluppo di metodi di estensione o altri scopi?
A cosa serve IQueryable
nel contesto di LINQ?
È utilizzato per lo sviluppo di metodi di estensione o altri scopi?
Risposte:
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 Products
e 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 ....
IQueryable<Product>
- sarebbe specifico per il tuo ORM o repository, ecc.
foreach
o call ToList()
), non si raggiunge effettivamente il DB.
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 Expression
alberi invece che delegati (che è ciò che Enumerable
usa).
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
, Expression
e 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 Queryable
alberi 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.
Sebbene Reed Copsey e Marc Gravell abbiano già descritto IQueryable
(e anche IEnumerable
) abbastanza, voglio aggiungere altro qui fornendo un piccolo esempio IQueryable
e IEnumerable
quanti 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.cs
come 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> GetEmployeeAndPersonDetailIQueryable
che
IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable
of SomeServiceClass
contengono una riga comune nelle query linq
where employeesToCollect.Contains(e.PersonId)
Per questo motivo la
AND (N'M' = [Extent1].[Gender])
parte manca nella IEnumerable
parte di esecuzione, mentre in entrambe le funzioni abbiamo usato Where(i => i.Gender == "M") in
program.cs`
Ora siamo nel punto in cui si è verificata la differenza tra
IQueryable
eIEnumerable
Cosa fa il framwork di entità quando IQueryable
viene 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
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 IEnumerable
metodo, 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 IEnumerable
come, 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.
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.