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;
На этом я заканчиваю то, что я использовал и смог найти по теме.