220 votes

Quelle(s) différence(s) y a-t-il entre .ToList(), .AsEnumerable(), AsQueryable() ?

Je connais quelques différences entre LINQ to Entities et LINQ to Objects, que le premier met en œuvre. IQueryable et le second met en œuvre IEnumerable et le champ de mes questions se situe dans le cadre de EF 5.

Ma question est la suivante : quelles sont les différences techniques entre ces trois méthodes ? Je constate que dans de nombreuses situations, elles fonctionnent toutes. Je vois aussi des combinaisons de ces méthodes, comme .ToList().AsQueryable() .

  1. Que signifient ces méthodes, exactement ?

  2. Y a-t-il un problème de performance ou quelque chose qui conduirait à utiliser l'un plutôt que l'autre ?

  3. Pourquoi utiliserait-on, par exemple, .ToList().AsQueryable() au lieu de .AsQueryable() ?

0 votes

Void ReportTypeProperties<T>(T obj) { Console.WriteLine("Compile-time type : {0}", typeof(T).Name) ; Console.WriteLine("Type réel : {0}", obj.GetType().Name) ; }

453voto

Gert Arnold Points 27642

Il y a beaucoup de choses à dire à ce sujet. Permettez-moi de me concentrer sur AsEnumerable et AsQueryable et mentionner ToList() en cours de route.

Que font ces méthodes ?

AsEnumerable et AsQueryable couler ou convertir en IEnumerable ou IQueryable respectivement. Je dis couler ou convertir avec une raison :

  • Lorsque l'objet source implémente déjà l'interface cible, l'objet source lui-même est retourné mais moulage à l'interface cible. En d'autres termes, le type n'est pas modifié, mais le type à la compilation l'est.

  • Lorsque l'objet source ne met pas en œuvre l'interface cible, l'objet source est converti en un objet qui implémente l'interface cible. Ainsi, le type et le type au moment de la compilation sont tous deux modifiés.

Laissez-moi vous le montrer avec quelques exemples. J'ai cette petite méthode qui indique le type au moment de la compilation et le type réel d'un objet ( courtoisie de Jon Skeet ) :

void ReportTypeProperties<T>(T obj)
{
    Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
    Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}

Essayons un linq-to-sql arbitraire Table<T> qui met en œuvre IQueryable :

ReportTypeProperties(context.Observations);
ReportTypeProperties(context.Observations.AsEnumerable());
ReportTypeProperties(context.Observations.AsQueryable());

Le résultat :

Compile-time type: Table`1
Actual type: Table`1

Compile-time type: IEnumerable`1
Actual type: Table`1

Compile-time type: IQueryable`1
Actual type: Table`1

Vous voyez que la classe de table elle-même est toujours retournée, mais que sa représentation change.

Maintenant, un objet qui implémente IEnumerable pas IQueryable :

var ints = new[] { 1, 2 };
ReportTypeProperties(ints);
ReportTypeProperties(ints.AsEnumerable());
ReportTypeProperties(ints.AsQueryable());

Les résultats :

Compile-time type: Int32[]
Actual type: Int32[]

