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

LINQ to Objectsで、複数Join・複合キーでGroup化・並び替えをして、サマリを出す

C#

メソッド構文での引数の書き方をいつも忘れているので、自分用メモ。
例えば、「注文(Order)」「得意先(Customer)」「品目(Item)」みたいなオブジェクトがあり、得意先別・品目別の数量合計を出すとします。


それぞれの型は以下の通り。

public class Order
{
    public int No { get; set; }
    public int CustomerID { get; set; }
    public int ItemID { get; set; }
    public int Amount { get; set; }
}

public class Item
{
    public int ID { get; set; }
    public string ItemName { get; set; }
}

public class Customer
{
    public int ID { get; set; }
    public string CustomerName { get; set; }
}


例えば、こんなオブジェクトがあるとします。

var items = new List<Item>
{
    new Item(){ ID = 1, ItemName = "りんご" },
    new Item(){ ID = 2, ItemName = "みかん" },
    new Item(){ ID = 3, ItemName = "ぶどう" }
};

var customers = new List<Customer>
{
    new Customer(){ ID = 1, CustomerName = "hoge" },
    new Customer(){ ID = 2, CustomerName = "fuga" }
};

var orders = new List<Order>
{
    new Order() { No = 1, CustomerID = 1, ItemID = 1, Amount = 1 },
    new Order() { No = 2, CustomerID = 1, ItemID = 1, Amount = 2 },
    new Order() { No = 3, CustomerID = 1, ItemID = 2, Amount = 1 },
    new Order() { No = 4, CustomerID = 2, ItemID = 1, Amount = 1 },
    new Order() { No = 5, CustomerID = 1, ItemID = 1, Amount = 3 },
    new Order() { No = 6, CustomerID = 2, ItemID = 1, Amount = 2 }, // カンマ付もOK
};


その時に、以下のような結果を取得したいと考えています。

得意先名(IDの降順) 品目名(IDの昇順) 数量合計
fuga りんご 3
hoge りんご 6
hoge みかん 1


LINQは以下の通り。

//  o:Order, c:Customer, i:Item, oc:Order&Customer とする
var results = orders.Join(customers, o => o.CustomerID, c => c.ID, (o, c) => new { o, c })
                    .Join(items, oc => oc.o.ItemID, i => i.ID, (oc, i) => new { oc, i })
                    .GroupBy(g => new
                    {
                        //  匿名型では同じIDという名前を持てないので、新しく名前をつける
                        //  エラー「匿名型では、同じ名前を持つ複数のプロパティを含むことはできません」
                        CustomerID = g.oc.c.ID,
                        ItemID = g.i.ID
                    })
                    .OrderByDescending(d => d.Key.CustomerID)
                    .ThenBy(t => t.Key.ItemID)
                    .Select(s => new
                    {
                        //  CustomerNameとItemNameは、グループ化されていれば全部同じはずなので、最初のを使う
                        CustomerName = s.First().oc.c.CustomerName,
                        ItemName = s.First().i.ItemName,
                        Total = s.Sum(sum => sum.oc.o.Amount)
                    });


結果。

================
fuga
----------------
品名:りんご、数量:3
----------------

================
hoge
----------------
品名:りんご、数量:6
----------------

================
hoge
----------------
品名:みかん、数量:1
----------------

Enterキーで終了