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

SWa*_*SWa 10 c# linq

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

    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)
        });
Run Code Online (Sandbox Code Playgroud)

哪个输出:

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  
Run Code Online (Sandbox Code Playgroud)

309.60第一排的应该是简单154.80

我做错了什么?

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

谢谢大家的建议

ahr*_*uss 7

添加一个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); }
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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