join 子句 (C# 參考)
join 子句在使物件模型中沒有直接關係之不同來源序列的項目產生關聯上相當有用。 唯一的需求是每個來源的項目都共用可以比較相等性的某些值。 例如,食品經銷商可能有特定產品供應商的清單,以及買家的清單。 舉例來說,使用 join 子句可以建立特定區域中某產品的供應商和買家清單。
join 子句會採用兩個來源序列做為輸入。 每個序列的項目必須是屬性或包含屬性,該屬性可以與其他序列中的對應屬性做比較。 join 子句使用特殊的 equals 關鍵字比較指定索引鍵的相等性。 join 子句執行的所有聯結是等聯結 (Equijoin)。 join 子句輸出的形狀取決於您執行之聯結的特定類型。 以下是三種最常見的聯結類型:
內部聯結
群組聯結
左外部聯結
內部聯結
下列範例顯示簡單的內部等聯結。 這個查詢會產生「產品名稱/分類」配對的一般序列。 相同分類字串會顯示在多個項目中。 如果 categories 的項目沒有相符的 products,該分類不會顯示在結果中。
var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { ProductName = prod.Name, Category = category.Name }; //produces flat sequence
如需詳細資訊,請參閱 HOW TO:執行內部聯結 (C# 程式設計手冊)。
Group Join
具有 into 運算式的 join 子句稱為群組聯結。
var innerGroupJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select new { CategoryName = category.Name, Products = prodGroup };
群組聯結會產生階層式結果序列,使左側來源序列中的項目與右側來源序列中的一個或多個相符項目產生關聯。 群組聯結從關聯式角度來看沒有對等項目,它基本上是物件陣列的序列。
如果右側來源序列中找不到項目會符合左側來源的項目,join 子句會針對該項目產生空的陣列。 因此,群組聯結基本上仍然是內部等聯結,例外為結果序列會組織成群組。
如果您只選取群組聯結的結果,便可以存取項目,但是無法識別符合的索引鍵。 因此,通常在將群組聯結的結果選取至也具有索引鍵名稱的新類型時較為有用,如前述範例所示。
當然,您也可以使用群組聯結的結果做為其他子查詢的產生器:
var innerGroupJoinQuery2 =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from prod2 in prodGroup
where prod2.UnitPrice > 2.50M
select prod2;
如需詳細資訊,請參閱 HOW TO:執行群組聯結 (C# 程式設計手冊)。
左外部聯結
在左外部聯結中,會傳回左來源序列中的所有項目,即使在右序列中沒有相符項目也是如此。 若要在 LINQ 中執行左外部聯結,請使用 DefaultIfEmpty 方法與群組聯結的組合,指定如果左側項目沒有相符項目時產生預設右側項目。 您可以使用 null 做為任何參考型別的預設值,或者可以指定使用者定義的預設型別。 在下列範例中,會顯示使用者定義的預設型別:
var leftOuterJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from item in prodGroup.DefaultIfEmpty(new Product{Name = String.Empty, CategoryID = 0})
select new { CatName = category.Name, ProdName = item.Name };
如需詳細資訊,請參閱 HOW TO:執行左外部聯結 (C# 程式設計手冊)。
等於運算子
join 子句會執行等聯結。 也就是說,您只能根據兩個索引鍵的相等性進行比對。 不支援其他類型的比較,例如 "greater than" 或 "not equals"。 為了釐清所有聯結都是等聯結,join 子句使用 equals 關鍵字而非 == 運算子。 equals 關鍵字只能用在 join 子句,而且與 == 運算子有一個重要的差異。 使用 equals,則左索引鍵會使用外部來源序列,而右索引鍵會使用內部來源。 外部來源只在 equals 的左側範圍中,而內部來源序列只在右側範圍中。
非等聯結
您可以執行非等聯結、交叉聯結及其他自訂聯結作業,方法是使用多個 from 子句單獨將新序列引入查詢。 如需詳細資訊,請參閱HOW TO:執行自訂聯結作業 (C# 程式設計手冊)。
物件集合上的聯結和關聯式資料表
在 LINQ 查詢運算式中,聯結作業是在物件集合上執行。 物件集合不能用與兩個關聯式資料表完全相同的方法進行「聯結」。 在 LINQ 中,只有當兩個來源序列沒有由任何關聯性繫結時,才需要明確 join 子句。 使用 LINQ to SQL 時,外部索引鍵資料表在物件模型中會表示為主要資料表的屬性。 例如,在 Northwind 資料庫中,Customer 資料表與 Orders 資料表有外部索引鍵的關係。 當您將資料表對應至物件模型時,Customer 類別有 Orders 屬性,該屬性包含與 Customer 相關聯之 Orders 的集合。 實際上,您的聯結就已完成。
如需在 LINQ to SQL 內容中查詢所有關聯資料表的詳細資訊,請參閱 HOW TO:對應資料庫關聯性 (LINQ to SQL)。
複合索引鍵
您可以使用複合索引鍵測試多個值的相等性。 如需詳細資訊,請參閱HOW TO:使用複合索引鍵執行聯結 (C# 程式設計手冊)。 複合索引鍵也可以用於 group 子句。
範例
下列範例使用相同的相符索引鍵,比較相同資料來源上的內部聯結、群組聯結及左外部聯結的結果。 部分額外程式碼會加入至這些範例以釐清主控台顯示中的結果。
class JoinDemonstration
{
#region Data
class Product
{
public string Name { get; set; }
public int CategoryID { get; set; }
}
class Category
{
public string Name { get; set; }
public int ID { get; set; }
}
// Specify the first data source.
List<Category> categories = new List<Category>()
{
new Category(){Name="Beverages", ID=001},
new Category(){ Name="Condiments", ID=002},
new Category(){ Name="Vegetables", ID=003},
new Category() { Name="Grains", ID=004},
new Category() { Name="Fruit", ID=005}
};
// Specify the second data source.
List<Product> products = new List<Product>()
{
new Product{Name="Cola", CategoryID=001},
new Product{Name="Tea", CategoryID=001},
new Product{Name="Mustard", CategoryID=002},
new Product{Name="Pickles", CategoryID=002},
new Product{Name="Carrots", CategoryID=003},
new Product{Name="Bok Choy", CategoryID=003},
new Product{Name="Peaches", CategoryID=005},
new Product{Name="Melons", CategoryID=005},
};
#endregion
static void Main(string[] args)
{
JoinDemonstration app = new JoinDemonstration();
app.InnerJoin();
app.GroupJoin();
app.GroupInnerJoin();
app.GroupJoin3();
app.LeftOuterJoin();
app.LeftOuterJoin2();
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
void InnerJoin()
{
// Create the query that selects
// a property from each element.
var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { Category = category.ID, Product = prod.Name };
Console.WriteLine("InnerJoin:");
// Execute the query. Access results
// with a simple foreach statement.
foreach (var item in innerJoinQuery)
{
Console.WriteLine("{0,-10}{1}", item.Product, item.Category);
}
Console.WriteLine("InnerJoin: {0} items in 1 group.", innerJoinQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}
void GroupJoin()
{
// This is a demonstration query to show the output
// of a "raw" group join. A more typical group join
// is shown in the GroupInnerJoin method.
var groupJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select prodGroup;
// Store the count of total items (for demonstration only).
int totalItems = 0;
Console.WriteLine("Simple GroupJoin:");
// A nested foreach statement is required to access group items.
foreach (var prodGrouping in groupJoinQuery)
{
Console.WriteLine("Group:");
foreach (var item in prodGrouping)
{
totalItems++;
Console.WriteLine(" {0,-10}{1}", item.Name, item.CategoryID);
}
}
Console.WriteLine("Unshaped GroupJoin: {0} items in {1} unnamed groups", totalItems, groupJoinQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}
void GroupInnerJoin()
{
var groupJoinQuery2 =
from category in categories
orderby category.ID
join prod in products on category.ID equals prod.CategoryID into prodGroup
select new
{
Category = category.Name,
Products = from prod2 in prodGroup
orderby prod2.Name
select prod2
};
//Console.WriteLine("GroupInnerJoin:");
int totalItems = 0;
Console.WriteLine("GroupInnerJoin:");
foreach (var productGroup in groupJoinQuery2)
{
Console.WriteLine(productGroup.Category);
foreach (var prodItem in productGroup.Products)
{
totalItems++;
Console.WriteLine(" {0,-10} {1}", prodItem.Name, prodItem.CategoryID);
}
}
Console.WriteLine("GroupInnerJoin: {0} items in {1} named groups", totalItems, groupJoinQuery2.Count());
Console.WriteLine(System.Environment.NewLine);
}
void GroupJoin3()
{
var groupJoinQuery3 =
from category in categories
join product in products on category.ID equals product.CategoryID into prodGroup
from prod in prodGroup
orderby prod.CategoryID
select new { Category = prod.CategoryID, ProductName = prod.Name };
//Console.WriteLine("GroupInnerJoin:");
int totalItems = 0;
Console.WriteLine("GroupJoin3:");
foreach (var item in groupJoinQuery3)
{
totalItems++;
Console.WriteLine(" {0}:{1}", item.ProductName, item.Category);
}
Console.WriteLine("GroupJoin3: {0} items in 1 group", totalItems, groupJoinQuery3.Count());
Console.WriteLine(System.Environment.NewLine);
}
void LeftOuterJoin()
{
// Create the query.
var leftOuterQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select prodGroup.DefaultIfEmpty(new Product() { Name = "Nothing!", CategoryID = category.ID });
// Store the count of total items (for demonstration only).
int totalItems = 0;
Console.WriteLine("Left Outer Join:");
// A nested foreach statement is required to access group items
foreach (var prodGrouping in leftOuterQuery)
{
Console.WriteLine("Group:", prodGrouping.Count());
foreach (var item in prodGrouping)
{
totalItems++;
Console.WriteLine(" {0,-10}{1}", item.Name, item.CategoryID);
}
}
Console.WriteLine("LeftOuterJoin: {0} items in {1} groups", totalItems, leftOuterQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}
void LeftOuterJoin2()
{
// Create the query.
var leftOuterQuery2 =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from item in prodGroup.DefaultIfEmpty()
select new { Name = item == null ? "Nothing!" : item.Name, CategoryID = category.ID };
Console.WriteLine("LeftOuterJoin2: {0} items in 1 group", leftOuterQuery2.Count());
// Store the count of total items
int totalItems = 0;
Console.WriteLine("Left Outer Join 2:");
// Groups have been flattened.
foreach (var item in leftOuterQuery2)
{
totalItems++;
Console.WriteLine("{0,-10}{1}", item.Name, item.CategoryID);
}
Console.WriteLine("LeftOuterJoin2: {0} items in 1 group", totalItems);
/*Output:
InnerJoin:
Cola 1
Tea 1
Mustard 2
Pickles 2
Carrots 3
Bok Choy 3
Peaches 5
Melons 5
InnerJoin: 8 items in 1 group.
Unshaped GroupJoin:
Group:
Cola 1
Tea 1
Group:
Mustard 2
Pickles 2
Group:
Carrots 3
Bok Choy 3
Group:
Group:
Peaches 5
Melons 5
Unshaped GroupJoin: 8 items in 5 unnamed groups
GroupInnerJoin:
Beverages
Cola 1
Tea 1
Condiments
Mustard 2
Pickles 2
Vegetables
Bok Choy 3
Carrots 3
Grains
Fruit
Melons 5
Peaches 5
GroupInnerJoin: 8 items in 5 named groups
GroupJoin3:
Cola:1
Tea:1
Mustard:2
Pickles:2
Carrots:3
Bok Choy:3
Peaches:5
Melons:5
GroupJoin3: 8 items in 1 group
Left Outer Join:
Group:
Cola 1
Tea 1
Group:
Mustard 2
Pickles 2
Group:
Carrots 3
Bok Choy 3
Group:
Nothing! 4
Group:
Peaches 5
Melons 5
LeftOuterJoin: 9 items in 5 groups
LeftOuterJoin2: 9 items in 1 group
Left Outer Join 2:
Cola 1
Tea 1
Mustard 2
Pickles 2
Carrots 3
Bok Choy 3
Nothing! 4
Peaches 5
Melons 5
LeftOuterJoin2: 9 items in 1 group
Press any key to exit.
*/
備註
後面沒有 into 的 join 子句會轉譯成 Join 方法呼叫。 後面有 into 的 join 子句會轉譯成 GroupJoin 方法呼叫。
請參閱
工作
HOW TO:排序 Join 子句的結果 (C# 程式設計手冊)
HOW TO:使用複合索引鍵執行聯結 (C# 程式設計手冊)