Il y a beaucoup à dire à ce sujet. Permettez - moi de mettre l' accent sur AsEnumerableet AsQueryableet mentionner le ToList()long du chemin.
Que font ces méthodes?
AsEnumerableet AsQueryablecast ou convertir en IEnumerableou IQueryable, respectivement. Je dis lancer ou convertir avec une raison:
Lorsque l'objet source implémente déjà l'interface cible, l'objet source lui-même est renvoyé mais 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 au moment de la compilation sont modifiés.
Laissez-moi vous montrer cela avec quelques exemples. J'ai cette petite méthode qui rapporte le type de 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 arbitraire Table<T>, 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 un EnumerableQuery, qui "représente unIEnumerable<T> collection en tant que IQueryable<T>source de données». (MSDN).
Quel en est l'usage?
AsEnumerableest fréquemment utilisé pour passer de toute IQueryableimplémentation à LINQ vers des objets (L2O), principalement parce que le premier ne prend pas en charge les fonctions de 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 limité de méthodes. Donc, si, par exemple, nous devons utiliser l'une de nos propres méthodes dans une requête, nous écrirons 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 a List<T>- est également souvent utilisé à cette fin. L'avantage d'utiliser AsEnumerablevs. ToListest qu'il AsEnumerablen'exécute pas la requête.AsEnumerablepré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.
AsQueryablepeut être utilisé pour qu'une collection énumérable accepte des expressions dans des instructions LINQ. Voir ici pour plus de détails: Ai-je vraiment besoin d'utiliser AsQueryable () sur la collecte? .
Remarque sur la toxicomanie!
AsEnumerablefonctionne comme une drogue. C'est une solution rapide, mais à un coût et ne résout pas le problème sous-jacent.
Dans de nombreuses réponses Stack Overflow, je vois des personnes postuler AsEnumerablepour résoudre à peu près n'importe quel problème avec des 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 proprement traduit en une instruction SQL qui filtre ( Where) et projette ( Select). Autrement dit, 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 la rédaction). Ah, heureusement, il y a le AsEnumerablecorrectif:
context.MyLongWideTable.AsEnumerable()
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate.Date })
Bien sûr, il fonctionne, probablement. Mais il tire la table entière en mémoire et applique ensuite le filtre et les projections. Eh bien, la plupart des gens sont assez intelligents pour faire le Wherepremier:
context.MyLongWideTable
.Where(x => x.Type == "type").AsEnumerable()
.Select(x => new { x.Name, x.CreateDate.Date })
Cependant, toutes les colonnes sont récupérées en premier et la projection est effectuée 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?
Restaurer les capacités IQueryable
Maintenant une mise en garde importante. Quand tu fais
context.Observations.AsEnumerable()
.AsQueryable()
vous vous retrouverez avec l'objet source représenté par IQueryable. (Parce que les deux méthodes ne font que lancer et ne pas convertir)
Mais quand tu fais
context.Observations.AsEnumerable().Select(x => x)
.AsQueryable()
quel sera le résultat?
Le Selectproduit un WhereSelectEnumerableIterator. Il s'agit d'une classe .Net interne qui implémente IEnumerable, nonIQueryable . Ainsi, une conversion vers un autre type a eu lieu et la suiteAsQueryable ne peut plus jamais retourner la source d'origine.
L'implication de cela est que l'utilisation AsQueryablen'est pas un moyen d'injecter par magie un fournisseur de requêtes avec ses fonctionnalités spécifiques dans un énumérable. Supposez 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 d'instructions LINQ coupe définitivement la connexion avec le fournisseur de requêtes de structure d'entité.
Je montre délibérément cet exemple parce que j'ai vu des questions ici où les gens, par exemple, essaient d'injecter des Includecapacités dans une collection en appelant AsQueryable. Il se compile et s'exécute, mais il ne fait rien car l'objet sous-jacent n'a pas deInclude implémentation.
Exécuter
Les deux AsQueryableet AsEnumerablen'exécutent pas (ou n'énumèrent ) pas l'objet source. Ils changent uniquement de type ou de représentation. Les deux interfaces impliquées, IQueryableet IEnumerable, ne sont rien d'autre qu'une "énumération en attente de se produire". Ils ne sont pas exécutés avant d'être obligés de le faire, par exemple, comme mentionné ci-dessus, en appelant ToList().
Cela signifie que l'exécution d'un IEnumerableobtenu en appelant AsEnumerableun IQueryableobjet exécutera le sous-jacent IQueryable. Une exécution ultérieure du IEnumerableexécutera à nouveau le IQueryable. Ce qui peut être très coûteux.
Implémentations spécifiques
Jusqu'à présent, il ne s'agissait que des méthodes d'extension Queryable.AsQueryableet Enumerable.AsEnumerable. Mais bien sûr, n'importe qui 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 de AsEnumerableméthode d'extension spécifique est DataTableExtensions.AsEnumerable. DataTablen'implémente pas IQueryableou IEnumerable, donc les méthodes d'extension habituelles ne s'appliquent pas.