Freigeben über


Komplexe Abfrageoperatoren

Language Integrated Query (LINQ) enthält viele komplexe Operatoren, die mehrere Datenquellen kombinieren oder komplexe Verarbeitungen durchführen. Nicht alle LINQ-Operatoren verfügen über geeignete Übersetzungen auf serverseitiger Seite. Manchmal wird eine Abfrage in einer Form auf den Server übersetzt, aber wenn sie in einer anderen Form formuliert ist, wird sie nicht übersetzt, selbst wenn das Ergebnis identisch ist. Auf dieser Seite werden einige der komplexen Operatoren und deren unterstützte Variationen beschrieben. In zukünftigen Versionen erkennen wir möglicherweise mehr Muster und fügen ihre entsprechenden Übersetzungen hinzu. Es ist auch wichtig zu beachten, dass die Übersetzungsunterstützung zwischen Anbietern variiert. Eine bestimmte Abfrage, die in SqlServer übersetzt wird, funktioniert möglicherweise nicht für SQLite-Datenbanken.

Tipp

Das in diesem Artikel verwendete Beispiel finden Sie auf GitHub.

Beitreten

Mit dem LINQ Join-Operator können Sie zwei Datenquellen basierend auf der Schlüsselauswahl für jede Quelle verbinden und ein Tupel von Werten generieren, wenn der Schlüssel übereinstimmt. Die Übersetzung in relationale Datenbanken erfolgt selbstverständlich als INNER JOIN. Während die LINQ-Verknüpfung äußere und innere Schlüsselmarkierer enthält, erfordert die Datenbank eine einzige Verknüpfungsbedingung. Daher generiert EF Core eine Verknüpfungsbedingung, indem der äußere Schlüsselmarkierer mit dem inneren Schlüsselmarkierer für Gleichheit verglichen wird.

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]

Weiterhin generiert EF Core eine Verknüpfungsbedingung, um die Gleichheit einzelner Komponenten zu vergleichen, wenn die Schlüsselselektoren anonyme Typen sind.

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

Gruppenbeitritt

Mit dem LINQ GroupJoin-Operator können Sie zwei Datenquellen ähnlich wie beim Join miteinander verbinden, wobei eine Gruppe von inneren Werten für übereinstimmende äußere Elemente erstellt wird. Das Ausführen einer Abfrage wie im folgenden Beispiel generiert ein Ergebnis von Blog & IEnumerable<Post>. Da Datenbanken (insbesondere relationale Datenbanken) keine Möglichkeit haben, eine Auflistung clientseitiger Objekte darzustellen, übersetzt GroupJoin in vielen Fällen nicht auf den Server. Es erfordert, dass Sie alle Daten vom Server abrufen, um GroupJoin ohne eine spezielle Auswahl auszuführen (erste Abfrage unten). Wenn die Auswahl jedoch daten begrenzt, die ausgewählt werden, kann das Abrufen aller Daten vom Server zu Leistungsproblemen führen (zweite Abfrage unten). Deshalb übersetzt EF Core GroupJoin nicht.

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

Mit dem LINQ SelectMany-Operator können Sie für jedes äußere Element einen Sammlungsmarkierer aufzählen und Tupel von Werten aus jeder Datenquelle generieren. Auf eine Weise ist es eine Verknüpfung, aber ohne Bedingung, sodass jedes äußere Element mit einem Element aus der Sammlungsquelle verbunden ist. Je nachdem, wie die Sammlungsauswahl mit der äußeren Datenquelle verknüpft ist, kann SelectMany in verschiedene Abfragen serverseitig übersetzt werden.

Die Auswahl der Sammlung verweist nicht auf das Äußere.

Wenn die Sammlungsauswahl nichts von der äußeren Quelle referenziert, ist das Ergebnis ein kartesisches Produkt beider Datenquellen. In relationalen Datenbanken wird dies zu CROSS JOIN übersetzt.

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]

Sammelauswahl verweist in einer where-Klausel auf äußere Elemente.

Wenn der Sammlungsselektor eine Where-Klausel aufweist, die auf das äußere Element verweist, übersetzt EF Core diese in eine Datenbankverknüpfung und verwendet das Prädikat als Verknüpfungsbedingung. Normalerweise tritt dieser Fall auf, wenn die Navigationsfunktion in der Sammlung im äußeren Element als Sammlungsauswähler verwendet wird. Wenn die Auflistung für ein äußeres Element leer ist, werden für dieses äußere Element keine Ergebnisse generiert. Wenn DefaultIfEmpty jedoch auf den Sammlungsselektor angewendet wird, wird das äußere Element mit einem Standardwert des inneren Elements verbunden. Aufgrund dieser Unterscheidung werden diese Arten von Abfragen in INNER JOIN übersetzt, in Abwesenheit von DefaultIfEmpty und LEFT JOIN, wenn DefaultIfEmpty angewendet wird.

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]

Verweise auf Sammlungsselektoren, die außerhalb eines 'where'-Falls sind

