LINQ to SQL转换溢出

Ben*_*Ben 11 c# linq overflow

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

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

为清楚起见,这是表模式:

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

这是我想出的:

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

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

抛开过多的嵌套,我只看到一个问题: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() });
Run Code Online (Sandbox Code Playgroud)

现在生成的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]
Run Code Online (Sandbox Code Playgroud)

结果是:

Average                  Count
0.000518750000000        16
Run Code Online (Sandbox Code Playgroud)

以前的方法给出了:

Average                  Count
0.000518750000000000000  16
Run Code Online (Sandbox Code Playgroud)

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

有任何想法吗?

srg*_*erg 4

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

因此,在您的示例中,a.Weight当 SQL 小数转换为 CLR 时,bySystem.Decimal.的除法因此被视为除法,并且右操作数 ( ) 必须转换为 CLR Linq,然后将此类型转换转换回 SQL,从而导致 Count 为转换为a.Weighta.CountSystem.Decimala.CountSystem.Decimal.DECIMAL(29,4).

很遗憾,

a.Weight / (double) a.Count
Run Code Online (Sandbox Code Playgroud)

不起作用,因为正确的操作数必须转换为 a System.Decimal,但 double 不能像 int 那样自动转换。然而,

(double) a.Weight / a.Count
Run Code Online (Sandbox Code Playgroud)

会起作用,因为除法现在被视为双精度除法,而不是System.Decimals,因此生成的 SQL 如下所示:

SELECT (CONVERT(Float,[t0].[Weight])) / (CONVERT(Float,[t0].[Count])) AS [value]
...
Run Code Online (Sandbox Code Playgroud)

您真正想要的是 Linq 将a.Count其视为小数,而不是整数。您可以通过更改 DBML 文件中 Count 属性的类型来完成此操作(请参阅此处)。当我这样做时,Linq 查询:

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

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

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

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

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

请注意,有两次调用,AVG而不是一次。Decimal(29,4)另请注意,由于 Linq 仍在进行除法,因此转换仍然存在System.Decimal