語言整合式查詢 (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 的數據源,但它會建立一組內部值來比對外部元素。 執行類似下列範例的查詢會產生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 運算子可讓您針對每個外部元素列舉集合選取器,並從每個數據源產生成對的值的元組。 在某種程度上,它是一種聯結,但沒有任何條件,因此每個外部元素都會與集合來源中的元素連接。 根據集合選取器與外部數據源的關聯方式,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
被套用且沒有 DefaultIfEmpty
和 LEFT 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
被套用且沒有 DefaultIfEmpty
和 OUTER 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>
的結果,其中 TKey
和 TElement
可以是任何任意類型。 此外, 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。