我真的坚持这个。我在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中我无法强制执行数据类型。
有什么想法吗?
答案 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 ...