Partage via


Opérateurs de requête complexes

LINQ (Language Integrated Query) contient de nombreux opérateurs complexes, qui combinent plusieurs sources de données ou effectuent un traitement complexe. Les opérateurs LINQ n’ont pas tous des traductions appropriées côté serveur. Parfois, une requête sous une forme se traduit côté serveur, mais si elle est écrite sous une autre forme, elle ne se traduit pas, même si le résultat est le même. Cette page décrit certains des opérateurs complexes et leurs variations prises en charge. Dans les versions ultérieures, nous pourrons reconnaître plus de modèles et ajouter les traductions correspondantes. Il est également important de garder à l’esprit que la prise en charge de la traduction varie d’un fournisseur à l’autre. Une requête particulière, qui est traduite dans SqlServer, peut ne pas fonctionner pour les bases de données SQLite.

Conseil

Vous pouvez afficher cet exemple sur GitHub.

Join

L’opérateur LINQ Join vous permet de connecter deux sources de données en fonction du sélecteur de clé pour chaque source, en générant un tuple de valeurs lorsque la clé correspond. Il se traduit naturellement par des INNER JOIN sur des bases de données relationnelles. Alors que LINQ Join possède des sélecteurs de clés externes et internes, la base de données requiert une seule condition de jointure. Ainsi, EF Core génère une condition de jointure en comparant le sélecteur de clé externe au sélecteur de clé interne pour l’égalité.

var query = from photo in context.Set<PersonPhoto>()
            join person in context.Set<Person>()
                on photo.PersonPhotoId equals person.PhotoId
            select new { person, photo };
SELECT [p].[PersonId], [p].[Name], [p].[PhotoId], [p0].[PersonPhotoId], [p0].[Caption], [p0].[Photo]
FROM [PersonPhoto] AS [p0]
INNER JOIN [Person] AS [p] ON [p0].[PersonPhotoId] = [p].[PhotoId]

En outre, si les sélecteurs de clé sont des types anonymes, EF Core génère une condition de jointure pour comparer les composants en termes d’égalité.

var query = from photo in context.Set<PersonPhoto>()
            join person in context.Set<Person>()
                on new { Id = (int?)photo.PersonPhotoId, photo.Caption }
                equals new { Id = person.PhotoId, Caption = "SN" }
            select new { person, photo };
SELECT [p].[PersonId], [p].[Name], [p].[PhotoId], [p0].[PersonPhotoId], [p0].[Caption], [p0].[Photo]
FROM [PersonPhoto] AS [p0]
INNER JOIN [Person] AS [p] ON ([p0].[PersonPhotoId] = [p].[PhotoId] AND ([p0].[Caption] = N'SN'))

GroupJoin

L’opérateur LINQ GroupJoin vous permet de connecter deux sources de données similaires à Join, mais il crée un groupe de valeurs internes pour les éléments externes correspondants. L’exécution d’une requête comme l’exemple suivant génère un résultat de Blog et IEnumerable<Post>. Étant donné que les bases de données (notamment les bases de données relationnelles) n’ont pas de moyen de représenter une collection d’objets côté client, GroupJoin ne se traduit pas souvent côté serveur. Cela vous oblige à obtenir toutes les données du serveur pour effectuer GroupJoin sans un sélecteur spécial (première requête ci-dessous). Toutefois, si le sélecteur limite les données sélectionnées, l’extraction de toutes les données du serveur peut entraîner des problèmes de performances (deuxième requête ci-dessous). C’est la raison pour laquelle EF Core ne traduit pas GroupJoin.

var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            select new { b, grouping };
var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            select new { b, Posts = grouping.Where(p => p.Content.Contains("EF")).ToList() };

SelectMany

L’opérateur SelectMany de LINQ vous permet d’énumérer un sélecteur de collection pour chaque élément externe et de générer des tuples de valeurs à partir de chaque source de données. En d’autres termes, il s’agit d’une jointure, mais sans aucune condition, chaque élément externe est connecté avec un élément de la source de la collection. Selon la façon dont le sélecteur de collection est lié à la source de données externe, SelectMany peut se traduire par différentes requêtes côté serveur.

