次の方法で共有


join 句 (C# リファレンス)

join句を使用して、オブジェクト モデルに直接関係のないさまざまなソース シーケンスの要素を関連付けます。 唯一の要件は、各ソースの要素が、等しいかどうかを比較できる値を共有することです。 たとえば、食品ディストリビューターには、特定の製品のサプライヤーの一覧と購入者のリストが含まれます。 join句を使用して、指定した同じリージョンに存在する、その製品のサプライヤーと購入者の一覧を作成できます。

C# 言語リファレンスには、C# 言語の最新リリース バージョンが記載されています。 また、今後の言語リリースのパブリック プレビューの機能に関する初期ドキュメントも含まれています。

このドキュメントでは、言語の最後の 3 つのバージョンまたは現在のパブリック プレビューで最初に導入された機能を特定します。

ヒント

C# で機能が初めて導入された時期を確認するには、 C# 言語バージョン履歴に関する記事を参照してください。

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

詳細については、「 内部結合の実行」を参照してください。

グループ結合

join式を持つinto句は、グループ結合と呼ばれます。

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 nullequals 演算子で false として評価され、== 演算子では true として評価されます。 最後に、スコープの動作は異なります。 equalsでは、左キーは外部ソース シーケンスを使用し、右側のキーは内部ソースを使用します。 外部ソースは equals の左側のスコープ内にのみあり、内部ソース シーケンスは右側のスコープ内にのみ含まれます。

非等結合

複数の from 句を使用して新しいシーケンスをクエリに個別に導入することで、非等結合、クロス結合、およびその他のカスタム結合操作を実行できます。 詳細については、「 カスタム結合操作の実行」を参照してください。

オブジェクト コレクションとリレーショナル テーブルの結合

LINQ クエリ式では、オブジェクト コレクションに対して結合操作を実行します。 2 つのリレーショナル テーブルとまったく同じ方法でオブジェクト コレクションを結合することはできません。 LINQ では、2 つのソース シーケンスにリレーションシップがない場合にのみ、明示的な join 句が必要です。 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 メソッド呼び出しに変換されます。 intoが続くjoin句は、GroupJoin メソッド呼び出しに変換されます。

こちらもご覧ください