SqlMetal ошибочно генерирует тип возврата моего сохраненного процесса (LINQ)

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

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

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

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

Есть идеи?

1 ответ

Решение

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

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

Это поддерживается Мэттом Уорреном из Microsoft:

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

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

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

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

Кроме того, в разделе "Обработка нескольких форм результатов из 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;

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

Другие вопросы по тегам