Linq Sum()精度

时间:2014-05-05 14:29:51

标签: c# mysql linq nhibernate floating-point-precision

在我的项目中,我一直在使用Linq's Sum()。它由MySQL上的NHibernate提供支持。在我的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()对结果进行汇总以及如何解决?< / p>

编辑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);生效。

1 个答案:

答案 0 :(得分:2)

x.Amount正在从&#34; LINQ-to-SQL&#34;转换为低精度最小类型。转换,因为您的收藏是IQueryable。

有几种解决方法,最简单的方法是将集合的类型更改为IList,或者调用集合上的ToList(),强制linq查询作为LINQ-to-Objects运行。

var opasSum = opasForThisIp.ToList().Sum(x => x.Amount);

注意:如果您不希望通过远离IQueryable而丢失延迟执行,您可以尝试将Amount转换为linq查询中的小数。

来自MSDN decimal and numeric (Transact-SQL)

  

在Transact-SQL语句中,带小数点的常量是   使用最小值自动转换为数字数据值   精度和规模必要。例如,常数12.345是   转换为精度为5且比例为3的数值。

编辑(包含对不同.NET集合类型的精彩解释:

取自this SO question.

的答案
  

IQueryable旨在允许查询提供程序(例如,   ORM,如LINQ to SQL或Entity Framework)来使用表达式   包含在查询中以将请求转换为另一种格式。在   换句话说,LINQ-to-SQL查看实体上的属性   你正在使用你正在制作和实际比较的比较   创建一个SQL语句来表达(希望)一个等效的请求。

     

IEnumerable比IQueryable更通用(尽管如此)   IQueryable的实例实现IEnumerable)并且只定义   一个序列。但是,有可用的扩展方法   可枚举的类,在其上定义一些查询类型运算符   接口并使用普通代码来评估这些条件。

     

List只是一种输出格式,并且实现时   IEnumerable与查询没有直接关系。

     

换句话说,当您使用IQueryable时,您需要定义和   表达式被翻译成其他东西。即使   你正在编写代码,代码永远不会被执行,它只会得到   检查并转换为其他内容,就像实际的SQL查询一样。   因此,只有某些内容在这些内容中有效   表达式。例如,你不能调用普通的函数   你可以在这些表达式中定义,因为LINQ-to-SQL并不是这样   知道如何将您的调用转换为SQL语句。大多数这些   不幸的是,限制仅在运行时进行评估。

     

当您使用IEnumerable进行查询时,您正在使用   LINQ到对象,这意味着您正在编写实际代码   用于评估您的查询或转换结果,所以有   一般来说,你可以做什么没有限制。你可以打电话   这些表达式中的其他功能可以自由使用。