2011-02-24 81 views
10

我真的被困在這一個。我在SQL方面有廣泛的背景,但我剛開始一個新工作,他們更喜歡使用LINQ進行簡單查詢。 所以在學習的精神,我試圖重新寫這個簡單的SQL查詢:LINQ to 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 Profiler查看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#十進制數據類型。 [計數]是一個INT,所以它需要轉換,但[Weight]是一個DECIMAL(8,4)。我不知道爲什麼LINQ會使用這麼大的數據類型。

爲什麼LINQ CONVERT會導致數據類型溢出?無論如何改變這種行爲?還是我在正確的軌道上?

此外,Data.Context.AverageWeight由SqlMetal生成,我驗證了Weight是一個小數,列屬性是正確的(十進制(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

「CONVERT(DECIMAL(...))」正在應用於* Count *,而不是「Weight」。 – 2011-02-24 23:42:36

+0

這是Linq到SQL還是實體框架? – 2011-02-25 00:41:20

+0

爲什麼GroupBy在LINQ中卻不是原來的SQL? – 2011-02-25 00:57:35

回答

2

我不是專家,但是看一下SQL-CLR類型映射表(例如http://msdn.microsoft.com/en-us/library/bb386947.aspx),你可以看到,將SQL十進制值轉換爲CLR System.Decimal類型,並將CLR System.Decimal值轉換爲SQL DECIMAL(29,4)類型。

在你的例子

所以,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是這樣的:

SELECT (CONVERT(Float,[t0].[Weight]))/(CONVERT(Float,[t0].[Count])) AS [value] 
... 

你真正想要的是LINQ到治療a.Count就好像它已經是小數,而不是整數。您可以通過更改DBML文件中的計數屬性類型(see here)來完成此操作。當我這樣做,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仍在執行System.Decimal部門,轉換爲Decimal(29,4)仍然存在。

0

我沒有測試過,但你可以嘗試投a.Count:

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

這可能會起作用,但我也必須將Weight加倍,以便此工作,然後我的返回類型將是雙倍。 我真的不認爲它是將所有東西都轉換爲浮點數據類型的解決方案。 Linq應該能夠正確使用定點數。 無論如何,我可以想出幾種方法來解決這個問題。我真正希望的是有人能夠很好地理解Linq,向我解釋爲什麼會發生這種情況...... – Ben 2011-02-25 15:12:22

+0

此外,(不幸的是)將它轉換爲十進制(c#)不會改變LINQ生成SQL的方式。 – Ben 2011-02-25 15:21:38