web-dev-qa-db-fra.com

Utiliser IQueryable avec Linq

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?

242
user190560

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

476
Reed Copsey

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.

177
Marc Gravell

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") inprogram.cs`

Nous sommes maintenant au point où la différence est venue entre IQueryable et IEnumerable

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,

  • seules les lignes renseignées par le serveur SQL pouvant être valides pour toute l'exécution de la requête linq
  • améliore les performances du serveur SQL en ne sélectionnant pas les lignes inutiles
  • coût du réseau se réduire

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.

13
Moumit

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.

1
dove