承接上篇,本文深入 GroupJoin 層級結果結構、into 關鍵字語義、預設左外連接行為、轉為內連接過濾、扁平化左外連接實作模式。
var q = from c in customers
join p in purchases on c.ID equals p.CustomerID
into custPurchases // into 將右側群組引入作用域
select custPurchases; // 選擇群組本身
// 結果型別:IEnumerable>
// 列舉方式:
foreach (var purchaseSeq in q) {
foreach (var p in purchaseSeq) Console.WriteLine(p.Description);
}
// 保留客戶名稱與其訂單群組
var q = from c in customers
join p in purchases on c.ID equals p.CustomerID
into custPurchases
select new { CustName = c.Name, custPurchases };
// c 仍在作用域內可被引用
var q = from c in customers
select new {
CustName = c.Name,
custPurchases = purchases.Where(p => c.ID == p.CustomerID)
};
// 邏輯相同,但無 Join 的雜湊查找優化 (本地查詢)
customers) **所有元素保留**,無對應內部元素者群組為空序列。where custPurchases.Any()。
// 內連接語義
var inner = from c in customers
join p in purchases on c.ID equals p.CustomerID
into custPurchases
where custPurchases.Any()
select new { c.Name, Orders = custPurchases };
into 後的 where 作用於**整個子序列** (群組層級)。
// 正確:先過濾購買單再連接
var q = from c in customers
join p in purchases.Where(p2 => p2.Price > 1000)
on c.ID equals p.CustomerID
into custPurchases
select new { c.Name, HighValueOrders = custPurchases };
需求:左外連接語義 + **扁平結果集** (非層級)。解法:GroupJoin → DefaultIfEmpty → SelectMany (查詢運算式第二個 from)。
var q = from c in customers
join p in purchases on c.ID equals p.CustomerID
into custPurchases
from cp in custPurchases.DefaultIfEmpty() // 空群組產生 null
select new {
CustName = c.Name,
Price = cp == null ? (decimal?)null : cp.Price
};
DefaultIfEmpty():空序列 → 單一 null 元素序列。from 編譯為 SelectMany,將群組展平合併。Price = null。into 的雙重身分| 位置 | 語義 | 產生型別 |
|---|---|---|
join ... into | GroupJoin (群組連接) | IEnumerable |
select/group ... into | 查詢延續 (引入新範圍變數) | 依後續子句 |
兩者皆「引入新查詢變數」,但前者建立群組結構,後者單純延續查詢管線,**極易混淆需精確辨識**。