开发者

Can you Sum() more than one value in a single linq statement...without grouping?

Given class:

public class FooAmo开发者_运维百科unts
int Id
decimal Month1
decimal Month2
...
decimal Month12
decimal Year2
decimal Year3
decimal Future

I have IEnumerable<FooAmounts> with 5 entries (Id's 1-5 funnily enough!)

I'd like to create a single new FooAmounts which has the totals of each month/year but only where Id==1 + Id==2 e.g.

var footle = (from f in foolist
where f.Id == 1 || f.Id == 2
select new FooAmounts{
Month1 = Sum(month 1s),
Month2 = Sum(month 2s),
etc etc.
Future = Sum(futures)
).FirstOrDefault();

I am trying to re-create this t-sql:

select SUM(Month1) as Month1, SUM(Month2) as Month2, SUM(Month3) as Month3 from FooAmount where Id=1 or Id=2"

I can acheive similar using a group by f.Id into grp clause but that leaves me with two items and therefore I can't use First/FoD, meaning I would have to manually Sum them still...which seems like I must be missing a trick somewhere?

Note: The FirstOrDefault() is only there so I get back a single object rather than an enumerable containing one object. I think (perhaps wrongly!) that this should always return only one item anyway...there is only one result of a Sum(), regardless as to how many items went into the calculation.


Regardless of whether you have one value or many, it doesn't make sense to take the first result of a sum.

If I have 10 people, I can find the age of the first one, or I can find the sum of the ages of all of them - but I can't find the first sum of the ages. So you can do:

 var matches = fooList.Where(f => f.Id == 1 || f.Id == 2);

 var sum = new FooAmounts { Month1 = matches.Sum(f => f.Month1),
                            Month2 = matches.Sum(f => f>Month2),
                            ... };

Now that will execute the query multiple times, of course. You can materialize the query instead, and then do the summing over that:

 // Materialize the result so we only filter once
 var matches = fooList.Where(f => f.Id == 1 || f.Id == 2).ToList();

 var sum = new FooAmounts { Month1 = matches.Sum(f => f.Month1),
                            Month2 = matches.Sum(f => f>Month2),
                            ... };

Or alternatively you could use aggregation:

 var sum = fooList.Where(f => f.Id == 1 || f.Id == 2)
                  .Aggregate(new FooAmounts(), // Seed
                             (sum, item) => new FooAmounts {
                                 Month1 = sum.Month1 + item.Month1,
                                 Month2 = sum.Month2 + item.Month2,
                                 ...
                             });

This will only iterate over the sequence once, and not create a big buffer in memory, but will create a lot of FooAmounts instances as it iterates.

You could modify the accumulator in place, of course:

 var sum = fooList.Where(f => f.Id == 1 || f.Id == 2)
                  .Aggregate(new FooAmounts(), // Seed
                             (sum, item) => {
                                 sum.Month1 += item.Month1;
                                 sum.Month2 += item.Month2;
                                 ...
                                 return sum;
                             });

That's feels slightly nasty to me, but it doesn't really have side-effects in a bad way, as it's only mutating the object which is initialized within the call anyway.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