【C#】LINQのJoinメソッドで2つのリストを結合する(内部結合)

リレーショナルデータベース(RDB)において、テーブル同士を共通のIDで紐付ける「JOIN(結合)」は頻繁に行われる操作です。C#のLINQにおいても、Joinメソッドを使用することで、メモリ上の2つのコレクションに対して同様の結合操作を行うことができます。

今回は、商品カテゴリのリストと商品のリストを紐付け、カテゴリ名を含んだ商品一覧を作成するシナリオを例に、Joinメソッドの具体的な使い方を解説します。


目次

Joinメソッドの概要

Joinメソッドは、2つのシーケンス(コレクション)を指定されたキーに基づいて結合します。これはSQLにおける**内部結合(INNER JOIN)**に相当します。

内部結合であるため、以下の特性を持ちます。

  1. マッチするものだけ残る: 両方のリストにキーが存在する要素同士だけが結合されます。
  2. マッチしないものは除外: 片方のリストにしか存在しないキーを持つ要素は、結果に含まれません。

実践的なコード例:商品とカテゴリの紐付け

以下のコードは、「カテゴリ」と「商品」という独立した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でもう片方がlongint?(Nullable)の場合、コンパイルエラーになるか、意図通りに動作しないことがあります。型が異なる場合は、明示的にキャストを行うなどして揃える必要があります。

3. グループ化して結合したい場合(GroupJoin)

Joinメソッドは「1対1」のフラットな結果セットを返します(SQLのJOINと同じ)。もし、「1つのカテゴリに対して、紐付く商品をリストとして持ちたい」(階層構造にしたい)場合は、Joinではなく**GroupJoin**メソッドを使用します。これにより、「親レコード1つにつき、子レコードの配列」という構造を作ることが可能です。

まとめ

Joinメソッドを使用することで、関連性のある複数のデータソースを簡潔なコードで統合できます。データベースから取得したデータと、CSVから読み込んだメモリ上のデータを結合するといった、異なるソース間のデータ結合においても非常に有効な手段です。まずは「どのキーで紐付けるか」を明確にして実装してみてください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次