Join 與 GroupJoin 是 LINQ 兩大集合連接運算子,對應 SQL INNER JOIN 與 LEFT OUTER JOIN。本文深入解析語法形式、本地查詢效能優勢、多鍵連接、Lambda 語法與查詢運算式對比。
| 運算子 | SQL 對應 | 結果結構 | 適用場景 |
|---|---|---|---|
Join | INNER JOIN | 扁平序列 | 一對一/多對一、需合併欄位 |
GroupJoin | LEFT OUTER JOIN (預設) | 層級序列 (群組) | 一對多、保留外部所有元素 |
// 列出所有客戶及其訂單 (不使用導航屬性)
var q = from c in ctx.Customers
join p in ctx.Purchases on c.ID equals p.CustomerID
select c.Name + " bought a " + p.Description;
// 先將資料載入記憶體陣列
Customer[] customers = ctx.Customers.ToArray();
Purchase[] purchases = ctx.Purchases.ToArray();
// 1. SelectMany (巢狀迴圈) - O(N*M) 慢
var slow = from c in customers
from p in purchases
where c.ID == p.CustomerID
select c.Name + " bought a " + p.Description;
// 2. Join (雜湊連接) - O(N+M) 快
var fast = from c in customers
join p in purchases on c.ID equals p.CustomerID
select c.Name + " bought a " + p.Description;
原理: Join 在 Enumerable 實作中**預先將內部序列載入雜湊查找表**,避免重複枚舉。LINQ to SQL 則翻譯為 SQL JOIN,由資料庫優化器處理,**兩者效能相當**。
// 等效寫法
from p in purchases join c in customers on p.CustomerID equals c.ID ...
多重連接鏈:
from c in customers
join p in purchases on c.ID equals p.CustomerID
join pi in purchaseItems on p.ID equals pi.PurchaseID
select new { c.Name, p.Price, pi.Detail };
使用**匿名型別**建立複合鍵,編譯器保證結構相同:
from x in seqX
join y in seqY
on new { K1 = x.Prop1, K2 = x.Prop2 }
equals new { K1 = y.Prop3, K2 = y.Prop4 }
select ...;
// 查詢運算式
var q = from c in customers
join p in purchases on c.ID equals p.CustomerID
select new { c.Name, p.Description, p.Price };
// 等效 Lambda
var qLambda = customers.Join(
purchases, // inner sequence
c => c.ID, // outerKeySelector
p => p.CustomerID, // innerKeySelector
(c, p) => new { c.Name, p.Description, p.Price } // resultSelector
);
// 需在 Join 後 OrderBy Price
var q = customers.Join(purchases, c => c.ID, p => p.CustomerID,
(c, p) => new { c, p }) // resultSelector 產生匿名型別保留 c, p
.OrderBy(x => x.p.Price)
.Select(x => x.c.Name + " bought a " + x.p.Description);
結論: 連接操作**強烈建議使用查詢運算式語法**,可讀性、型別推斷、後續子句串接皆優於 Lambda。