[c#] Renvoyer IEnumerable <T> par rapport à IQueryable <T>



Answers

La meilleure réponse 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 une 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 le savoir parce que les deux sont appelés en utilisant une syntaxe identique:

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

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

  • Lorsque vous utilisez 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 qui décrit ce que fait votre code dans un format facilement digestible.

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

Oh, cela ressemble à un boost de performance 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 lui. Convertir quelque chose comme une List régulière en IQueryable ne vous donnera aucun avantage.

Question

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;

Est-ce que l'exécution sera différée et à quel moment l'un devrait-il être préféré à l'autre?




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

Pour retourner IQueryable <T> si vous souhaitez activer le développeur en utilisant votre méthode pour affiner la requête que vous renvoyez avant l'exécution.

Si vous voulez transporter juste un ensemble d'objets à énumérer, prenez simplement IEnumerable.

Imaginez un IQueryable comme ce que c'est, une "requête" pour les données (que vous pouvez affiner si vous voulez)

Un IEnumerable est un ensemble d'objets (qui a déjà été reçu ou créé) sur lequel vous pouvez énumérer.




La principale différence entre "IEnumerable" et "IQueryable" concerne l'endroit où la logique de filtrage est exécutée. 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 utilisateurs actifs, donc dans ce cas si nous utilisons "IEnumerable" alors d'abord il charge tous les 10 000 enregistrements en mémoire et puis applique le filtre IsActive qui renvoie finalement les 900 utilisateurs actifs.

Tandis que d'un autre côté 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.

Link référence




Beaucoup a déjà été dit, mais de retour aux sources, d'une 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'itérer (ce qui est très facile dans foreach loop, bien que vous ne puissiez aller qu'avec IEnumerator ). Ils résident dans la mémoire en l'état.
  2. IQueryable est un arbre d'expression qui sera traduit en quelque chose d'autre à un moment donné avec la capacité 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 un arbre d'expression (une requête, simplement) qui sera traduit en quelque chose par le fournisseur de requêtes sous-jacent dès que les API de release sont appelées, comme les fonctions agrégées 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 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, comment ces expressions sont exécutées et vers quoi elles se tournent-elles? Tout dépend de ce que l'on appelle les fournisseurs de requêtes (les exécuteurs d'expressions que 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ête), les expressions IQueryable sont traduites en requêtes T-SQL natives. Nhibernate fait des choses similaires avec eux. Vous pouvez écrire votre propre code en suivant les concepts décrits dans LINQ: Création d'un lien IQueryable Provider , 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 objets IQueryable sont construits tout le long jusqu'à ce que nous les libérions explicitement et disons au système de les réécrire en 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 s'agit d'une fonctionnalité LINQ pour conserver le schéma d'arbre d'expression dans la mémoire et l'envoyer dans l'exécution uniquement à la demande, lorsque certaines API sont appelées contre la séquence (Count, ToList, etc.).

L'utilisation correcte des deux dépend fortement des tâches que vous faites face à l'affaire spécifique. Pour le modèle de référentiel bien connu, j'opte personnellement pour retourner IList , c'est-à-dire IEnumerable sur les listes (indexeurs et autres). Donc, c'est mon conseil d'utiliser IQueryable seulement dans les dépôts et IEnumerable n'importe où ailleurs dans le code. Ne pas dire à propos des problèmes de testabilité que IQueryable décompose et ruine le principe de séparation des préoccupations . Si vous renvoyez une expression depuis l'intérieur de dépôts, les consommateurs peuvent jouer avec la couche de persistance comme ils le souhaitent.

Un petit ajout au désordre :) (à partir d'une discussion dans les commentaires)) Aucun d'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 fait de cette façon) de considérer IEnumerables comme des collections en mémoire alors 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 provoque 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 dans la mémoire. Il va entrer si vous le faites, de toute façon, si ce n'est pas vide. IQueryables sont juste des requêtes, pas les données.




Je voudrais clarifier quelques points en raison de réponses apparemment contradictoires (entourant principalement IEnumerable).

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

(2) IQueryable et IEnumerable LINQ tentent tous deux d'effectuer 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 n'est 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ées, peut-être paresseusement) car il n'a pas de fournisseur de données abstrait. IQueryables s'appuie sur un fournisseur abstrait (comme LINQ-to-SQL), bien que cela puisse également être le fournisseur .NET en mémoire.

Exemple de cas d'utilisation

(a) Récupérer la liste des enregistrements comme IQueryable du contexte EF. (Aucun enregistrement n'est en mémoire.)

(b) Passez l' IQueryable à une vue dont le modèle est IEnumerable . ( IQueryable étend IEnumerable .)

(c) Itérer et accéder aux enregistrements, aux entités enfants et aux propriétés de l'ensemble de données à partir de la vue. (Peut causer des exceptions!)

Problèmes possibles

(1) Le chargement paresseux tentatives IEnumerable 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 (valeur par défaut) et vous tentez d'accéder à un objet (virtuel) associé avec un contexte de données expiré. Identique à (1).

(3) ensembles de résultats actifs multiples (MARS). Si vous itérez sur IEnumerable dans un bloc foreach( var record in resultSet ) et que vous tentez simultanément d'accéder à record.childEntity.childProperty , vous risquez de vous retrouver avec MARS en raison du chargement paresseux de l'ensemble de données et de l'entité relationnelle. Cela provoquera une exception si elle n'est pas activée dans votre chaîne de connexion.

Solution

  • J'ai trouvé 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 d'être bien compris et explicitement désiré.

Exécutez la requête et stockez les résultats en resultList = resultSet.ToList() Cela semble être le moyen le plus simple de s'assurer 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. Soit cela, soit vous pouvez désactiver les proxys d'entité et Include explicitement les entités connexes de votre DbSet .




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

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

Renvoyer un IEnumerable forcera automatiquement le moteur d'exécution à utiliser LINQ to Objects pour interroger votre collection.

Renvoyer un IQueryable (qui implémente IEnumerable, d'ailleurs) 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.).




En plus des 2 premières très bonnes réponses (par driis & par 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 déplacer 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, donc l'application reçoit des données rapidement.

Lorsque la requête est exécutée, IEnumerable charge toutes les données, et si nous avons besoin de le filtrer, le filtrage lui-même est effectué du 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 naviguer dans 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. 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, il peut être traité légèrement 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 seulement de certaines données filtrées, il est préférable d'utiliser IQueryable.






Related