Delen via


Complexe queryoperators

Language Integrated Query (LINQ) bevat veel complexe operators, die meerdere gegevensbronnen combineren of complexe verwerkingen uitvoeren. Niet alle LINQ-operators hebben geschikte vertalingen aan de serverzijde. Soms wordt een query in één formulier omgezet naar de server, maar als deze in een ander formulier is geschreven, wordt dit niet vertaald, zelfs niet als het resultaat hetzelfde is. Op deze pagina worden enkele complexe operators en de ondersteunde variaties beschreven. In toekomstige releases herkennen we mogelijk meer patronen en voegen we hun bijbehorende vertalingen toe. Het is ook belangrijk om te onthouden dat vertaalondersteuning varieert tussen providers. Een bepaalde query, die wordt vertaald in SqlServer, werkt mogelijk niet voor SQLite-databases.

Aanbeveling

U kunt het voorbeeld van dit artikel bekijken op GitHub.

Aansluiten

Met de LINQ Join-operator kunt u twee gegevensbronnen verbinden op basis van de sleutelselector voor elke bron, die een tuple met waarden genereert wanneer de sleutel overeenkomt. Het wordt natuurlijk vertaald naar INNER JOIN in relationele databases. Hoewel de LINQ Join buitenste en binnenste sleutelkiezers heeft, is voor de database één joinvoorwaarde vereist. EF Core genereert dus een joinvoorwaarde door de externe sleutelkiezer te vergelijken met de interne sleutelkiezer op gelijkheid.

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]

Als de sleutelkiezers anonieme typen zijn, genereert EF Core bovendien een joinvoorwaarde om gelijkheidscomponenten te vergelijken.

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'))

GroepSamenvoegen

Met de LINQ GroupJoin-operator kunt u twee gegevensbronnen verbinden die vergelijkbaar zijn met Join, maar er wordt een groep binnenste waarden gemaakt voor overeenkomende buitenste elementen. Als u een query uitvoert zoals in het volgende voorbeeld, wordt een resultaat van Blog & gegenereerd IEnumerable<Post>. Aangezien databases (met name relationele databases) geen manier hebben om een verzameling objecten aan de clientzijde weer te geven, vertaalt GroupJoin zich in veel gevallen niet naar de server. Hiervoor moet u alle gegevens van de server ophalen om GroupJoin uit te voeren zonder een speciale selector (eerste query hieronder). Maar als de selector de geselecteerde gegevens beperkt, kan het ophalen van alle gegevens van de server prestatieproblemen veroorzaken (tweede query hieronder). Daarom vertaalt EF Core GroupJoin niet.

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

Met de LINQ SelectMany-operator kunt u een verzamelingskiezer voor elk buitenste element inventariseren en tuples met waarden genereren uit elke gegevensbron. Op een manier is het een join, maar zonder enige voorwaarde, zodat elk buitenste element is verbonden met een element uit de verzamelingsbron. Afhankelijk van hoe de verzamelingskiezer is gerelateerd aan de buitenste gegevensbron, kan SelectMany worden omgezet in verschillende query's aan de serverzijde.

Verzamelingskiezer verwijst niet naar outer

Wanneer de collection selector niet naar iets uit de buitenste bron verwijst, is het resultaat een cartesisch product van beide gegevensbronnen. Het vertaalt zich naar CROSS JOIN in relationele databases.

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]

De verzamelingsselector verwijst naar een outer in een where-clausule

Wanneer de verzamelingskiezer een where-component heeft, die verwijst naar het buitenste element, vertaalt EF Core het naar een databasedeelname en gebruikt het predicaat als de joinvoorwaarde. Normaal gesproken ontstaat dit geval bij het gebruik van verzamelingsnavigatie op het buitenste element als de verzamelingkiezer. Als de verzameling leeg is voor een buitenste element, worden er geen resultaten gegenereerd voor dat buitenste element. Maar als DefaultIfEmpty op de verzamelingskiezer wordt toegepast, wordt het buitenste element verbonden met een standaardwaarde van het binnenste element. Vanwege dit onderscheid vertaalt dit soort query's zich naar INNER JOIN in de afwezigheid van DefaultIfEmpty en LEFT JOIN wanneer DefaultIfEmpty wordt toegepast.

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]

Collectieselector verwijst naar buiten in een niet-where-geval

