Utilisation d'IQueryable avec Linq

257

À quoi sert IQueryabledans le contexte de LINQ?

Est-il utilisé pour développer des méthodes d'extension ou à toute autre fin?

user190560
la source

Réponses:

508

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

Reed Copsey
la source
9
Où est la définition de "GetQueryableProducts ();"?
Pankaj
12
@StackOverflowUser Il s'agit d'une méthode qui renvoie un IQueryable<Product>- serait spécifique à votre ORM ou référentiel, etc.
Reed Copsey
droite. mais vous avez mentionné la clause where après cet appel de fonction. Le système n'est donc toujours pas au courant du filtre. Je veux dire qu'il récupère toujours tous les enregistrements de produits. droite?
Pankaj
@StackOverflowUser Non - c'est la beauté de IQueryable <T> - il peut être configuré pour évaluer lorsque vous obtenez les résultats - ce qui signifie que la clause Where, utilisée après coup, sera toujours traduite en une instruction SQL exécutée sur le serveur, et tirez seulement les éléments requis à travers le fil ...
Reed Copsey
40
@Testing Vous n'allez toujours pas à la base de données. Jusqu'à ce que vous ayez réellement énuméré les résultats (c'est-à-dire: utilisez a foreachou appelez ToList()), vous ne frappez pas réellement la base de données.
Reed Copsey
188

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 Expressionarbres plutôt que des délégués (ce qui est ce qui Enumerableutilise).

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

Marc Gravell
la source
2
Si cela ne vous dérange pas, pouvez-vous me mettre à jour avec un exemple simple et compréhensible (si vous avez le temps).
user190560
Pouvez-vous expliquer "Où est la définition de" GetQueryableProducts (); "?" dans la réponse de M. Reed Copsey
Pankaj
Apprécié la ligne de traduction des expressions à une requête est "l'art noir en soi" ... beaucoup de vérité à cela
afreeland
Pourquoi rien de tout cela ne serait possible si vous aviez utilisé un délégué?
David Klempfner
1
@Backwards_Dave car un délégué pointe (essentiellement) vers IL, et IL n'est pas suffisamment expressif pour qu'il soit raisonnable d'essayer de déconstruire l'intention suffisamment pour construire SQL. IL autorise également trop de choses - c'est-à-dire que la plupart des choses qui peuvent être exprimées en IL ne peuvent pas être exprimées dans la syntaxe limitée qu'il est raisonnable de transformer en choses comme SQL
Marc Gravell
17

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 IQueryableet IEnumerableautant 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 Employeeest é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.cscomme 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> GetEmployeeAndPersonDetailIQueryableet IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerablede SomeServiceClasscontient 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 IEnumerablepartie d'exécution, alors que dans les deux fonctions, nous avons utilisé Where(i => i.Gender == "M") inprogram.cs`

Maintenant, nous sommes au point où la différence est venue entre IQueryableet IEnumerable

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,

  • seules les lignes remplies par le serveur sql qui pourraient être valides par toute l'exécution de la requête linq
  • aide les performances du serveur SQL en ne sélectionnant pas les lignes inutiles
  • réduction du coût du réseau

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

Moumit
la source
2

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.

Colombe
la source