Le sélecteur de collection ne fait pas référence à l’extérieur

Lorsque le sélecteur de collection ne référence rien de la source externe, le résultat est un produit cartésien des deux sources de données. Il se traduit par des CROSS JOIN dans des bases de données relationnelles.

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>()
            select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
CROSS JOIN [Posts] AS [p]

Le sélecteur de collection fait référence à l’élément externe dans une clause WHERE

Lorsque le sélecteur de collection a une clause WHERE, qui fait référence à l’élément externe, EF Core le convertit en une jointure de base de données et utilise le prédicat comme condition de jointure. Ce cas se produit généralement lors de l’utilisation de la navigation de collection sur l’élément externe comme sélecteur de collection. Si la collection est vide pour un élément externe, aucun résultat ne sera généré pour cet élément externe. Toutefois, si DefaultIfEmpty est appliqué au sélecteur de collection, l’élément externe est connecté avec une valeur par défaut de l’élément interne. En raison de cette distinction, ce type de requêtes se traduit par INNER JOIN en l’absence de DefaultIfEmpty et de LEFT JOIN lorsque DefaultIfEmpty est appliqué.

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>().Where(p => b.BlogId == p.BlogId)
            select new { b, p };

var query2 = from b in context.Set<Blog>()
             from p in context.Set<Post>().Where(p => b.BlogId == p.BlogId).DefaultIfEmpty()
             select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

Le sélecteur de collection fait référence à l’élément externe dans un cas autre que WHERE

Lorsque le sélecteur de collection fait référence à l’élément externe, qui n’est pas dans une clause WHERE (comme le cas ci-dessus), il ne se convertit pas en jointure de base de données. C’est pourquoi nous devons évaluer le sélecteur de collection pour chaque élément externe. Il se traduit par des opérations APPLY dans de nombreuses bases de données relationnelles. Si la collection est vide pour un élément externe, aucun résultat ne sera généré pour cet élément externe. Toutefois, si DefaultIfEmpty est appliqué au sélecteur de collection, l’élément externe est connecté avec une valeur par défaut de l’élément interne. En raison de cette distinction, ce type de requêtes se traduit par CROSS APPLY en l’absence de DefaultIfEmpty et de OUTER APPLY lorsque DefaultIfEmpty est appliqué. Certaines bases de données telles que SQLite ne prennent pas en charge les opérateurs APPLY, ce type de requête peut ne pas être traduit.

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>().Select(p => b.Url + "=>" + p.Title)
            select new { b, p };

var query2 = from b in context.Set<Blog>()
             from p in context.Set<Post>().Select(p => b.Url + "=>" + p.Title).DefaultIfEmpty()
             select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], ([b].[Url] + N'=>') + [p].[Title] AS [p]
FROM [Blogs] AS [b]
CROSS APPLY [Posts] AS [p]

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], ([b].[Url] + N'=>') + [p].[Title] AS [p]
FROM [Blogs] AS [b]
OUTER APPLY [Posts] AS [p]

GroupBy

Les opérateurs LINQ GroupBy créent un résultat de type IGrouping<TKey, TElement>TKey et TElement peuvent être n’importe quel type arbitraire. En outre, IGrouping implémente IEnumerable<TElement>, ce qui signifie que vous pouvez le composer à l’aide de n’importe quel opérateur LINQ après le regroupement. Dans la mesure où aucune structure de base de données ne peut représenter un IGrouping, les opérateurs GroupBy n’ont pas de traduction dans la plupart des cas. Quand un opérateur d’agrégation est appliqué à chaque groupe, qui retourne une valeur scalaire, il peut être traduit en SQL GROUP BY dans des bases de données relationnelles. Le SQL GROUP BY est également restrictif. Il vous oblige à regrouper uniquement par valeurs scalaires. La projection peut uniquement contenir des colonnes clés de regroupement ou n’importe quel agrégat appliqué à une colonne. EF Core identifie ce modèle et le convertit en serveur, comme dans l’exemple suivant :

