Question Renvoyer IEnumerable par rapport à IQueryable


Quelle est la différence entre retourner IQueryable<T> contre. 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?


898
2018-05-20 18:13


origine


Réponses:


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

La différence est que IQueryable<T> est l'interface qui permet à LINQ-to-SQL (LINQ.-to-anything) de fonctionner. Donc, si vous affinez 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> Dans le cas contraire, 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);

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


1522
2018-05-20 18:19



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.

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

  • le IQueryable la signature de la version 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>") travaille sur les deux IEnumerable et IQueryable

  • En utilisant Where() sur un IEnumerable collection, le compilateur transmet une fonction compilée à Where()

  • En utilisant Where() sur un IQueryable collection, le compilateur passe 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 coup de pouce 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 un régulier List à IQueryable ne vous donnera aucun avantage.


195
2018-02-14 08:00



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:

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 contre une table WebLog qui a 1 million d'enregistrements.

Ainsi, tous les enregistrements de table sont chargés dans la mémoire en tant qu'objets, puis avec chaque .Where (), il y aura un autre filtre en mémoire contre 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'arbre qui commence à être créé lorsque nous avons utilisé le 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.


56
2018-06-08 01:11



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


54
2018-05-20 18:18



En général, vous souhaitez conserver le type statique d'origine 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 changez pas le type.

Si vous commencez avec un IQueryable<>, vous voulez généralement le garder comme un IQueryable<> jusqu'à ce qu'il y ait une raison impérieuse de le changer. La raison en est que vous voulez 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 voulez que SQL Server le sache afin qu'il puisse optimiser ses plans de requête et ne vous envoyer que 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 la mise en œuvre de IQueryable<> dans votre objet particulier ne peut pas gérer ou gère inefficacement. Dans ce cas, vous pouvez convertir le type en IEnumerable<> (en affectant à une variable de type IEnumerable<> ou en utilisant le AsEnumerable méthode d'extension par exemple) pour que les fonctions d'extension que vous appelez finissent par être celles du Enumerable classe au lieu de la Queryable classe.


23
2018-05-15 00:00



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.


21
2018-04-11 13:09



Il y a un billet de blog avec un bref échantillon de code source sur la façon dont l'abus de IEnumerable<T> peut considérablement affecter les performances des requêtes LINQ: Entity Framework: IQueryable par rapport à IEnumerable.

Si nous creusons plus profond et regardons dans les sources, nous pouvons voir qu'il y a évidemment différentes méthodes d'extension sont réalisées 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 renvoie un itérateur énumérable et le second crée une requête via le fournisseur de requête, spécifié dans IQueryable la source.


17
2018-04-23 22:45



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 (rend facile pour l'intérieur foreach boucle, même si vous pouvez aller avec IEnumerator seulement). 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 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 un arbre d'expression (une requête, simplement) qui sera traduit en autre 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 IQueryable les objets mettent également en œuvre IEnumerable, IEnumerable<T> pour 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 des requêtes. Le bon terme est qu'ils sont 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écutants d'expression que nous pouvons penser).

dans le Cadre d'entité monde (qui est ce fournisseur de source de données mystique sous-jacent, ou le fournisseur de requête) IQueryable les expressions sont traduites en natif T-SQL requêtes. Nhibernate fait des choses similaires avec eux. Vous pouvez écrire votre propre suivant les concepts assez bien décrits dans LINQ: Création d'un fournisseur IQueryable lien, 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 en gros, IQueryable Les objets sont construits tout le long jusqu'à ce que nous les libérions explicitement et demandons 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 si différé l'exécution c'est un LINQ fonctionnalité pour maintenir le schéma d'arbre d'expression dans la mémoire et l'envoyer dans l'exécution seulement sur demande, chaque fois que certaines API sont appelées contre la séquence (les mêmes 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 le retour IList, C'est IEnumerable sur les listes (indexeurs et similaires). 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é IQueryable se décompose et ruine le séparation des préoccupations principe. 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'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 cela a du sens (et c'est pourquoi même MSDN Disons-le 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.


16
2018-06-05 16:23



J'ai récemment rencontré un problème avec IEnumrable v. IQueryable. L'algorithme utilisé a d'abord exécuté une requête IQueryable pour obtenir un ensemble de résultats. Ceux-ci ont ensuite été passés à une boucle foreach, avec les éléments instanciés comme une classe EF. Cette classe EF a ensuite été utilisée dans la clause from d'une requête Linq to Entity, provoquant le résultat à IEnumerable. Je suis relativement nouveau pour EF et Linq pour les entités, il a donc fallu un certain temps pour comprendre le goulot d'étranglement. À 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 secondes pour s'exécuter. Il y avait trois tables impliquées et, après avoir lu ceci, je crois que la requête IEnumerable formait réellement un produit croisé de trois tables et filtrait les résultats.

Essayez d'utiliser IQueryables comme une règle de base et de profiler votre travail pour rendre vos changements mesurables.


9
2017-07-12 20:39



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

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

(2) Les deux IQueryable et IEnumerable LINQ tente 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ées, peut-être paresseusement) parce qu'il n'a pas de fournisseur de données abstraites. IQueryables compter sur un fournisseur abstrait (comme LINQ-to-SQL), bien que cela puisse aussi être le fournisseur .NET en mémoire.

Exemple de cas d'utilisation

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

(b) Passez le IQueryable à une vue dont le modèle est IEnumerable. (Valide. IQueryable s'é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 IEnumerable tentatives de 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 (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 au cours de la IEnumerable dans un foreach( var record in resultSet ) bloquer et tenter simultanément d'accéder record.childEntity.childProperty, vous pouvez vous retrouver avec MARS en raison d'un 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écuter la requête et stocker les résultats en invoquant 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, ou vous pouvez désactiver les proxys d'entité et explicitement Include entités liées de votre DbSet.


8
2018-01-22 20:56



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 il charge d'abord 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.

Référence Lien


7
2018-05-09 12:03