關鍵字: Join
當二個序列資料(sequences)存在可以用來比較的屬性欄位時,我們就可以用 Join來建立二個序列資料間的關連。 你可以使用 equals關鍵字來判斷二個資料的 Key 值是否相等。
在 Linq 中操作 Join 包含三種型態:
- Inner Join:Join 查詢。
- Group Join:分組查詢。
- Left Join:一對多選擇。
Inner Join
要建立 INNER JOIN 關連的 LINQ 語法,有以下幾種寫法:
類型一
from p in Products
from od in OrderDetails
where od.ProductID == p.ProductID
select new {
od.OrderID, od.ProductID, p.ProductName
}
若是使用具有關連性的 Entity 模型中的物件,可以直接這樣使用:
from p in Products
from od in p.OrderDetails
select new {
od.OrderID, od.ProductID, p.ProductName
}
以上所產生的 T-SQL 語法如下:
SELECT [t0].[OrderID], [t0].[ProductID], [t1].[ProductName]
FROM [Order Details] AS [t0], [Products] AS [t1]
WHERE [t0].[ProductID] = [t1].[ProductID]
類型二
from p in Products
join od in OrderDetails on p.ProductID equals od.ProductID
select new { od.OrderID, od.ProductID, p.ProductName } //produces flat sequence
Products
.Join (
OrderDetails,
p => p.ProductID,
od => od.ProductID,
(p, od) =>
new
{
OrderID = od.OrderID,
ProductID = od.ProductID,
ProductName = p.ProductName
}
)
SELECT [t1].[OrderID], [t1].[ProductID], [t0].[ProductName]
FROM [Products] AS [t0]
INNER JOIN [Order Details] AS [t1] ON [t0].[ProductID] = [t1].[ProductID]
Group Join
將 join語法加入 into表示,就是 Group Join 。
類型一
Group Join 會回應一個階層式的序列結果。若比對不到的元素,就回應一個空陣列。
from o in Orders
join od in OrderDetails on o.OrderID equals od.OrderID into tmpDetail
select new { o.OrderID, tmpDetail }
Orders
.GroupJoin (
OrderDetails,
o => o.OrderID,
od => od.OrderID,
(o, tmpDetail) =>
new
{
OrderID = o.OrderID,
tmpDetail = tmpDetail
}
)
類型二
你也可以針對 Join 後的結果再進行篩選
from o in Orders
join od in OrderDetails on o.OrderID equals od.OrderID into tmpDetail
from detail in tmpDetail
where detail.UnitPrice > 80
select new { o.OrderID, tmpDetail }
SELECT [t0].[OrderID], [t2].[OrderID] AS [OrderID2], [t2].[ProductID], [t2].[UnitPrice], [t2].[Quantity], [t2].[Discount]
FROM [Orders] AS [t0]
CROSS JOIN [Order Details] AS [t1]
LEFT OUTER JOIN [Order Details] AS [t2] ON [t0].[OrderID] = [t2].[OrderID]
WHERE ([t1].[UnitPrice] > @p0) AND ([t0].[OrderID] = [t1].[OrderID])
ORDER BY [t0].[OrderID], [t1].[OrderID], [t1].[ProductID], [t2].[ProductID]
上面這個例子,我們的條件是商品單價大於80,但是 Select 中,我們取的是整個 detail ,所以回傳的是反是訂單中有含80元以上的品項就會全部回傳。
若是你只想取得商品編號,改成這樣就可以了
from o in Orders
join od in OrderDetails on o.OrderID equals od.OrderID into tmpDetail
from detail in tmpDetail
where detail.UnitPrice > 80
select new { o.OrderID, o.CustomerID, detail.ProductID }
Left Join
使用 Inner Join 時,若二者之間沒有 matching 時,就比對不出來。 使用 Left Join 時,不管有沒有 matching 到,左半部的序列資料都會被 return 。
類型一:透過關連性
在 Linq to SQL 資料模型,由於 Order 和 Customer 透過 CustomerID 存在關連,你可以簡單的使用以下語法得到 Left Join 結果。
下面這個例子中,因為 Order 表格的 CustomerID 是一個 Nullable 欄位,所以會使用 Left Join 轉譯成 T-SQL. 如果 Order 表格的 CustomerID 是非 Null 欄位,則會使用 Inner Join 轉譯成 T-SQL
from o in Orders
select new { o.OrderID, o.Customer.ContactName }
SELECT [t0].[CustomerID],
(CASE
WHEN ([t1].[OrderID]) IS NULL THEN @p0
ELSE [t1].[OrderID]
END) AS [OrderID2]
FROM [Customers] AS [t0]
LEFT OUTER JOIN [Orders] AS [t1] ON [t0].[CustomerID] = [t1].[CustomerID]
ORDER BY [t1].[OrderID]
類型二:使用 DefaultIfEmpty 屬性
在 SQL 中,通常使用 Left Join 時,比較多的情況是用在一對多,在不確定關連性下, 若要使用 LINQ 語法來建立 Left Join 關連,只要在 Group Join 加上 DefaultIfEmpty以處理比對不到時的回應資料。
下面例子,我們可以透過 Left Join ,找出所有客戶的所有訂單編號。
from C in Customers
join O in Orders on C.CustomerID equals O.CustomerID into tmp
from T in tmp.DefaultIfEmpty()
select new {
C.CustomerID,
T.OrderID
}
上面這個例子,執行時你會發現以下錯誤:
這是在 select 的匿名型別中,每個欄位其實都是個物件,若為 null 時,你必須給定一個對等的輸出。 所以這個例子,若為 null ,就必須 new 一個 Nullable
from C in Customers
join O in Orders on C.CustomerID equals O.CustomerID into tmp
from T in tmp.DefaultIfEmpty()
orderby T.OrderID
select new {
C.CustomerID,
OrderID = T.OrderID == null ? new Nullable() : T. OrderID
}
SELECT [t0].[CustomerID],
(CASE
WHEN ([t1].[OrderID]) IS NULL THEN @p0
ELSE [t1].[OrderID]
END) AS [OrderID2]
FROM [Customers] AS [t0]
LEFT OUTER JOIN [Orders] AS [t1] ON [t0].[CustomerID] = [t1].[CustomerID]
ORDER BY [t1].[OrderID]
類型三:使用 SelectMany
例一
上面那個範例,我們可以直接透過 SelectMany這個擴充方法來做,可以簡便許多。
Customers.SelectMany(
O => O.Orders.DefaultIfEmpty(),
(C, O) => new
{
C.CustomerID,
OrderID = O.OrderID == null ? new Nullable() : O.OrderID
}
)
.OrderBy(O => O.OrderID)
SELECT [t2].[CustomerID], [t2].[value] AS [OrderID2]
FROM (
SELECT [t0].[CustomerID],
(CASE
WHEN ([t1].[OrderID]) IS NULL THEN @p0
ELSE [t1].[OrderID]
END) AS [value]
FROM [Customers] AS [t0]
LEFT OUTER JOIN [Orders] AS [t1] ON [t1].[CustomerID] = [t0].[CustomerID]
) AS [t2]
ORDER BY [t2].[value]
使用 SelectMany 方法時,如上面程式碼,若少了 DefaultIfEmpty屬性設定,那麼轉譯的 T-SQL 將會使用 Inner Join ,結果就無法 Join 到空的資料。
例二:Select vs SelectMane
在說明前,先看看底下這二段 LINQ ,意義有什麼不同?
Customers
.Where(C => C.City == "Madrid")
.Select( C => C.Orders )
Customers
.Where(C => C.City == "Madrid")
.SelectMany( C => C.Orders )
上面二段 LINQ 程式碼,同樣是要找出住在馬德里的客戶的訂單。但其回傳結果分別得到 3筆 和 9筆 二個結果如下圖:
所以從回傳的結果,可以看的出來其回傳型別分別是:
使用 Select 取得訂單,則不會合併訂單集合,則回傳結果的型別是 IEnumerable>。
改用 SelectMany 取得資料庫中每位客戶的訂單,則回傳結果的型別是 IEnumerable
SelectMany 方法會先列舉 TSource 的每個項目,接著叫用 selector,並傳回一系列的值,以得到一個二維的結果集。最後再將這個二維的集合簡化為一維並回傳。 以下是它們的原型宣告:
public static IEnumerableSelect (this, Func selector);
public static IEnumerableSelectMany (Expression >> selector)
public static IEnumerableSelectMany (Expression >> collectionSelector, Expression > resultSelector)
//TR:TResult
//TS:TSource
//TC:TCollectionTOP
Enumerable.Take
:從序列開頭傳回指定的連續項目數目。
DataTable employees = TestData.GetEmployeeDataTable();
var result = ( from employee in employees.AsEnumerable()
select employee ).Take(3);