87 votes

GroupBy côté client n'est pas supporté

J'ai la requête suivante d'Entity Framework Core 3.0 :

var units = await context.Units
  .SelectMany(y => y.UnitsI18N)
  .OrderBy(y => y.Name)
  .GroupBy(y => y.LanguageCode)
  .ToDictionaryAsync(y => y.Key, y => y.Select(z => z.Name));

Je reçois l'erreur suivante :

Client side GroupBy is not supported.

Pour exécuter la requête sur le client, ou une partie de celui-ci, je ferais ce qui suit :

var units = context.Units
  .SelectMany(y => y.UnitsI18N)
  .OrderBy(y => y.Name)
  .AsEnumerable()
  .GroupBy(y => y.LanguageCode)
  .ToDictionary(y => y.Key, y => y.Select(z => z.Name));

Maintenant, ça marche.

Pourquoi est-ce que je reçois cette erreur si je n'exécute pas la requête sur le client ?

0 votes

Je suppose que GroupBy s'est trompé. Même si vous n'écrivez que GroupBy dans la requête, vous obtenez la même erreur. Ma seule solution était également d'utiliser AsEnumerable() avant GroupBy().

0 votes

J'ai le même problème et j'ai rétrogradé vers dotnetcore 2.2 et .NetStandard 2.0 pour continuer à travailler. Il n'est pas logique de bloquer une fonctionnalité qui fonctionne. D'accord, cela pénalise les performances, mais je le sais et j'en ai besoin.

0 votes

@VanoMaisuradze Je pense que dans EF Core 3.0 il est toujours nécessaire d'utiliser une fonction comme MAX, AVG, ... avant le GroupBy. J'essaie de comprendre quelle est la meilleure façon de résoudre ce problème... En général, en SQL, il suffit de SELECTIONNER la colonne qui est utilisée dans le GroupBy pour que cela fonctionne ...

201voto

janw Points 5164

Il semble qu'il y ait une fausse idée commune sur ce que LINQ GroupBy et ce que fait SQL GROUP BY est capable de faire. Étant donné que je suis tombé dans le même piège et que j'ai dû me faire une raison récemment, j'ai décidé d'écrire une explication plus approfondie de cette question.


Réponse courte :

Le LINQ GroupBy est très différent à partir du SQL GROUP BY déclaration : LINQ juste divise la collection sous-jacente en morceaux en fonction d'une clé, tandis que le SQL ajoute applique une fonction d'agrégation pour condenser chacun de ces morceaux dans un valeur unique .

C'est pourquoi EF doit effectuer votre LINQ-kind GroupBy en mémoire.

Avant EF Core 3.0, on procédait ainsi implicitement EF a donc téléchargé toutes les lignes de résultats et a ensuite appliqué la méthode LINQ GroupBy . Cependant, ce comportement implicite pourrait laisser le programmeur s'attendre à ce que l'élément tout le site Les requêtes LINQ sont exécutées en SQL, avec un impact potentiellement énorme sur les performances lorsque l'ensemble de résultats est assez grand. Pour cette raison, l'évaluation implicite côté client de la requête GroupBy était désactivé complètement dans EF Core 3.0 .

Il est maintenant nécessaire d'appeler explicitement des fonctions telles que .AsEnumerable() o .ToList() qui téléchargent l'ensemble des résultats et poursuivent les opérations LINQ en mémoire.


Longue réponse :

Le tableau suivant solvedExercises sera l'exemple courant pour cette réponse :

+-----------+------------+
| StudentId | ExerciseId |
+-----------+------------+
|         1 |          1 |
|         1 |          2 |
|         2 |          2 |
|         3 |          1 |
|         3 |          2 |
|         3 |          3 |
+-----------+------------+

Un dossier X | Y dans ce tableau indique que l'étudiant X a résolu l'exercice Y .

Dans la question, un cas courant d'utilisation de la fonction LINQ GroupBy est décrite : Prenez une collection et regroupez-la en chunks, où les lignes de chaque chunk partagent une clé commune.

Dans notre exemple, nous pourrions vouloir obtenir une Dictionary<int, List<int>> qui contient une liste d'exercices résolus pour chaque étudiant. Avec LINQ, c'est très simple :

var result = solvedExercises
    .GroupBy(e => e.StudentId)
    .ToDictionary(e => e.Key, e => e.Select(e2 => e2.ExerciseId).ToList());

Sortie (pour le code complet, voir dotnetfiddle ) :

Student #1: 1 2 
Student #2: 2 
Student #3: 1 2 3 

Ceci est facile à représenter avec les types de données C#, puisque nous pouvons imbriquer les éléments suivants List y Dictionary aussi profond que nous le souhaitons.

Essayons maintenant de l'imaginer comme un résultat de requête SQL. Les résultats des requêtes SQL sont généralement représentés sous la forme d'un tableau, dans lequel nous pouvons choisir librement les colonnes renvoyées. Pour représenter notre requête ci-dessus en tant que résultat de requête SQL, nous devrions

  • générer plusieurs tableaux de résultats,
  • mettre les lignes groupées dans un tableau ou
  • d'une manière ou d'une autre, insérer un "séparateur d'ensembles de résultats".