var query = from p in context.Set<Post>()
            group p by p.AuthorId
            into g
            select new { g.Key, Count = g.Count() };
SELECT [p].[AuthorId] AS [Key], COUNT(*) AS [Count]
FROM [Posts] AS [p]
GROUP BY [p].[AuthorId]

EF Core traduit également les requêtes dans lesquelles un opérateur d’agrégation sur le regroupement apparaît dans un opérateur LINQ WHERE ou OrderBy (ou autre classement). Il utilise la clause HAVING dans SQL pour la clause WHERE. La partie de la requête avant l’application de l’opérateur GroupBy peut être une requête complexe, à condition qu’elle puisse être traduite en serveur. En outre, une fois que vous appliquez des opérateurs d’agrégation sur une requête de regroupement pour supprimer des regroupements de la source résultante, vous pouvez composer dessus comme n’importe quelle autre requête.

var query = from p in context.Set<Post>()
            group p by p.AuthorId
            into g
            where g.Count() > 0
            orderby g.Key
            select new { g.Key, Count = g.Count() };
SELECT [p].[AuthorId] AS [Key], COUNT(*) AS [Count]
FROM [Posts] AS [p]
GROUP BY [p].[AuthorId]
HAVING COUNT(*) > 0
ORDER BY [p].[AuthorId]

Les opérateurs d’agrégation qu’EF Core prend en charge sont les suivants :

.NET SQL
Average(x => x.Property) AVG(Property)
Count() COUNT(*)
LongCount() COUNT(*)
Max(x => x.Property) MAX(Property)
Min(x => x.Property) MIN(Property)
Sum(x => x.Property) SUM(Property)

Des opérateurs d’agrégation supplémentaires peuvent être pris en charge. Consultez la documentation de votre fournisseur pour obtenir d’autres mappages de fonction.

Même s’il n’existe aucune structure de base de données pour représenter un IGrouping, dans certains cas, EF Core 7.0 et versions ultérieures peut créer les regroupements une fois les résultats retournés à partir de la base de données. Cela est similaire au fonctionnement de l’opérateur Include lors de l’inclusion de collections associées. La requête LINQ suivante utilise l’opérateur GroupBy pour regrouper les résultats par la valeur de leur propriété Price.

var query = context.Books.GroupBy(s => s.Price);
SELECT [b].[Price], [b].[Id], [b].[AuthorId]
FROM [Books] AS [b]
ORDER BY [b].[Price]

Dans ce cas, l’opérateur GroupBy ne se traduit pas directement en clause GROUP BY dans SQL, mais à la place, EF Core crée les regroupements une fois les résultats retournés par le serveur.

Left Join

Bien que Left Join ne soit pas un opérateur LINQ, les bases de données relationnelles ont le concept d’une jointure de gauche qui est fréquemment utilisée dans les requêtes. Un modèle particulier dans les requêtes LINQ donne le même résultat qu’un LEFT JOIN sur le serveur. EF Core identifie de tels modèles et génère l’équivalent LEFT JOIN côté serveur. Le modèle implique la création d’une GroupJoin entre les deux sources de données, puis l’aplatissement du regroupement à l’aide de l’opérateur SelectMany avec DefaultIfEmpty sur la source de regroupement pour correspondre à Null lorsque l’élément interne n’a pas d’élément lié. L’exemple suivant montre à quoi ressemble ce modèle et ce qu’il génère.

var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            from p in grouping.DefaultIfEmpty()
            select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

Le modèle ci-dessus crée une structure complexe dans l’arborescence de l’expression. Pour cette raison, EF Core vous oblige à aplatir les résultats de regroupement de l’opérateur GroupJoin dans une étape qui suit immédiatement l’opérateur. Même si GroupJoin-DefaultIfEmpty-SelectMany est utilisé, mais dans un modèle différent, il est possible qu’il ne soit pas identifié en tant que Left Join.