我有以下计算客户帐户状态的运行总计,但是他的第一个值总是被添加到自己,我不知道为什么 – 虽然我怀疑我错过了一些明显的事情:
decimal? runningTotal = 0; IEnumerable<StatementModel> statement = sage.Repository<FDSSLTransactionHistory>() .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的评论,我在我的View中对结果调用了Any(),导致第一次被评估两次 – 将我附加的ToList()解析为我的查询.
感谢大家的建议
解决方法
在调用结束时添加ToList(),以避免重复调用选择器.
这是具有副作用的有状态LINQ查询,这本质上是不可预测的.在代码的其他地方,你调用了一些导致第一个元素被评估的东西,像First()或Any().一般来说,在LINQ查询中产生副作用是很危险的,当你发现自己需要它们的时候,是时候考虑它是否应该是一个foreach.
编辑或为什么会发生这种情况?
这是LINQ查询被评估的结果:直到你实际使用查询的结果,没有什么真正发生的集合.它不评估任何元素.相反,它存储Abstract Expression Trees或只需要评估查询所需的代理.然后,只有当需要结果时,才会评估这些结果,除非您明确存储结果,否则将被丢弃,并在下次重新评估.
所以这个问题为什么每次都有不同的结果?答案是,runTotal仅在第一次初始化.之后,它的值是上次执行查询后的值,这可能会导致奇怪的结果.
这意味着问题可能很容易就是“为什么总是总是两倍呢?”如果asker做这样的事情:
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,它规避并强制立即执行.