web-dev-qa-db-fra.com

Renvoi IEnumerable <T> vs. IQueryable <T>

Quelle est la différence entre renvoyer IQueryable<T> et IEnumerable<T>?

IQueryable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

IEnumerable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

L’exécution différée est-elle à la fois et quand faut-il préférer l’une à l’autre?

1004

Oui, les deux vous donneront une exécution différée .

La différence est que IQueryable<T> est l'interface qui permet à LINQ-to-SQL (LINQ.-à-n'importe quoi) de fonctionner. Donc, si vous affinez davantage votre requête sur un IQueryable<T> , cette requête sera exécutée dans la base de données, si possible.

Pour le cas IEnumerable<T> , ce sera LINQ-to-object, ce qui signifie que tous les objets correspondant à la requête d'origine devront être chargés en mémoire à partir de la base de données. .

Dans du code:

IQueryable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);

Ce code exécutera SQL pour ne sélectionner que des clients Gold. D'autre part, le code suivant exécutera la requête d'origine dans la base de données, puis filtrera les clients non or de la mémoire:

IEnumerable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);

C'est une différence assez importante, et travailler sur IQueryable<T> peut dans de nombreux cas vous éviter de retourner trop de lignes de la base de données. Un autre bon exemple est la pagination: si vous utilisez Take et Skip sur IQueryable , vous n’obtiendrez que le nombre de lignes demandées; faire cela sur un IEnumerable<T> entraînera le chargement de toutes vos lignes en mémoire.

1673
driis

La réponse principale est bonne, mais elle ne mentionne pas les arbres d'expression qui expliquent "comment" les deux interfaces diffèrent. Fondamentalement, il existe deux ensembles identiques d'extensions LINQ. Where(), Sum(), Count(), FirstOrDefault(), etc. ont tous deux versions: une qui accepte les fonctions et l'autre qui accepte les expressions.

  • La signature de la version IEnumerable est: Where(Func<Customer, bool> predicate)

  • La signature de la version IQueryable est: Where(Expression<Func<Customer, bool>> predicate)

Vous avez probablement utilisé les deux sans vous en rendre compte, car les deux sont appelées avec une syntaxe identique:

par exemple. Where(x => x.City == "<City>") fonctionne à la fois avec IEnumerable et IQueryable

  • Lors de l'utilisation de Where() sur une collection IEnumerable, le compilateur transmet une fonction compilée à Where()

  • Lors de l'utilisation de Where() sur une collection IQueryable, le compilateur transmet une arborescence d'expression à Where(). Un arbre d'expression est comme le système de réflexion mais pour le code. Le compilateur convertit votre code en une structure de données décrivant ce que votre code fait dans un format facilement assimilable.

Pourquoi s'embêter avec cette chose d'arborescence d'expression? Je veux juste que Where() filtre mes données. La raison principale est que les deux ORM EF et Linq2SQL peuvent convertir les arborescences d'expression directement en SQL où votre code s'exécutera beaucoup plus rapidement.

Oh, cela ressemble à un gain de performances gratuit, devrais-je utiliser AsQueryable() partout dans ce cas? Non, IQueryable n'est utile que si le fournisseur de données sous-jacent peut faire quelque chose avec. Convertir quelque chose comme un List ordinaire en IQueryable ne vous apportera aucun avantage.

268
Jacob

Oui, les deux utilisent une exécution différée. Illustrons la différence en utilisant le profileur SQL Server ....

Quand on lance le code suivant:

MarketDevEntities db = new MarketDevEntities();

IEnumerable<WebLog> first = db.WebLogs;
var second = first.Where(c => c.DurationSeconds > 10);
var third = second.Where(c => c.WebLogID > 100);
var result = third.Where(c => c.EmailAddress.Length > 11);

Console.Write(result.First().UserName);

Dans le profileur SQL Server, nous trouvons une commande égale à:

"SELECT * FROM [dbo].[WebLog]"

Il faut environ 90 secondes pour exécuter ce bloc de code sur une table WebLog contenant 1 million d'enregistrements.

