Переполнение преобразования LINQ в SQL

Я действительно застрял на этом. У меня обширный опыт в 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() });

данные.Контекст.AverageWeight-это объект Linq to SQL, созданный SQLMetal. Если я попытаюсь 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, поэтому его нужно преобразовать, но [Weight] является десятичным(8, 4). Я понятия не имею, почему LINQ будет использовать такой большой тип данных.

почему LINQ преобразуется в тип данных, который вызывает и переполнение? Есть ли способ изменить такое поведение? Или я на верном пути?

Кроме Того, Данных.Контекст.AverageWeight был создан с помощью sqlmetal и я проверил вес-десятичное и атрибут столбец правильное (десятичной(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 ответов


я не эксперт, но глядя на таблицы сопоставления типов 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 decimal преобразуется в CLR System.Decimal. отдела a.Weight by a.Count поэтому рассматривается как System.Decimal деление и правый операнд (a.Count) должен быть преобразован в CLR System.Decimal. Linq затем переводит это преобразование типа обратно в SQL, в результате чего Count преобразуется в DECIMAL(29,4).

к сожалению,

a.Weight / (double) a.Count

не будет работать, поскольку правый операнд должен быть преобразован в System.Decimal но двойной не может быть автоматически преобразован, как int может. Однако,

(double) a.Weight / a.Count

будет работать, потому что разделение теперь рассматривается как разделение двойников, а не System.Decimals, таким образом, результирующий SQL выглядит так:

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

то, что вы действительно хотите, чтобы Linq лечить a.Count как будто это уже десятичное, а не int. Это можно сделать, изменив тип свойства Count в файле DBML (посмотреть здесь). Когда я это сделал, запрос 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]

что является желаемым результатом. Однако, изменение типа свойства count в DBML-файл может иметь и другие побочные эффекты.

кстати, SQL, сгенерированный из вашего обновленного запроса Linq, кажется неправильным. 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, а не только один. Также обратите внимание, что преобразование Decimal(29,4) все еще присутствует, так как Linq все еще делает System.Decimal деление.


Я не тестировал, но вы можете попробовать бросить.Граф:

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