简体   繁体   中英

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.

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:

System.InvalidOperationException: The null value cannot be assigned to a member with type System.Decimal which is a non-nullable value type.

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!

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):

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:

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

There are two issues here:

  1. Sum() is overloaded
  2. LINQ to SQL follows SQL semantics, not C# semantics.

In SQL, SUM(no rows) returns null , not zero. However, the type inference for your query gives you decimal as the type parameter, instead of 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.

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.

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

There's another question devoted to the (ir)rationale .

it throws an exception because the result of the combined sql query is null and this cant be assigned to the decimal var. If you did the following then your variable would be null (I assume ClaimedAmount is decimal):

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.

If t has a property like a 'HasValue', then I would change the expression to:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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