Ainsi, tous les enregistrements de la table sont chargés en mémoire en tant qu’objets, puis avec chaque .Where (), ce sera un autre filtre en mémoire pour ces objets.

Lorsque nous utilisons IQueryable au lieu de IEnumerable dans l'exemple ci-dessus (deuxième ligne):

Dans le profileur SQL Server, nous trouvons une commande égale à:

"SELECT TOP 1 * FROM [dbo].[WebLog] WHERE [DurationSeconds] > 10 AND [WebLogID] > 100 AND LEN([EmailAddress]) > 11"

Il faut environ quatre secondes pour exécuter ce bloc de code en utilisant IQueryable.

IQueryable a une propriété appelée Expression qui stocke une expression d'arborescence qui commence à être créée lorsque nous avons utilisé result dans notre exemple (appelé exécution différée) et à la fin, cette expression sera convertie en une requête SQL à exécuter sur le moteur de base de données. .

73
Kasper Roma

Les deux vous donneront une exécution différée, oui.

Quant à savoir ce qui est préféré à l’autre, cela dépend de la source de données sous-jacente.

Le renvoi d'un IEnumerableobligera automatiquement le moteur d'exécution à utiliser LINQ to Objects pour interroger votre collection.

Le renvoi d'un IQueryable(qui implémente IEnumerablename__, en passant) fournit la fonctionnalité supplémentaire pour traduire votre requête en un élément susceptible de mieux fonctionner sur la source sous-jacente (LINQ to SQL, LINQ to XML, etc.).

55
Justin Niessner

En termes généraux, je recommanderais ce qui suit:

  • Renvoyez IQueryable<T> si vous souhaitez activer le développeur utilisant votre méthode pour affiner la requête que vous retournez avant de l'exécuter.

  • Renvoyez IEnumerablesi vous souhaitez transporter un ensemble d'objets à énumérer.

Imaginez un IQueryableidentique à ce qu’il est - une "requête" de données (que vous pouvez affiner si vous le souhaitez). IEnumerableest un ensemble d'objets (déjà reçus ou créés) sur lesquels vous pouvez énumérer.

27
sebastianmehler

