共用方式為


複雜查詢運算符

語言整合式查詢 (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'))

群組加入

LINQ GroupJoin 運算子可讓您連接兩個類似 Join 的數據源,但它會建立一組內部值來比對外部元素。 執行類似下列範例的查詢會產生BlogIEnumerable<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 運算子可讓您針對每個外部元素列舉集合選取器,並從每個數據源產生成對的值的元組。 在某種程度上,它是一種聯結,但沒有任何條件,因此每個外部元素都會與集合來源中的元素連接。 根據集合選取器與外部數據源的關聯方式,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 套用在集合選取器上,則外部元素會以內部元素的預設值連接。 由於這項區別,當INNER JOIN 被套用且沒有 DefaultIfEmptyLEFT JOIN 時,這種查詢會轉譯為 DefaultIfEmpty

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 套用在集合選取器上,則外部元素會以內部元素的預設值連接。 由於這項區別,當CROSS APPLY 被套用且沒有 DefaultIfEmptyOUTER APPLY 時,這種查詢會轉譯為 DefaultIfEmpty。 某些資料庫,例如 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 可以是任何任意類型。 此外, 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 運算子中的查詢。 它在 SQL 中使用 HAVING 子句作為 where 子句。 套用 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
平均(x => x.屬性) 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 展開群組。當內部源沒有相關元素時,則以此方式處理空值 (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。