into + DefaultIfEmpty()LINQ to SQL 實現 Left Join 的關鍵在於 into 關鍵字建立分組連接,再配合 DefaultIfEmpty() 處理右表無匹配行的情況。
var query =
from r in db.Roles
join sr in
(
from ss in db.StaffInRoles
where ss.StaffId == staffId
select ss
)
on r.Id equals sr.RoleId
into x // 分組連接,x 為 IGrouping<int, StaffInRole>
from y in x.DefaultIfEmpty() // 若無匹配,y 為 null
select new RoleInfo
{
RoleId = r.Id,
RoleName = r.Name,
IsSet = y != null // 透過 null 判斷右表是否存在
};
into x:將連接結果分組,每個左表行對應一個分組(可能為空)。from y in x.DefaultIfEmpty():展開分組;若分組為空,產生單一 null 元素,確保左表行不丟失。y != null:判斷右表是否有匹配行,轉為布林旗標 IsSet。exec sp_executesql N'
SELECT [t0].[Id] AS [RoleId], [t0].[Name] AS [RoleName],
(CASE
WHEN [t2].[test] IS NOT NULL THEN 1
ELSE 0
END) AS [IsSet]
FROM [Role] AS [t0]
LEFT OUTER JOIN (
SELECT 1 AS [test], [t1].[StaffId], [t1].[RoleId]
FROM [StaffInRole] AS [t1]
) AS [t2] ON ([t0].[Id] = [t2].[RoleId]) AND ([t2].[StaffId] = @p0)
', N'@p0 int', @p0=1
LEFT OUTER JOIN 語法,右表子查詢預先篩選 StaffId。test = 1,供外層 CASE WHEN ... IS NOT NULL 判斷。@p0),防注入且利於計畫快取。join ... on ... equals ... 不加 into → 內連接DefaultIfEmpty() → 右表無匹配時左表行消失y == null 而非 y != null 導致邏輯反轉var query =
from a in db.TableA
join b in db.TableB on a.Id equals b.AId into ab
from b in ab.DefaultIfEmpty()
join c in db.TableC on a.Id equals c.AId into ac
from c in ac.DefaultIfEmpty()
select new { a, b, c };
每個 into + DefaultIfEmpty() 獨立處理一個右表,邏輯清晰可組合。