Komplexní operátory dotazů

LinQ (Language Integrated Query) obsahuje mnoho složitých operátorů, které kombinují více zdrojů dat nebo zpracovávají složité zpracování. Ne všechny operátory LINQ mají na straně serveru vhodné překlady. Někdy se dotaz v jednom formuláři přeloží na server, ale pokud je napsaný v jiném formátu, nepřeloží se ani v případě, že je výsledek stejný. Tato stránka popisuje některé komplexní operátory a jejich podporované varianty. V budoucích verzích můžeme rozpoznat více vzorů a přidat jejich odpovídající překlady. Je také důležité mít na paměti, že podpora překladu se mezi poskytovateli liší. Pro databáze SQLite nemusí fungovat konkrétní dotaz, který je přeložen na SqlServer.

Tip

Ukázku pro tento článek najdete na GitHubu.

Připojení

Operátor LINQ Join umožňuje propojit dva zdroje dat na základě selektoru klíče pro každý zdroj a generovat řazenou kolekci hodnot, když se klíč shoduje. Přirozeně se překládá na INNER JOIN relační databáze. Zatímco LINQ Join má vnější a vnitřní selektory klíčů, databáze vyžaduje jednu podmínku spojení. EF Core proto vygeneruje podmínku spojení porovnáním selektoru vnějšího klíče s selektorem vnitřního klíče pro rovnost.

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]

Pokud jsou selektory klíčů anonymní typy, EF Core vygeneruje podmínku spojení pro porovnání komponent rovnosti.

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

Operátor LINQ GroupJoin umožňuje připojit dva zdroje dat podobné join, ale vytvoří skupinu vnitřních hodnot pro odpovídající vnější prvky. Provedení dotazu jako v následujícím příkladu vygeneruje výsledek Blog & IEnumerable<Post>. Vzhledem k tomu, že databáze (zejména relační databáze) nemají způsob, jak reprezentovat kolekci objektů na straně klienta, groupJoin se v mnoha případech nepřekládá na server. Vyžaduje, abyste získali všechna data ze serveru, abyste mohli provést GroupJoin bez speciálního selektoru (první dotaz níže). Pokud ale selektor omezuje výběr dat, může načtení všech dat ze serveru způsobit problémy s výkonem (druhý dotaz níže). Proto EF Core nepřekládá 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

Operátor LINQ SelectMany umožňuje vytvořit výčet nad selektorem kolekce pro každý vnější prvek a vygenerovat řazené kolekce členů hodnot z každého zdroje dat. Je to spojení, ale bez jakékoli podmínky, takže každý vnější prvek je propojený s prvkem ze zdroje kolekce. V závislosti na tom, jak se výběr kolekce vztahuje k vnějšímu zdroji dat, může SelectMany přeložit na různé různé dotazy na straně serveru.

Selektor kolekce neodkazuje na vnější

Pokud selektor kolekce neodkazuje na nic z vnějšího zdroje, výsledkem je kartézský součin obou zdrojů dat. Překládá se do CROSS JOIN relačních databází.

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 kolekce odkazuje na vnější v klauzuli where

Pokud selektor kolekce obsahuje klauzuli where, která odkazuje na vnější prvek, EF Core ji přeloží na spojení databáze a použije predikát jako podmínku spojení. Obvykle k tomuto případu dochází při použití navigace kolekce na vnějším prvku jako selektor kolekce. Pokud je kolekce pro vnější prvek prázdná, nebudou pro tento vnější prvek generovány žádné výsledky. DefaultIfEmpty Pokud je však použit na selektor kolekce, vnější prvek bude připojen s výchozí hodnotou vnitřního prvku. Vzhledem k tomuto rozdílu se tento druh dotazů překládá na INNER JOIN absenci DefaultIfEmpty a LEFT JOIN kdy DefaultIfEmpty se používá.

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 kolekce odkazuje na vnější v případě, že není případ