Wenn der Sammlungsselektor auf das äußere Element, das nicht Teil einer Where-Klausel ist, verweist (wie oben beschrieben), wird dies nicht in eine Datenbankverknüpfung übersetzt. Deshalb müssen wir den Sammlungsselektor für jedes äußere Element auswerten. Sie wird in APPLY Vorgänge in vielen relationalen Datenbanken übersetzt. Wenn die Auflistung für ein äußeres Element leer ist, werden für dieses äußere Element keine Ergebnisse generiert. Wenn DefaultIfEmpty jedoch auf den Sammlungsselektor angewendet wird, wird das äußere Element mit einem Standardwert des inneren Elements verbunden. Aufgrund dieser Unterscheidung werden diese Arten von Abfragen in CROSS APPLY übersetzt, in Abwesenheit von DefaultIfEmpty und OUTER APPLY, wenn DefaultIfEmpty angewendet wird. Bestimmte Datenbanken wie SQLite unterstützen APPLY keine Operatoren, sodass diese Art von Abfrage möglicherweise nicht übersetzt wird.

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]

GruppierenNach

LINQ GroupBy-Operatoren erstellen ein Ergebnis vom Typ IGrouping<TKey, TElement>, wobei sowohl TKey als auch TElement beliebige Typen sein können. Außerdem implementiert IGroupingIEnumerable<TElement>, was bedeutet, dass Sie nach der Gruppierung mit einem beliebigen LINQ-Operator darüber verfassen können. Da keine Datenbankstruktur eine IGroupingDatenbank darstellen kann, haben GroupBy-Operatoren in den meisten Fällen keine Übersetzung. Wenn ein Aggregatoperator auf jede Gruppe angewendet wird, die einen Skalar zurückgibt, kann er in relationale Datenbanken in SQL GROUP BY übersetzt werden. Die SQL-Datei GROUP BY ist ebenfalls restriktiv. Es erfordert, dass Sie nur nach skalaren Werten gruppieren. Die Projektion kann nur Gruppierungsschlüsselspalten oder ein beliebiges Aggregat enthalten, das auf eine Spalte angewendet wird. EF Core identifiziert dieses Muster und übersetzt es auf den Server, wie im folgenden Beispiel gezeigt:

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 übersetzt auch Abfragen, bei denen ein Aggregatoperator für die Gruppierung in einem LINQ-Operator wie "Where" oder "OrderBy" (oder einer anderen Sortierfolge) verwendet wird. Sie verwendet HAVING Klausel in SQL für die Where-Klausel. Der Teil der Abfrage, bevor der GroupBy-Operator angewendet wird, kann eine beliebige komplexe Abfrage sein, solange sie auf den Server übersetzt werden kann. Darüber hinaus können Sie, sobald Sie Aggregatoperatoren auf eine Gruppierungsabfrage anwenden, um Gruppierungen aus der resultierenden Quelle zu entfernen, wie jede andere Abfrage darüber verfassen.

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]

Die von EF Core unterstützten Aggregatoperatoren sind wie folgt:

.NETTO SQL
Average(x => x.Property) AVG(Eigenschaft)
Anzahl() ANZAHL(*)
LongCount() ANZAHL(*)
Max(x => x.Property) MAX(Eigenschaft)
Min(x => x.Property) MIN(Eigenschaft)
Sum(x => x.Property) SUMME(Eigenschaft)

Zusätzliche Aggregatoperatoren können unterstützt werden. Überprüfen Sie Ihre Anbieterdokumente auf weitere Funktionszuordnungen.

Obwohl es keine Datenbankstruktur gibt, die ein IGrouping darstellt, können in manchen Fällen unter EF Core 7.0 und neuer die Gruppierungen erstellt werden, nachdem die Ergebnisse aus der Datenbank zurückgegeben wurden. Dies ähnelt der Funktionsweise des Include Operators beim Einschließen verwandter Auflistungen. Die folgende LINQ-Abfrage verwendet den GroupBy-Operator, um die Ergebnisse nach dem Wert ihrer Price-Eigenschaft zu gruppieren.

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

In diesem Fall übersetzt der GroupBy-Operator nicht direkt in eine GROUP BY Klausel in sql, sondern EF Core erstellt die Gruppierungen, nachdem die Ergebnisse vom Server zurückgegeben wurden.

Linker Beitritt

Während Left Join kein LINQ-Operator ist, haben relationale Datenbanken das Konzept einer Left Join, die häufig in Abfragen verwendet wird. Ein bestimmtes Muster in LINQ-Abfragen gibt dasselbe Ergebnis wie ein LEFT JOIN auf dem Server. EF Core identifiziert solche Muster und generiert das Äquivalent LEFT JOIN auf serverseitiger Seite. Das Muster umfasst das Erstellen eines GroupJoins zwischen den Datenquellen und das anschließende Abflachen der Gruppierung, indem mithilfe des SelectMany-Operators mit DefaultIfEmpty auf der Gruppierungsquelle null zugeordnet wird, wenn das Innere über kein verknüpftes Element verfügt. Das folgende Beispiel zeigt, wie das Muster aussieht und was es generiert.

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]

Das obige Muster erstellt eine komplexe Struktur im Ausdrucksbaum. Aus diesem Grund erfordert EF Core, dass Sie die Gruppierungsergebnisse des GroupJoin-Operators in einem Schritt, der unmittelbar auf den Operator folgt, abflächen. Auch wenn GroupJoin-DefaultIfEmpty-SelectMany verwendet wird, jedoch in einem anderen Muster, könnte es möglicherweise nicht als Linksverknüpfung identifiziert werden.