简体   繁体   English

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

[英]How to do Linq aggregates when there might be an empty set?

I have a Linq collection of Things , where Thing has an Amount (decimal) property. 我有一个Linq的Things集合,其中Thing有一个Amount (十进制)属性。

I'm trying to do an aggregate on this for a certain subset of Things: 我正在尝试针对特定事物的子集对此进行聚合:

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

and that works nicely. 这很好用。 But then I added a condition that left me with no Things in the result: 但后来我添加了一个条件,让我在结果中没有任何东西:

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

And instead of getting total = 0 or null, I get an error: 而不是得到total = 0或null,我得到一个错误:

System.InvalidOperationException: The null value cannot be assigned to a member with type System.Decimal which is a non-nullable value type. System.InvalidOperationException:无法将null值分配给类型为System.Decimal的成员,该成员是非可空值类型。

That is really nasty, because I didn't expect that behavior. 这真是令人讨厌,因为我没想到这种行为。 I would have expected total to be zero, maybe null - but certainly not to throw an exception! 我本来期望总数为零,也许是null - 但肯定不会抛出异常!

What am I doing wrong? 我究竟做错了什么? What's the workaround/fix? 什么是解决方法/修复?

EDIT - example 编辑 - 例子

Thanks to all for your comments. 感谢大家的评论。 Here's some code, copied and pasted (not simplified). 这是一些代码,复制和粘贴(未简化)。 It's LinqToSql (perhaps that's why you couldn't reproduce my problem): 它是LinqToSql(也许这就是为什么你无法重现我的问题):

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

I can reproduce your problem with the following LINQPad query against Northwind: 我可以使用针对Northwind的以下LINQPad查询重现您的问题:

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

There are two issues here: 这里有两个问题:

  1. Sum() is overloaded Sum()重载
  2. LINQ to SQL follows SQL semantics, not C# semantics. LINQ to SQL遵循SQL语义,而不是C#语义。

In SQL, SUM(no rows) returns null , not zero. 在SQL中, SUM(no rows)返回null ,而不是零。 However, the type inference for your query gives you decimal as the type parameter, instead of decimal? 但是,查询的类型推断会将decimal作为类型参数而不是decimal? . The fix is to help type inference select the correct type, ie: 修复是帮助类型推断选择正确的类型,即:

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

Now the correct Sum() overload will be used. 现在将使用正确的Sum()重载。

To get a non-nullable result, you need to cast the amount to a nullable type, and then handle the case of Sum returning null. 要获得不可为空的结果,您需要将数量转换为可空类型,然后处理Sum返回null的情况。

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

There's another question devoted to the (ir)rationale . 还有另一个专门讨论(ir)理由的问题

it throws an exception because the result of the combined sql query is null and this cant be assigned to the decimal var. 它会抛出异常,因为组合的sql查询的结果为null,并且无法将其分配给decimal var。 If you did the following then your variable would be null (I assume ClaimedAmount is decimal): 如果你执行了以下操作,那么你的变量将为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?);

then you should get the functionality you desire. 那么你应该得到你想要的功能。

You could also do ToList() at the point of the where statement and then the sum would return 0 but that would fall foul of what has been said elsewhere about LINQ aggregates. 您也可以在where语句的位置执行ToList(),然后总和将返回0,但这将违背其他地方关于LINQ聚合的说法。

If t has a property like a 'HasValue', then I would change the expression to: 如果t具有类似'HasValue'的属性,那么我将表达式更改为:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM