Linq运行总计第一个值加到自身

我有以下计算客户帐户状态的运行总计,但是他的第一个值总是添加到自身,我不知道为什么 – 虽然我怀疑我错过了一些明显的东西:

decimal? runningTotal = 0; IEnumerable statement = sage.Repository() .Queryable() .Where(x => x.CustomerAccountNumber == sageAccount) .OrderBy(x=>x.UniqueReferenceNumber) .AsEnumerable() .Select(x => new StatementModel() { SLAccountId = x.CustomerAccountNumber, TransactionReference = x.TransactionReference, SecondReference = x.SecondReference, Currency = x.CurrencyCode, Value = x.GoodsValueInAccountCurrency, TransactionDate = x.TransactionDate, TransactionType = x.TransactionType, TransactionDescription = x.TransactionTypeName, Status = x.Status, RunningTotal = (runningTotal += x.GoodsValueInAccountCurrency) }); 

哪个输出:

 29/02/2012 00:00:00 154.80 309.60 30/04/2012 00:00:00 242.40 552.00 30/04/2012 00:00:00 242.40 794.40 30/04/2012 00:00:00 117.60 912.00 

第一排的309.60应该只是154.80

我做错了什么?

编辑:根据下面的ahruss的评论,我在我的视图中调用结果上的Any() ,导致第一次被评估两次 – 解决我将ToList()附加到我的查询。

谢谢大家的建议

在调用结束时添加ToList()以避免重复调用选择器。

这是一个带有副作用的有状态LINQ查询,这本质上是不可预测的。 在代码的其他地方,你调用的东西导致第一个元素被评估,比如First()Any() 。 一般来说,在LINQ查询中存在副作用是危险的,当你发现自己需要它们时,是时候考虑它是否应该只是一个foreach

编辑,或为什么会发生这种情况?

这是对LINQ查询进行评估的结果:在您实际使用查询结果之前,集合中没有任何实际情况发生。 它不评估任何元素。 相反,它存储抽象表达式树或仅存储评估查询所需的委托。 然后,它仅在需要结果时才对其进行评估,除非您明确存储结果,否则它们会在之后被丢弃,并在下次重新评估。

所以这就是为什么每次都有不同的结果? 答案是runningTotal只是第一次初始化。 之后,它的值是上次执行查询后的值,这可能会导致奇怪的结果。

这意味着问题可能很容易就是“为什么总数应该是它应该是的两倍?” 如果提问者做了这样的事情:

 Console.WriteLine(statement.Count()); // this enumerates all the elements! foreach (var item in statement) { Console.WriteLine(item.Total); } 

因为获得序列中元素数量的唯一方法是实际评估所有元素。

同样,在这个问题中实际发生的事情是某处有像这样的代码:

 if (statement.Any()) // this actually involves getting the first result { // do something with the statement } // ... foreach (var item in statement) { Console.WriteLine(item.Total); } 

它似乎无害,但是如果你知道LINQ和IEnumerable是如何工作的,你知道.Any().GetEnumerator().MoveNext()基本相同,这使得它更需要获得第一个元素。

这一切都归结为LINQ基于延迟执行的事实,这就是为什么解决方案是使用ToList ,它绕过它并强制立即执行。

如果您不想使用ToList冻结结果,则外部范围变量问题的解决方案是使用迭代器函数 ,如下所示:

 IEnumerable GetStatement(IEnumerable source) { decimal runningTotal = 0; foreach (var x in source) { yield return new StatementModel() { ... RunningTotal = (runningTotal += x.GoodsValueInAccountCurrency) }; } } 

然后将源查询传递给此函数(不包括Select ):

 var statement = GetStatement(sage.Repository...AsEnumerable()); 

现在可以安全地多次枚举statement 。 基本上,这会创建一个枚举,在每个枚举上重新执行整个块,而不是执行选择器(仅等于foreach部分) – 因此将重置runningTotal