您描述的场景是设计使然。我已经使用 .NET 3.5 和 .NET 4.0 Beta 2 进行了测试,得到了相同的结果。给定一个使用 IF/ELSE 结构的 SPROC,生成的结果和使用的工具是:
- SqlMetal:IMultipleResults
- LINQ To SQL 设计器(在 VS IDE 中拖放):ISingleResult
这得到了Microsoft的 Matt Warren的支持:
设计人员无法识别具有多个返回值的存储过程,并将它们全部映射为返回单个整数。
SQLMetal 命令行工具确实可以识别多个结果,并将方法的返回正确地键入为 IMultipleResults。您可以使用 SQLMetal 或手动修改 DBML,或者将此存储过程的方法签名添加到您自己的 DataContext 分部类中。
在这篇博文中, Dinesh Kulkarni 评论了设计师不添加 IMultipleResults 而是使用 ISingleResult 的相反场景。他说(强调补充):
不,设计师不支持此功能。所以你必须在你的部分类中添加方法。然而,SqlMetal 确实提取了存储过程。原因在于实现细节:两者使用相同的代码生成器,但使用不同的数据库模式提取器。
此外,Scott Gu 的帖子中标题为“处理来自 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
显然,类型需要相似或可以转换为其他类型。例如,如果一个是 an INT
,另一个是 a 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
as 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
在我的情况下)。就我而言,该类将使用代码OrderId
中步骤 1 中的原始列名。事实上,如果步骤 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
和。完成后,您可以像往常一样使用上面的 ISingleResult,例如:_OrderId
_FakeId
int fakeId = dc.FakeODIds(i).Single().FakeId;
这总结了我使用过的内容并且能够找到关于该主题的内容。