リレーショナルデータベース(RDB)において、テーブル同士を共通のIDで紐付ける「JOIN(結合)」は頻繁に行われる操作です。C#のLINQにおいても、Joinメソッドを使用することで、メモリ上の2つのコレクションに対して同様の結合操作を行うことができます。
今回は、商品カテゴリのリストと商品のリストを紐付け、カテゴリ名を含んだ商品一覧を作成するシナリオを例に、Joinメソッドの具体的な使い方を解説します。
Joinメソッドの概要
Joinメソッドは、2つのシーケンス(コレクション)を指定されたキーに基づいて結合します。これはSQLにおける**内部結合(INNER JOIN)**に相当します。
内部結合であるため、以下の特性を持ちます。
- マッチするものだけ残る: 両方のリストにキーが存在する要素同士だけが結合されます。
- マッチしないものは除外: 片方のリストにしか存在しないキーを持つ要素は、結果に含まれません。
実践的なコード例:商品とカテゴリの紐付け
以下のコードは、「カテゴリ」と「商品」という独立した2つのデータソースを、共通項であるCategoryIdを使って結合する例です。
using System;
using System.Collections.Generic;
using System.Linq;
namespace ECommerceSystem
{
// 商品カテゴリ
public record Category(int Id, string Name);
// 商品データ
public record Product(string Name, int Price, int CategoryId);
class Program
{
static void Main()
{
// シナリオ:
// カテゴリ情報と商品情報が別々のリストとして管理されている。
// これらを結合して、「商品名」と「そのカテゴリ名」を一覧表示したい。
// カテゴリのマスターデータ(親)
var categories = new[]
{
new Category(1, "家電"),
new Category(2, "書籍"),
new Category(3, "食品"),
new Category(99, "廃番予定") // 商品が紐付かないカテゴリ
};
// 商品データ(子)
var products = new[]
{
new Product("4Kモニター", 45000, 1),
new Product("ワイヤレスマウス", 3000, 1),
new Product("C#入門書", 2800, 2),
new Product("技術書総集編", 5000, 2),
new Product("謎の未登録品", 100, 5) // カテゴリID:5 はマスタに存在しない
};
// Joinメソッドによる結合
// 第1引数: 結合する相手のリスト(products)
// 第2引数: 自分の結合キー(categoriesのId)
// 第3引数: 相手の結合キー(productsのCategoryId)
// 第4引数: 結果を生成するセレクタ関数
var productListWithCategory = categories.Join(
products,
category => category.Id, // Outer Key
product => product.CategoryId, // Inner Key
(category, product) => new // Result Selector
{
ProductName = product.Name,
CategoryName = category.Name,
Price = product.Price
}
);
// 結果の出力
Console.WriteLine("--- 商品一覧(カテゴリ付き) ---");
foreach (var item in productListWithCategory)
{
Console.WriteLine($"カテゴリ: {item.CategoryName,-4} | 商品名: {item.ProductName} ({item.Price:C})");
}
}
}
}
実行結果
--- 商品一覧(カテゴリ付き) ---
カテゴリ: 家電 | 商品名: 4Kモニター (¥45,000)
カテゴリ: 家電 | 商品名: ワイヤレスマウス (¥3,000)
カテゴリ: 書籍 | 商品名: C#入門書 (¥2,800)
カテゴリ: 書籍 | 商品名: 技術書総集編 (¥5,000)
解説: 結果を見ると、「廃番予定」カテゴリ(商品なし)や、「謎の未登録品」(存在しないカテゴリIDを参照)は出力されていません。これが内部結合(互いにキーが一致するものだけを抽出)の挙動です。
技術的なポイント
1. 引数の構造を理解する
Joinメソッドは引数が多く複雑に見えますが、順序には明確な意味があります。
OuterList.Join(
InnerList, // 1. 結合する相手(内部シーケンス)
outerKeySelector, // 2. 自分のどのプロパティをキーにするか
innerKeySelector, // 3. 相手のどのプロパティをキーにするか
resultSelector // 4. マッチしたペアから何を作るか
);
2. キーの型の一致
第2引数と第3引数で指定するキーは、同じ型である必要があります。例えば、片方がintでもう片方がlongやint?(Nullable)の場合、コンパイルエラーになるか、意図通りに動作しないことがあります。型が異なる場合は、明示的にキャストを行うなどして揃える必要があります。
3. グループ化して結合したい場合(GroupJoin)
Joinメソッドは「1対1」のフラットな結果セットを返します(SQLのJOINと同じ)。もし、「1つのカテゴリに対して、紐付く商品をリストとして持ちたい」(階層構造にしたい)場合は、Joinではなく**GroupJoin**メソッドを使用します。これにより、「親レコード1つにつき、子レコードの配列」という構造を作ることが可能です。
まとめ
Joinメソッドを使用することで、関連性のある複数のデータソースを簡潔なコードで統合できます。データベースから取得したデータと、CSVから読み込んだメモリ上のデータを結合するといった、異なるソース間のデータ結合においても非常に有効な手段です。まずは「どのキーで紐付けるか」を明確にして実装してみてください。
