Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
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 mithilfe eines Vergleichs der Gleichheit des äußeren und inneren Schlüsselselektors.
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'))
GroupJoin
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 einen Sammlungsselektor für äußere Elemente aufzählen und Tupel von Werten aus einzelnen Datenquellen 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 der Sammlungsselektor im Bezug mit der äußeren Datenquelle steht, kann SelectMany in verschiedene Abfragen auf der Serverseite übersetzt werden.
Sammlungsselektor referenziert nicht die äußere Quelle
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]
Sammlungsselektor referenziert die äußere Datenquelle in einer WHERE-Klausel
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 Sammlungsnavigation im äußeren Element als Sammlungsselektor 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 wird diese Art von Abfrage bei Abwesenheit von DefaultIfEmpty in INNER JOIN übersetzt und in LEFT JOIN, wenn DefaultIfEmpty angewendet wurde.
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]
Sammlungsselektor referenziert die äußere Datenquelle in einem Fall ohne WHERE-Klausel
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. Daher muss der Sammlungsselektor für alle äußeren Elemente ausgewertet werden. Für viele relationale Datenbanken wird dies in APPLY-Vorgänge ü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 wird diese Art von Abfrage bei Abwesenheit von DefaultIfEmpty in CROSS APPLY übersetzt und in OUTER APPLY, wenn DefaultIfEmpty angewendet wurde. 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]
GroupBy
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 gruppierende Schlüsselspalten oder auf eine Spalte angewendete Aggregate-Operatoren enthalten. 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 Abfragen auch, wenn ein Aggregate-Operator für die Gruppierung in einem WHERE- oder OrderBy-LINQ-Operator (oder in anderen Order-Operatoren) vorliegt. 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. Sobald Sie Aggregate-Operatoren in einer Gruppierungsabfrage anwenden, um Gruppierungen aus der resultierenden Quelle zu entfernen, können Sie sie außerdem wie jede andere Abfrage zusammensetzen.
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:
| .NET | SQL |
|---|---|
| Durchschnitt(x => x.Property) | AVG(Property) |
| Anzahl() | ANZAHL(*) |
| LongCount() | ANZAHL(*) |
| Max(x => x.Property) | MAX(Property) |
| Min(x => x.Property) | MIN(Property) |
| Summe(x => x.Eigenschaft) | SUM(Property) |
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 Join
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 die Erstellung eines GroupJoin-Operators auf beiden Datenquellen sowie die anschließende Vereinfachung der Gruppierung mithilfe des SelectMany-Operators mit DefaultIfEmpty für die Gruppierungsquelle, um eine Übereinstimmung mit NULL zu suchen, wenn die innere Quelle kein zugehöriges Element aufweist. 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. Daher erfordert EF Core, dass Sie die Gruppierungsergebnisse des GroupJoin-Operators in dem Schritt vereinfachen, der direkt auf den Operator folgt. Selbst wenn GroupJoin-DefaultIfEmpty-SelectMany in einem anderen Muster verwendet wird, wird dies möglicherweise nicht als Left Join identifiziert.