Il y a beaucoup à dire à ce sujet. Permettez - moi de mettre l' accent sur AsEnumerable
et AsQueryable
et mentionner le ToList()
long du chemin.
Que font ces méthodes?
AsEnumerable
et AsQueryable
cast ou convertir en IEnumerable
ou 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?
AsEnumerable
est fréquemment utilisé pour passer de toute IQueryable
implé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 AsEnumerable
vs. ToList
est qu'il 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 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!
AsEnumerable
fonctionne 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 AsEnumerable
pour 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 AsEnumerable
correctif:
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 Where
premier:
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 Select
produit 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 AsQueryable
n'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 Include
capacité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 AsQueryable
et AsEnumerable
n'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, IQueryable
et 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 IEnumerable
obtenu en appelant AsEnumerable
un IQueryable
objet exécutera le sous-jacent IQueryable
. Une exécution ultérieure du IEnumerable
exé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.AsQueryable
et 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 AsEnumerable
méthode d'extension spécifique est DataTableExtensions.AsEnumerable
. DataTable
n'implémente pas IQueryable
ou IEnumerable
, donc les méthodes d'extension habituelles ne s'appliquent pas.