LINQ to SQL转换溢出

时间:2011-02-24 23:35:34

标签: c# linq overflow

我真的坚持这个。我在SQL方面有广泛的背景,但我刚刚开始了一项新工作,他们更喜欢使用LINQ进行简单查询。 所以本着学习的精神,我试着重写这个简单的SQL查询:

SELECT
    AVG([Weight] / [Count]) AS [Average],
    COUNT(*) AS [Count]
FROM [dbo].[Average Weight]
WHERE
    [ID] = 187

为了清楚起见,这是表格架构:

CREATE TABLE [dbo].[Average Weight]
(
    [ID] INT NOT NULL,
    [Weight] DECIMAL(8, 4) NOT NULL,
    [Count] INT NOT NULL,
    [Date] DATETIME NOT NULL,
    PRIMARY KEY([ID], [Date])
)

以下是我提出的建议:

var averageWeight = Data.Context.AverageWeight
    .Where(i => i.ID == 187)
    .GroupBy(w => w.ID)
    .Select(i => new { Average = i.Average(a => a.Weight / a.Count), Count = i.Count() });

Data.Context.AverageWeight是由SQLMetal生成的Linq To SQL对象。如果我尝试averageWeight.First(),我会收到OverflowException。我使用SQL事件探查器来查看LINQ生成的参数化查询是什么样的。重新缩进,如下所示:

EXEC sp_executesql N'
SELECT TOP(1)
    [t2].[value] AS [Average],
    [t2].[value2] AS [Count]
FROM (
        SELECT
            AVG([t1].[value]) AS [value],
            COUNT(*) AS [value2]
        FROM (
                SELECT
                    [t0].[Weight] / (CONVERT(DECIMAL(29, 4), [t0].[Count])) AS 
                    [value],
                    [t0].[ID]
                FROM [dbo].[Average Weight] AS [t0]
             ) AS [t1]
        WHERE
            ([t1].[ID] = @p0)
        GROUP BY
            [t1].[ID]
     ) AS [t2]',
    N'@p0 int',
     @p0 = 187

过度嵌套,我只看到一个问题:DECIMAL(29,4)。 (查询运行并给出预期的结果。)我的理解是,28以上的任何内容都会溢出C#十进制数据类型。 [Count]是一个INT,所以它确实需要被CONVERT,但[Weight]是DECIMAL(8,4)。我不知道为什么LINQ会使用这么大的数据类型。

为什么LINQ CONVERT会导致导致和溢出的数据类型?反正有改变这种行为吗?或者我是否走在正确的轨道上?

此外,Data.Context.AverageWeight是由SqlMetal生成的,我验证了Weight是小数,Column属性是正确的(Decimal(8,4))。

提前致谢。

更新 所以看起来LINQ to SQL可能是罪魁祸首。我改变了我的LINQ:

var averageWeight = Data.Context.AverageWeight
    .Where(i => i.ID == 187)
    .GroupBy(w => w.ID)
    .Select(i => new { Average = i.Average(a => a.Weight) / (decimal)i.Average(a => a.Count), Count = i.Count() });

现在生成的SQL看起来像这样:

SELECT TOP(1)
    [t2].[value] AS [Average],
    [t2].[value2] AS [Count]
FROM (
        SELECT
            AVG([t1].[value]) AS [value],
            COUNT(*) AS [value2]
        FROM (
                SELECT
                    [t0].[Weight] / (CONVERT(DECIMAL(16, 4), [t0].[Count])) AS [value],
                    [t0].[ID]
                FROM [dbo].[Average Weight] AS [t0]
             ) AS [t1]
        WHERE
            ([t1].[ID] = 187)
        GROUP BY
            [t1].[ID]
     ) AS [t2]

结果是:

Average                  Count
0.000518750000000        16

之前的方法给出了:

Average                  Count
0.000518750000000000000  16

不再有溢出,但查询效率较低。我不知道为什么LINQ to SQL会转换为如此高的精度。其他变量不是那么精确。据我所知,在LINQ中我无法强制执行数据类型。

有什么想法吗?

2 个答案:

答案 0 :(得分:2)

我不是专家,但是查看SQL-CLR类型映射表(例如http://msdn.microsoft.com/en-us/library/bb386947.aspx),您可以看到SQL十进制值转换为CLR System.Decimal类型和CLR {{1值转换为SQL System.Decimal类型。

因此,在您的示例中,DECIMAL(29,4)作为SQL小数转换为CLR a.Weight System.Decimal.除以a.Weight因此被视为a.Count必须将除法和右操作数(System.Decimal)转换为CLR a.Count Linq然后将此类型转换转换回SQL,这会导致Count转换为System.Decimal.

不幸的是,

DECIMAL(29,4).

将无效,因为必须将右操作数转换为a.Weight / (double) a.Count ,但不能像int一样自动转换double。然而,

System.Decimal

将起作用,因为该分区现在被视为双精度分区,而不是(double) a.Weight / a.Count ,因此生成的SQL如下所示:

System.Decimals,

你真正想要的是Linq将SELECT (CONVERT(Float,[t0].[Weight])) / (CONVERT(Float,[t0].[Count])) AS [value] ... 视为已经是小数,而不是int。您可以通过更改DBML文件中的Count属性类型(see here)来完成此操作。当我这样做时,Linq查询:

a.Count

导致SQL:

var averageweight = context.AverageWeights
            .Where(i => i.ID == 187)
            .GroupBy(w => w.ID)
            .Select(i => new {Average = i.Average(a => a.Weight/a.Count), Count = i.Count()});

这是期望的结果。但是,更改DBML文件中Count属性的类型可能会产生其他意外的副作用。

顺便说一句,从更新的Linq查询生成的SQL似乎是错误的。 Linq明确要求所有权重的平均值除以所有计数的平均值,但这不是SQL的作用。当我编写相同的Linq查询时,我得到的SQL是:

SELECT AVG([t0].[Weight] / [t0].[Count]) AS [Average], COUNT(*) AS [Count]
FROM [dbo].[AverageWeight] AS [t0]
WHERE [t0].[ID] = @p0
GROUP BY [t0].[ID]

请注意,有两次拨打SELECT [t1].[value] / (CONVERT(Decimal(29,4),[t1].[value2])) AS [Average], [t1].[value3] AS [Count] FROM ( SELECT AVG([t0].[Weight]) AS [value], AVG([t0].[Count]) AS [value2], COUNT(*) AS [value3] FROM [dbo].[Average Weight] AS [t0] WHERE [t0].[ID] = @p0 GROUP BY [t0].[ID] ) AS [t1] 而不是一次。另请注意,由于Linq仍在进行AVG分区,因此转换为Decimal(29,4)仍然存在。

答案 1 :(得分:0)

我没有测试,但你可以尝试施放a.Count:

... a.Weight / (double) a.Count ...