いろいろ備忘録日記

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

Linq入門記-05 (クエリキーワード, クエリ構文, グルーピング, group, IGrouping)


続いて、今回はgroupキーワードです。
名前の通り、Linqにてグルーピングを行う場合に利用します。


概念的にやっぱりSQLのgroup byに近いです。
なので、SQLが分かっている人には理解しやすいと思います。


以下のように記述します。

from x in ListA
group x by {グルーピングを行う単位}


byの後ろにグルーピングする条件を記述します。
SQLだと

SELECT
    ...
FROM
    ...
GROUP BY
    tbl.grouping_column

のようにしますので、似ているといえば似てますね。


で、クエリ発行後のデータの取得ですが、グルーピングを行った場合は
今までと少し取得の仕方が変わります。

グルーピングした場合は、結果がグループオブジェクトとして返されます。
グループオブジェクトは以下の構造となっています。

  • キー
  • グループ化されたデータ


つまり、実際のデータを一つ一つ取得するには2重でループさせないといけないことになります。

var groupedQuery = from x in ListA
                   group x by x.GroupingColumn;

foreach(var groupedX in groupedQuery) {
    //
    // 1つめのループで取得できるのはグループオブジェクト自身となります。
    //
    Console.WriteLine("KEY={0}", groupedX.Key);

    //
    // 2つめのループで対象となるオブジェクトが取得できます。
    //
    foreach(var itemX in groupedX) {
        Console.WriteLine(itemX);
    }
}


厳密にいいますと、groupキーワードを使用した場合は
結果が以下の型となります。

IGrouping<キーの型, グループ化されたオブジェクトの型>

キーの型は、groupキーワードのbyの右で指定した項目の型です。
グループ化されたオブジェクトの型は、対象となった型自身です。


ちなみに、IGroupingインターフェースはIEnumerableを継承していますので
2つめのループにてそのまま利用できるという構造になっています。

public interface IGrouping<TKey, TElement> : IEnumerable<TElement> {
    ...
}


例を挙げると
Personというクラスがあり

class Person {
    public string Name   { get; set; }
    public string Country{ get; set; }
}

// サンプルデータ
IEnumerable<Person> person = new []{
                                new Person{ Name="gsf_zero1", Country="Japan" }
                                new Person{ Name="gsf_zero2", Country="Japan" }
                                new Person{ Name="gsf_zero3", Country="America" }
                             }

となっているとします。その状態で

var query = from person in persons
            group person by person.Country;

とすると、クエリの結果は

IGrouping<string, Person>

となります。
(キーがstring型のCountryで、アイテムがPersonオブジェクト)

foreach(IGrouping<string, Person> groupedPerson in query) {
    Console.WriteLine("Key={0}", groupedPerson.Key);

    foreach(Person person in groupedPerson) {
        Console.WriteLine("Name={0}", person.Name);
    }
}

という風になります。


てことで、以下サンプルです。

#region LinqSamples-05
    public class LinqSamples05 : IExecutable {

        enum Country {
             Japan
            ,America
            ,China
        }

        class Person {
            public string Id{ get; set; }
            public string Name{ get; set; }
            public AddressInfo Address{ get; set;}
            public Country Country{ get; set; }
        }

        class AddressInfo {
            public string   PostCode{ get; set; }
            public string   Prefecture{ get; set; }
            public string   Municipality{ get; set; }
            public string   HouseNumber{ get; set; }
            public string[] Tel{ get; set; }
            public string[] Frends{ get; set; }
        }

        IEnumerable<Person> CreateSampleData() {

            return new Person[]{
                 new Person{ 
                         Id="00001"
                        ,Name="gsf_zero1"
                        ,Address=new AddressInfo{
                                         PostCode="999-8888"
                                        ,Prefecture="東京都"
                                        ,Municipality="どこか1"
                                        ,HouseNumber="番地1"
                                        ,Tel=new []{"090-xxxx-xxxx"}
                                        ,Frends=new string[]{}
                        }
                        ,Country=Country.Japan
                 }
                ,new Person{ 
                         Id="00002"
                        ,Name="gsf_zero2"
                        ,Address=new AddressInfo{
                                         PostCode="888-7777"
                                        ,Prefecture="京都府"
                                        ,Municipality="どこか2"
                                        ,HouseNumber="番地2"
                                        ,Tel=new []{"080-xxxx-xxxx"}
                                        ,Frends=new []{"00001"}
                        }
                        ,Country=Country.Japan
                }
                ,new Person{ 
                         Id="00003"
                        ,Name="gsf_zero3"
                        ,Address=new AddressInfo{
                                         PostCode="777-6666"
                                        ,Prefecture="北海道"
                                        ,Municipality="どこか3"
                                        ,HouseNumber="番地3"
                                        ,Tel=new []{"070-xxxx-xxxx"}
                                        ,Frends=new []{"00001", "00002"}
                        }
                        ,Country=Country.America
                }
                ,new Person{ 
                         Id="00004"
                        ,Name="gsf_zero4"
                        ,Address=new AddressInfo{
                                         PostCode="777-6666"
                                        ,Prefecture="北海道"
                                        ,Municipality="どこか4"
                                        ,HouseNumber="番地4"
                                        ,Tel=new []{"060-xxxx-xxxx", "111-111-1111", "222-222-2222"}
                                        ,Frends=new []{"00001", "00003"}
                        }
                        ,Country=Country.America
                }
            };
        }

        public void Execute() {

            IEnumerable<Person> persons = CreateSampleData();

            //
            // into句を使用しない標準的なgroupの利用
            // (Person.Countryでグルーピング)
            //
            var query1 = from person in persons
                         group person by person.Country;

            //
            // 結果は、キーと該当するグループの状態で取得できる.
            //
            foreach(var groupedPerson in query1) {
                // キー
                Console.WriteLine("Country={0}", groupedPerson.Key);

                // グループ
                // グループを取得するには、もう一度ループする必要がある。
                foreach(var person in groupedPerson) {
                    Console.WriteLine("\tId={0}, Name={1}", person.Id, person.Name);
                }
            }

            //
            // 同じ結果を、var無しで表現.
            //
            // groupキーワードの結果は以下の型となる。
            //      IGrouping<TKey, TElement>
            // IGroupingインターフェースは、IEnumerable<TElement>を
            // 継承しているので、ループさせるとTElementが順次取得できるようになっている。
            //
            foreach(IGrouping<Country, Person> groupedPerson in query1) {
                Console.WriteLine("Country={0}", groupedPerson.Key);

                foreach(Person person in groupedPerson) {
                    Console.WriteLine("\tId={0}, Name={1}", person.Id, person.Name);
                }
            }
        }
    }
#endregion


でも、このままだとグルーピングしか出来ません。
時には、グルーピングした結果をさらにそのクエリ内で利用したい場合もあります。


次は、そのためのキーワードであるintoを記述する予定です。