.NET LinqでGroupByを使用して、クエリ内容をグルーピングする際に、場合によって、グルーピング対象を動的に変更したい場合がある。
これを実装するのに一番簡単な方法を見つけたのでメモ。
Enumerable.GroupBy拡張メソッドのオーバーロードの1つに、以下の物がある。
GroupBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>, IEqualityComparer<TKey>)
このオーバーロードのパラメータ、IEqualityComparer<TKey>をインプリメントしたクラスを作ることで、グルーピング単位を外部から指定できるようになる。
例えば、以下のようなクラスのリストを「年」+「月」でグルーピングしたり、「年」+「月」+「セールスマン」でグルーピングしたりしたい場合、
public partial class Sales
{
public int Year { get; set; } // 年
public int Month { get; set; } // 月
public string SalesMan { get; set; } // セールスマン
public string ProductName { get; set; } // 商品名
public long Qty { get; set; } // 販売数
}
IEqualityComparer<Sales>をインプリメントして、以下のようなクラスを作る。
class SalesEqaulityComparer : IEqualityComparer<Sales> {
public bool useYear { get; set; }
public bool useMonth { get; set; }
public bool useSalesMan { get; set; }
public bool useProductName { get; set; }
// 比較方法(IEqualityComparer<T>)
public bool Equals(Sales x, Sales y) {
if (useYear && !x.Year.Equals(y.Year)) return false;
if (useMonth && !x.Month.Equals(y.Month)) return false;
if (useSalesMan && !x.SalesMan.Equals(y.SalesMan)) return false;
if (useProductName && !x.ProductName.Equals(y.ProducName)) return false;
return true;
}
// HashCodeの取得(IEqualityComparer<T>)
public int GetHashCode(Sales s) {
int hash = 0;
if (useYear) hash ^= s.Year.GetHashCode();
if (useMonth) hash ^= s.Month.GetHashCode();
if (useSalesMan) hash ^= s.SalesMan.GetHashCode();
if (useProductName) hash ^= s.ProductName.GetHashCode();
return hash;
}
}
実際にグルーピングするときは、このクラスインスタンスを作成し、グルーピングしたいuseXXXにtrueを設定することにより、指定した項目でグルーピングができる。
例えば、「年」,「月」でグルーピングしたい場合は、useYearとuseMonthにtrueを設定し、その他はfalseに設定。「年」,「月」,「セールスマン」でグルーピングしたい場合は、useYear,useMonth,useSalesManにtrueを設定して、その他はfalseを設定して、以下のようなクエリを発行する。
List<Sales> SalesList = new SalesList();
・・・
var salescomparer = new SalesEqaulityComparer();
// グルーピングしたい項目の指定
salescompare.useYear = true;
salescompare.useMonth = true;
・・・
var g = SalesList.GroupBy(s=>s,salescomparer);
foreach(var itm in g) {
Console.WriteLine($"{itm.First().Year},{itm.First().Month},・・・{itm.Sum(v=>v.Qty)}}");
}
意外と使えます。