C# LINQ到SQL转换溢出

C# LINQ到SQL转换溢出,c#,linq,overflow,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 We

我真的被这件事难住了。我有丰富的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到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
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() });
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]
SELECT (CONVERT(Float,[t0].[Weight])) / (CONVERT(Float,[t0].[Count])) AS [value]
...
撇开过度嵌套不谈,我只看到一个问题:十进制(29,4)。(查询运行并给出预期结果。)据我所知,任何高于28的数据都会溢出C#decimal数据类型。[Count]是一个整数,因此需要进行转换,但[Weight]是一个小数(8,4)。我不知道LINQ为什么会使用如此大的数据类型

为什么LINQ会转换成一种会导致错误和溢出的数据类型?有没有办法改变这种行为?还是说我走对了路

另外,Data.Context.AverageWeight是由SqlMetal生成的,我验证了Weight是十进制的,列属性是正确的(十进制(8,4))

提前谢谢

更新: 因此,看起来LinqtoSQL可能是罪魁祸首。我这样改变了我的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
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() });
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]
SELECT (CONVERT(Float,[t0].[Weight])) / (CONVERT(Float,[t0].[Count])) AS [value]
...
现在生成的SQL如下所示:

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
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() });
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]
SELECT (CONVERT(Float,[t0].[Weight])) / (CONVERT(Float,[t0].[Count])) AS [value]
...
其结果是:

Average                  Count
0.000518750000000        16
先前的方法给出了:

Average                  Count
0.000518750000000000000  16
不再存在溢出,但查询效率较低。我不知道为什么LINQ到SQL会转换成如此高的精度。其他变量中没有一个如此精确。据我所知,在LINQ中我无法强制使用数据类型


有什么想法吗?

我还没有测试过,但你可以试着投一个。计数:

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

我不是专家,但查看SQL-CLR类型映射表(例如),您可以看到SQL十进制值转换为CLR
System。decimal
type和CLR
System。decimal
值转换为SQL
decimal(29,4)
type

因此,在您的示例中,
a.Weight
作为SQL十进制被转换为CLR
System.decimal。
因此,
a.Weight
除以
a.Count
被视为
System.decimal
除法和右操作数(
a.Count
)必须转换为CLR
System.Decimal。
Linq然后将此类型转换回SQL,从而导致计数转换为
Decimal(29,4)。

不幸的是

a.Weight / (double) a.Count
无法工作,因为必须将正确的操作数转换为System.Decimal,但double不能像int那样自动转换。但是,

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

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
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() });
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]
SELECT (CONVERT(Float,[t0].[Weight])) / (CONVERT(Float,[t0].[Count])) AS [value]
...
您真正想要的是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()});
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]
这是理想的结果。但是,更改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]

请注意,对
AVG
有两个调用,而不是一个。还请注意,由于Linq仍在进行
系统.Decimal
除法,因此到
十进制(29,4)
的转换仍然存在。

转换(十进制(…)正在应用于计数,不是
Weight
。这是Linq到SQL还是实体框架?为什么在Linq中使用GroupBy而不是原始SQL?Kirk:正在转换是的计数,但它正在转换,因为权重是十进制的。Chris:在SQL中不需要GroupBy,因为我正在将ID(在WHERE子句中)分组为单个值。查询将只返回一行。将GROUP BY添加到SQL语句不会更改其结果。据我所知,Linq需要一个群组来使用聚合。这可能会起作用,但我也必须将权重加倍才能起作用,然后我的返回类型将是加倍。我真的不认为把所有东西都转换成浮点数据类型是一种解决方案。Linq应该能够正确使用定点编号。无论如何,我可以想出几种方法来解决这个问题。我真正希望的是,有人对Linq有足够的了解,能够向我解释为什么会发生这种情况……而且,(不幸的是)将其转换为十进制(c#)不会改变Linq生成SQL的方式。