読者です 読者をやめる 読者になる 読者になる

いろいろ備忘録日記

主に .NET と Python絡みのメモを公開しています。

Linq入門記-09 (クエリキーワード, クエリ構文, 結合, join, 内部結合, inner join, equals, cross join)

C# Linq


今回から数回に分けて、結合について記述します。


結合とは、文字通り特定のシーケンスと特定のシーケンスをキー単位で等しいもの
同士をくっつける処理です。


Linqでは、以下の結合処理が用意されています。

  • 内部結合
  • グループ化結合
  • 左外部結合


今回は、内部結合についてです。


利用するキーワードはjoinです。
構文は以下のようになります。

from x in ListA
join y in ListB on x.Id equals y.Id


結合では2つのシーケンスが必要となりまして
上記の例でいうとListAを外側のシーケンス、ListBを内側のシーケンスと
いいます。onの右に結合条件を記述します。


つまり以下のようになります。

from x in 外側のシーケンス
join y in 内側のシーケンス on 外側のシーケンスのキー equals 内側のシーケンスのキー

結合条件の部分ですが、かならず上記の順番でないといけません。
また、Linqは等結合しかサポートされていません。
常に、一致する条件を指定する必要があります。


後、これ結構重要な事なのですが、joinキーワード内での内側のシーケンスは
join内で完結させていないといけません。日本語にするのが難しいのですが

from x in ListX
join y in ListY on x.Id equals y.Id

はオッケイですが、

from x in ListX
from y in ListY
join z in y.ListZ on x.Id equals z.Id

は、yという名前はコンテキストに存在しないというエラーになります。
joinはgroup byと同じく新たなコンテキストを発生させるので
こうなる模様です。


この制約は、1対1でオブジェクトが紐づく場合は問題にならないのですが
以下のように一つのオブジェクト内に複数のIDを持っているオブジェクトが
N件ある場合などが問題なります。

class Person {
    public string Id { get; set; }
    ...
}

class Project {
    ...
    public IEnumerable<string> Members { get; set; }
}

IEnumerable<Person> persons  = new []{ new Person(Id="0001"), new Person(Id="0002"), new Person(Id="0003"), ... }; 
IEnumerable<Person> projects = new []{ new Project(Members=new []{"0001", "0002", "0003"}), .... };


上記のような場合に

from project in projects
from member  in project.Members
join person  in persons on member equals person.Id

とするのは、エラーとなりません。内側のシーケンスである
personsはjoin内で収まっているからです。
でも、

from person  in persons
from project in projects
join member  in project.Member on person.Id equals member

はエラーとなります。


実際、このような場合は

from person in persons
join project in (
    from prj    in projects
    from member in prj.Members
    select new { Id=..., Name=..., Member=member }
) on person.Id equals project.Member

のようにサブクエリにしてやるとうまくいきます。
(私がLinq勉強中なのでやり方知らないだけかもしれません。いいやり方あったら教えて下さい。m(_ _)m)



