À quoi sert IQueryable
dans le contexte de LINQ?
Est-il utilisé pour développer des méthodes d'extension ou à toute autre fin?
À quoi sert IQueryable
dans le contexte de LINQ?
Est-il utilisé pour développer des méthodes d'extension ou à toute autre fin?
Réponses:
La réponse de Marc Gravell est très complète, mais j'ai pensé ajouter quelque chose à ce sujet du point de vue de l'utilisateur également ...
La principale différence, du point de vue d'un utilisateur, est que, lorsque vous utilisez IQueryable<T>
(avec un fournisseur qui prend correctement en charge les choses), vous pouvez économiser beaucoup de ressources.
Par exemple, si vous travaillez sur une base de données distante, avec de nombreux systèmes ORM, vous avez la possibilité de récupérer les données d'une table de deux manières, l'une qui renvoie IEnumerable<T>
et l'autre qui renvoie un IQueryable<T>
. Supposons, par exemple, que vous ayez une table Produits et que vous souhaitiez obtenir tous les produits dont le coût est> 25 $.
Si tu fais:
IEnumerable<Product> products = myORM.GetProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
Ce qui se passe ici, c'est que la base de données charge tous les produits et les transmet à votre programme. Votre programme filtre ensuite les données. En substance, la base de données fait un SELECT * FROM Products
et vous renvoie TOUS les produits.
En IQueryable<T>
revanche, avec le bon fournisseur, vous pouvez faire:
IQueryable<Product> products = myORM.GetQueryableProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
Le code a la même apparence, mais la différence ici est que le SQL exécuté le sera SELECT * FROM Products WHERE Cost >= 25
.
De votre POV en tant que développeur, cela se ressemble. Cependant, du point de vue des performances, vous ne pouvez renvoyer que 2 enregistrements sur le réseau au lieu de 20 000 ....
IQueryable<Product>
- serait spécifique à votre ORM ou référentiel, etc.
foreach
ou appelez ToList()
), vous ne frappez pas réellement la base de données.
En substance, son travail est très similaire à IEnumerable<T>
- pour représenter une source de données interrogeable - la différence étant que les différentes méthodes LINQ (on Queryable
) peuvent être plus spécifiques, pour construire la requête en utilisant des Expression
arbres plutôt que des délégués (ce qui est ce qui Enumerable
utilise).
Les arborescences d'expression peuvent être inspectées par le fournisseur LINQ de votre choix et transformées en une requête réelle - bien qu'il s'agisse d'un art noir en soi.
C'est vraiment dû au ElementType
,Expression
et Provider
- mais en réalité, vous avez rarement besoin de vous en soucier en tant qu'utilisateur . Seul un implémenteur LINQ a besoin de connaître les détails sanglants.
Re commentaires; Je ne sais pas trop ce que vous voulez à titre d'exemple, mais considérez LINQ-to-SQL; l'objet central ici est un DataContext
, qui représente notre enveloppe de base de données. Cela a généralement une propriété par table (par exemple, Customers
) et une table implémente IQueryable<Customer>
. Mais nous n'utilisons pas beaucoup cela directement; considérer:
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);
}
}
cela devient (par le compilateur C #):
var qry = ctx.Customers.Where(cust => cust.Region == "North")
.Select(cust => new { cust.Id, cust.Name });
qui est à nouveau interprété (par le compilateur C #) comme:
var qry = Queryable.Select(
Queryable.Where(
ctx.Customers,
cust => cust.Region == "North"),
cust => new { cust.Id, cust.Name });
Il est important de noter que les méthodes statiques sur les Queryable
arborescences d'expression take, qui - plutôt que l'IL normal, sont compilées en un modèle objet. Par exemple - en regardant simplement le "Où", cela nous donne quelque chose de comparable à:
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) ...
Le compilateur n'a-t-il pas fait beaucoup pour nous? Ce modèle d'objet peut être déchiré, inspecté pour ce qu'il signifie et reconstitué par le générateur TSQL - donnant quelque chose comme:
SELECT c.Id, c.Name
FROM [dbo].[Customer] c
WHERE c.Region = 'North'
(la chaîne peut finir en paramètre; je ne me souviens pas)
Rien de tout cela ne serait possible si nous venions d'utiliser un délégué. Et c'est le point de / : il fournit le point d'entrée pour l' utilisation des arbres d'expression.Queryable
IQueryable<T>
Tout cela est très complexe, c'est donc un bon travail que le compilateur le rend agréable et facile pour nous.
Pour plus d'informations, consultez « C # en profondeur » ou « LINQ en action », qui offrent tous deux une couverture de ces sujets.
Bien que Reed Copsey et Marc Gravell aient déjà décrit à propos IQueryable
(et aussi IEnumerable
) assez, mJ veux ajouter un peu plus ici en fournissant un petit exemple sur IQueryable
et IEnumerable
autant d'utilisateurs l'ont demandé
Exemple : j'ai créé deux tables dans la base de données
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 clé primaire ( PersonId
) de la table Employee
est également une clé forgein ( personid
) de la tablePerson
Ensuite, j'ai ajouté le modèle d'entité ado.net dans mon application et créé ci-dessous la classe de service
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;
}
}
ils contiennent le même linq. Il a appelé program.cs
comme défini ci-dessous
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();
}
}
La sortie est la même pour les deux évidemment
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
La question est donc quelle est / où est la différence? Il ne semble pas y avoir de différence non? Vraiment!!
Jetons un coup d'œil aux requêtes SQL générées et exécutées par l'entité framwork 5 au cours de cette période
Partie d'exécution 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]
Partie d'exécution 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 commun pour les deux parties d'exécution
/* 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
*/
Vous avez donc quelques questions maintenant, laissez-moi deviner celles-ci et essayez d'y répondre
Pourquoi différents scripts sont-ils générés pour le même résultat?
Permet de découvrir quelques points ici,
toutes les requêtes ont une partie commune
WHERE [Extent1].[PersonId] IN (0,1,2,3)
Pourquoi? Parce que la fonction IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable
et
IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable
de SomeServiceClass
contient une ligne commune dans les requêtes linq
where employeesToCollect.Contains(e.PersonId)
Que pourquoi la
AND (N'M' = [Extent1].[Gender])
partie est manquante dans la IEnumerable
partie d'exécution, alors que dans les deux fonctions, nous avons utilisé Where(i => i.Gender == "M") in
program.cs`
Maintenant, nous sommes au point où la différence est venue entre
IQueryable
etIEnumerable
Quel encadrement d'entité fait lorsqu'un IQueryable
méthode est appelée, elle prend une instruction linq écrite à l'intérieur de la méthode et essaie de savoir si plus d'expressions linq sont définies sur l'ensemble de résultats, elle rassemble ensuite toutes les requêtes linq définies jusqu'à ce que le résultat doive être récupéré et construit un sql plus approprié requête à exécuter.
Il offre de nombreux avantages comme,
comme ici dans l'exemple, le serveur SQL n'a renvoyé à l'application que deux lignes après l'exécution de IQueryable` mais a renvoyé TROIS lignes pour la requête IEnumerable pourquoi?
En cas de IEnumerable
méthode, le framework d'entité a pris l'instruction linq écrite à l'intérieur de la méthode et construit la requête sql lorsque le résultat doit être récupéré. il n'inclut pas la partie rest linq pour construire la requête sql. Comme ici, aucun filtrage n'est effectué dans le serveur SQL sur la colonne gender
.
Mais les sorties sont les mêmes? Parce que 'IEnumerable filtre le résultat plus loin au niveau de l'application après avoir récupéré le résultat du serveur SQL
Alors, que doit-on choisir? Personnellement, je préfère définir le résultat de la fonction car, IQueryable<T>
car il présente de nombreux avantages IEnumerable
, vous pouvez joindre deux ou plusieurs fonctions IQueryable, qui génèrent un script plus spécifique pour le serveur SQL.
Ici, dans l'exemple, vous pouvez voir un IQueryable Query(IQueryableQuery2)
génère un script plus spécifique que IEnumerable query(IEnumerableQuery2)
ce qui est beaucoup plus acceptable à mon point de vue.
Il permet de poursuivre les requêtes plus loin sur la ligne. Si cela dépassait une limite de service, par exemple, l'utilisateur de cet objet IQueryable serait autorisé à en faire plus.
Par exemple, si vous utilisiez le chargement différé avec nhibernate, cela pourrait entraîner le chargement du graphique quand / si nécessaire.