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

您可以在 GitHub 上檢視此文章的 範例 \(英文\)。

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 的資料來源,但它會建立一組內部值來比對專用項目。 執行類似下列範例的 Blog&IEnumerable<Post> 查詢會產生 的結果。 由於資料庫 (特別是關係資料庫,) 無法代表用戶端物件的集合,因此 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 集合選取器上套用 ,專用項目將會以內部元素的預設值連接。 由於這項區別,這種查詢會在沒有時和 LEFT JOIN 套用時 DefaultIfEmpty 轉譯為 INNER JOINDefaultIfEmpty

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 集合選取器上套用 ,專用項目將會以內部元素的預設值連接。 由於這項區別,這種查詢會在沒有時和 OUTER APPLY 套用時 DefaultIfEmpty 轉譯為 CROSS APPLYDefaultIfEmpty 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]

LINQ GroupBy 運算子會建立類型 IGrouping<TKey, TElement> 的結果,其中 TKeyTElement 可以是任何任意類型。 此外,實作 IEnumerable<TElement>IGrouping 這表示您可以在群組之後使用任何 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 支援如下

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

雖然 Left Join 不是 LINQ 運算子,但關係資料庫具有在查詢中經常使用的 Left Join 概念。 LINQ 查詢中的特定模式會提供與 LEFT JOIN 伺服器上的 相同結果。 EF Core 會識別這類模式,並在伺服器端產生對等 LEFT JOIN 專案。 此模式牽涉到在資料來源之間建立 GroupJoin,然後使用 SelectMany 運算子搭配 GroupIng 來源上的 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,但在不同的模式中,我們可能不會將其識別為左聯結。