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()
.
Que veulent dire exactement ces méthodes?
Y at-il un problème de performance ou quelque chose qui pourrait conduire à l’utilisation de l’un sur l’autre?
Pourquoi utiliserait-on, par exemple, .ToList().AsQueryable()
au lieu de .AsQueryable()
?
Il y a beaucoup à dire à ce sujet. Permettez-moi de me concentrer sur AsEnumerable
et AsQueryable
et de mentionner ToList()
en cours de route.
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).
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? .
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 ...)
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
.
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.
ToList ()
AsEnumerable ()
Func<TSource, bool>
AsQueryable ()
Expression<Func<TSource, bool>>
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.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.
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();