Linq Sum() 精度
Posted
技术标签:
【中文标题】Linq Sum() 精度【英文标题】:Linq Sum() precision 【发布时间】:2014-06-21 21:40:16 【问题描述】:在我的项目中,我经常使用Linq's Sum()
。它由 NHibernate on mysql 提供支持。在我的 Session Factory
中,我明确要求 NHibernate 处理 decimals
时的小数点后 8 位:
public class DecimalsConvention : IPropertyConvention
public void Apply(IPropertyInstance instance)
if (instance.Type.GetUnderlyingSystemType() == typeof(decimal))
instance.Scale(8);
instance.Precision(20);
但是,我发现.Sum()
将数字四舍五入到小数点后 5 位:
var opasSum = opasForThisIp.Sum(x => x.Amount); // Amount is a decimal
在上面的语句中opaSum
等于2.46914而应该是2.46913578(直接在MySQL上计算)。 opasForThisIp
的类型为 IQueryable<OutgoingPaymentAssembly>
。
当涉及到decimals
时,我需要所有 Linq 计算来处理 8 位小数。
关于如何解决这个问题的任何想法?
编辑 1:我发现 var opasSum = Enumerable.Sum(opasForThisIp, opa => opa.Amount);
可以产生正确的结果,但问题仍然存在,为什么 .Sum()
会四舍五入结果,我们该如何解决?
编辑2:生成的SQL好像有问题:
select cast(sum(outgoingpa0_.Amount) as DECIMAL(19,5)) as col_0_0_
from `OutgoingPaymentAssembly` outgoingpa0_
where outgoingpa0_.IncomingPayment_id=?p0
and (outgoingpa0_.OutgoingPaymentTransaction_id is not null);
?p0 = 24 [Type: UInt64 (0)]
编辑 3:var opasSum = opasForThisIp.ToList().Sum(x => x.Amount);
也会产生正确的结果。
编辑 4:将 IQueryable<OutgoingPaymentAssembly>
转换为 IList<OutgoingPaymentAssembly>
使原始查询:var opasSum = opasForThisIp.Sum(x => x.Amount);
起作用。
【问题讨论】:
在运行var opasSum = opasForThisIp.Sum(x => x.Amount);
时,有没有检查生成的SQL语句?
@DominicKexel 说得好,SQL语句确实有问题,看我的编辑。
我不明白。 .Sum()
确实是Enumerable.Sum()
的扩展方法,为什么会产生不同的SQL语句?
opasForThisIp 是什么类型的?
如果您将 IQueryable 强制列在列表中,我很想知道生成的 SQL 会发生什么。 var opasSum = opasForThisIp.ToList().Sum(x => x.Amount);看起来应该和调用 Enumerable.Sum() 的效果一样,但我不能肯定。
【参考方案1】:
x.Amount 正在从“LINQ-to-SQL”转换转换为低精度最小类型,因为您的集合是 IQueryable。
有几种解决方法,其中最简单的是将集合的类型更改为 IList,或在集合上调用 ToList(),强制 linq 查询作为 LINQ-to-Objects 运行。
var opasSum = opasForThisIp.ToList().Sum(x => x.Amount);
注意: 如果您不想因离开 IQueryable 而失去延迟执行,您可以尝试在 linq 查询中将 Amount 转换为小数。
来自MSDN decimal and numeric (Transact-SQL):
在 Transact-SQL 语句中,带小数点的常量是 使用最小值自动转换为数值数据值 精度和规模是必要的。例如,常数 12.345 是 转换为精度为 5、小数位数为 3 的数值。
编辑(包括对不同 .NET 集合类型的详细解释:
取自this SO question.的回答
IQueryable 旨在允许查询提供程序(例如, ORM(如 LINQ to SQL 或实体框架)来使用表达式 包含在查询中以将请求转换为另一种格式。在 换句话说,LINQ-to-SQL 查看实体的属性 你正在使用与你正在做的比较,实际上 创建一个 SQL 语句来表达(希望)一个等效的请求。
IEnumerable 比 IQueryable 更通用(尽管所有 IQueryable 的实例实现 IEnumerable) 并且只定义 一个序列。但是,在 在其上定义一些查询类型运算符的可枚举类 接口并使用普通代码来评估这些条件。
List 只是一种输出格式,虽然它实现了 IEnumerable,与查询没有直接关系。
换句话说,当您使用 IQueryable 时,您正在定义和 被翻译成别的东西的表达。虽然 您正在编写代码,该代码永远不会被执行,它只会得到 检查并变成其他东西,例如实际的 SQL 查询。 正因为如此,只有某些东西在这些范围内是有效的 表达式。例如,你不能调用一个普通函数 您从这些表达式中定义,因为 LINQ-to-SQL 没有 知道如何将您的调用转换为 SQL 语句。其中大部分 不幸的是,限制仅在运行时评估。
当您使用 IEnumerable 进行查询时,您正在使用 LINQ-to-Objects,这意味着您正在编写实际的代码 用于评估您的查询或转换结果,所以有 一般来说,您可以做的事情没有任何限制。你可以打电话 这些表达式中的其他功能。
【讨论】:
此外,您可能会幸运地检查出 SqlFunctions 方法。我自己对他们了解不多。 msdn.microsoft.com/en-us/library/…以上是关于Linq Sum() 精度的主要内容,如果未能解决你的问题,请参考以下文章