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.
5 votes
stackoverflow.com/questions/3628425/ et stackoverflow.com/questions/1106802/
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) ; }