Quelle est l'utilité de IQueryable
dans le contexte de LINQ ?
Est-il utilisé pour développer des méthodes d'extension ou pour tout autre objectif ?
Quelle est l'utilité de IQueryable
dans le contexte de LINQ ?
Est-il utilisé pour développer des méthodes d'extension ou pour tout autre objectif ?
La réponse de Marc Gravell est très complet, 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 de l'utilisateur, est que, lorsque vous utilisez la fonction 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 des données d'une table de deux façons, l'une renvoyant l'information suivante IEnumerable<T>
et un autre qui renvoie un IQueryable<T>
. Disons, par exemple, que vous disposez d'une table Produits et que vous souhaitez obtenir tous les produits dont le coût est supérieur à 25 dollars.
Si vous le faites :
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 par câble à votre programme. Votre programme filtre ensuite les données. En fait, la base de données fait un SELECT * FROM Products
et vous renvoie TOUT le produit.
Avec le bon IQueryable<T>
fournisseur, par contre, vous pouvez le faire :
IQueryable<Product> products = myORM.GetQueryableProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
Le code est identique, mais la différence ici est que le SQL exécuté sera SELECT * FROM Products WHERE Cost >= 25
.
De votre point de vue de développeur, c'est la même chose. Cependant, du point de vue des performances, vous pouvez ne renvoyer que 2 enregistrements sur le réseau au lieu de 20 000.....
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 (sur Queryable
) peut être plus spécifique, pour construire la requête en utilisant Expression
arbres plutôt que des délégués (ce qui est ce que Enumerable
utilise).
Les arbres d'expression peuvent être inspectés par le fournisseur LINQ que vous avez choisi et transformés en un fichier de type actual l'interrogation - bien que ce soit un art noir en soi.
Cela dépend vraiment de la ElementType
, Expression
y Provider
- mais en réalité vous rarement besoin de se soucier de cela en tant que utilisateur . Seul un LINQ exécutant a besoin de connaître les détails sanglants.
En ce qui concerne les commentaires, je ne suis pas tout à fait sûr de ce que vous voulez comme exemple, mais considérez LINQ-to-SQL ; l'objet central ici est une DataContext
qui représente notre enveloppe de base de données. Celui-ci possède généralement une propriété par table (par exemple, Customers
), et une table met en œuvre IQueryable<Customer>
. Mais nous n'en utilisons pas autant directement ; pensez-y :
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 - plutôt que des IL réguliers, sont compilés en un modèle objet. Par exemple - en regardant simplement le "Where", 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 savoir ce qu'il signifie, et reconstitué par le générateur TSQL, ce qui donne quelque chose comme.. :
SELECT c.Id, c.Name
FROM [dbo].[Customer] c
WHERE c.Region = 'North'
(la chaîne de caractères peut se retrouver en tant que paramètre ; je ne me souviens pas)
Rien de tout cela ne serait possible si nous avions simplement utilisé un délégué. Et ce 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, c'est donc une bonne chose que le compilateur le rende agréable et facile pour nous.
Pour plus d'informations, consultez le site " C# en profondeur " ou " LINQ en action "Ces deux documents traitent de ces sujets.
Bien que Reed Copsey y Marc Gravell déjà décrite à propos de IQueryable
(et aussi IEnumerable
) suffisamment, je veux ajouter un petit plus ici en fournissant un petit exemple sur IQueryable
y IEnumerable
car de nombreux utilisateurs l'ont demandé
Ejemplo : 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
) du tableau Employee
est également une clé forgée( personid
) du tableau Person
Ensuite, j'ai ajouté un modèle d'entité ado.net dans mon application et créé la classe de service ci-dessous.
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 est appelé en 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 de savoir où et comment se situe la différence. Il semble qu'il n'y ait pas avoir aucune différence, n'est-ce pas ? Vraiment !
Jetons un coup d'oeil aux requêtes sql générées et exécutées par l'entité. framwork 5 pendant 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]
IEnumerable partie exécution
--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 de l'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 les deviner et essayer d'y répondre.
Pourquoi différents scripts sont-ils générés pour un même résultat ?
Découvrons quelques points ici,
toutes les requêtes ont une partie commune
WHERE [Extent1].[PersonId] IN (0,1,2,3)
Pourquoi ? Parce que les deux fonctions IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable
y IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable
de SomeServiceClass
contient une ligne commune dans les requêtes linq
where employeesToCollect.Contains(e.PersonId)
Alors pourquoi le AND (N'M' = [Extent1].[Gender])
est manquante dans IEnumerable
partie exécution, tandis que dans les deux appels de fonction nous avons utilisé Where(i => i.Gender == "M") in
programme.cs`
Maintenant nous sommes au point où la différence est venue entre
IQueryable
yIEnumerable
Que fait le cadre de l'entité lorsqu'un IQueryable
appelée, elle prend l'instruction linq écrite à l'intérieur de la méthode et essaie de savoir si d'autres expressions linq sont définies dans le jeu de résultats, elle rassemble ensuite toutes les requêtes linq définies jusqu'au résultat à récupérer 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 a renvoyé à l'application seulement deux lignes après l'exécution de IQueryable` mais il a retourné THREE rangs pour la requête IEnumerable pourquoi ?
En cas de IEnumerable
le cadre d'entité a pris la déclaration linq écrite dans la méthode et construit la requête sql quand le résultat doit être récupéré. il n'inclut pas la partie linq restante 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 au niveau de l'application après avoir récupéré le résultat du serveur sql.
SO, que doit-on choisir ? Personnellement, je préfère définir le résultat de la fonction comme IQueryable<T>
parce qu'il y a beaucoup d'avantages qu'il a sur IEnumerable
comme, vous pourriez joindre deux ou plusieurs fonctions IQueryable, qui génèrent des script plus spécifiques au serveur sql.
Dans cet 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 de mon point de vue.
Cela permet d'effectuer d'autres requêtes plus tard dans la vie. Si cela dépassait les limites d'un service, par exemple, l'utilisateur de cet objet IQueryable serait autorisé à en faire plus.
Par exemple, si vous utilisiez le chargement paresseux avec nhibernate, le graphique pourrait être chargé quand/si nécessaire.
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.