web-dev-qa-db-fra.com

Comprendre .AsEnumerable () dans LINQ to SQL

Étant donné la requête LINQ to SQL suivante:

var test = from i in Imports
           where i.IsActive
           select i;

L'instruction SQL interprétée est:

SELECT [t0].[id] AS [Id] .... FROM [Imports] AS [t0] WHERE [t0].[isActive] = 1

Disons que je voulais effectuer une action dans la sélection qui ne peut pas être convertie en SQL. Je crois comprendre que la manière conventionnelle d'y parvenir est de faire AsEnumerable(), le convertissant ainsi en un objet réalisable.

Compte tenu de ce code mis à jour:

var test = from i in Imports.AsEnumerable()
           where i.IsActive
           select new 
           { 
               // Make some method call 
           };

Et SQL mis à jour:

SELECT [t0].[id] AS [Id] ... FROM [Imports] AS [t0] 

Notez l'absence d'une clause where dans l'instruction SQL exécutée.

Est-ce à dire que l'intégralité de la table "Imports" est mise en cache dans la mémoire? Est-ce que cela ralentirait les performances si la table contenait une grande quantité d'enregistrements?

Aidez-moi à comprendre ce qui se passe réellement dans les coulisses ici.

44
Mike Fielden

La raison de AsEnumerable est de

AsEnumerable (TSource) (IEnumerable (TSource)) peut être utilisé pour choisir entre les implémentations de requête lorsqu'une séquence implémente IEnumerable (T) mais dispose également d'un ensemble différent de méthodes de requête publiques disponibles

Ainsi, lorsque vous appeliez la méthode Where auparavant, vous appeliez une méthode Where différente de la IEnumerable.Where. Cette instruction Where était destinée à la conversion de LINQ en SQL, la nouvelle Where est la IEnumerable qui prend un IEnumerable, l'énumère et donne les éléments correspondants . Ce qui explique pourquoi vous voyez les différents SQL générés. Le tableau sera entièrement extrait de la base de données avant que l'extension Where soit appliquée dans votre deuxième version du code. Cela pourrait créer un goulot d'étranglement sérieux, car toute la table doit être en mémoire, ou pire, la table entière devrait voyager entre les serveurs. Autorisez SQL Server à exécuter le Where et faites ce qu'il fait le mieux.

34
Yuriy Faktorovich

Au point où l'énumération est énumérée, la base de données sera alors interrogée et l'ensemble des résultats récupéré.

Une solution partielle peut être la solution. Considérer

var res = (
    from result in SomeSource
    where DatabaseConvertableCriterion(result)
    && NonDatabaseConvertableCriterion(result)
    select new {result.A, result.B}
);

Disons également que NonDatabaseConvertableCriterion nécessite le champ C du résultat. Étant donné que NonDatabaseConvertableCriterion fait ce que son nom suggère, cela doit être effectué comme une énumération. Cependant, considérez:

var partWay =
(
    from result in SomeSource
    where DatabaseConvertableCriterion(result)
    select new {result.A, result.B, result.C}
);
var res =
(
    from result in partWay.AsEnumerable()
    where NonDatabaseConvertableCriterion select new {result.A, result.B}
);

Dans ce cas, lorsque res est énuméré, interrogé ou utilisé d'une autre manière, autant de travail que possible sera transmis à la base de données, qui renverra suffisamment pour continuer le travail. En supposant qu'il est vraiment impossible de réécrire pour que tout le travail puisse être envoyé à la base de données, cela peut être un compromis approprié.

6
Jon Hanna

Il existe trois implémentations de AsEnumerable.

DataTableExtensions.AsEnumerable

Étend un DataTable pour lui donner une interface IEnumerable afin que vous puissiez utiliser Linq contre le DataTable.

Enumerable.AsEnumerable<TSource> et ParallelEnumerable.AsEnumerable<TSource>

La méthode AsEnumerable<TSource>(IEnumerable<TSource>) n'a d'autre effet que de changer le type de source au moment de la compilation d'un type qui implémente IEnumerable<T> En IEnumerable<T> Lui-même.

AsEnumerable<TSource>(IEnumerable<TSource>) peut être utilisé pour choisir entre les implémentations de requête lorsqu'une séquence implémente IEnumerable<T> mais dispose également d'un ensemble différent de méthodes de requête publiques disponibles. Par exemple, étant donné une classe générique Table qui implémente IEnumerable<T> Et a ses propres méthodes telles que Where, Select et SelectMany, un appel à Where invoquerait la méthode publique Where de Table. Un type Table qui représente une table de base de données pourrait avoir une méthode Where qui prend l'argument prédicat comme une arborescence d'expression et convertit l'arborescence en SQL pour une exécution à distance. Si l'exécution à distance n'est pas souhaitée, par exemple parce que le prédicat appelle une méthode locale, la méthode AsEnumerable<TSource> Peut être utilisée pour masquer les méthodes personnalisées et rendre à la place les opérateurs de requête standard disponibles.

En d'autres termes.

Si j'ai un

IQueryable<X> sequence = ...;

d'un LinqProvider, comme Entity Framework, et je le fais,

sequence.Where(x => SomeUnusualPredicate(x));

cette requête sera composée et exécutée sur le serveur. Cela échouera lors de l'exécution car EntityFramework ne sait pas comment convertir SomeUnusualPredicate en SQL.

Si je veux plutôt exécuter l'instruction avec Linq to Objects, je le fais,

sequence.AsEnumerable().Where(x => SomeUnusualPredicate(x));

maintenant le serveur renverra toutes les données et le Enumerable.Where de Linq vers Objects sera utilisé à la place de l'implémentation du fournisseur de requête.

Peu importe qu'Entity Framework ne sache pas interpréter SomeUnusualPredicate, ma fonction sera utilisée directement. (Cependant, cela peut être une approche inefficace car toutes les lignes seront renvoyées par le serveur.)

5
Jodrell

Je crois que AsEnumerable indique simplement au compilateur les méthodes d'extension à utiliser (dans ce cas, celles définies pour IEnumerable au lieu de celles pour IQueryable). L'exécution de la requête est toujours différée jusqu'à ce que vous appeliez ToArray ou que vous l'énumériez.

2
Razvi