C#でのLinqを利用したデータのグループ化

549
NO IMAGE

C# でヘッダファイル、明細ファイルを読み込んでグループ化する

表現方法はいくつかありますが今回はクエリ式と Linq の GroupJoin の実装を行ってみます。

要件としてはヘッダと明細を HeaderID を元にグループ化して結合します。
ヘッダと明細は 1 対 n の関係ですが明細が存在しない場合あります (n >= 0) 。

static void Main(string[] args)
{
    var headerList = Enumerable.Range(1, 3)
                        .Select(n => new Header() { HeaderID = n })
                        .ToList();

    var detailList = Enumerable.Range(1, 30)
                        .Select(n => new Detail() { HeaderID = (n / 10) + 1, DetailID = n })
                        .ToList();

    // Header のみ存在する場合
    headerList.Add(new Header() { HeaderID = 4 });
    headerList.Add(new Header() { HeaderID = 5 });

    // クエリ式の場合
    var query = from h in headerList
                join d in detailList
                    on h.HeaderID equals d.HeaderID into sub
                from d in sub.DefaultIfEmpty()      // Linq での left Outer join の表現
                orderby h.HeaderID descending
                group new
                {
                    Detail = d
                } by h;

    // Linq の場合
    var query2 = headerList.GroupJoin(
                                detailList,                 // 結合先
                                header => header.HeaderID,  // 結合キー①
                                detail => detail.HeaderID,  // 結合キー②
                                (h, d) =>
                                    new
                                    {
                                        h,
                                        d
                                    }
                            ).SelectMany(x => x.d.DefaultIfEmpty(), (x, y) =>
                                new
                                {
                                    Header = x.h.HeaderID,
                                    Detail = y
                                }
                            ).OrderByDescending (h=>h.Header);

    Debug.WriteLine("クエリ式の場合");
    foreach (var g in query)
    {
        Debug.WriteLine("HeaderID : " + g.Key.HeaderID);
        foreach (var detail in g)
        {
            if (detail.Detail != null)
            {
                Debug.WriteLine("  Detail: " + detail.Detail.DetailID);
            }
            else
            {
                Debug.WriteLine("  Detail: null");
            }
        }
    }

    Debug.WriteLine("\r\nLinq の場合");
    int hID;
    int previousValue = -1;
    foreach (var g in query2)
    {
        hID = g.Header;
        if (hID != previousValue)
        {
            Debug.WriteLine("HeaderID : " + hID);
            previousValue = hID;
        }
        if (g.Detail != null)
        {
            Debug.WriteLine("  Detail: " + g.Detail.DetailID);
        }
        else
        {
            Debug.WriteLine("  Detail: null");
        }

    }
}

// ヘッダクラス
class Header
{
    public int HeaderID { get; set; }
}

// 明細クラス
class Detail
{
    public int HeaderID { get; set; }
    public int DetailID { get; set; }
}

実行結果

いずれも下記の出力が得られます。

HeaderID : 5
  Detail: null
HeaderID : 4
  Detail: 30
HeaderID : 3
  Detail: 20
  Detail: 21
  Detail: 22
  Detail: 23
  Detail: 24
  Detail: 25
  Detail: 26
  Detail: 27
  Detail: 28
  Detail: 29
HeaderID : 2
  Detail: 10
  Detail: 11
  Detail: 12
  Detail: 13
  Detail: 14
  Detail: 15
  Detail: 16
  Detail: 17
  Detail: 18
  Detail: 19
HeaderID : 1
  Detail: 1
  Detail: 2
  Detail: 3
  Detail: 4
  Detail: 5
  Detail: 6
  Detail: 7
  Detail: 8
  Detail: 9

所感

どちらの実装でも問題ないと思われますが、これまでの経験的にクエリ式の方が直感的に実装できました。
ただ、慣れてしまえばどちらもあまり変わらないと思われるので好みの問題かもしれません。