複雜查詢運算子

語言整合式查詢 (LINQ) 包含許多複雜運算符,這些運算子結合了多個數據源或執行複雜的處理。 並非所有 LINQ 運算符在伺服器端都有適當的翻譯。 有時候,一個窗體中的查詢會轉譯為伺服器,但如果以不同的窗體撰寫,即使結果相同,也不會轉譯。 此頁面描述一些複雜運算元及其支持的變化。 在未來版本中,我們可能會辨識更多模式,並新增其對應的翻譯。 請記住,翻譯支援在提供者之間有所不同也很重要。 SqlServer 中轉譯的特定查詢可能無法用於 SQLite 資料庫。

提示

您可以檢視本文中的 GitHut 範例

聯結

LINQ Join 運算符可讓您根據每個來源的索引鍵選取器來連接兩個數據源,並在索引鍵相符時產生值的 Tuple。 它自然會轉譯為 INNER JOIN 關係資料庫。 雖然 LINQ Join 具有外部和內部索引鍵選取器,但資料庫需要單一聯結條件。 因此,EF Core 會藉由比較外部索引鍵選取器與內部索引鍵選取器來產生聯結條件,以取得相等。

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]

此外,如果索引鍵選取器是匿名類型,EF Core 會產生聯結條件來比較相等元件。

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

LINQ GroupJoin 運算子可讓您連接兩個類似 Join 的數據源,但它會建立一組內部值來比對外部元素。 執行類似下列範例的查詢會產生 和IEnumerable<Post>的結果Blog。 因為資料庫(特別是關係資料庫)沒有方法可以代表客戶端物件的集合,因此 GroupJoin 在許多情況下不會轉譯為伺服器。 它要求您從伺服器取得所有數據,以執行 GroupJoin,而不需要特殊的選取器(以下的第一個查詢)。 但是,如果選取器限制選取的數據,則從伺服器擷取所有數據可能會導致效能問題(下面的第二個查詢)。 這就是為什麼 EF Core 不會翻譯 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

LINQ SelectMany 運算子可讓您列舉每個外部元素的集合選取器,並從每個數據源產生值的 Tuple。 一方面,它是聯結,但沒有任何條件,因此每個外部專案都會與集合來源中的項目連接。 根據集合選取器與外部數據源的關聯方式,SelectMany 可以轉譯為伺服器端的各種不同查詢。

集合選取器未參考外部

當集合選取器未參考來自外部來源的任何專案時,結果是這兩個數據源的笛卡兒乘積。 它會在關係資料庫中轉譯為 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]

集合選取器參考 where 子句中的外部

當集合選取器具有 where 子句,其會參考外部元素時,EF Core 會將它轉譯為資料庫聯結,並使用述詞做為聯結條件。 通常,當在外部元素上使用集合導覽做為集合選取器時,就會發生此情況。 如果外部元素的集合是空的,則不會針對該外部項目產生任何結果。 但是,如果 DefaultIfEmpty 套用在集合選取器上,則外部元素會以內部元素的預設值連接。 由於這項區別,這種查詢會在沒有且套用時DefaultIfEmpty轉譯為 INNER JOINDefaultIfEmptyLEFT JOIN

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]

集合選取器在非案例中參考外部

當集合選取器參考外部專案時,它不在 where 子句中(如上述案例),它不會轉譯為資料庫聯結。 這就是為什麼我們需要評估每個外部元素的集合選取器。 它會轉譯成 APPLY 許多關係資料庫中的作業。 如果外部元素的集合是空的,則不會針對該外部項目產生任何結果。 但是,如果 DefaultIfEmpty 套用在集合選取器上,則外部元素會以內部元素的預設值連接。 由於這項區別,這種查詢會在沒有且套用時DefaultIfEmpty轉譯為 CROSS APPLYDefaultIfEmptyOUTER APPLY 某些資料庫,例如 SQLite 不支援 APPLY 運算符,因此這類查詢可能無法轉譯。

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 運算符會建立類型 IGrouping<TKey, TElement> 的結果,其中 TKeyTElement 可以是任何任意類型。 此外, IGrouping 實作 IEnumerable<TElement>,這表示您可以在群組之後使用任何 LINQ 運算符來撰寫。 因為沒有任何資料庫結構可以代表 IGrouping,因此在大部分情況下,GroupBy 運算子都沒有轉譯。 當匯總運算子套用至傳回純量的每個群組時,它可以轉譯為關係資料庫中的 SQL GROUP BY 。 SQL GROUP BY 也有限制。 它要求您只依純量值分組。 投影只能包含群組索引鍵數據行,或套用至數據行的任何匯總。 EF Core 會識別此模式,並將其轉譯為伺服器,如下列範例所示:

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 也會轉譯群組上的匯總運算符出現在 Where 或 OrderBy (或其他排序) LINQ 運算子中的查詢。 它會針對 where 子句使用 HAVING SQL 中的 子句。 套用 GroupBy 運算子之前的查詢部分可以是任何複雜的查詢,只要它可以轉譯為伺服器即可。 此外,一旦您在群組查詢上套用匯總運算符,以從產生的來源移除群組,就可以像任何其他查詢一樣在上面撰寫。

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 支援如下

.NET SQL
Average(x => x.Property) AVG(屬性)
Count() COUNT\
LongCount() COUNT\
Max(x => x.Property) MAX(屬性)
Min(x => x.Property) MIN(屬性)
Sum(x => x.Property) SUM(屬性)

可能支援其他匯總運算符。 請檢查您的提供者檔以取得更多函式對應。

即使沒有代表 IGrouping的資料庫結構,在某些情況下,EF Core 7.0 和更新的版本可以在從資料庫傳回結果之後建立群組。 這類似於運算子在包含相關集合時的運作方式 Include 。 下列 LINQ 查詢會使用 GroupBy 運算符,依其 Price 屬性的值來分組結果。

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

在此情況下,GroupBy 運算符不會直接轉譯為 GROUP BY SQL 中的 子句,而是 EF Core 會在從伺服器傳回結果之後建立群組。

左聯結

雖然 Left Join 不是 LINQ 運算子,但關係資料庫具有在查詢中經常使用的 Left Join 概念。 LINQ 查詢中的特定模式會提供與伺服器上的 相同結果 LEFT JOIN 。 EF Core 會識別這類模式,並在伺服器端產生對等 LEFT JOIN 專案。 此模式牽涉到在數據源之間建立 GroupJoin,然後使用 SelectMany 運算符搭配 DefaultIfEmpty 在群組來源上使用 SelectMany 運算符,在內部沒有相關元素時比對 Null。 下列範例顯示該模式的外觀及其產生的內容。

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]

上述模式會在表達式樹狀結構中建立複雜結構。 因此,EF Core 會要求您將 GroupJoin 運算子的分組結果扁平化,並緊接在運算符之後的步驟中。 即使使用 GroupJoin-DefaultIfEmpty-SelectMany,但在不同的模式中,我們可能不會將其識別為 Left Join。