しばやん雑記

ASP.NET とメイドさんが大好きなフリーランスのプログラマーのブログ

Entity Framework や LightSpeed で集計系のメソッドを使う時の注意点

例えば、以下のようなテーブルが用意されているとします。

f:id:shiba-yan:20130509161421p:plain

その中にある Point カラムの値の合計を取得したい場合に、以下のような LINQ を書いて取得するのが一般的だと思います。

// userId は現在のユーザー ID
var totalPoint = uow.PointHistories.Where(p => p.UserId == userId).Sum(p => p.Point);

殆どの場合はこのコードで問題なく動作するんですが、例えば Where の時点で該当するレコードが 0 件になった場合には例外が投げられてしまうんですよね。

f:id:shiba-yan:20130509162923p:plain

今回は LightSpeed で試していますが、LINQ to SQL や Entity Framework でも同様の結果になります。

書いてる人間的にはレコードが 0 件の時には 0 を返してもらいたいんですが、SQL の実行結果が null となるので Nullable 型で扱わないといけないという、これまた非常にめんどくさいことになります。

// 泣く泣く Nullable<int> と ?? で処理する
var totalPoint = uow.PointHistories.Where(p => p.UserId == userId).Sum(p => (int?)p.Point) ?? 0;

毎回このコードは書きたくないですよね。

もしくは AsEnumerable/ToList/ToArray メソッドを呼び出して、IQueryable から IEnumerable に変換してから集計すると 0 が返ってきます。

// パフォーマンス的には最悪なのはわかってる…
var totalPoint = uow.PointHistories.Where(p => p.UserId == userId).ToList().Sum(p => p.Point);

今回は Sum メソッドを使いましたが、他にも SQL の集計関数にマッピングされる Average/Max/Min などのメソッドもレコードが 0 件の時に null になるので、IEnumerable の気分で使っていると例外になってしまいます。

// 全部 null になるので例外
uow.PointHistories.Where(p => p.UserId == userId).Average(p => p.Point);
uow.PointHistories.Where(p => p.UserId == userId).Min(p => p.Point);
uow.PointHistories.Where(p => p.UserId == userId).Max(p => p.Point);

ORM 側で対応してもらいたい気がするのは私だけでしょうか…?