Pour autant que je sache, aucune de ces approches n'est mise en œuvre dans la pratique. Tout au plus, il existe des solutions de fortune comme la méthode de MySQL GROUP_CONCAT qui permet de combiner les lignes de résultats en une chaîne de caractères ( réponse SO pertinente ).

Ainsi nous voyons, que SQL ne peut pas donnent des résultats qui correspondent à la notion de LINQ de GroupBy .

Au lieu de cela, SQL ne permet que les soi-disant agrégation : Si nous voulons, par exemple, compter combien d'exercices ont été réussis par un élève, nous écrirons

SELECT StudentId,COUNT(ExerciseId)
FROM solvedExercises
GROUP BY StudentId

...ce qui donnera

+-----------+-------------------+
| StudentId | COUNT(ExerciseId) |
+-----------+-------------------+
|         1 |                 2 |
|         2 |                 1 |
|         3 |                 3 |
+-----------+-------------------+

Les fonctions d'agrégation réduisent un ensemble de lignes en une seule valeur, généralement un scalaire. Les exemples sont le nombre de lignes, la somme, la valeur maximale, la valeur minimale et la moyenne.

Ce site es mis en œuvre par EF Core : Exécution de

var result = solvedExercises
    .GroupBy(e => e.StudentId)
    .Select(e => new { e.Key, Count = e.Count() })
    .ToDictionary(e => e.Key, e => e.Count);

génère le SQL ci-dessus. Notez le Select qui indique à EF quel fonction d'agrégation qu'il doit utiliser pour la requête SQL générée.


En résumé, le système LINQ GroupBy est beaucoup plus générale que la fonction SQL GROUP BY qui, en raison des restrictions de SQL, ne permet de renvoyer qu'un seul tableau de résultats à deux dimensions. Ainsi, les requêtes comme celle de la question et le premier exemple de cette réponse doivent être évaluées en mémoire, après avoir téléchargé le jeu de résultats SQL.

Au lieu de implicitement Pour ce faire, dans EF Core 3.0 les développeurs a choisi de lancer une exception dans ce cas ; cela permet d'éviter le téléchargement accidentel d'une table entière, potentiellement importante, comportant des millions de lignes, qui pourrait passer inaperçue pendant le développement en raison d'une petite base de données de test.

42voto

Thanh Nguyen Points 36

Votre .GroupBy(y => y.LanguageCode).ToDictionaryAsync(y => y.Key, y => y.Select(z => z.Name)); ne peuvent pas être convertis en SQL. EF Core 3.0 lèvera une exception pour s'assurer que vous savez que tous les enregistrements dans le fichier Units seront récupérés de la base de données avant d'être groupés et mis en correspondance avec le dictionnaire.

C'est un changement majeur dans EF Core 3.0. https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes

10 votes

Votre réponse est bonne, mais de EF Core, sérieusement ? Ma table a des millions d'entrées, je ne vais pas les charger en premier sur le client. Pas de solution de rechange ?

1 votes

Solution : J'ai créé une vue avec des données groupées et j'ai créé une nouvelle entité et dans EF 3 vous pouvez utiliser modelBuilder.Entity<Transaction>.ToView("VM_Transactions") ;

0 votes

Et si nous essayons d'utiliser un ToList sur le IQueryable et ensuite un GroupBy ?

-1voto

balon Points 205

Une solution possible (qui fonctionne pour moi) est de faire en sorte que l'option GroupBy sur un List objet.

var units = (
  await context.Units
  .SelectMany(y => y.UnitsI18N)
  .GroupBy(y => y.LanguageCode)
  .ToDictionaryAsync(y => y.Key, y => y.Select(z => z.Name))
  ).ToList().OrderBy(y => y.Name);

4 votes

Dans cette réponse, le GroupBy est toujours exécuté sur la requête.

-2voto

Mirac Bektas Points 1
var test = unitOfWork.PostCategory.GetAll().Include(u=>u.category).GroupBy(g => g.category.name).Select(s => new
                {

                    name = s.Key,
                    count = s.Count()

                }).OrderBy(o=>o.count).ToList();

vous pouvez essayer cette partie de code ... il fonctionne ... J'ai essayé

-3voto

carlin.scott Points 145

La méthode GroupBy de linq peut faire des choses qu'une requête de base de données ne peut pas faire. C'est pourquoi linq lève l'exception. Il ne s'agit pas d'une fonctionnalité manquante, mais dans les anciennes versions de linq, il suffisait d'énumérer l'ensemble de la table, puis d'exécuter le GroupBy localement.

Il se trouve que la syntaxe de requête Linq possède une fonction group mot-clé qui peut être traduit en une requête de base de données.

Voici un exemple qui fonctionne bien et qui montre comment exécuter votre requête sur la base de données en utilisant la syntaxe de requête :

var kvPairs = from y in context.Units
              from u in y.UnitsI18N
              orderby u.Name
              group u by u.LanguageCode into g
              select new KeyValuePair<string,IEnumerable<string>>(g.Key, g.Select(z => z.Name));

return new Dictionary<string,IEnumerable<string>>>(kvPairs);

Voir cet article de Microsoft pour plus d'informations : https://docs.microsoft.com/en-us/ef/core/querying/complex-query-operators#groupby

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X