Když selektor kolekce odkazuje na vnější prvek, který není v klauzuli where (jak je uvedeno výše), nepřeloží se na spojení databáze. Proto musíme vyhodnotit selektor kolekce pro každý vnější prvek. Překládá se na APPLY operace v mnoha relačních databázích. Pokud je kolekce pro vnější prvek prázdná, nebudou pro tento vnější prvek generovány žádné výsledky. DefaultIfEmpty Pokud je však použit na selektor kolekce, vnější prvek bude připojen s výchozí hodnotou vnitřního prvku. Vzhledem k tomuto rozdílu se tento druh dotazů překládá na CROSS APPLY absenci DefaultIfEmpty a OUTER APPLY kdy DefaultIfEmpty se používá. Některé databáze, jako je SQLite, nepodporují APPLY operátory, takže tento druh dotazu nemusí být přeložený.

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

Operátory LINQ GroupBy vytvoří výsledek typu IGrouping<TKey, TElement> , kde TKey a TElement může být libovolný typ. Kromě toho implementuje IEnumerable<TElement>, což znamená, IGrouping že můžete vytvořit přes něj pomocí libovolného LINQ operátoru po seskupení. Vzhledem k tomu, že žádná struktura databáze nemůže představovat IGrouping, operátory GroupBy nemají ve většině případů žádný překlad. Pokud je u každé skupiny použit agregační operátor, který vrací skalár, lze ho přeložit do SQL GROUP BY v relačních databázích. GROUP BY SQL je také omezující. Vyžaduje seskupení pouze podle skalárních hodnot. Projekce může obsahovat pouze seskupovací klíčové sloupce nebo jakoukoli agregaci použitou u sloupce. EF Core identifikuje tento vzor a přeloží ho na server, jak je znázorněno v následujícím příkladu:

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 také překládá dotazy, kde se agregační operátor ve seskupování zobrazuje v operátoru LINQ Where nebo OrderBy (nebo jiné řazení). Používá HAVING klauzuli v SQL pro klauzuli where. Část dotazu před použitím operátoru GroupBy může být jakýkoli složitý dotaz, pokud jej lze přeložit na server. Kromě toho, jakmile použijete agregační operátory na seskupovací dotaz k odebrání seskupení z výsledného zdroje, můžete nad ním vytvořit stejně jako jakýkoli jiný dotaz.

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]

Agregační operátory EF Core podporují následující:

.NET SQL
Average(x => x.Property) AVG(Vlastnost)
Count() COUNT(*)
LongCount() COUNT(*)
Max(x => x.Property) MAX(Vlastnost)
Min(x => x.Property) MIN(Vlastnost)
Sum(x => x.Property) SUMA(Vlastnost)

Mohou být podporovány další agregační operátory. Další mapování funkcí najdete v dokumentaci zprostředkovatele.

I když neexistuje žádná struktura databáze, která by představovala IGrouping, v některých případech ef Core 7.0 a novější může vytvořit seskupení po vrácení výsledků z databáze. Podobá se tomu, jak Include operátor funguje při zahrnutí souvisejících kolekcí. Následující dotaz LINQ používá operátor GroupBy k seskupení výsledků podle hodnoty jejich Price vlastnost.

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

V tomto případě se operátor GroupBy nepřeloží přímo na GROUP BY klauzuli v SQL, ale EF Core vytvoří seskupení po vrácení výsledků ze serveru.

Levé spojení

Zatímco Left Join není operátor LINQ, relační databáze mají koncept levého spojení, který se často používá v dotazech. Konkrétní vzor v dotazech LINQ poskytuje stejný výsledek jako LEFT JOIN na serveru. EF Core identifikuje takové vzory a vygeneruje ekvivalent LEFT JOIN na straně serveru. Tento model zahrnuje vytvoření GroupJoin mezi zdroji dat a následné zploštění seskupení pomocí operátoru SelectMany s DefaultIfEmpty ve zdroji seskupení tak, aby odpovídal hodnotě null, pokud vnitřní prvek nemá související prvek. Následující příklad ukazuje, jak tento vzor vypadá a co 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]

Výše uvedený vzor vytvoří ve stromu výrazů složitou strukturu. Z tohoto důvodu ef Core vyžaduje, abyste zploštěli výsledky seskupení operátoru GroupJoin v kroku bezprostředně za operátorem. I když se používá GroupJoin-DefaultIfEmpty-SelectMany, ale v jiném vzoru, nemusíme ji identifikovat jako levé spojení.