Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
LinQ (Language Integrated Query) contient de nombreux opérateurs complexes, qui combinent plusieurs sources de données ou effectuent un traitement complexe. Tous les opérateurs LINQ ne disposent pas de traductions appropriées côté serveur. Parfois, une requête d’un formulaire se traduit par le serveur, mais si elle est écrite dans un autre formulaire, 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 variantes prises en charge. Dans les versions ultérieures, nous pouvons reconnaître d’autres modèles et ajouter leurs traductions correspondantes. Il est également important de garder à l’esprit que la prise en charge de la traduction varie entre les fournisseurs. Une requête particulière, traduite dans SqlServer, peut ne pas fonctionner pour les bases de données SQLite.
Conseil / Astuce
Vous pouvez afficher l’exemple de cet article sur GitHub.
Unir
L’opérateur LINQ Join vous permet de connecter deux sources de données en fonction du sélecteur de clé pour chaque source, ce qui génère un tuple de valeurs lorsque la clé correspond. Il se traduit naturellement par INNER JOIN
des bases de données relationnelles. Bien que la jointure LINQ possède des sélecteurs de clés externes et internes, la base de données nécessite une condition de jointure unique. EF Core génère donc 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és sont des types anonymes, EF Core génère une condition de jointure pour comparer les composants 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'))
RejoindreGroupe
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 la correspondance d’éléments externes. L’exécution d’une requête comme l’exemple suivant génère un résultat de Blog
& IEnumerable<Post>
. Étant donné que les bases de données (en particulier 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 par le serveur dans de nombreux cas. Il vous oblige à obtenir toutes les données du serveur pour effectuer GroupJoin sans 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 pourquoi 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 LINQ SelectMany 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. D'une certaine manière, il s'agit d'une jointure, mais sans condition, de sorte que chaque élément externe soit connecté à un élément de la source de collection. Selon la façon dont le sélecteur de collection est lié à la source de données externe, SelectMany peut se traduire en différentes requêtes côté serveur.
Le sélecteur de collection ne fait pas référence à un élément externe
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 CROSS JOIN
dans les 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]
Références de sélecteur de collection externes 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 traduit en jointure de base de données et utilise le prédicat comme condition de jointure. Normalement, ce cas se produit lors de l’utilisation de la navigation de collection sur l’élément externe en tant que sélecteur de collection. Si la collection est vide pour un élément externe, aucun résultat n’est généré pour cet élément externe. Mais si DefaultIfEmpty
est appliqué sur le sélecteur de collection, l’élément externe sera associé à la 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 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]
Références à un sélecteur de collection externe dans le cas d'une absence de clause 'where'
Lorsque le sélecteur de collection fait référence à l’élément externe, qui n’est pas dans une clause where (comme dans le cas ci-dessus), il ne se traduit 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 en APPLY
opérations dans de nombreuses bases de données relationnelles. Si la collection est vide pour un élément externe, aucun résultat n’est généré pour cet élément externe. Mais si DefaultIfEmpty
est appliqué sur le sélecteur de collection, l’élément externe sera associé à la 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 OUTER APPLY
lorsque DefaultIfEmpty
est appliqué. Certaines bases de données, comme SQLite, ne prennent pas en charge APPLY
les opérateurs, donc 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]
Grouper par
Les opérateurs LINQ GroupBy créent un résultat de type IGrouping<TKey, TElement>
où TKey
et TElement
peuvent être n’importe quel type arbitraire. En outre, IGrouping
implémente IEnumerable<TElement>
, ce qui signifie que vous pouvez se composer sur en utilisant n’importe quel opérateur LINQ après le regroupement. Étant donné qu’aucune structure de base de données ne peut représenter un IGrouping
, les opérateurs GroupBy n'ont généralement pas de traduction dans la plupart des cas. Lorsqu’un opérateur d’agrégation est appliqué à chaque groupe, qui retourne une scalaire, il peut être traduit en SQL GROUP BY
dans les bases de données relationnelles. Le code SQL GROUP BY
est également restrictif. Il vous oblige à regrouper uniquement par valeurs scalaires. La projection ne peut contenir que des colonnes clés de regroupement ou tout agrégat appliqué sur une colonne. EF Core identifie ce modèle et le traduit sur le 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 où un opérateur d’agrégation sur le regroupement apparaît dans un opérateur LINQ Where ou OrderBy (ou autre ordering). Il utilise la clause HAVING
dans SQL pour la clause where. La partie de la requête avant d’appliquer l’opérateur GroupBy peut être n’importe quelle requête complexe tant qu’elle peut être traduite sur le serveur. En outre, une fois que vous appliquez des opérateurs d’agrégation sur une requête de regroupement pour supprimer les regroupements de la source résultante, vous pouvez le composer en haut de celui-ci 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 pris en charge par EF Core sont les suivants :
.FILET | SQL |
---|---|
Moyenne(x => x.Propriété) | AVG(Property) |
Compter() | COUNT(*) |
LongCount() | COUNT(*) |
Max(x => x.Property) | MAX(Propriété) |
Min(x => x.Propriété) | MIN(Propriété) |
Sum(x => x.Property) | SOMME(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 peuvent créer les regroupements une fois les résultats retournés par 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.
Jointure gauche
Bien que Left Join ne soit pas un opérateur LINQ, les bases de données relationnelles ont le concept d’une jointure 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 que LEFT JOIN
sur le serveur. EF Core identifie ces modèles et génère l’équivalent LEFT JOIN
côté serveur. Le modèle implique la création d’un 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 qu’il corresponde à null lorsque l’élément interne n’a pas d’élément associé. 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 d’expressions. En raison de cela, EF Core vous oblige à aplatir les résultats de regroupement de l’opérateur GroupJoin dans une étape immédiatement après l’opérateur. Même si groupJoin-DefaultIfEmpty-SelectMany est utilisé mais dans un modèle différent, nous ne pouvons pas l’identifier comme une jointure gauche.