web-dev-qa-db-fra.com

Quelle est la différence entre .ToList (), .AsEnumerable (), AsQueryable ()?

Je connais certaines différences entre LINQ to Entities et LINQ to Objects que le premier implémente IQueryable et le second implémente IEnumerable et la portée de ma question est comprise dans EF 5.

Ma question est quelle est la différence technique (s) de ces 3 méthodes? Je vois que dans beaucoup de situations, ils fonctionnent tous. Je vois aussi en utiliser des combinaisons comme .ToList().AsQueryable().

  1. Que veulent dire exactement ces méthodes?

  2. Y at-il un problème de performance ou quelque chose qui pourrait conduire à l’utilisation de l’un sur l’autre?

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

160
Amin Saqi

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

Que font ces méthodes?

AsEnumerable et AsQueryable exprimés ou convertis en IEnumerable ou IQueryable, respectivement. Je dis jette ou convertis avec une raison:

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

  • Lorsque l'objet source n'implémente pas l'interface cible, l'objet source est converti en un objet qui implémente l'interface cible. Ainsi, le type et le type à la compilation sont modifiés.

Laissez-moi montrer ceci avec quelques exemples. J'ai cette petite méthode qui rapporte le type à la compilation et le type réel d'un objet ( avec la permission 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 _Table<T>_ arbitraire, qui implémente 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 renvoyée, mais 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 resultats:

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

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

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

Le voilà. AsQueryable() a converti le tableau en EnumerableQuery , qui "représente une collection _IEnumerable<T>_ en tant que source de données _IQueryable<T>_". " (MSDN).

Quel en est l'usage?

AsEnumerable est fréquemment utilisé pour passer d'une implémentation IQueryable à LINQ en objets (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, par exemple, nous devons utiliser une de nos propres méthodes dans une requête, nous écrivons généralement quelque chose comme

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

ToList - qui convertit un _IEnumerable<T>_ en un _List<T>_ - est souvent utilisé à cette fin. L’utilisation de AsEnumerable par rapport à ToList présente l’avantage 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 contenues dans les instructions LINQ. Voir ici pour plus de détails: Dois-je vraiment utiliser AsQueryable () sur la collection? .

Note sur la toxicomanie!

AsEnumerable fonctionne comme une drogue. C'est une solution rapide, mais à un coût et cela ne résout pas le problème sous-jacent.

Dans de nombreuses réponses Stack Overflow, je vois des personnes appliquer AsEnumerable pour résoudre pratiquement tout problème lié à l'utilisation de méthodes non prises en charge dans les expressions LINQ. Mais le prix n'est pas toujours clair. Par exemple, si vous faites ceci:

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

... tout est parfaitement traduit en une instruction SQL qui filtre (Where) et des projets (Select). C'est-à-dire que la longueur et la largeur, respectivement, du jeu de résultats SQL sont réduites.

Supposons maintenant que les utilisateurs ne souhaitent voir que la partie date de 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 l'écriture). Ah, heureusement, il y a le correctif AsEnumerable:

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

Bien sûr, ça fonctionne, probablement. Mais il enregistre toute la table en mémoire, puis applique le filtre et les projections. Eh bien, la plupart des gens sont assez intelligents pour faire le Where en premier:

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

Mais toutes les colonnes sont extraites en premier et la projection est faite 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 nécessite juste un peu plus de connaissances ...)

Que ne font PAS ces méthodes?

Maintenant, une mise en garde importante. Quand tu fais

_context.Observations.AsEnumerable()
                    .AsQueryable()
_

vous obtiendrez l'objet source représenté par IQueryable. (Parce que les deux méthodes ne font que convertir et ne convertissent pas).

Mais quand tu fais

_context.Observations.AsEnumerable().Select(x => x)
                    .AsQueryable()
_

quel sera le résultat?

Select produit un WhereSelectEnumerableIterator. Il s'agit d'une classe .Net interne qui implémente IEnumerable, et non pas IQueryable. Donc, une conversion en un autre type a eu lieu et le AsQueryable suivant ne peut plus jamais retourner la source originale.

L'implication de ceci est que l'utilisation de AsQueryable est n'est pas un moyen d'injecter magiquement un fournisseur de requêtes avec ses fonctionnalités spécifiques dans un énumérable. Supposons que vous faites

_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 des instructions LINQ coupe définitivement la connexion avec le fournisseur de requêtes du framework d'entité.

Je montre volontairement cet exemple parce que j'ai vu des questions ici où des personnes tentent par exemple d'injecter des capacités Include dans une collection en appelant AsQueryable. Il compile et s'exécute, mais il ne fait rien car l'objet sous-jacent n'a plus d'implémentation Include.

Implémentations spécifiques

Jusqu'ici, il ne s'agissait que des méthodes d'extension Queryable.AsQueryable et Enumerable.AsEnumerable . Mais bien sûr, n'importe qui peut écrire des méthodes d'instance ou d'extension avec les mêmes noms (et fonctions).

En fait, un exemple courant d'une méthode d'extension AsEnumerable spécifique est DataTableExtensions.AsEnumerable . DataTable n'implémente pas IQueryable ni IEnumerable, les méthodes d'extension standard ne s'appliquent donc pas.

318
Gert Arnold

ToList ()

  • Exécuter la requête immédiatement

AsEnumerable ()

  • paresseux (exécuter la requête plus tard)
  • Paramètre: Func<TSource, bool>
  • Chargez EVERY enregistrez dans la mémoire de l'application, puis gérez/filtrez-les. (par exemple, Où/Take/Skip, il sélectionnera * dans la mémoire de table1, puis sélectionnera les premiers éléments X) (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 d’IQueryable pour obtenir une requête efficace.
  • Ne chargez pas tous les enregistrements, par exemple si Take (5), il générera une sélection top 5 * SQL en arrière-plan. Cela signifie que ce type est plus adapté à la base de données SQL. C'est pourquoi ce type offre généralement des performances supérieures et est recommandé pour les bases de données.
  • Donc, 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.
39
Xin

ToList () sera tout en mémoire et vous y travaillerez ensuite. donc, ToList (). where (appliquer un filtre) est exécuté localement. AsQueryable () exécutera tout à distance, c’est-à-dire qu’un filtre est envoyé à la base de données pour application. Queryable ne fait rien jusqu'à ce que vous l'exécutiez. ToList, cependant s'exécute immédiatement.

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

EDIT: De même, dans votre cas, une fois que vous avez fait ToList (), chaque opération suivante est locale, y compris AsQueryable (). Vous ne pouvez pas passer à distance une fois que vous commencez à exécuter localement. J'espère que cela le rend un peu plus clair.

12
ashutosh raina

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

Fixe 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 si possible, essayez de ne pas être utilisé comme IEnumerable.

Mettre à jour . Il peut être encore simplifié en une seule expression, merci Gert Arnold .

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