連接Join
主要方法:
Join: 應用一個搜尋策略去匹配兩個集合中的元素, 並返回一個扁平的結果集, SQL對應語法為INNER JOIN
Group Join: 同上, 但返回的是一個層級的結果集, SQL對應語法為INNER JOIN, LEFT OUTER JOIN
概要
Join和GroupJoin將兩個輸入序列編織成一個單一的輸出序列, Join返回一個扁平的輸出結果集, GroupJoin則返回一個層級結果集.
Join和GroupJoin提供了Select與SelectMany之外的另一個選擇. Join和GroupJoin的優勢在於針對本地查詢更加高效,因為它首先將內部序列加載到一個lookup目錄當中,避免重復枚舉每一個內部元素. 它的劣勢在於只能對應於INNER JOIN和LEFT OUTER JOIN, 交叉連接和非等連接依然還是需要Select和SelectMany. 而對於LINQ to SQL, Join和GroupJoin對比Select和SelectMany並沒有提供任何額外的好處.
Join
Join操作符執行一個內連接(inner join), 輸出一個扁平序列
最簡單的演示Join用處的做法是使用LINQ to SQL, 以下的查詢列出所有的客戶以及他們的訂單信息而沒有使用關聯屬性
1: IQueryable<string> query = 2: 3: from c in dataContext.Customers 4: 5: join p in dataContext.Purchases 6: 7: on c.ID equals p.CustomerID 8: 9: select c.Name + ” bought a “ + p.Description;
結果與我們使用SelectMany查詢得到的結果一致
要了解Join相對於SelectMany額外的好處, 我們必須將它轉換為本地查詢, 以下的例子先將所有的客戶和采購訂單轉換為數組, 然後再做進一步的查詢:
1: Customer[] customers = dataContext.Customers.ToArray( ); 2: 3: Purchase[] purchases = dataContext.Purchases.ToArray( ); 4: 5: var slowQuery = 6: 7: from c in customers 8: 9: from p in purchases where c.ID == p.CustomerID 10: 11: select c.Name + ” bought a “ + p.Description; 12: 13: var fastQuery = 14: 15: from c in customers 16: 17: join p in purchases on c.ID equals p.CustomerID 18: 19: select c.Name + ” bought a “ + p.Description;
雖然兩種方式返回的結果集是一樣的, 但是Join查詢執行得更快一些, 因為它在Enumerable當中的實現預加載了內聯集合(purchases)到一個有鍵的字典中
Join執行一個內連接操作, 這意味著那些沒有采購訂單的客戶將被排除在輸出結果之外. 使用inner join, 你可以將inner和outer序列互換, 並且仍然可以得到同樣的結果:
1: from p in purchases 2: 3: join c in customers on p.CustomerID equals c.ID
我們可以增加更多的join語句到相同的查詢中, 例如, 假設每個采購訂單包含一或多個的采購明細, 我們可以像下面這樣將他們連接在一起:
1: from c in customers 2: 3: join p in purchases on c.ID equals p.CustomerID 4: 5: join pi in purchaseItems on p.ID equals pi.PurchaseID
Purchases在第一個連接中扮演了inner序列, 而在第二個連接中則扮演了outer序列的角色, 我們可以使用嵌套foreach得到相同的結果, 但是效率不高:
1: foreach (Customer c in customers) 2: 3: foreach (Purchase p in purchases) 4: 5: if (c.ID == p.CustomerID) 6: 7: foreach (PurchaseItem pi in purchaseItems) 8: 9: if (p.ID == pi.PurchaseID) 10: 11: Console.WriteLine (c.Name + “,” + p.Price + 12: 13: “,” + pi.Detail);
多主鍵連接
我們可以使用匿名類型來進行多主鍵鏈接操作:
1: from x in seqX 2: 3: join y in seqY on new { K1 = x.Prop1, K2 = x.Prop2 } 4: 5: equals new { K1 = y.Prop3, K2 = y.Prop4 }
為了能夠運行這個查詢, 兩個匿名類型的結構必須是相同的. 編譯器會將它們實現為相同的內部類型, 因此多主鍵鏈接能夠運行.
Lambda方式的連接
以下的示例使用了復合查詢語法:
1: from c in customers 2: 3: join p in purchases on c.ID equals p.CustomerID 4: 5: select new { c.Name, p.Description, p.Price };
使用Lambda表達式的話則可以改成這樣:
1: customers.Join ( // outer collection 2: 3: purchases, // inner collection 4: 5: c => c.ID, // outer key selector 6: 7: p => p.CustomerID, // inner key selector 8: 9: (c, p) => new // result selector 10: 11: { c.Name, p.Description, p.Price } 12: 13: );
最後的結果選擇器表達式創建了輸出序列中的每一個元素, 如果你還有額外的查詢語句需要去執行, 例如orderby:
1: from c in customers 2: 3: join p in purchases on c.ID equals p.CustomerID 4: 5: orderby p.Price 6: 7: select c.Name + ” bought a “ + p.Description;
在Lambda方式中, 我們就必須在結果選擇器表達式中去生成一個臨時的匿名類型. 這樣可以保持c和p在同一個join作用范圍內:
1: customers.Join ( // outer collection 2: 3: purchases, // inner collection 4: 5: c => c.ID, // outer key selector 6: 7: p => p.CustomerID, // inner key selector 8: 9: (c, p) => new { c, p } ) // result selector 10: 11: .OrderBy (x => x.p.Price) 12: 13: .Select (x => x.c.Name + ” bought a “ 14: 15: + x.p.Description);
可以看得出來復合查詢語法更加的直觀一點, 這也是在使用joining操作時推薦的做法.