Wanneer de verzamelingskiezer verwijst naar het buitenste element, dat zich niet in een where-component bevindt (zoals hierboven beschreven), wordt deze niet omgezet in een databasedeelname. Daarom moeten we de collectieselector voor elk buitenste element evalueren. Het vertaalt zich naar APPLY bewerkingen in veel relationele databases. Als de verzameling leeg is voor een buitenste element, worden er geen resultaten gegenereerd voor dat buitenste element. Maar als DefaultIfEmpty op de verzamelingskiezer wordt toegepast, wordt het buitenste element verbonden met een standaardwaarde van het binnenste element. Vanwege dit onderscheid vertaalt dit soort query's zich naar CROSS APPLY in de afwezigheid van DefaultIfEmpty en OUTER APPLY wanneer DefaultIfEmpty wordt toegepast. Bepaalde databases zoals SQLite bieden geen ondersteuning voor APPLY operators, zodat dit type query mogelijk niet wordt vertaald.

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

LINQ GroupBy-operators maken een resultaat van het type IGrouping<TKey, TElement> waar TKey en TElement elk willekeurig type kunnen zijn. Bovendien implementeert IGroupingu IEnumerable<TElement> , wat betekent dat u erover kunt opstellen met behulp van een LINQ-operator na de groepering. Aangezien geen databasestructuur een IGroupingkan vertegenwoordigen, hebben GroupBy-operators in de meeste gevallen geen vertaling. Wanneer een statistische operator wordt toegepast op elke groep, die een scalaire waarde retourneert, kan deze worden vertaald naar SQL GROUP BY in relationele databases. De SQL GROUP BY is ook beperkend. Hiervoor moet u alleen groeperen op scalaire waarden. De projectie kan alleen sleutelkolommen groeperen of een aggregatie bevatten die wordt toegepast op een kolom. EF Core identificeert dit patroon en vertaalt dit naar de server, zoals in het volgende voorbeeld:

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 vertaalt ook query's waarbij een aggregate-functie voor de groepering voorkomt in een LINQ-operator Where of OrderBy (of andere ordenings-). Het gebruikt HAVING clausule in SQL voor de where-clausule. Het deel van de query voordat u de GroupBy-operator toepast, kan elke complexe query zijn zolang deze kan worden vertaald naar de server. Als u bovendien aggregatieoperatoren toepast op een groeperingsquery om groeperingen uit de resulterende bron te verwijderen, kunt u er net als bij elke andere query verder op voortbouwen.

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]

De aggregatie-operators die EF Core ondersteunt zijn als volgt

.NET SQL
Gemiddelde(x => x.Eigenschap) AVG(eigenschap)
Count() AANTAL(*)
LongCount() AANTAL(*)
Max(x => x.Eigenschap) MAX(eigenschap)
Min(x => x.Property) MIN(eigenschap)
Sum(x => x.Eigenschap) SOM(Eigenschap)

Er kunnen extra aggregatie-operators worden ondersteund. Controleer uw providerdocumenten voor meer functietoewijzingen.

Hoewel er geen databasestructuur is om een IGroupingte vertegenwoordigen, kunnen EF Core 7.0 en hoger de groeperingen maken nadat de resultaten zijn geretourneerd uit de database. Dit is vergelijkbaar met hoe de Include operator werkt bij het opnemen van gerelateerde verzamelingen. De volgende LINQ-query maakt gebruik van de GroupBy-operator om de resultaten te groeperen op basis van de waarde van de eigenschap Price.

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

In dit geval vertaalt de GroupBy-operator zich niet rechtstreeks naar een GROUP BY component in de SQL, maar in plaats daarvan maakt EF Core de groeperingen nadat de resultaten van de server zijn geretourneerd.

Linkssamenvoeging

Hoewel Left Join geen LINQ-operator is, hebben relationele databases het concept van een Left Join die vaak wordt gebruikt in query's. Een bepaald patroon in LINQ-query's geeft hetzelfde resultaat als een LEFT JOIN op de server. EF Core identificeert dergelijke patronen en genereert het equivalent LEFT JOIN aan de serverzijde. Het patroon omvat het creëren van een GroupJoin tussen beide gegevensbronnen en vervolgens het afvlakken van de groepering door de SelectMany-operator met DefaultIfEmpty te gebruiken op de bron van de groepering, zodat deze null overeenkomt wanneer de inner join geen gerelateerd element heeft. In het volgende voorbeeld ziet u hoe dat patroon eruitziet en hoe dit wordt gegenereerd.

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]

Dit patroon creëert een complexe structuur in de expressieboom. Daarom moet u met EF Core de groeperingsresultaten van de GroupJoin-operator in een stap onmiddellijk na de operator platmaken. Zelfs als de GroupJoin-DefaultIfEmpty-SelectMany wordt gebruikt, maar in een ander patroon, kunnen we deze mogelijk niet identificeren als een Left Join.