Beaucoup a été dit précédemment, mais revenons à la racine, de manière plus technique:

  1. IEnumerable est une collection d'objets en mémoire que vous pouvez énumérer - une séquence en mémoire qui permet d'effectuer une itération (facilite la tâche à l'intérieur). foreach loop, bien que vous ne puissiez utiliser que IEnumerator). Ils résident dans la mémoire telle quelle.
  2. IQueryable est un arbre d'expression qui sera traduit ultérieurement en quelque chose d'autre avec possibilité d'énumérer le résultat final. . Je suppose que c'est ce qui confond la plupart des gens.

Ils ont évidemment des connotations différentes.

IQueryable représente une arborescence d'expression (une requête, simplement) qui sera traduite par le fournisseur de requêtes sous-jacent dès que les API d'édition seront appelées, comme les fonctions d'agrégat LINQ (Sum, Count, etc.) ou ToList [Array, Dictionary, ...] Et les objets IQueryable implémentent également IEnumerable, IEnumerable<T> afin que s’ils représentent une requête , le résultat de cette requête pourrait être itéré. Cela signifie que IQueryable ne doit pas être uniquement une requête. Le bon terme est ce sont des arbres d'expression .

Maintenant, comment ces expressions sont exécutées et à quoi elles se tournent jusqu’à ce qu’on appelle des fournisseurs de requêtes (des exécuteurs d’expressions auxquels on peut penser).

Dans le monde Entity Framework (qui est ce fournisseur de source de données sous-jacent mystique, ou le fournisseur de requêtes), les expressions IQueryable sont traduites en requêtes natives T-SQL . Nhibernate fait des choses similaires avec eux. Vous pouvez écrire le vôtre en suivant les concepts assez bien décrits dans le lien LINQ: Construction d'un fournisseur IQueryable , par exemple, et vous souhaiterez peut-être une API d'interrogation personnalisée pour votre service de fournisseur de magasin de produits.

Donc, en gros, les objets IQueryable sont en cours de construction jusqu'à ce que nous les publiions explicitement et demandions au système de les réécrire en SQL ou autre et que nous envoyions la chaîne d'exécution pour un traitement ultérieur.

Comme si pour une exécution différée , il s’agit d’une fonction LINQ qui permet de conserver le schéma d’arbre des expressions dans la mémoire et envoyez-le à l'exécution uniquement à la demande, chaque fois que certaines API sont appelées dans la séquence (le même compte, la même liste, etc.).

L'utilisation appropriée des deux dépend fortement des tâches à accomplir dans chaque cas particulier. Pour le modèle de référentiel bien connu, j'ai personnellement opté pour le renvoi de IList, c'est-à-dire IEnumerable sur les listes (indexeurs, etc.). C'est pourquoi je vous conseille d'utiliser IQueryable uniquement dans les référentiels et IEnumerable ailleurs dans le code. Ne pas en dire plus sur les problèmes de testabilité selon lesquels IQueryable décompose et ruine le principe séparation des problèmes . Si vous retournez une expression depuis les référentiels, les utilisateurs peuvent utiliser la couche de persistance comme ils le souhaitent.

Un petit ajout au désordre :) (d'après une discussion dans les commentaires)) Aucun d'entre eux n'est un objet en mémoire car ce ne sont pas de vrais types en soi, ce sont des marqueurs d'un type - si vous voulez aller aussi loin. Mais il est logique (et c'est pourquoi même MSDN ainsi) de penser à IEnumerables en tant que collections en mémoire, tandis que IQueryables en tant qu'arbres d'expression. Le fait est que l'interface IQueryable hérite de l'interface IEnumerable, de sorte que si elle représente une requête, les résultats de cette requête peuvent être énumérés. L'énumération entraîne l'exécution de l'arborescence d'expression associée à un objet IQueryable. Donc, en fait, vous ne pouvez pas appeler un membre IEnumerable sans avoir l’objet dans la mémoire. De toute façon, il y arrivera, si ce n'est pas vide. IQueryables ne sont que des requêtes, pas les données.

25
Arman McHitarian

En règle générale, vous souhaitez conserver le type statique initial de la requête jusqu'à ce que cela soit important.

Pour cette raison, vous pouvez définir votre variable comme 'var' au lieu de IQueryable<> ou IEnumerable<> et vous saurez que vous ne modifiez pas le type.

Si vous commencez avec un IQueryable<>, vous voulez généralement le conserver sous la forme d'un IQueryable<> jusqu'à ce qu'il y ait une raison impérieuse de le modifier. La raison en est que vous souhaitez donner au processeur de requêtes autant d’informations que possible. Par exemple, si vous n'utilisez que 10 résultats (vous avez appelé Take(10)), vous souhaitez que SQL Server le sache afin qu'il puisse optimiser ses plans de requête et vous envoyer uniquement les données que vous utiliserez.

Une raison impérieuse de changer le type de IQueryable<> en IEnumerable<> peut être que vous appelez une fonction d'extension que l'implémentation de IQueryable<> dans votre objet particulier ne peut pas ou ne gère pas de manière efficace. Dans ce cas, vous voudrez peut-être convertir le type en IEnumerable<> (en affectant une variable de type IEnumerable<> ou en utilisant la méthode d'extension AsEnumerable, par exemple), de sorte que les fonctions d'extension que vous appelez soient celles de la classe Enumerable au lieu de la classe Queryable.

24
AJS

Un article de blog avec un bref exemple de code source explique comment une utilisation abusive de IEnumerable<T> peut avoir un impact considérable sur les performances des requêtes LINQ: Entity Framework: IQueryable vs. IEnumerable .

Si nous creusons plus profondément et examinons les sources, nous constatons qu'il existe évidemment différentes méthodes d'extension pour IEnumerable<T>:

// Type: System.Linq.Enumerable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Enumerable
{
    public static IEnumerable<TSource> Where<TSource>(
        this IEnumerable<TSource> source, 
        Func<TSource, bool> predicate)
    {
        return (IEnumerable<TSource>) 
            new Enumerable.WhereEnumerableIterator<TSource>(source, predicate);
    }
}

et IQueryable<T>:

// Type: System.Linq.Queryable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(
        this IQueryable<TSource> source, 
        Expression<Func<TSource, bool>> predicate)
    {
        return source.Provider.CreateQuery<TSource>(
            Expression.Call(
                null, 
                ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(
                    new Type[] { typeof(TSource) }), 
                    new Expression[] 
                        { source.Expression, Expression.Quote(predicate) }));
    }
}

Le premier retourne un itérateur énumérable, et le second crée une requête via le fournisseur de requêtes, spécifié dans source IQueryable.

18

J'ai récemment rencontré un problème avec IEnumerable v. IQueryable. L'algorithme utilisé a d'abord exécuté une requête IQueryable pour obtenir un ensemble de résultats. Celles-ci ont ensuite été transmises à une boucle foreach, avec les éléments instanciés en tant que classe Entity Framework (EF). Cette classe EF a ensuite été utilisée dans la clause from d'une requête Linq vers entité, le résultat étant donc IEnumerable.

EF et Linq pour les entités étant relativement nouveau pour moi, il a fallu un certain temps pour comprendre le problème. À l'aide de MiniProfiling, j'ai trouvé la requête, puis converti toutes les opérations individuelles en une seule requête IQueryable Linq for Entities. Le IEnumerable a pris 15 secondes et le IQueryable a pris 0,5 seconde pour être exécuté. Il y avait trois tables impliquées et, après avoir lu ceci, je crois que la requête IEnumerable formait en fait un produit croisé à trois tables et filtrait les résultats.

Essayez d’utiliser IQueryables comme règle générale et profilez votre travail pour que vos modifications soient mesurables.

11
sscheider

ce sont quelques différences entre IQueryable<T> et IEnumerable<T>

difference between returning IQueryable<T> vs. IEnumerable<T>

11
Basheer AL-MOMANI

J'aimerais clarifier quelques points en raison de réponses apparemment contradictoires (principalement autour de IEnumerable).

(1) IQueryableétend l'interface IEnumerablename__. (Vous pouvez envoyer un IQueryableà quelque chose qui attend IEnumerablesans erreur.)

(2) IQueryableet IEnumerableLINQ tentent un chargement paresseux lors de l'itération du jeu de résultats. (Notez que l'implémentation est visible dans les méthodes d'extension d'interface pour chaque type.)

