Złożone operatory zapytań

Zapytanie zintegrowane z językiem (LINQ) zawiera wiele złożonych operatorów, które łączą wiele źródeł danych lub wykonuje złożone przetwarzanie. Nie wszystkie operatory LINQ mają odpowiednie tłumaczenia po stronie serwera. Czasami zapytanie w jednym formularzu przekłada się na serwer, ale jeśli zostało zapisane w innym formularzu, nie przekłada się nawet wtedy, gdy wynik jest taki sam. Na tej stronie opisano niektóre złożone operatory i ich obsługiwane odmiany. W przyszłych wersjach możemy rozpoznać więcej wzorców i dodać odpowiednie tłumaczenia. Należy również pamiętać, że obsługa tłumaczenia różni się między dostawcami. Określone zapytanie, które jest tłumaczone w programie SqlServer, może nie działać w przypadku baz danych SQLite.

Napiwek

Przykład z tego artykułu można zobaczyć w witrynie GitHub.

Dołączanie

Operator sprzężenia LINQ umożliwia łączenie dwóch źródeł danych na podstawie selektora kluczy dla każdego źródła, generując krotkę wartości po dopasowaniu klucza. Naturalnie przekłada się na INNER JOIN relacyjne bazy danych. Chociaż sprzężenie LINQ ma selektory kluczy zewnętrznych i wewnętrznych, baza danych wymaga jednego warunku sprzężenia. Dlatego program EF Core generuje warunek sprzężenia, porównując selektor kluczy zewnętrznych z selektorem klucza wewnętrznego pod kątem równości.

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]

Ponadto, jeśli selektory kluczy są typami anonimowymi, program EF Core generuje warunek sprzężenia w celu porównania składników równości mądry.

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

Operator LINQ GroupJoin umożliwia łączenie dwóch źródeł danych podobnych do sprzężenia, ale tworzy grupę wartości wewnętrznych do dopasowywania elementów zewnętrznych. Wykonanie zapytania, takiego jak w poniższym przykładzie, powoduje wygenerowanie wyniku parametru Blog & IEnumerable<Post>. Ponieważ bazy danych (zwłaszcza relacyjne bazy danych) nie mają możliwości reprezentowania kolekcji obiektów po stronie klienta, grupa GroupJoin nie przekłada się na serwer w wielu przypadkach. Wymaga to pobrania wszystkich danych z serwera do wykonania GroupJoin bez specjalnego selektora (pierwsze zapytanie poniżej). Jeśli jednak selektor ogranicza wybrane dane, pobieranie wszystkich danych z serwera może powodować problemy z wydajnością (drugie zapytanie poniżej). Dlatego program EF Core nie tłumaczy elementu 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

Operator LINQ SelectMany umożliwia wyliczanie selektora kolekcji dla każdego elementu zewnętrznego i generowanie krotki wartości z każdego źródła danych. W pewien sposób jest to sprzężenia, ale bez żadnego warunku, więc każdy element zewnętrzny jest połączony z elementem ze źródła kolekcji. W zależności od tego, jak selektor kolekcji jest powiązany z zewnętrznym źródłem danych, selectMany może tłumaczyć się na różne zapytania po stronie serwera.

Selektor kolekcji nie odwołuje się do zewnętrznego

Gdy selektor kolekcji nie odwołuje się do niczego ze źródła zewnętrznego, wynikiem jest kartezjański produkt obu źródeł danych. Przekłada się na CROSS JOIN w relacyjnych bazach danych.

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]

Selektor kolekcji odwołuje się do elementów zewnętrznych w klauzuli where

Gdy selektor kolekcji ma klauzulę where, która odwołuje się do elementu zewnętrznego, program EF Core tłumaczy go na sprzężenie bazy danych i używa predykatu jako warunku sprzężenia. Zwykle ten przypadek występuje podczas korzystania z nawigacji kolekcji w elemecie zewnętrznym jako selektora kolekcji. Jeśli kolekcja jest pusta dla elementu zewnętrznego, nie zostaną wygenerowane żadne wyniki dla tego elementu zewnętrznego. DefaultIfEmpty Jeśli jednak zostanie zastosowany do selektora kolekcji, element zewnętrzny zostanie połączony z wartością domyślną elementu wewnętrznego. Ze względu na to rozróżnienie tego rodzaju zapytania przekładają się na INNER JOIN brak DefaultIfEmpty i LEFT JOIN kiedy DefaultIfEmpty są stosowane.

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]

Selektor kolekcji odwołuje się do zewnętrznego elementu w przypadku innego niż przypadek