で、以下サンプルです。

    #region LinqSamples-09
    public class LinqSamples09 : IExecutable {

        public class Person {
            public string Id {
                get;
                set;
            }
            public string Name {
                get;
                set;
            }
            public int Age {
                get;
                set;
            }
            public DateTime Birthday {
                get;
                set;
            }
        }

        public class Team {
            public string Id {
                get;
                set;
            }
            public string Name {
                get;
                set;
            }
            public IEnumerable<string> Members {
                get;
                set;
            }
        }

        public enum ProjectState {
             NotStarted
            ,Processing
            ,Done
        }

        public class Project {
            public string Id {
                get;
                set;
            }
            public string Name {
                get;
                set;
            }
            public IEnumerable<string> Members {
                get;
                set;
            }
            public DateTime From {
                get;
                set;
            }
            public DateTime? To {
                get;
                set;
            }
            public ProjectState State {
                get;
                set;
            }
        }

        // メンバー
        IEnumerable<Person> persons =
                                new[]{
                                     new Person {
                                          Id   = "001"
                                         ,Name = "gsf_zero1"
                                         ,Age  = 30
                                         ,Birthday = new DateTime(1979, 11, 18)
                                     }
                                    ,new Person {
                                          Id   = "002"
                                         ,Name = "gsf_zero2"
                                         ,Age  = 20
                                         ,Birthday = new DateTime(1989, 11, 18)
                                     }
                                    ,new Person {
                                          Id   = "003"
                                         ,Name = "gsf_zero3"
                                         ,Age  = 18
                                         ,Birthday = new DateTime(1991, 11, 18)
                                    }
                                    ,new Person {
                                          Id   = "004"
                                         ,Name = "gsf_zero4"
                                         ,Age  = 40
                                         ,Birthday = new DateTime(1969, 11, 18)
                                    }
                                    ,new Person {
                                          Id   = "005"
                                         ,Name = "gsf_zero5"
                                         ,Age  = 44
                                         ,Birthday = new DateTime(1965, 11, 18)
                                    }
                                };

        // チーム
        IEnumerable<Team> teams =
                                new[]{
                                     new Team {
                                          Id      = "001"
                                         ,Name    = "team_1"
                                         ,Members = new []{ "001", "004" }
                                     }
                                    ,new Team {
                                          Id      = "002"
                                         ,Name    = "team_2"
                                         ,Members = new []{ "002", "003" }
                                     }
                                };

        // プロジェクト
        IEnumerable<Project> projects =
                                new[]{
                                     new Project {
                                          Id      = "001"
                                         ,Name    = "project_1"
                                         ,Members = new []{ "001", "002" }
                                         ,From    = new DateTime(2009, 8, 1)
                                         ,To      = new DateTime(2009, 10, 15)
                                         ,State   = ProjectState.Done
                                     }
                                    ,new Project {
                                          Id      = "002"
                                         ,Name    = "project_2"
                                         ,Members = new []{ "003", "004" }
                                         ,From    = new DateTime(2009, 9, 1)
                                         ,To      = new DateTime(2009, 11, 25)
                                         ,State   = ProjectState.Done
                                     }
                                    ,new Project {
                                          Id      = "003"
                                         ,Name    = "project_3"
                                         ,Members = new []{ "001" }
                                         ,From    = new DateTime(2007, 1, 10)
                                         ,To      = null
                                         ,State   = ProjectState.Processing
                                     }
                                    ,new Project {
                                          Id      = "004"
                                         ,Name    = "project_4"
                                         ,Members = new []{ "001", "002", "003", "004" }
                                         ,From    = new DateTime(2010, 1, 5)
                                         ,To      = null
                                         ,State   = ProjectState.NotStarted
                                     }
                                };

        public void Execute() {
            //
            // joinを用いた内部結合(INNER JOIN)のサンプル.
            //
            // joinキーワードの構文は以下の通り.
            //      join 内部側の変数 in 内部側のシーケンス on 外部側のキー equals 内部側のキー
            //
            // Linqのjoinでは等結合(つまり等値の場合の結合)のみが行える。
            //
            //====================================================================================
            // Teamから辿るパターン
            var query1 = from team in teams
                         from personId in team.Members
                         join person in persons on personId equals person.Id
                         orderby team.Id ascending, person.Id ascending
                         select new {
                             Team = team.Name
                            ,Person = person.Name
                         };

            Console.WriteLine("=========================================");
            foreach (var item in query1) {
                Console.WriteLine(item);
            }

            //
            // Personから辿るパターン.
            // joinキーワードを利用せずに手動で結合処理.
            //
            // この場合、上記と同じように
            //
            // from person   in persons
            // from team     in teams
            // join personId in team.Members on person.Id equals personId
            //
            // とすると、コンテストが違うというエラーとなる。
            // これは、join内部で新たなコンテキストが発生するからである。
            // query1では、内部側のシーケンスをjoin内部だけで利用していたので
            // 問題ない.
            //
            var query2 = from person in persons
                         from team in teams
                         where team.Members.Contains(person.Id)
                         orderby team.Id ascending, person.Id ascending
                         select new {
                              Team = team.Name
                             ,Person = person.Name
                         };

            Console.WriteLine("=========================================");
            foreach (var item in query2) {
                Console.WriteLine(item);
            }

            //
            // query2をjoinで収めるパターン.
            //
            // 上記の例をjoinに収めるためには、join内で目的のシーケンスを
            // 構築する必要がある。そのため、以下の例では目的のシーケンスを作成するために
            // join内にて、さらにサブクエリを作成している。
            //
            var query3 = from person in persons
                         join team in (
                             from team in teams
                             from member in team.Members
                             select new {
                                  Id = team.Id
                                 ,Name = team.Name
                                 ,PersonId = member
                             }
                         ) on person.Id equals team.PersonId
                         orderby team.Id ascending, person.Id ascending
                         select new {
                              Team = team.Name
                             ,Person = person.Name
                         };

            Console.WriteLine("=========================================");
            foreach (var item in query3) {
                Console.WriteLine(item);
            }

            //
            // CROSS JOIN.
            //
            // 普通にfromを並べればCROSS JOIN状態となる。
            //
            var query4 = from person in persons
                         from team in teams
                         orderby team.Id ascending, person.Id ascending
                         select new {
                              Team = team.Name
                             ,Person = person.Name
                         };

            Console.WriteLine("=========================================");
            foreach (var item in query4) {
                Console.WriteLine(item);
            }
        }
    }
    #endregion


実行すると以下のようになります。

  =========================================
  { Team = team_1, Person = gsf_zero1 }
  { Team = team_1, Person = gsf_zero4 }
  { Team = team_2, Person = gsf_zero2 }
  { Team = team_2, Person = gsf_zero3 }
  =========================================
  { Team = team_1, Person = gsf_zero1 }
  { Team = team_1, Person = gsf_zero4 }
  { Team = team_2, Person = gsf_zero2 }
  { Team = team_2, Person = gsf_zero3 }
  =========================================
  { Team = team_1, Person = gsf_zero1 }
  { Team = team_1, Person = gsf_zero4 }
  { Team = team_2, Person = gsf_zero2 }
  { Team = team_2, Person = gsf_zero3 }
  =========================================
  { Team = team_1, Person = gsf_zero1 }
  { Team = team_1, Person = gsf_zero2 }
  { Team = team_1, Person = gsf_zero3 }
  { Team = team_1, Person = gsf_zero4 }
  { Team = team_1, Person = gsf_zero5 }
  { Team = team_2, Person = gsf_zero1 }
  { Team = team_2, Person = gsf_zero2 }
  { Team = team_2, Person = gsf_zero3 }
  { Team = team_2, Person = gsf_zero4 }
  { Team = team_2, Person = gsf_zero5 }