En d'autres termes, IEnumerablesne sont pas exclusivement "en mémoire". IQueryablesne sont pas toujours exécutés sur la base de données. IEnumerabledoit charger des éléments en mémoire (une fois récupérés, éventuellement paresseux) car il n’a pas de fournisseur de données abstrait. IQueryabless'appuie sur un fournisseur abstrait (tel que LINQ-to-SQL), bien que cela puisse également être le fournisseur .NET en mémoire.

Exemple d'utilisation

(a) Récupérez la liste des enregistrements sous la forme IQueryableà partir du contexte EF. (Aucun enregistrement en mémoire.)

(b) Passez le IQueryableà une vue dont le modèle est IEnumerablename__. (Valid. IQueryableétend IEnumerablename__.)

(c) Parcourez et accédez aux enregistrements, aux entités enfants et aux propriétés du jeu de données à partir de la vue. (Peut causer des exceptions!)

Problèmes possibles

(1) IEnumerabletente un chargement paresseux et votre contexte de données a expiré. Exception levée car le fournisseur n'est plus disponible.

(2) Les proxies d'entité Entity Framework sont activés (par défaut) et vous tentez d'accéder à un objet associé (virtuel) avec un contexte de données expiré. Identique à (1).

(3) Plusieurs ensembles de résultats actifs (MARS). Si vous parcourez IEnumerabledans un bloc foreach( var record in resultSet ) et tentez simultanément d'accéder à record.childEntity.childProperty, vous pouvez vous retrouver avec MARS en raison du chargement paresseux du jeu de données et de l'entité relationnelle. Cela provoquera une exception s'il n'est pas activé dans votre chaîne de connexion.

Solution

  • J'ai constaté que l'activation de MARS dans la chaîne de connexion fonctionne de manière non fiable. Je vous suggère d'éviter MARS à moins que cela ne soit bien compris et explicitement souhaité.

