Megosztás a következőn keresztül:


Összetett lekérdezési operátorok

A Language Integrated Query (LINQ) számos összetett operátort tartalmaz, amelyek több adatforrást egyesítenek, vagy összetett feldolgozást végeznek. Nem minden LINQ-operátor rendelkezik megfelelő fordítással a kiszolgáló oldalán. Néha egy lekérdezés lefordítódik a szerverre, de ha más formában van megfogalmazva, nem fordítódik le, még akkor sem, ha az eredmény ugyanaz. Ez a lap néhány összetett operátort és azok támogatott változatait ismerteti. A jövőbeli kiadásokban több mintát ismerhetünk fel, és hozzáadhatjuk a hozzájuk tartozó fordításokat. Azt is fontos szem előtt tartani, hogy a fordítás támogatása szolgáltatónként eltérő. Előfordulhat, hogy az SqlServerben lefordított lekérdezések nem működnek az SQLite-adatbázisokban.

Jótanács

A cikk mintáját a GitHubon tekintheti meg.

Csatlakozz

A LINQ Join operátor lehetővé teszi két adatforrás csatlakoztatását, a források kulcsválasztói alapján, így a kulcs egyezésekor egy értékekből álló pár jön létre. Természetesen INNER JOIN-re fordul át a relációs adatbázisokon. Bár a LINQ-illesztés külső és belső kulcsválasztókkal rendelkezik, az adatbázishoz egyetlen csatlakozási feltétel szükséges. Az EF Core tehát egy illesztési feltételt hoz létre a külső kulcsválasztó és a belső kulcsválasztó összehasonlításával az egyenlőség érdekében.

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]

Továbbá, ha a kulcsválasztók névtelen típusok, az EF Core létrehoz egy illesztési feltételt az egyenlőség komponen-szinten történő összehasonlítására.

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

Csatlakozás csoporthoz

A LINQ GroupJoin operátor lehetővé teszi két, a Joinhoz hasonló adatforrás csatlakoztatását, de belső értékek egy csoportját hozza létre a külső elemeknek való megfeleléshez. Egy a következő példához hasonló lekérdezés végrehajtása eredményként a Blog és IEnumerable<Post> értékeket generálja. Mivel az adatbázisoknak (különösen a relációs adatbázisoknak) nincs módjuk ügyféloldali objektumok gyűjteményének ábrázolására, a GroupJoin sok esetben nem fordítja le a kiszolgálóra. Ehhez be kell szereznie az összes adatot a kiszolgálóról a GroupJoin speciális választó nélkül történő elvégzéséhez (az első lekérdezés alább). Ha azonban a választó korlátozza a kiválasztott adatokat, akkor az összes adat lekérése a kiszolgálóról teljesítményproblémákat okozhat (az alábbi második lekérdezés). Ezért nem fordítja le az EF Core a GroupJoin-t.

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

A LINQ SelectMany operátor lehetővé teszi az egyes külső elemek gyűjteményválasztóinak számbavételét, és az egyes adatforrásokból származó értékek összesítését. Ez egy illesztés, de feltétel nélkül, így minden külső elem a gyűjtemény forrásából származó elemhez csatlakozik. Attól függően, hogy a gyűjteményválasztó hogyan kapcsolódik a külső adatforráshoz, a SelectMany különböző lekérdezéseket tud lefordítani a kiszolgáló oldalán.

A gyűjteményválasztó nem hivatkozik a külső kontextusra

Ha a gyűjteményválasztó nem hivatkozik semmire a külső forrásból, az eredmény mindkét adatforrás cartesian-terméke. Relációs adatbázisokban fordítható le CROSS JOIN .

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]

A gyűjteményválasztó külső hivatkozásokat tartalmaz a where záradékban

Ha a gyűjteményválasztó rendelkezik egy where záradékkal, amely a külső elemre hivatkozik, akkor az EF Core lefordítja azt egy adatbázis-illesztésre, és a predikátumot használja illesztési feltételként. Ez az eset általában akkor fordul elő, ha a külső elemen a gyűjtemény navigációját használja gyűjtemény választóként. Ha a gyűjtemény üres egy külső elemhez, akkor a külső elemhez nem jön létre eredmény. DefaultIfEmpty Ha azonban a gyűjteményválasztóra van alkalmazva, a külső elem a belső elem alapértelmezett értékével lesz összekapcsolva. Emiatt a megkülönböztetés az ilyen típusú lekérdezések INNER JOIN a hiányában DefaultIfEmpty és LEFT JOIN alkalmazása esetén DefaultIfEmpty fordulnak elő.

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]

A gyűjteményválasztó külső hivatkozásokat tartalmaz egy nem WHERE esetben.