Compile-time type: IEnumerable`1
Actual type: Int32[]

Compile-time type: IQueryable`1
Actual type: EnumerableQuery`1

C'est là. AsQueryable() a converti le tableau en un EnumerableQuery qui "représente un IEnumerable<T> comme une IQueryable<T> source de données". (MSDN).

A quoi ça sert ?

AsEnumerable est fréquemment utilisé pour passer de n'importe quel IQueryable à LINQ to objects (L2O), principalement parce que la première ne prend pas en charge les fonctions dont dispose L2O. Pour plus de détails, voir Quel est l'effet de AsEnumerable() sur une entité LINQ ? .

Par exemple, dans une requête Entity Framework, nous ne pouvons utiliser qu'un nombre restreint de méthodes. Ainsi, si nous avons besoin d'utiliser une de nos propres méthodes dans une requête, nous écrirons typiquement quelque chose comme

var query = context.Observations.Select(o => o.Id)
                   .AsEnumerable().Select(x => MySuperSmartMethod(x))

ToList - qui convertit un IEnumerable<T> à un List<T> - est souvent utilisé à cette fin également. L'avantage d'utiliser AsEnumerable vs. ToList c'est que AsEnumerable n'exécute pas la requête. AsEnumerable préserve l'exécution différée et ne construit pas une liste intermédiaire souvent inutile.

D'autre part, lorsque l'exécution forcée d'une requête LINQ est souhaitée, ToList peut être un moyen de le faire.

AsQueryable peut être utilisé pour qu'une collection énumérable accepte les expressions dans les déclarations LINQ. Voir ici pour plus de détails : Ai-je vraiment besoin d'utiliser AsQueryable() sur une collection ? .

Note sur la toxicomanie !

AsEnumerable fonctionne comme une drogue. C'est une solution rapide, mais elle a un coût et elle ne s'attaque pas au problème sous-jacent.

Dans de nombreuses réponses à Stack Overflow, je vois des personnes qui appliquent les règles suivantes AsEnumerable pour résoudre à peu près tous les problèmes liés aux méthodes non prises en charge dans les expressions LINQ. Mais le prix n'est pas toujours clair. Par exemple, si vous faites ça :

context.MyLongWideTable // A table with many records and columns
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, x.CreateDate })

...tout est proprement traduit en une instruction SQL qui filtres ( Where ) et projets ( Select ). En d'autres termes, la longueur et la largeur, respectivement, de l'ensemble de résultats SQL sont réduites.

Supposons maintenant que les utilisateurs ne veulent voir que la partie date de l'écran. CreateDate . Dans Entity Framework, vous découvrirez rapidement que...

.Select(x => new { x.Name, x.CreateDate.Date })

...n'est pas pris en charge (au moment de la rédaction). Ah, heureusement, il y a le AsEnumerable fixer :

context.MyLongWideTable.AsEnumerable()
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, x.CreateDate.Date })

Bien sûr, il fonctionne, probablement. Mais il met en mémoire la table entière et applique ensuite le filtre et les projections. Eh bien, la plupart des gens sont assez intelligents pour faire le Where d'abord :

context.MyLongWideTable
       .Where(x => x.Type == "type").AsEnumerable()
       .Select(x => new { x.Name, x.CreateDate.Date })

Mais toutes les colonnes sont toujours récupérées en premier et la projection se fait en mémoire.

La vraie solution est :

context.MyLongWideTable
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) })

(Mais cela demande un peu plus de connaissances...)

Que ne font PAS ces méthodes ?

Restaurer les capacités de IQueryable

Maintenant, une mise en garde importante. Lorsque vous faites

context.Observations.AsEnumerable()
                    .AsQueryable()

vous vous retrouverez avec l'objet source représenté sous forme de IQueryable . (Parce que les deux méthodes ne font que couler et ne convertissent pas).

Mais quand vous le faites

context.Observations.AsEnumerable().Select(x => x)
                    .AsQueryable()

quel sera le résultat ?

Le site Select produit un WhereSelectEnumerableIterator . Il s'agit d'une classe interne .Net qui implémente IEnumerable , pas IQueryable . Ainsi, une conversion vers un autre type a eu lieu et l'opération suivante AsQueryable ne peut plus jamais retourner la source originale.

Il en résulte que l'utilisation de AsQueryable est pas un moyen d'injecter comme par magie un fournisseur de requêtes avec ses caractéristiques spécifiques dans un énumérable. Supposons que vous fassiez

var query = context.Observations.Select(o => o.Id)
                   .AsEnumerable().Select(x => x.ToString())
                   .AsQueryable()
                   .Where(...)

La condition where ne sera jamais traduite en SQL. AsEnumerable() suivi d'instructions LINQ coupe définitivement la connexion avec le fournisseur de requêtes du cadre d'entités.

Je montre délibérément cet exemple parce que j'ai vu des questions ici où les gens essaient par exemple d'injecter Include dans une collection en appelant AsQueryable . Il compile et s'exécute, mais il ne fait rien parce que l'objet sous-jacent n'a pas d'objet de type Include plus de mise en œuvre.

Exécuter

Les deux sites AsQueryable et AsEnumerable n'exécutent pas (ou énumérer ) l'objet source. Ils ne changent que leur type ou leur représentation. Dans les deux cas, il s'agit d'interfaces, IQueryable et IEnumerable ne sont rien d'autre qu'une "énumération en attente". Ils ne sont pas exécutés avant d'être forcés à le faire, par exemple, comme mentionné ci-dessus, en appelant ToList() .

Cela signifie que l'exécution d'un IEnumerable obtenu en appelant AsEnumerable sur un IQueryable exécutera l'objet sous-jacent IQueryable . Une exécution ultérieure de la IEnumerable exécutera à nouveau le IQueryable . Ce qui peut être très coûteux.

Mises en œuvre spécifiques

Jusqu'à présent, il ne s'agissait que de la Queryable.AsQueryable et Enumerable.AsEnumerable méthodes d'extension. Mais bien sûr, tout le monde peut écrire des méthodes d'instance ou des méthodes d'extension avec les mêmes noms (et fonctions).

En fait, un exemple courant d'une AsEnumerable La méthode d'extension est DataTableExtensions.AsEnumerable . DataTable ne met pas en œuvre IQueryable ou IEnumerable Les méthodes d'extension habituelles ne s'appliquent donc pas.

0 votes

Merci pour la réponse, pouvez-vous s'il vous plaît partager votre réponse à la 3ème question de l'OP -. 3. Pourquoi utiliser, par exemple, .ToList().AsQueryable() au lieu de .AsQueryable() ? ?

0 votes

@ikram Je ne vois pas en quoi cela pourrait être utile. Comme je l'ai expliqué, appliquer AsQueryable() est souvent basée sur des idées fausses. Mais je vais laisser mijoter cette idée dans un coin de ma tête pendant un moment et voir si je peux approfondir cette question.

1 votes

Excellente réponse. Pourriez-vous préciser ce qui se passe si l'IEnumerable obtenu en appelant AsEnumerable() sur un IQueryable est énuméré plusieurs fois ? La requête sera-t-elle exécutée plusieurs fois, ou les données déjà chargées en mémoire depuis la base de données seront-elles réutilisées ?

71voto

Xin Points 5528

ToList()

  • Exécuter la requête immédiatement

AsEnumerable()

  • paresseux (exécuter la requête plus tard)
  • Paramètre : Func<TSource, bool>
  • Chargement CHAQUE dans la mémoire de l'application, puis les traiter/filtrer. (par exemple, Where/Take/Skip, il sélectionnera * de table1, dans la mémoire, puis sélectionnera les X premiers éléments). (Dans ce cas, ce qu'il a fait : Linq-to-SQL + Linq-to-Object)

AsQueryable()

  • paresseux (exécuter la requête plus tard)
  • Paramètre : Expression<Func<TSource, bool>>
  • Convertissez l'expression en T-SQL (avec le fournisseur spécifique), interrogez à distance et chargez le résultat dans la mémoire de votre application.
  • C'est pourquoi DbSet (dans Entity Framework) hérite également de IQueryable pour obtenir une requête efficace.
  • Ne pas charger chaque enregistrement, par exemple, si Take(5), il générera select top 5 * SQL en arrière-plan. Cela signifie que ce type est plus convivial pour la base de données SQL, et c'est pourquoi ce type a généralement des performances plus élevées et est recommandé lorsqu'il s'agit d'une base de données.
  • Alors AsQueryable() fonctionne généralement beaucoup plus rapidement que AsEnumerable() car il génère d'abord T-SQL, qui inclut toutes vos conditions where dans votre Linq.

14voto

ashutosh raina Points 1936

ToList() va tout mettre en mémoire et ensuite vous allez travailler dessus. Ainsi, ToList().where ( appliquer un filtre ) est exécuté localement. AsQueryable() exécutera tout à distance, c'est-à-dire qu'un filtre sera envoyé à la base de données pour être appliqué. Queryable ne fait rien tant que vous ne l'exécutez pas. ToList, par contre, s'exécute immédiatement.

Regardez aussi cette réponse Pourquoi utiliser AsQueryable() au lieu de List() ? .

EDIT : De plus, dans votre cas, une fois que vous faites ToList(), toutes les opérations suivantes sont locales, y compris AsQueryable(). Vous ne pouvez pas passer en mode distant une fois que vous avez commencé à exécuter en local. J'espère que cela rend les choses un peu plus claires.

2 votes

"AsQueryable() exécutera tout à distance" Seulement si l'énumérable est déjà interrogeable. Sinon, ce n'est pas possible, et tout toujours s'exécute localement. La question contient "....ToList().AsQueryable()", ce qui pourrait être clarifié dans votre réponse, selon moi.

2voto

Rm558 Points 1286

J'ai rencontré une mauvaise performance sur le code ci-dessous.

void DoSomething<T>(IEnumerable<T> objects){
    var single = objects.First(); //load everything into memory before .First()
    ...
}

Fixé avec

void DoSomething<T>(IEnumerable<T> objects){
    T single;
    if (objects is IQueryable<T>)
        single = objects.AsQueryable().First(); // SELECT TOP (1) ... is used
    else
        single = objects.First();

}

Pour un IQueryable, restez dans IQueryable quand c'est possible, essayez de ne pas être utilisé comme IEnumerable.

Mise à jour . On peut encore le simplifier en une seule expression, grâce à Gert Arnold .

T single =  objects is IQueryable<T> q? 
                    q.First(): 
                    objects.First();

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X