Utiliser IQueryable avec Linq

à quoi sert IQueryable dans le contexte de LINQ?

est-il utilisé pour mettre au point des méthodes d'extension ou à toute autre fin?

225
demandé sur SteveC 2009-10-16 19:23:56

4 réponses

la réponse de Marc Gravell est très complète, mais j'ai pensé que je voudrais ajouter quelque chose à ce sujet du point de vue de l'utilisateur, ainsi...


la principale différence, du point de vue de l'utilisateur, est que, lorsque vous utilisez IQueryable<T> (avec un fournisseur qui supporte les choses correctement), vous pouvez économiser beaucoup de ressources.

par exemple, si vous travaillez contre une base de données distante, avec de nombreux systèmes ORM, vous avez la possibilité de récupérer des données à partir d'une table de deux façons, l'une qui retourne IEnumerable<T> , et l'autre qui retourne un IQueryable<T> . Par exemple, vous avez une table de produits, et vous voulez obtenir tous les produits dont le coût est >$25.

si vous le faites:

 IEnumerable<Product> products = myORM.GetProducts();
 var productsOver25 = products.Where(p => p.Cost >= 25.00);

ce qui se passe ici, est la base de données charge tous les produits, et les passe à travers le fil à votre programme. Votre programme filtre ensuite les données. Essentiellement, la base de données fait un SELECT * FROM Products , et vous renvoie chaque produit.

avec le droit IQueryable<T> fournisseur, d'autre part, vous pouvez faire:

 IQueryable<Product> products = myORM.GetQueryableProducts();
 var productsOver25 = products.Where(p => p.Cost >= 25.00);

le code ressemble au même, mais la différence ici est que le SQL exécuté sera SELECT * FROM Products WHERE Cost >= 25 .

de votre POV en tant que développeur, cela ressemble à la même chose. Cependant, du point de vue de la performance, vous ne pouvez retourner que 2 enregistrements sur le réseau au lieu de 20 000....

448
répondu Reed Copsey 2017-05-23 11:47:10

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 délégués (ce qui est ce que Enumerable utilise).

les arbres d'expression peuvent être inspectés par votre fournisseur de LINQ choisi et transformés en une requête réelle - bien que ce soit un art noir dans m'.

c'est en fait le ElementType , Expression et Provider - mais en réalité vous rarement besoin de se soucier de cela comme un utilisateur . Seul un LINQ implémenter a besoin de connaître les détails sanglants.


Re des commentaires; je ne suis pas tout à fait sûr de ce que vous voulez, par exemple, mais envisager de LINQ-to-SQL; l'objet central ici est un DataContext , qui représente l'enveloppe de notre base de données. Il possède généralement une propriété par table (par exemple, Customers ), et une table implémente IQueryable<Customer> . Mais nous n'utilisons pas tant que cela directement; considérons:

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 de 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 });

fait important, les méthodes statiques sur Queryable prennent expression les arbres , qui-plutôt que IL régulier, obtenir compilé à un modèle d'objet. Par exemple-en regardant juste le "où", cela nous donne quelque chose 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 assemblé à nouveau 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 pourrait finir comme un paramètre; je ne me souviens pas)

rien de tout cela ne serait possible si nous avions eu recours à 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, donc c'est un bon travail que le compilateur rend agréable et facile pour nous.

pour plus d'information, regardez " C# en profondeur " ou " LINQ en Action ", tous les deux qui fournissent la couverture de ces sujets.

170
répondu Marc Gravell 2016-10-26 21:35:54

bien que Reed Copsey et Marc Gravell déjà décrit sur IQueryable (et aussi IEnumerable ) assez, mI veulent ajouter un peu plus ici en fournissant un petit exemple sur IQueryable et IEnumerable comme de nombreux utilisateurs l'ont demandé

exemple : j'ai créé deux tableaux 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 clef primaire ( PersonId ) du tableau Employee est également une fausse clé ( personid ) du tableau Person

ensuite j'ai ajouté ado.net modèle d'entité dans mon application et créer en dessous de la classe de service sur cela

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 a appelé 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

la question est donc quelle est/Quelle est la différence? Il n'a pas semblent toute différence de droite? Vraiment!!

regardons les requêtes sql générées et exécutées par entity framwork 5 pendant cette période

IQueryable l'exécution de la partie

--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 l'exécution de la partie

--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 l'exécution de la partie

/* 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 peu de questions maintenant, laissez-moi deviner ceux-ci et essayer de répondre à eux

pourquoi des scripts différents sont-ils générés pour le même résultat?

voyons 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 et IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable de SomeServiceClass contient une ligne commune dans les requêtes linq

where employeesToCollect.Contains(e.PersonId)

que Pourquoi est le La partie AND (N'M' = [Extent1].[Gender]) est manquante dans la partie IEnumerable d'exécution, alors que dans les deux cas nous avons utilisé le programme Where(i => i.Gender == "M") in .cs '

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

Ce entity framwork n'quand un IQueryable méthode appelée, elle fait tourner l'instruction linq écrite à l'intérieur de la méthode et essaye de savoir si plus d'expressions linq sont définies sur le jeu de résultats, elle rassemble alors toutes les requêtes linq définies jusqu'à ce que le résultat ait besoin de fetch et construit une requête sql plus appropriée à exécuter.

Il fournir beaucoup d'avantages comme,

  • exécution des requêtes linq whole
  • aide les performances du serveur sql en ne sélectionnant pas les lignes inutiles
  • réduction des coûts de réseau

comme ici dans l'exemple sql server retourné à l'application seulement deux lignes après l'exécution IQueryable 'mais retourné trois lignes pour IEnumerable question pourquoi?

dans le cas de la méthode IEnumerable , entity framework a pris la déclaration linq écrite à l'intérieur de la méthode et construit la requête sql quand résultat besoin de récupérer. il n'inclut pas la partie rest linq pour construire la requête sql. Comme ici aucun filtrage n'est fait dans sql server sur la colonne gender .

Mais les sorties sont les mêmes? Parce que 'filtres IEnumerable le résultat plus loin dans le niveau d'application après avoir récupéré le résultat du serveur sql

, que doit-on choisir? Personnellement, je préfère définir fonction résultat comme IQueryable<T> parce qu'Il ya beaucoup de l'avantage, il a plus de IEnumerable comme, vous pourriez rejoindre deux ou plusieurs fonctions IQueryable, qui génèrent script plus spécifique à sql server.

ici dans l'exemple vous pouvez voir un IQueryable Query(IQueryableQuery2) génère un script plus spécifique que IEnumerable query(IEnumerableQuery2) qui est beaucoup plus acceptable à mon point de vue.

12
répondu Moumit 2018-03-21 13:05:28

il permet d'interroger plus loin sur la ligne. Si cela était au-delà d'une limite de service, par exemple, alors l'utilisateur de cet objet IQueryable serait autorisé à en faire plus avec lui.

par exemple, si vous utilisiez le chargement paresseux avec nhibernate, cela pourrait conduire à charger graph quand/si nécessaire.

1
répondu dove 2009-10-16 15:29:24