Exécutez la requête et stockez les résultats en appelant resultList = resultSet.ToList(). Cela semble être le moyen le plus simple de garantir que vos entités sont en mémoire.

Dans les cas où vous accédez à des entités associées, vous pouvez toujours avoir besoin d'un contexte de données. Vous pouvez également désactiver les mandataires d’entité et explicitement les entités liées Includeà partir de votre DbSetname__.

10
Alexander Pritchard

La principale différence entre "IEnumerable" et "IQueryable" concerne le lieu d'exécution de la logique de filtrage. L'un s'exécute côté client (en mémoire) et l'autre s'exécute sur la base de données.

Par exemple, nous pouvons considérer un exemple où nous avons 10 000 enregistrements pour un utilisateur dans notre base de données et supposons seulement 900 utilisateurs actifs. Ainsi, dans ce cas, si nous utilisons "IEnumerable", les 10 000 enregistrements en mémoire sont d'abord chargés. applique ensuite le filtre IsActive sur celui-ci qui renvoie finalement les 900 utilisateurs actifs.

Par ailleurs, si nous utilisons "IQueryable" dans le même cas, il appliquera directement le filtre IsActive à la base de données, qui renverra directement les 900 utilisateurs actifs.

Référence Lien

9
Tabish Usman

Nous pouvons utiliser les deux de la même manière, et ils ne sont différents que dans la performance.

IQueryable ne s'exécute que sur la base de données de manière efficace. Cela signifie qu'il crée une requête de sélection complète et récupère uniquement les enregistrements associés.

Par exemple, nous voulons prendre les clients top 1 dont le nom commence par "Nimal". Dans ce cas, la requête de sélection sera générée sous la forme select top 10 * from Customer where name like ‘Nimal%’.

Mais si nous utilisions IEnumerable, la requête ressemblerait à select * from Customer where name like ‘Nimal%’ et les dix premiers seraient filtrés au niveau de codage C # (il récupère tous les enregistrements client de la base de données et les passe en C #).

5
user3710357

En plus des 2 premières bonnes réponses (de driis et de Jacob):

L’interface IEnumerable se trouve dans l’espace de noms System.Collections.

L'objet IEnumerable représente un ensemble de données en mémoire et ne peut avancer que sur ces données. La requête représentée par l'objet IEnumerable est exécutée immédiatement et complètement afin que l'application reçoive les données rapidement.

Lorsque la requête est exécutée, IEnumerable charge toutes les données et si nous devons les filtrer, le filtrage lui-même est effectué côté client.

L'interface IQueryable se trouve dans l'espace de noms System.Linq.

L'objet IQueryable fournit un accès à distance à la base de données et vous permet de parcourir les données dans un ordre direct du début à la fin ou dans l'ordre inverse. Lors de la création d'une requête, l'objet renvoyé est IQueryable, la requête est optimisée. En conséquence, moins de mémoire est consommée pendant son exécution, moins de bande passante réseau, mais en même temps, elle peut être traitée légèrement plus lentement qu'une requête renvoyant un objet IEnumerable.

Que choisir?

Si vous avez besoin de l’ensemble des données renvoyées, il est préférable d’utiliser IEnumerable, qui fournit la vitesse maximale.

Si vous N'AVEZ PAS besoin de tout le jeu de données renvoyées, mais seulement de quelques données filtrées, il est préférable d'utiliser IQueryable.

5
Gleb B

En plus de ce qui précède, il est intéressant de noter que vous pouvez obtenir des exceptions si vous utilisez IQueryable au lieu de IEnumerable:

Ce qui suit fonctionne bien si products est un IEnumerable:

products.Skip(-4);

Cependant, si products est un IQueryable et qu'il tente d'accéder aux enregistrements à partir d'une table de base de données, vous obtiendrez cette erreur:

Le décalage spécifié dans une clause OFFSET ne peut pas être négatif.

En effet, la requête suivante a été construite:

SELECT [p].[ProductId]
FROM [Products] AS [p]
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS

et OFFSET ne peut pas avoir une valeur négative.

0
Backwards_Dave