如果可能有空集,如何进行Linq聚合?

时间:2010-03-16 15:08:10

标签: c# linq linq-to-sql aggregate

我有一个Things的Linq集合,其中Thing具有Amount(十进制)属性。

我正在尝试针对某个事物的子集对此进行聚合:

var total = myThings.Sum(t => t.Amount);

这很好用。但后来我添加了一个让我没有结果的条件:

var total = myThings.Where(t => t.OtherProperty == 123).Sum(t => t.Amount);

而不是得到total = 0或null,我得到一个错误:

  

System.InvalidOperationException:无法将null值分配给   System.Decimal类型的成员,它是一个不可为空的值类型。

这真是令人讨厌,因为我没想到这种行为。我原以为总数为零,可能为空 - 但肯定不会抛出异常!

我做错了什么?什么是变通方法/修复方法?

编辑 - 示例

感谢大家的评论。这是一些代码,复制和粘贴(未简化)。这是LinqToSql(也许这就是为什么你无法重现我的问题):

var claims = Claim.Where(cl => cl.ID < 0);
var count = claims.Count(); // count=0
var sum = claims.Sum(cl => cl.ClaimedAmount); // throws exception

4 个答案:

答案 0 :(得分:23)

我可以使用针对Northwind的以下LINQPad查询重现您的问题:

Employees.Where(e => e.EmployeeID == -999).Sum(e => e.EmployeeID)

这里有两个问题:

  1. Sum()已超载
  2. LINQ to SQL遵循SQL语义,而不是C#语义。
  3. 在SQL中,SUM(no rows)返回null,而不是零。但是,查询的类型推断会将decimal作为类型参数,而不是decimal?。修复是帮助类型推断选择正确的类型,即:

    Employees.Where(e => e.EmployeeID == -999).Sum(e => (int?)e.EmployeeID)
    

    现在将使用正确的Sum()重载。

答案 1 :(得分:5)

要获得不可为空的结果,您需要将金额转换为可空类型,然后处理Sum返回null的情况。

decimal total = myThings.Sum(t => (decimal?)t.Amount) ?? 0;

还有另一个问题专门讨论(ir)rationale

答案 2 :(得分:2)

它会抛出异常,因为组合的sql查询的结果为null,并且无法将其分配给decimal var。如果你执行了以下操作,那么你的变量将为null(我假设ClaimedAmount是十进制的):

var claims = Claim.Where(cl => cl.ID < 0);
var count = claims.Count(); // count=0
var sum = claims.Sum(cl => cl.ClaimedAmount as decimal?);

然后你应该得到你想要的功能。

您也可以在where语句的位置执行ToList(),然后总和将返回0,但这会违反其他地方关于LINQ聚合的内容。

答案 3 :(得分:-1)

如果t具有类似'HasValue'的属性,那么我将表达式更改为:

var total = 
     myThings.Where(t => (t.HasValue) && (t.OtherProperty == 123)).Sum(t => t.Amount);