LINQ to SQL Преобразование переполнения - PullRequest
10 голосов
/ 25 февраля 2011

Я действительно застрял на этом.У меня обширный опыт работы с 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 - это объект Linq To SQL, созданный SQLMetal.Если я пытаюсь 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 #.[Count] - INT, поэтому его нужно конвертировать, но [Weight] - ДЕСЯТИЧНО (8, 4).Я понятия не имею, почему LINQ использовал бы такой большой тип данных.

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

Кроме того, Data.Context.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 ]

2 голосов
/ 01 марта 2011

Я не эксперт, но, глядя на таблицы сопоставления типов 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, что приводит к преобразованию 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. Вы можете сделать тизменив свойство Type of 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 деление.

0 голосов
/ 25 февраля 2011

Я не проверял, но вы можете попробовать разыграть. Количество:

... a.Weight / (double) a.Count ...
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...