Gdy selektor kolekcji odwołuje się do elementu zewnętrznego, który nie znajduje się w klauzuli where (jak w powyższym przypadku), nie jest tłumaczony na sprzężenie bazy danych. Dlatego musimy ocenić selektor kolekcji dla każdego elementu zewnętrznego. Przekłada się na APPLY operacje w wielu relacyjnych bazach danych. Jeśli kolekcja jest pusta dla elementu zewnętrznego, nie zostaną wygenerowane żadne wyniki dla tego elementu zewnętrznego. DefaultIfEmpty Jeśli jednak zostanie zastosowany do selektora kolekcji, element zewnętrzny zostanie połączony z wartością domyślną elementu wewnętrznego. Ze względu na to rozróżnienie tego rodzaju zapytania przekładają się na CROSS APPLY brak DefaultIfEmpty i OUTER APPLY kiedy DefaultIfEmpty są stosowane. Niektóre bazy danych, takie jak SQLite, nie obsługują APPLY operatorów, więc tego rodzaju zapytanie może nie zostać przetłumaczone.

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

Operatory LINQ GroupBy tworzą wynik typu IGrouping<TKey, TElement> , w którym TKey i TElement może być dowolnym typem. Ponadto implementuje metodę IEnumerable<TElement>, co oznacza, IGrouping że można utworzyć ją przy użyciu dowolnego operatora LINQ po zgrupowaniu. Ponieważ żadna struktura bazy danych nie może reprezentować IGrouping, operatory GroupBy nie mają tłumaczenia w większości przypadków. Gdy operator agregacji jest stosowany do każdej grupy, która zwraca skalarny, można ją przetłumaczyć na język SQL GROUP BY w relacyjnych bazach danych. GROUP BY Sql również jest restrykcyjny. Wymaga to grupowania tylko według wartości skalarnych. Projekcja może zawierać tylko kolumny klucza grupowania lub dowolną agregację zastosowaną w kolumnie. Program EF Core identyfikuje ten wzorzec i tłumaczy go na serwer, jak w poniższym przykładzie:

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]

Program EF Core tłumaczy również zapytania, w których operator agregacji na grupowaniu pojawia się w operatorze LINQ Where lub OrderBy (lub innej kolejności). Używa HAVING klauzuli w języku SQL dla klauzuli where. Część zapytania przed zastosowaniem operatora GroupBy może być dowolnym złożonym zapytaniem, o ile można je przetłumaczyć na serwer. Ponadto po zastosowaniu operatorów agregacji w zapytaniu grupowania w celu usunięcia grupowania z wynikowego źródła można utworzyć na nim podobne do każdego innego zapytania.

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]

Operatory agregacji obsługiwane przez platformę EF Core są następujące

.NET SQL
Average(x => x.Property) AVG(Właściwość)
Count() COUNT(*)
LongCount() COUNT(*)
Max(x => x.Property) MAX(Właściwość)
Min(x => x.Property) MIN(Właściwość)
Sum(x => x.Property) SUM(Właściwość)

Dodatkowe operatory agregacji mogą być obsługiwane. Sprawdź dokumenty dostawcy, aby uzyskać więcej mapowań funkcji.

Mimo że w niektórych przypadkach nie ma struktury bazy danych reprezentującej IGroupingelement , program EF Core 7.0 i nowsze wersje mogą utworzyć grupowania po powrocie wyników z bazy danych. Jest to podobne do sposobu Include działania operatora w przypadku dołączania powiązanych kolekcji. Następujące zapytanie LINQ używa operatora GroupBy do grupowania wyników według wartości właściwości Price.

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

W tym przypadku operator GroupBy nie tłumaczy się bezpośrednio na klauzulę GROUP BY w języku SQL, ale zamiast tego program EF Core tworzy grupowania po powrocie wyników z serwera.

Sprzężenia po lewej stronie

Chociaż lewe sprzężenia nie jest operatorem LINQ, relacyjne bazy danych mają pojęcie sprzężenia lewego, który jest często używany w zapytaniach. Określony wzorzec zapytań LINQ daje taki sam wynik jak LEFT JOIN na serwerze. Program EF Core identyfikuje takie wzorce i generuje odpowiednik LEFT JOIN po stronie serwera. Wzorzec obejmuje utworzenie elementu GroupJoin między źródłami danych, a następnie spłaszczenie grupowania przy użyciu operatora SelectMany z ustawieniem DefaultIfEmpty w źródle grupowania, aby dopasować wartość null, gdy wewnętrzny nie ma powiązanego elementu. W poniższym przykładzie pokazano, jak wygląda ten wzorzec i jak je generuje.

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]

Powyższy wzorzec tworzy złożoną strukturę w drzewie wyrażeń. W związku z tym program EF Core wymaga spłaszczenia wyników grupowania operatora GroupJoin w kroku bezpośrednio po operatorze . Nawet jeśli parametr GroupJoin-DefaultIfEmpty-SelectMany jest używany, ale w innym wzorcu możemy nie zidentyfikować go jako sprzężenia lewego.