Quelle est l'utilisation de IQueryable
dans le contexte de LINQ?
Est-il utilisé pour développer des méthodes d'extension ou à d'autres fins?
La réponse de Marc Gravell est très complet, mais je pensais ajouter quelque chose à ce sujet du point de vue de l'utilisateur, aussi ...
La principale différence, du point de vue de l'utilisateur, est que, lorsque vous utilisez IQueryable<T>
(avec un fournisseur qui prend en charge les choses correctement), 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é d'extraire les données d'une table de deux manières: l'une qui retourne IEnumerable<T>
et l'autre qui retourne 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 supérieur à 25 USD.
Si tu fais:
IEnumerable<Product> products = myORM.GetProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
Dans ce cas, 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 crée un SELECT * FROM Products
et vous renvoie TOUS les produits.
Avec le bon fournisseur IQueryable<T>
, vous pouvez par contre:
IQueryable<Product> products = myORM.GetQueryableProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
Le code a le même aspect, mais la différence est que le SQL exécuté sera SELECT * FROM Products WHERE Cost >= 25
.
En tant que développeur, votre POV a le même aspect. Toutefois, en termes de performances, vous ne pouvez renvoyer que 2 enregistrements sur le réseau au lieu de 20 000 ....
En substance, son travail est très similaire à IEnumerable<T>
- représenter une source de données interrogeable - la différence étant que les différentes méthodes LINQ (sur Queryable
) peuvent être plus spécifiques, pour construire la requête à l'aide de Expression
arbres plutôt que des délégués (ce qui est ce que 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û à la ElementType
, Expression
et Provider
- mais en réalité, vous rarement devez vous en soucier en tant que utilisateur . Seul un implémenteur LINQ a besoin de connaître les détails sanglants.
Re commentaires; Je ne suis pas sûr de ce que vous voulez comme exemple, mais considérons LINQ-to-SQL; l'objet central ici est un DataContext
, qui représente notre wrapper 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'en utilisons pas beaucoup 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 Queryable
prennent des arbres d’expression qui, au lieu d’IL standard, sont compilés dans un modèle objet. Par exemple, en regardant 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 objet peut être déchiré, inspecté pour en comprendre le sens, puis reconstitué par le générateur TSQL, donnant ainsi:
SELECT c.Id, c.Name
FROM [dbo].[Customer] c
WHERE c.Region = 'North'
(la chaîne pourrait finir en paramètre, je ne m'en souviens pas)
Rien de tout cela ne serait possible si nous venions d'utiliser un délégué. Et this est le point de Queryable
/IQueryable<T>
: il fournit le point d’entrée pour l’utilisation des arbres d’expression.
Tout cela est très complexe, il est donc bon que le compilateur le rend agréable et facile pour nous.
Pour plus d'informations, consultez " C # en profondeur " ou " LINQ en action ", qui fournissent tous deux une couverture de ces sujets.
Bien que Reed Copsey et Marc Gravell déjà décrit à propos de IQueryable
(et aussi IEnumerable
), assez, je 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é forgée (personid
) de la table Person
Ensuite, j'ai ajouté le modèle d'entité ado.net dans mon application et créer la classe de service ci-dessous sur cette
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 même linq. Il appelle dans 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
Donc, la question est quoi/où est la différence? Cela ne semble pas avoir de différence, n'est-ce pas? Vraiment!!
Regardons les 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 aux 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
*/
Donc, vous avez quelques questions maintenant, laissez-moi deviner ceux-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 les fonctions IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable
et IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable
de SomeServiceClass
contiennent une ligne commune dans les requêtes linq
where employeesToCollect.Contains(e.PersonId)
Than pourquoi la partie AND (N'M' = [Extent1].[Gender])
est manquante dans la partie exécution IEnumerable
, alors que dans les deux appels de fonction, nous avons utilisé Where(i => i.Gender == "M") in
program.cs`
Nous sommes maintenant au point où la différence est venue entre
IQueryable
etIEnumerable
Quel cadre fait une entité quand une méthode IQueryable
est appelée, elle prend une instruction linq écrite à l'intérieur de la méthode et essaie de déterminer si d'autres expressions linq sont définies sur le jeu de résultats; et construit une requête SQL plus appropriée à exécuter.
Il offre de nombreux avantages tels que,
comme ici, dans l'exemple, le serveur SQL n'a renvoyé à l'application que deux lignes après l'exécution IQueryable, mais il a renvoyé TROIS lignes pour une requête IEnumerable, pourquoi?
Dans le cas de la méthode IEnumerable
, le cadre de l'entité prend l'instruction linq écrite dans la méthode et construit la requête SQL lorsque le résultat doit être récupéré. la partie linq restante n’est pas incluse dans la construction de 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 davantage au niveau de l'application après avoir récupéré le résultat du serveur SQL
SO, que doit choisir quelqu'un? Personnellement, je préfère définir le résultat de la fonction sous la forme IQueryable<T>
car il présente de nombreux avantages par rapport à IEnumerable
comme si vous pouviez joindre deux ou plusieurs fonctions IQueryable, qui génèrent un script plus spécifique sur le serveur SQL.
Ici, dans l'exemple, vous pouvez voir qu'une IQueryable Query(IQueryableQuery2)
génère un script plus spécifique que IEnumerable query(IEnumerableQuery2)
qui est beaucoup plus acceptable à mon point de vue.
Cela permet d’interroger davantage en aval. Si cela se situait au-delà d'une limite de service, l'utilisateur de cet objet IQueryable serait alors autorisé à en utiliser davantage.
Par exemple, si vous utilisez le chargement paresseux avec nhibernate, cela peut entraîner le chargement du graphique lorsque/si nécessaire.