Ha a gyűjteményválasztó a külső elemre hivatkozik, amely nem szerepel a where záradékban (mint a fenti esetben), az nem fordítható le adatbázis-illesztésre. Ezért az egyes külső elemek esetében ki kell értékelnünk a gyűjteményválasztót. Számos relációs adatbázisban ezt APPLY műveletekre fordítja le. Ha a gyűjtemény üres egy külső elemhez, akkor a külső elemhez nem jön létre eredmény. DefaultIfEmpty Ha azonban a gyűjteményválasztóra van alkalmazva, a külső elem a belső elem alapértelmezett értékével lesz összekapcsolva. Emiatt a megkülönböztetés az ilyen típusú lekérdezések CROSS APPLY a hiányában DefaultIfEmpty és OUTER APPLY alkalmazása esetén DefaultIfEmpty fordulnak elő. Bizonyos adatbázisok, például az SQLite nem támogatják APPLY az operátorokat, ezért előfordulhat, hogy az ilyen típusú lekérdezések nem lesznek lefordítva.

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]

Csoportosítás

A LINQ GroupBy operátorok olyan típusú IGrouping<TKey, TElement> eredményt hoznak létre, ahol a TKey és TElement tetszőleges típus lehet. A IGrouping implementálja a IEnumerable<TElement>, ami azt jelenti, hogy a csoportosítás után bármilyen LINQ-operátorral komponálhat rá. Mivel egyetlen adatbázis-struktúra sem képviselhet egy adatbázist IGrouping, a GroupBy operátorok a legtöbb esetben nem rendelkeznek fordítással. Ha minden csoportra alkalmaz egy összesítő operátort, amely skaláris értéket ad vissza, az SQL-be GROUP BY fordítható le relációs adatbázisokban. Az SQL GROUP BY is korlátozó. Ehhez csak skaláris értékek szerint kell csoportosítani. A vetítés csak csoportosítási kulcsoszlopokat vagy oszlopra alkalmazott összesítéseket tartalmazhat. Az EF Core azonosítja ezt a mintát, és lefordítja a kiszolgálóra, ahogyan az alábbi példában is látható:

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]

Az EF Core olyan lekérdezéseket is lefordít, ahol a csoportosítás összesítő operátora egy Where vagy OrderBy (vagy egyéb rendelési) LINQ operátorban jelenik meg. Az SQL-ben záradékot használ HAVING a where záradékhoz. A GroupBy operátor alkalmazása előtt a lekérdezés bármely összetett lekérdezés lehet, amíg lefordítható a kiszolgálóra. Továbbá, ha összesítő operátorokat alkalmaz egy csoportosítási lekérdezésre, hogy eltávolítsa a csoportosításokat az eredményül kapott forrásból, a többi lekérdezéshez hasonlóan írhat rá.

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]

Az összesítő operátorok EF Core-támogatásai a következők:

.NET SQL
Átlag(x => x.Tulajdonság) AVG(tulajdonság)
Darabszám() DARAB(*)
LongCount() DARAB(*)
Max(x => x.Property) MAX(tulajdonság)
Min(x => x.Tulajdonság) MIN(Tulajdonság)
Sum(x => x.Tulajdonság) SUM(Tulajdonság)

További összesítő operátorok is támogatottak lehetnek. További függvényleképezésekért tekintse meg a szolgáltatói dokumentumokat.

Annak ellenére, hogy az EF Core 7.0 és újabb rendszer bizonyos esetekben nem rendelkezik IGroupingadatbázis-struktúrával, létrehozhatja a csoportosításokat az eredmények adatbázisból való visszaadása után. Ez hasonló az operátor működéséhez a Include kapcsolódó gyűjtemények felvételekor. Az alábbi LINQ-lekérdezés a GroupBy operátorral csoportosítja az eredményeket a Price tulajdonság értéke alapján.

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

Ebben az esetben a GroupBy operátor nem fordítja le közvetlenül az SQL egyik GROUP BY záradékát, hanem az EF Core hozza létre a csoportosításokat az eredmények kiszolgálóról való visszaadása után.

Bal oldali illesztés

Bár a Left Join nem LINQ-operátor, a relációs adatbázisok a lekérdezésekben gyakran használt Left Join fogalmat használják. A LINQ-lekérdezések egy adott mintája ugyanazt az eredményt adja, mint a LEFT JOIN kiszolgálón. Az EF Core azonosítja az ilyen mintákat, és a kiszolgálóoldalon létrehozza az ezzel egyenértékűt LEFT JOIN . A minta magában foglalja a GroupJoin létrehozását mindkét adatforrás között, majd a csoportosított elemek kisimítását a SelectMany operátorral és a DefaultIfEmpty használatával a csoportosítási forráson úgy, hogy nullával párosít, ha a belső nem tartalmaz kapcsolódó elemet. Az alábbi példa bemutatja, hogyan néz ki ez a minta, és mit hoz létre.

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]

A fenti minta egy összetett struktúrát hoz létre a kifejezésfában. Emiatt az EF Core megköveteli, hogy a GroupJoin operátor csoportosítási eredményeit simítsuk ki az operátort közvetlenül követő lépésben. Még ha a GroupJoin-DefaultIfEmpty-SelectMany-t használják is, de más mintában, előfordulhat, hogy nem azonosítjuk bal oldali illesztésként.