Comment effectuer une jointure de groupe dans .NET Core 3.0 Entity Framework?

13

Avec les modifications apportées à .NET Core 3.0, je reçois

... Échec de NavigationExpandingExpressionVisitor. Cela peut indiquer un bogue ou une limitation dans EF Core. Voir https://go.microsoft.com/fwlink/?linkid=2101433 pour des informations plus détaillées.) ---> System.InvalidOperationException: Traitement de l'expression LINQ 'GroupJoin, ...

Il s'agit d'une requête très simple, il doit donc y avoir un moyen de l'exécuter dans .NET CORE 3.0:

 var queryResults1 = await patients
            .GroupJoin(
                _context.Studies,
                p => p.Id,
                s => s.Patient.Id,
                (p, studies) => new 
                {
                    p.DateOfBirth,
                    p.Id,
                    p.Name,
                    p.Sex,
                   Studies =studies.Select(s1=>s1)
                }
            )
            .AsNoTracking().ToListAsync();

Je recherche essentiellement une requête Linq (ou une syntaxe de méthode comme ci-dessus) qui joindra les études aux patients et définira les études sur une liste vide ou nulle s'il n'y a pas d'études pour le patient donné.

Des idées? Cela fonctionnait dans .NET Core 2.2. Le lien MSFT ci-dessus mentionne également que le changement de rupture de clé est lié à l'évaluation côté client et évite que la requête générée ne lit des tables entières qui doivent ensuite être jointes ou filtrées côté client. Cependant, avec cette simple requête, la jointure doit être facilement réalisable côté serveur.

Shelbypereira
la source

Réponses:

11

Comme indiqué ici , vous essayez une requête qui n'est pas prise en charge par la base de données. EF Core 2 a utilisé l'évaluation côté client pour faire fonctionner votre code, mais EF Core 3 refuse, car la commodité côté client se fait au prix de problèmes de performances difficiles à déboguer à mesure que l'ensemble de données augmente.

Vous pouvez utiliser use DefaultIfEmptypour rejoindre à gauche les études des patients puis les regrouper manuellement avec ToLookup.

var query =
    from p in db.Patients
    join s in db.Studies on p.Id equals s.PatientId into studies
    from s in studies.DefaultIfEmpty()
    select new { Patient = p, Study = s };

var grouping = query.ToLookup(e => e.Patient); // Grouping done client side

L'exemple ci-dessus récupère les entités patient et étude complètes, mais vous pouvez choisir des colonnes à la place. Si les données dont vous avez besoin du patient sont trop grandes pour être répétées pour chaque étude, dans la requête jointe, sélectionnez uniquement l'ID du patient, en interrogeant le reste des données du patient dans une requête distincte non jointe.

Edward Brey
la source
2
La réponse fonctionne! Je suppose qu'il y a encore du travail à faire dans le traducteur de requêtes. Une requête simple comme celle-ci devrait être traduisible. Il ne devrait pas y avoir de problèmes de performances pour une simple jointure de groupe de 2 tables car l'ensemble de données augmente en supposant que FK / index sont corrects. Je soupçonne que beaucoup de gens auront ce problème, une jointure de groupe à 2 tables est une requête assez standard et souvent utilisée.
shelbypereira
@ she72 Je suis d'accord. Il semble que le problème découle de la différence dans la façon dont LINQ et SQL utilisent le mot-clé "group". EF Core devrait traduire le LINQ groupbyen jointures de gauche où cela ne fait pas reculer plus de lignes que prévu. J'ai posté un commentaire en conséquence.
Edward Brey
J'ai une question complémentaire, j'essaie toujours de comprendre pourquoi le regroupement pour ce type de requête doit être fait côté client, semble une limitation du nouveau framework LINQ. Dans le cas ci-dessus, je ne vois aucun risque de ralentir l'exécution côté client de manière inattendue. Pouvez-vous clarifier?
shelbypereira
1
Et comme autre suivi, la principale préoccupation est: dans votre requête reformulée qui regroupe le côté client si j'ai 1000 études par patient, je chargerai chaque patient 1000 fois à partir de la base de données? existe-t-il une alternative pour forcer ce travail à être effectué dans la base de données et retourner les résultats groupés?
shelbypereira
1
@ shev72 Le seul groupe que la base de données comprend implique des agrégats, par exemple une requête de patients avec un nombre d'études par patient. La base de données renvoie toujours un ensemble de données rectangulaire. Un groupement hiérarchique doit être composé par le client. Vous pouvez le considérer comme une évaluation côté client ou comme faisant partie de l'ORM . Dans un regroupement hiérarchique, les données de l'entité parent sont répétées, mais pas redemandées.
Edward Brey
0

Eu exactement le même problème et une grande lutte avec lui. Il s'avère que .net Core 3.0 ne prend pas en charge Join ou Groupjoin dans la syntaxe de la méthode (pour le moment?). La partie amusante est cependant, cela fonctionne dans la syntaxe de requête.

Essayez ceci, c'est la syntaxe de requête avec un peu de syntaxe de méthode. Cela se traduit bien par la requête SQL correcte avec une belle jointure externe gauche et elle est traitée dans la base de données. Je n'ai pas vos modèles, vous devez donc vérifier vous-même la syntaxe ....

var queryResults1 = 
    (from p in _context.patients
    from s in _context.Studies.Where(st => st.PatientId == p.Id).DefaultIfEmpty()
    select new
    {
        p.DateOfBirth,
        p.Id,
        p.Name,
        p.Sex,
        Studies = studies.Select(s1 => s1)
    }).ToListAsync();
hwmaat
la source
Soit dit en passant, la syntaxe Join et GroupJoin with Method DO fonctionne avec le framework non-core et EF. Et traduisez à la bonne requête qui est traitée côté serveur
hwmaat
1
qu'est-ce que les études dans les études.Sélectionnez (s1 => s1)
Ankur Arora
Les modèles n'étaient pas inclus dans la question donc je ne connais pas le modèle des études. Ma meilleure supposition est qu'il s'agit d'une collection virtuelle dans le modèle.
hwmaat