join 句 (C# リファレンス)
オブジェクト モデル内で直接的な関係を持たない、異なるソース シーケンスの要素どうしを関連付けるには、join 句を使用すると便利です。唯一の要件は、各ソースの要素が、等価比較できるような値を共有していることです。たとえば、食品販売業者が、特定の商品についての仕入先一覧とバイヤー一覧を持っているとします。join 句を使用すると、その商品に関して、指定した同じ地域の仕入先とバイヤーの一覧を作成できます。
join 句は、入力として 2 つのソース シーケンスを受け取ります。各シーケンス内の要素は、対応するもう一方のシーケンスのプロパティと比較できるプロパティであるか、このプロパティを含んでいる必要があります。join 句は、特別な equals キーワードを使用して、指定したキーが等しいかどうかを比較します。join 句によって実行される結合は、すべて等結合です。join 句の出力結果は、実行する結合の種類によって異なります。最も一般的な結合の種類は、次の 3 つです。
内部結合
グループ化結合
左外部結合
内部結合
簡単な内部等結合の例を次に示します。このクエリでは、"商品名/カテゴリ" ペアのフラットなシーケンスが生成されます。同じカテゴリ文字列が、複数の要素に表示されます。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
詳細については、「方法 : 内部結合を実行する (C# プログラミング ガイド)」を参照してください。
グループ化結合
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 };
グループ化結合では、階層構造の結果シーケンスが生成され、左側のソース シーケンスの要素が、右側のソース シーケンスに含まれる 1 つ以上の一致する要素に関連付けられます。グループ化結合は基本的にオブジェクト配列のシーケンスであり、リレーショナル データベースの用語には、これに相当するものがありません。
右側のソース シーケンスに左側のソースの要素と一致する要素がない場合、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;
詳細については、「方法 : グループ化結合を実行する (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 };
詳細については、「方法 : 左外部結合を実行する (C# プログラミング ガイド)」を参照してください。
等値演算子
join 句は、等結合を実行します。つまり、2 つのキーが等しい場合のみ、一致が結合されます。他の種類の比較 ("より大きい"、"等しくない" など) はサポートされません。すべての結合が等結合であることを明確にするため、join 句では、== 演算子の代わりに equals キーワードが使用されます。join 句では equals キーワードのみ使用できます。このキーワードと == 演算子には、重要な違いが 1 つあります。equals の場合、左辺のキーは外部のソース シーケンスを使用し、右辺のキーは内部のソースを使用します。外部のソースは equals の左辺のスコープにしかなく、内部のソース シーケンスは右辺のスコープにしかありません。
非等結合
複数の from 句を使用して新しいシーケンスをクエリに個別に導入すると、非等結合、クロス結合、および他のカスタム結合操作を実行できます。詳細については、「方法 : カスタム結合操作を実行する (C# プログラミング ガイド)」を参照してください。
オブジェクト コレクションの結合とリレーショナル テーブルの結合
LINQ クエリ式では、結合操作はオブジェクト コレクションに対して実行されます。オブジェクト コレクションは、2 つのリレーショナル テーブルを結合するのとまったく同じ方法で "結合" することはできません。LINQ では、2 つのソース シーケンスの間に何の関連付けもない場合のみ、明示的な join 句が必要です。LINQ to SQL を使用する場合、オブジェクト モデルの外部キー テーブルは、主テーブルのプロパティとして表されます。たとえば、Northwind データベースの Customer テーブルと Orders テーブルとの間には、外部キー リレーションシップがあります。この 2 つのテーブルをオブジェクト モデルにマップすると、Customer クラスは Orders プロパティを持っていて、Orders プロパティにはその Customer に関連付けられた Orders のコレクションが格納されていることになります。つまり、結合は既に自動的に行われています。
LINQ to SQL のコンテキストで複数の関連テーブルを照会する方法の詳細については、「Representing Database Relationships (LINQ to SQL)」を参照してください。
複合キー
複合キーを使用すると、複数の値が等しいかどうかをテストできます。詳細については、「方法 : 複合キーを使用して結合する (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.
*/
解説
join 句の後に into がない場合は、Join メソッドの呼び出しに変換されます。join 句の後に into がある場合は、GroupJoin メソッドの呼び出しに変換されます。
参照
処理手順
方法 : 左外部結合を実行する (C# プログラミング ガイド)
方法 : 内部結合を実行する (C# プログラミング ガイド)
方法 : グループ化結合を実行する (C# プログラミング ガイド)
方法 : join 句の結果の順序を指定する (C# プログラミング ガイド)
方法 : 複合キーを使用して結合する (C# プログラミング ガイド)