Oui, les deux vous donneront une exécution différée .
La différence est que IQueryable<T>
c'est l'interface qui permet à LINQ-to-SQL (LINQ.-to-everything vraiment) 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 IEnumerable<T>
cas, il s'agira de 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 sélectionner uniquement les clients Gold. Le code suivant, d'autre part, exécutera la requête d'origine dans la base de données, puis filtrera les clients non or dans 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 renvoyer trop de lignes de la base de données. Un autre bon exemple est la pagination: si vous utilisez Take
et Skip
allumez IQueryable
, vous n'obtiendrez que le nombre de lignes demandé; faire cela sur un IEnumerable<T>
entraînera le chargement de toutes vos lignes en mémoire.
IEnumerable
àIQueryable
est que toutes les opérations de LINQ sont pris en charge par tous les fournisseurs de LINQ. Donc, tant que vous savez ce que vous faites, vous pouvez utiliserIQueryable
pour envoyer autant de requêtes au fournisseur LINQ (LINQ2SQL, EF, NHibernate, MongoDB, etc.). Mais si vous laissez un autre code faire ce qu'il veut avec votre,IQueryable
vous finirez par avoir des problèmes parce que du code client quelque part a utilisé une opération non prise en charge. Je suis d'accord avec la recommandation de ne pas libérerIQueryable
s "dans la nature" après le référentiel ou la couche équivalente.La première réponse est bonne mais elle ne mentionne pas d'arbres d'expression qui expliquent "en quoi" les deux interfaces diffèrent. Fondamentalement, il existe deux ensembles identiques d'extensions LINQ.
Where()
,Sum()
,Count()
,FirstOrDefault()
, Etc ont tous deux versions: l' une qui accepte les fonctions et qui accepte les expressions.La
IEnumerable
signature de la version est:Where(Func<Customer, bool> predicate)
La
IQueryable
signature de la version est:Where(Expression<Func<Customer, bool>> predicate)
Vous avez probablement utilisé les deux sans vous en rendre compte, car les deux sont appelés en utilisant une syntaxe identique:
par exemple
Where(x => x.City == "<City>")
fonctionne à la fois surIEnumerable
etIQueryable
Lors de l'utilisation
Where()
sur uneIEnumerable
collection, le compilateur transmet une fonction compilée àWhere()
Lors de l'utilisation
Where()
sur unIQueryable
collection, 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 qui décrit ce que fait votre code dans un format facilement digestible.Pourquoi s'embêter avec ce truc d'arbre d'expression? je veux juste
Where()
filtrer mes données. La raison principale est que les 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 à une amélioration des performances gratuite, dois-je utiliser
AsQueryable()
partout dans ce cas? Non,IQueryable
n'est utile que si le fournisseur de données sous-jacent peut en faire quelque chose. Conversion de quelque chose comme un habituéList
deIQueryable
vous ne donnera aucun avantage.la source
IQueryable
ne pas les fonctions parse ainsi que leIEnumerable
fait: par exemple, si vous voulez savoir quels sont les éléments d'unDBSet<BookEntity>
ne sont pas enList<BookObject>
,dbSetObject.Where(e => !listObject.Any(o => o.bookEntitySource == e))
lève une exception:Expression of type 'BookEntity' cannot be used for parameter of type 'BookObject' of method 'Boolean Contains[BookObject] (IEnumerable[BookObject], BookObject)'
. J'ai dû ajouter.ToList()
aprèsdbSetObject
.Oui, les deux utilisent une exécution différée. Illustrons la différence en utilisant le profileur SQL Server ....
Lorsque nous exécutons le code suivant:
Dans le profileur SQL Server, nous trouvons une commande égale à:
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 table sont chargés en mémoire en tant qu'objets, puis avec chaque .Where (), ce sera un autre filtre en mémoire par rapport à ces objets.
Lorsque nous utilisons
IQueryable
au lieu deIEnumerable
dans l'exemple ci-dessus (deuxième ligne):Dans le profileur SQL Server, nous trouvons une commande égale à:
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'arbre qui commence à être créée lorsque nous avons utilisé leresult
dans notre exemple (qui est 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.la source
Les deux vous donneront une exécution différée, oui.
Quant à ce qui est préféré à l'autre, cela dépend de votre source de données sous-jacente.
Renvoyer un
IEnumerable
forcera automatiquement le runtime à utiliser LINQ to Objects pour interroger votre collection.Le retour d'un
IQueryable
(qui implémenteIEnumerable
, soit dit en passant) fournit la fonctionnalité supplémentaire pour traduire votre requête en quelque chose qui pourrait mieux fonctionner sur la source sous-jacente (LINQ to SQL, LINQ to XML, etc.).la source
En termes généraux, je recommanderais ce qui suit:
Renvoyez
IQueryable<T>
si vous souhaitez activer le développeur à l'aide de votre méthode pour affiner la requête que vous renvoyez avant de l'exécuter.Retournez
IEnumerable
si vous voulez transporter un ensemble d'objets à énumérer.Imaginez un
IQueryable
tel que c'est - une "requête" pour les données (que vous pouvez affiner si vous le souhaitez). AnIEnumerable
est un ensemble d'objets (qui a déjà été reçu ou a été créé) sur lequel vous pouvez énumérer.la source
Beaucoup a été dit précédemment, mais revenons aux racines, de manière plus technique:
IEnumerable
est une collection d'objets en mémoire que vous pouvez énumérer - une séquence en mémoire qui permet de parcourir (facilite laforeach
boucle intra , bien que vous ne puissiez y aller qu'avecIEnumerator
). Ils résident dans la mémoire telle quelle.IQueryable
est un arbre d'expression qui sera traduit en quelque chose d'autre à un moment donné avec la possibilité d'énumérer le résultat final . Je suppose que c'est ce qui déroute la plupart des gens.Ils ont évidemment des connotations différentes.
IQueryable
représente un arbre d'expression (une requête, simplement) qui sera traduit en quelque chose d'autre par le fournisseur de requête sous-jacent dès que les API de publication sont appelées, comme les fonctions d'agrégation LINQ (Sum, Count, etc.) ou ToList [Array, Dictionary ,. ..]. Et lesIQueryable
objets implémentent égalementIEnumerable
, deIEnumerable<T>
sorte que s'ils représentent une requête, le résultat de cette requête puisse être itéré. Cela signifie que IQueryable ne doit pas être uniquement des requêtes. Le bon terme est qu'ils sont des arbres d'expression .Maintenant, la façon dont ces expressions sont exécutées et vers quoi elles se tournent appartient aux soi-disant fournisseurs de requêtes (exécuteurs d'expressions auxquels nous pouvons 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
IQueryable
expressions sont traduites en requêtes T-SQL natives .Nhibernate
fait des choses similaires avec eux. Vous pouvez écrire le vôtre en suivant les concepts assez bien décrits dans LINQ: création d'un lien de fournisseur IQueryable , par exemple, et vous souhaiterez peut-être avoir une API de requête personnalisée pour votre service de fournisseur de magasin de produits.Donc, fondamentalement, les
IQueryable
objets sont construits tout le temps jusqu'à ce que nous les libérions explicitement et disions au système de les réécrire dans SQL ou autre et d'envoyer la chaîne d'exécution pour un traitement ultérieur.Comme pour une exécution différée, il est possible
LINQ
de conserver le schéma d'arborescence d'expressions dans la mémoire et de l'envoyer dans l'exécution uniquement à la demande, chaque fois que certaines API sont appelées par rapport à la séquence (les mêmes Count, ToList, etc.).La bonne utilisation des deux dépend fortement des tâches auxquelles vous êtes confronté dans le cas spécifique. Pour le modèle de référentiel bien connu, j'opte personnellement pour le retour
IList
, c'est-à-direIEnumerable
sur les listes (indexeurs et similaires). Il est donc mon conseil de n'utiliserIQueryable
que dans les référentiels et IEnumerable n'importe où ailleurs dans le code. Ne pas parler des problèmes de testabilité quiIQueryable
décomposent et ruinent le principe de séparation des préoccupations . Si vous renvoyez une expression à partir de référentiels, les consommateurs peuvent jouer avec la couche de persistance comme ils le souhaitent.Un petit ajout au gâchis :) (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 le dit ainsi) de considérer IEnumerables comme des collections en mémoire tandis que IQueryables comme des 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 vraiment appeler un membre IEnumerable sans avoir l'objet en mémoire. Il y entrera si vous le faites, de toute façon, s'il n'est pas vide. Les IQueryables ne sont que des requêtes, pas les données.
la source
En général, vous souhaitez conserver le type statique d'origine de la requête jusqu'à ce qu'il soit important.
Pour cette raison, vous pouvez définir votre variable comme «var» au lieu de l'un
IQueryable<>
ou de l'autreIEnumerable<>
et vous saurez que vous ne changez pas le type.Si vous commencez avec un
IQueryable<>
, vous voulez généralement le conserver commeIQueryable<>
jusqu'à ce qu'il y ait une raison impérieuse de le changer. La raison en est que vous souhaitez fournir 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 voulez 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<>
àIEnumerable<>
pourrait être que vous appelez une fonction d'extension que l'implémentation deIQueryable<>
dans votre objet particulier ne peut pas gérer ou gère de manière inefficace. Dans ce cas, vous souhaiterez peut-être convertir le type enIEnumerable<>
(en l'assignant à une variable de typeIEnumerable<>
ou en utilisant laAsEnumerable
méthode d'extension par exemple) afin que les fonctions d'extension que vous appelez finissent par être celles de laEnumerable
classe au lieu de laQueryable
classe.la source
Il existe un article de blog avec un bref exemple de code source sur la façon dont une mauvaise utilisation
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 pouvons voir qu'il existe évidemment différentes méthodes d'extension pour
IEnumerable<T>
:et
IQueryable<T>
:Le premier retourne l'itérateur énumérable, et le second crée une requête via le fournisseur de requêtes, spécifié dans la
IQueryable
source.la source
J'ai récemment rencontré un problème avec
IEnumerable
vIQueryable
. L'algorithme utilisé a d'abord effectué uneIQueryable
requête pour obtenir un ensemble de résultats. Celles-ci ont ensuite été passées à uneforeach
boucle, avec les éléments instanciés en tant que classe Entity Framework (EF). Cette classe EF a ensuite été utilisée dans lafrom
clause d'une requête Linq to Entity, provoquant le résultatIEnumerable
.Je suis assez nouveau pour EF et Linq for Entities, il a donc fallu un certain temps pour comprendre quel était le goulot d'étranglement. À l'aide de MiniProfiling, j'ai trouvé la requête, puis j'ai converti toutes les opérations individuelles en une seule
IQueryable
requête Linq for Entities. L' exécution aIEnumerable
duré 15 secondes etIQueryable
0,5 seconde. Il y avait trois tables impliquées et, après avoir lu ceci, je crois que laIEnumerable
requête formait en fait un produit croisé à trois tables et filtrait les résultats.Essayez d'utiliser IQueryables comme règle empirique et profilez votre travail pour rendre vos changements mesurables.
la source
IQueryable
s sont également bloqués dans la mémoire une fois que vous avez appelé l'une de ces API, mais sinon, vous pouvez passer l'expression dans la pile des couches et jouer avec les filtres jusqu'à l'appel de l'API. Un DAL bien conçu comme un référentiel bien conçu résoudra ce genre de problèmes;)Je voudrais clarifier certaines choses en raison de réponses apparemment contradictoires (principalement entourant IEnumerable).
(1)
IQueryable
étend l'IEnumerable
interface. (Vous pouvez envoyer unIQueryable
à quelque chose qui attendIEnumerable
sans erreur.)(2) Les deux
IQueryable
etIEnumerable
LINQ tentent un chargement paresseux lors de l'itération sur l'ensemble de résultats. (Notez que l'implémentation peut être vue dans les méthodes d'extension d'interface pour chaque type.)En d'autres termes,
IEnumerables
ne sont pas exclusivement "en mémoire".IQueryables
ne sont pas toujours exécutés sur la base de données.IEnumerable
doit charger les choses en mémoire (une fois récupéré, peut-être paresseusement) car il n'a pas de fournisseur de données abstrait.IQueryables
s'appuyer sur un fournisseur abstrait (comme LINQ-to-SQL), bien qu'il puisse également s'agir du fournisseur en mémoire .NET.Exemple d'utilisation
(a) Récupérer la liste des enregistrements à
IQueryable
partir du contexte EF. (Aucun enregistrement n'est en mémoire.)(b) Passez
IQueryable
à une vue dont le modèle estIEnumerable
. (Valide.IQueryable
S'étendIEnumerable
.)(c) Parcourez et accédez aux enregistrements de l'ensemble de données, aux entités enfants et aux propriétés à partir de la vue. (Peut provoquer des exceptions!)
Problèmes possibles
(1) Les
IEnumerable
tentatives de chargement différé et votre contexte de données ont expiré. Exception levée car le fournisseur n'est plus disponible.(2) Les proxys d'entité Entity Framework sont activés (par défaut) et vous tentez d'accéder à un objet (virtuel) associé avec un contexte de données expiré. Identique à (1).
(3) Plusieurs jeux de résultats actifs (MARS). Si vous parcourez le
IEnumerable
dans unforeach( var record in resultSet )
bloc et tentez simultanément d'accéderrecord.childEntity.childProperty
, vous pouvez vous retrouver avec MARS en raison du chargement paresseux de l'ensemble 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
Exécutez la requête et stockez les résultats en appelant
resultList = resultSet.ToList()
Cela semble être le moyen le plus simple de vous assurer que vos entités sont en mémoire.Dans les cas où vous accédez à des entités liées, vous pouvez toujours avoir besoin d'un contexte de données. Soit cela, soit vous pouvez désactiver les proxys d'entité et
Include
les entités explicitement liées de votreDbSet
.la source
La principale différence entre «IEnumerable» et «IQueryable» concerne l'emplacement 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 disons seulement 900 sur qui sont des utilisateurs actifs, donc dans ce cas, si nous utilisons «IEnumerable», il charge d'abord les 10 000 enregistrements en mémoire et applique ensuite le filtre IsActive sur celui-ci qui renvoie finalement les 900 utilisateurs actifs.
Alors que d'autre part sur le même cas si nous utilisons «IQueryable», il appliquera directement le filtre IsActive sur la base de données qui directement à partir de là retournera les 900 utilisateurs actifs.
Lien de référence
la source
Nous pouvons utiliser les deux de la même manière, et ils ne sont différents que dans les performances.
IQueryable s'exécute uniquement sur la base de données de manière efficace. Cela signifie qu'il crée une requête de sélection entière et n'obtient que les enregistrements associés.
Par exemple, nous voulons prendre les 10 premiers clients dont le nom commence par «Nimal». Dans ce cas, la requête de sélection sera générée en tant que
select top 10 * from Customer where name like ‘Nimal%’
.Mais si nous utilisions IEnumerable, la requête serait comme
select * from Customer where name like ‘Nimal%’
et les dix premiers seront filtrés au niveau du codage C # (il récupère tous les enregistrements clients de la base de données et les transmet en C #).la source
En plus des 2 premières bonnes réponses (par driis et par Jacob):
L'objet IEnumerable représente un ensemble de données en mémoire et ne peut avancer sur ces données que vers l'avant. La requête représentée par l'objet IEnumerable est exécutée immédiatement et complètement, de sorte que l'application reçoit rapidement les données.
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'objet IQueryable fournit un accès à distance à la base de données et vous permet de parcourir les données soit dans un ordre direct du début à la fin, soit dans l'ordre inverse. Dans le processus de création d'une requête, l'objet renvoyé est IQueryable, la requête est optimisée. Par conséquent, moins de mémoire est consommée lors de son exécution, moins de bande passante réseau, mais en même temps, elle peut être traitée un peu plus lentement qu'une requête qui renvoie 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 l'ensemble des données renvoyées, mais uniquement de certaines données filtrées, il est préférable d'utiliser IQueryable.
la source
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 deIEnumerable
:Ce qui suit fonctionne bien si
products
est unIEnumerable
:Cependant, si
products
est unIQueryable
et essaie d'accéder aux enregistrements d'une table de base de données, vous obtiendrez cette erreur:En effet, la requête suivante a été créée:
et OFFSET ne peut pas avoir de valeur négative.
la source