SqlMetal ошибочно генерирует тип возврата моего сохраненного процесса (LINQ) - PullRequest
6 голосов
/ 10 января 2010

Привет, есть сохраненный процесс, который всегда возвращает одну строку в зависимости от параметра:

IF @bleh = 1
  SELECT TOP 1 Xyz FROM Abc
ELSE
  SELECT TOP 1 Def FROM Abc

Я должен использовать SqlMetal для создания DataContext, но эта хранимая процедура возвращает IMultipleResults, что является ошибкой Вместо этого он должен вернуть ISingleResult ...

Если я удаляю if (помещая один вызов SELECT), генерируется тип возврата ISingleResult.

Есть идеи?

1 Ответ

10 голосов
/ 30 января 2010

Сценарий, который вы описываете, задуман. Я тестировал как .NET 3.5, так и .NET 4.0 Beta 2 и получил одинаковые результаты. С учетом того, что SPROC использует структуру IF / ELSE, как ваша, генерируемые результаты и используемые инструменты:

  • SqlMetal : IMultipleResults
  • LINQ To SQL Designer (перетаскивание в IDE VS): ISingleResult

Это , поддерживаемый Мэттом Уорреном в Microsoft:

Дизайнер не распознает сохраненные Procs с несколькими возвращаемыми значениями и сопоставит их всех с возвращением одно целое число

Инструмент командной строки SQLMetal делает распознать множественные результаты и напечатает возврат метода правильно как IMultipleResults. Вы можно использовать SQLMetal или изменить DBML от руки или добавить метод подпись для этого хранимого процесса на ваш собственный частичный класс для вашего DataContext.

В этом сообщении в блоге Динеш Кулькарни комментирует противоположный сценарий, когда дизайнер не добавляет IMultipleResults и вместо этого использует ISingleResult. Он заявляет (выделение добавлено):

И нет, дизайнер не поддерживает эта особенность. Таким образом, вы должны добавить метод в вашем частичном классе. SqlMetal однако извлекает sproc. The Причиной этого является реализация подробно: оба используют один и тот же код генератор, но другая база данных схемы извлечения.

Кроме того, в разделе «Обработка нескольких форм результатов из SPROC» в посте Скотта Гу и в этой статье MSDN оба показаны IMultipleResults, используемые с SPROC, которые используют ту же структуру.

Отлично, что теперь? Есть несколько обходных путей, некоторые из которых приятнее других.


Перепишите SPROC

Вы можете переписать SPROC, чтобы SqlMetal генерировал функцию, используя ISingleResult. Это может быть достигнуто с помощью

Переписать # 1 - Сохранение результата в переменной:

DECLARE @Result INT
IF @Input = 1
    SET @Result = (SELECT TOP 1 OrderId FROM OrderDetails)
ELSE
    SET @Result = (SELECT TOP 1 ProductId FROM OrderDetails ORDER BY ProductId DESC)

SELECT @Result As Result

Очевидно, что типы должны быть похожими или что-то, что может быть приведено к другому. Например, если один из них был INT, а другой - DECIMAL(8, 2), вы должны использовать десятичную дробь для сохранения точности.

Перепишите # 2 - используйте оператор case:

Это идентично предложению Марка .

SELECT TOP 1 CASE WHEN @Input = 1 THEN OrderId ELSE ProductId END FROM OrderDetails

Используйте UDF вместо SPROC

Вы можете использовать UDF со скалярным значением и настроить свой запрос для использования формата UDF (идентично переменному подходу, упомянутому выше). SqlMetal сгенерирует для него ISingleResult, поскольку возвращается только одно значение.

CREATE FUNCTION [dbo].[fnODIds] 
(
    @Input INT
)
RETURNS INT
AS
BEGIN
    DECLARE @Result INT

    IF @Input = 1
        SET @Result = (SELECT TOP 1 UnitPrice FROM OrderDetails)
    ELSE
        SET @Result = (SELECT TOP 1 Quantity FROM OrderDetails ORDER BY Quantity DESC)

    RETURN @Result

END

Подделать SPROC и выключить его

Это работает, но более утомительно, чем предыдущие варианты. Кроме того, будущее использование SqlMetal перезапишет эти изменения и потребует повторения процесса. Использование частичного класса и перемещение относительного кода поможет предотвратить это.

1) Измените SPROC так, чтобы он возвращал один оператор SELECT (закомментируйте ваш фактический код), например SELECT TOP 1 OrderId FROM OrderDetails

2) Использовать SqlMetal. Это сгенерирует ISingleResult:

[Function(Name = "dbo.FakeODIds")]
public ISingleResult<FakeODIdsResult> FakeODIds([Parameter(Name = "Input", DbType = "Int")] System.Nullable<int> input)
{
    IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), input);
    return ((ISingleResult<FakeODIdsResult>)(result.ReturnValue));
}

3) Верните SPROC обратно в исходную форму, но используйте тот же псевдоним для возвращаемого результата. Например, я верну и OrderId и ProductId как FakeId.

IF @Input = 1
    SELECT TOP 1 OrderId As FakeId FROM OrderDetails
ELSE
    SELECT TOP 1 Quantity As FakeId FROM OrderDetails ORDER BY Quantity DESC

Обратите внимание, что я здесь не использую переменную, а использую формат, с которого вы изначально начали напрямую.

4) Поскольку мы используем псевдоним FakeId, нам нужно настроить сгенерированный код. Если вы перейдете к отображенному классу, который был сгенерирован для вас на шаге 2 (FakeODIdsResult в моем случае). Класс будет использовать исходное имя столбца из шага 1 в коде, OrderId в моем случае. На самом деле, этого целого шага можно было бы избежать, если бы оператор на шаге 1 был псевдонимом для начала, т.е. SELECT TOP 1 OrderId As FakeId FROM OrderDetails. Если вы этого не сделали, вам нужно пойти и настроить вещи.

FakeODIdsResult будет использовать OrderId, который ничего не вернет, так как псевдоним FakeId. Это будет выглядеть примерно так:

public partial class FakeODIdsResult
{
    private System.Nullable<int> _OrderId;

    public FakeODIdsResult()
    {
    }

    [Column(Storage = "_OrderId", DbType = "Int")]
    public System.Nullable<int> OrderId
    {
        get
        {
            return this._OrderId;
        }
        set
        {
            if ((this._OrderId != value))
            {
                this._OrderId = value;
            }
        }
    }
}

Вам нужно переименовать OrderId в FakeId и _OrderId в _FakeId. Как только это будет сделано, вы можете использовать ISingleResult выше, как обычно, например:

int fakeId = dc.FakeODIds(i).Single().FakeId;

На этом заканчивается то, что я использовал и смог найти по теме.

...