いろいろ備忘録日記

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

Linq入門記-10 (クエリキーワード, クエリ構文, 結合, join, グループ化結合, group join, equals, into)


今回は、グループ化結合です。


グループ化結合って名前はすごく難しそうですが
実際は以下のように通常のjoinの後ろにintoを付けるだけです。

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


何が変わるかというと、結果の持ち方が変わります。
前回の内部結合が最終的に1対1でデータ同士を結合していたのに対して
今回のは以下のように記述します。

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

class Y {
    public string Id  { get; set; }
    public string XId { get; set; }
    ...
}

IEnumerable<X> ListX = new []{ new X(Id="0001"), new X(Id="0002"), new X(Id="0003")};
IEnumerable<Y> ListY = new []{ new Y(Id="01", XId="0001"), new Y(Id="02", XId="0001"), new Y(Id="03", XId="0002")};

from x in ListX
join y in ListY on x.Id equals y.Id into groupJoinY
select new {
     XId    = x.Id
    ,YGroup = groupJoinY
};


上記を実行したとすると以下のようになります。

var query = from x in ListX
            join y in ListY on x.Id equals y.XId into groupJoinY
            select new {
                 XId    = x.Id
                ,YGroup = groupJoinY
            };

foreach(var item in query) {

    Console.WriteLine("XId = {0}", item.XId);

    foreach(var y in item.YGroup) {
        Console.WriteLine("\tId={0}", y);
    }
}
XId=0001
    Id=01
    Id=02
XId=0002
    Id=03


つまり、グループ単位で結合した結果を保持するという事になります。
また、これも前回と同じくなのですが、1側からN側へはすんなり
かけるのですが、逆にN側から1側への結合はめんどくさいです。
(以下のサンプルでは、query2の部分)


以下、サンプルです。

    #region LinqSamples-10
    public class LinqSamples10 : 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時にintoキーワードを利用して結果を保持した
            // ものをグループ化結合という。
            //

            //
            // 所属するプロジェクトをメンバー単位で取得.
            // (Person側からProjectへのアプローチ)
            //
            // Project.MembersはN件存在するので、joinするために
            // 平坦化する必要がある。(SelectMany)
            //
            var query1 = from person in persons
                         join prj in (
                             from project in projects
                             from member in project.Members
                             select new {
                                  Id = project.Id
                                 ,Name = project.Name
                                 ,PersonId = member
                             }
                         ) on person.Id equals prj.PersonId into personProjects
                         select new {
                              Name = person.Name
                             ,Projects = personProjects
                         };

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

                foreach (var project in item.Projects) {
                    Console.WriteLine("\tProject={0}", project.Name);
                }
            }

            //
            // プロジェクトに所属するメンバーをプロジェクト単位で取得.
            // (Project側からPersonへのアプローチ)
            //
            // 以下の行程を経る必要がある。
            //
            // 1.Project.MembersはN件となるので平坦化.
            //   (メンバーが2人いるプロジェクトだと2データとなる。)
            // 2.1の結果とPersonを結合.
            // 3.2の結果をグルーピング.
            //
            var query2 = from project in (
                             // 1の処理.
                             from project in projects
                             from member in project.Members
                             select new {
                                 Id = project.Id,
                                 Name = project.Name,
                                 Member = member
                             }
                         )
                         // 2の処理.
                         join person in persons on project.Member equals person.Id into personByProject
                         select new {
                             Id = project.Id,
                             Name = project.Name,
                             Persons = personByProject
                         } into selectResult
                         // 3の処理.
                         group selectResult by new {
                             selectResult.Id,
                             selectResult.Name
                         };

            Console.WriteLine("======================================================");
            foreach (var item in query2) {
                Console.WriteLine("Name={0}", item.Key.Name);

                foreach (var groupedItem in item) {

                    foreach (var belongPerson in groupedItem.Persons) {
                        Console.WriteLine("\tPerson={0}", belongPerson.Name);
                    }
                }
            }

            //
            // joinを利用せずに記述した版.
            // (PersonからProject)
            //
            var query3 = from person in persons
                         from project in projects
                         where project.Members.Contains(person.Id)
                         select new {
                             Person = person,
                             Project = project
                         } into selectResult
                         group selectResult by selectResult.Project.Name into groupByResult
                         orderby groupByResult.Key ascending
                         select groupByResult;

            Console.WriteLine("======================================================");
            foreach(var item in query3) {
                Console.WriteLine("Project={0}", item.Key);

                foreach(var groupedItem in item) {
                    Console.WriteLine("\tPerson={0}", groupedItem.Person.Name);
                }
            }
        }
    }
    #endregion

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

 Name=gsf_zero1
       Project=project_1
       Project=project_3
       Project=project_4
 Name=gsf_zero2
       Project=project_1
       Project=project_4
 Name=gsf_zero3
       Project=project_2
       Project=project_4
 Name=gsf_zero4
       Project=project_2
       Project=project_4
 Name=gsf_zero5
 ======================================================
 Name=project_1
       Person=gsf_zero1
       Person=gsf_zero2
 Name=project_2
       Person=gsf_zero3
       Person=gsf_zero4
 Name=project_3
       Person=gsf_zero1
 Name=project_4
       Person=gsf_zero1
       Person=gsf_zero2
       Person=gsf_zero3
       Person=gsf_zero4
 ======================================================
 Project=project_1
       Person=gsf_zero1
       Person=gsf_zero2
 Project=project_2
       Person=gsf_zero3
       Person=gsf_zero4
 Project=project_3
       Person=gsf_zero1
 Project=project_4
       Person=gsf_zero1
       Person=gsf_zero2
       Person=gsf_zero3
       Person=gsf_zero4