join
句は、オブジェクト モデルに直接関係のないさまざまなソース シーケンスの要素を関連付けするのに役立ちます。 唯一の要件は、各ソースの要素が、等しいかどうかを比較できる値を共有することです。 たとえば、食品ディストリビューターには、特定の製品のサプライヤーの一覧と購入者のリストが含まれます。 たとえば、 join
句を使用して、同じ指定されたリージョンにあるその製品のサプライヤーと購入者の一覧を作成できます。
join
句は、入力として 2 つのソース シーケンスを受け取ります。 各シーケンスの要素は、他のシーケンス内の対応するプロパティと比較できるプロパティであるか、または含まれている必要があります。 join
句は、特殊な equals
キーワードを使用して、指定したキーの等価性を比較します。 join
句によって実行されるすべての結合は等結合です。 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
詳細については、「 内部結合の実行」を参照してください。
グループ結合
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;
詳細については、「 グループ化された結合の実行」を参照してください。
左外部結合
左外部結合では、一致する要素が右のシーケンスに存在しない場合でも、左側のソース シーケンス内のすべての要素が返されます。 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 };
詳細については、「 左外部結合の実行」を参照してください。
イコール演算子
join
句は等結合を実行します。 つまり、2 つのキーの等価性に基づく一致のみを使用できます。 "より大きい" や "等しくない" など、他の種類の比較はサポートされていません。 すべての結合が等結合であることを明確にするために、join
句では、==
演算子の代わりに equals
キーワードが使用されます。 equals
キーワードは、join
句でのみ使用でき、いくつかの重要な点で==
演算子とは異なります。 文字列を比較する場合、 equals
には値で比較するオーバーロードがあり、演算子 ==
は参照の等価性を使用します。 比較の両側に同じ文字列変数がある場合、 equals
と ==
は同じ結果になります: true。 これは、プログラムが 2 つ以上の同等の文字列変数を宣言すると、コンパイラがそれらすべてを同じ場所に格納するためです。 これは インターンと呼ばれます。 もう 1 つの重要な違いは、null 比較です。null equals null
は equals
演算子で false として評価され、==
演算子では true として評価されます。 最後に、スコープの動作は異なります。 equals
では、左キーは外部ソース シーケンスを使用し、右側のキーは内部ソースを使用します。 外部ソースは equals
の左側のスコープ内にのみあり、内部ソース シーケンスは右側のスコープ内にのみ含まれます。
非等結合
複数の from
句を使用して新しいシーケンスをクエリに個別に導入することで、非等結合、クロス結合、およびその他のカスタム結合操作を実行できます。 詳細については、「 カスタム結合操作の実行」を参照してください。
オブジェクト コレクションとリレーショナル テーブルの結合
LINQ クエリ式では、結合操作はオブジェクト コレクションに対して実行されます。 オブジェクト コレクションは、2 つのリレーショナル テーブルとまったく同じ方法で "結合" することはできません。 LINQ では、明示的な join
句は、2 つのソース シーケンスがリレーションシップによって関連付けられていない場合にのみ必要です。 LINQ to SQL を使用する場合、外部キー テーブルはオブジェクト モデルで主テーブルのプロパティとして表されます。 たとえば、Northwind データベースでは、Customer テーブルに Orders テーブルとの外部キーリレーションシップがあります。 テーブルをオブジェクト モデルにマップすると、Customer クラスには、その Customer に関連付けられている Orders のコレクションを含む Orders プロパティがあります。 実質的には、既に結合が実行されていることになります。
LINQ to SQL のコンテキストでの関連テーブル間のクエリの詳細については、「 方法: データベース リレーションシップをマップする」を参照してください。
複合キー
複合キーを使用して、複数の値の等価性をテストできます。 詳細については、「 複合キーを使用した結合」を参照してください。 複合キーは、 group
句でも使用できます。
例
次の例では、同じ一致するキーを使用して、同じデータ ソース上の内部結合、グループ結合、および左外部結合の結果を比較します。 コンソール表示の結果を明確にするために、これらの例に追加のコードがいくつか追加されています。
class JoinDemonstration
{
#region Data
class Product
{
public required string Name { get; init; }
public required int CategoryID { get; init; }
}
class Category
{
public required string Name { get; init; }
public required int ID { get; init; }
}
// Specify the first data source.
List<Category> categories =
[
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 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();
}
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: {innerJoinQuery.Count()} items in 1 group.");
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: {totalItems} items in {groupJoinQuery.Count()} unnamed groups");
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: {totalItems} items in {groupJoinQuery2.Count()} named groups");
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($" {item.ProductName}:{item.Category}");
}
Console.WriteLine($"GroupJoin3: {totalItems} items in 1 group");
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:");
foreach (var item in prodGrouping)
{
totalItems++;
Console.WriteLine(" {0,-10}{1}", item.Name, item.CategoryID);
}
}
Console.WriteLine($"LeftOuterJoin: {totalItems} items in {leftOuterQuery.Count()} groups");
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: {leftOuterQuery2.Count()} items in 1 group");
// 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: {totalItems} items in 1 group");
}
}
/*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 メソッド呼び出しに変換されます。 join
句の後に into
がある場合は、GroupJoin メソッド呼び出しに変換されます。
こちらもご覧ください
.NET