Sdílet prostřednictvím


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.

Návod

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

Připojte se

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 v relačních databázích. 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]

Dále, pokud jsou selektory klíčů anonymní typy, EF Core vygeneruje podmínku spojení pro srovnání rovnosti po jednotlivých komponentech.

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 iterovat přes výběr kolekcí pro každý vnější prvek a generovat n-tice hodnot z každého datového zdroje. Svým způsobem, je to spojení, ale bez jakékoli podmínky, takže každý vnější prvek je spojený s prvkem ze zdrojové kolekce. V závislosti na tom, jak je výběr kolekce spojen s externím zdrojem dat, může SelectMany přeložit do různých dotazů na straně serveru.

Selektor kolekce neodkazuje na externí kontext

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 jako CROSS JOIN v relačních databázích.

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 ve where klauzuli odkazuje na vnější objekt.

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 v kolekci na vnějším prvku, který je použit 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 v nepřítomnosti DefaultIfEmpty a LEFT JOIN kdy se DefaultIfEmpty 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ší strukturu v případě bez WHERE.

Když selektor kolekce odkazuje na vnější prvek, který není v klauzuli where (jak je tomu výše), nepřechází na databázové spojení. Proto musíme vyhodnotit selektor kolekce pro každý vnější element. 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 v nepřítomnosti DefaultIfEmpty a OUTER APPLY kdy se DefaultIfEmpty 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 IGrouping implementuje IEnumerable<TElement>, což znamená, že můžete nad ním provádět operace pomocí libovolného operátoru LINQ 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. Také GROUP BY SQL je omezující. Vyžaduje seskupení pouze podle skalárních hodnot. Projekce může obsahovat pouze skupinové klíčové sloupce nebo jakoukoli agregaci aplikovanou na sloupec. 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í objevuje v operátoru LINQ typu Where nebo OrderBy (nebo jiné řadicí operace). 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í ve výsledném zdroji, můžete s ním pracovat stejně jako s jakýmkoli jiným dotazem.

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]

EF Core podporuje následující agregační operátory:

platforma .NET SQL
Average(x => x.Property) AVG(Vlastnost)
Počet() COUNT(*)
LongCount() COUNT(*)
Max(x => x.Vlastnost) 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 vlastnosti „Price“.

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řevede přímo na GROUP BY klauzuli v SQL, nýbrž EF Core vytvoří seskupení až 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 zjednodušili výsledky seskupení operátoru GroupJoin v kroku přímo po operátoru. I když je používán GroupJoin-DefaultIfEmpty-SelectMany, ale v jiném vzorci, nemusíme jej identifikovat jako levé spojení.