您所描述的场景是设计使然。我已经使用 .NET 3.5 和 .NET 4.0 Beta 2 进行了测试,并得到了相同的结果。给定一个像您一样使用 IF/ELSE 结构的 SPROC,生成的结果和使用的工具是:
-
SqlMetal: I多重结果
-
LINQ To SQL 设计器(在 VS IDE 中拖放):单一结果
This is 马特·沃伦支持 http://social.msdn.microsoft.com/Forums/en-US/linqprojectgeneral/thread/ce90169d-70e7-41e7-9828-aca0eceab769/在微软:
设计者无法识别存储的
具有多个返回值的过程和
将把它们全部映射到返回
单个整数。
SQLMetal 命令行工具可以
识别多个结果并
将键入方法的返回值
正确地作为 IMultipleResults。你
可以使用 SQLMetal 或修改
手动 DBML 或添加方法
将此存储过程的签名发送给您
为你自己的部分课程
数据上下文。
In 这篇博文 http://blogs.msdn.com/dinesh.kulkarni/archive/2008/05/16/linq-to-sql-tips-7.aspxDinesh Kulkarni 评论了相反的情况,即设计者不添加 IMultipleResults 而是使用 ISingleResult。他指出(强调是后加的):
不,设计师不支持
此功能。所以你必须添加
部分类中的方法。 Sql金属
但是会提取存储过程。这
原因是一个实施
细节:两者使用相同的代码
生成器但数据库不同
模式提取器。
此外,标题为“处理存储过程中的多个结果形状”的部分斯科特·顾的帖子 http://weblogs.asp.net/scottgu/archive/2007/08/16/linq-to-sql-part-6-retrieving-data-using-stored-procedures.aspx and 这篇 MSDN 文章 http://msdn.microsoft.com/en-us/library/bb399371.aspx两者都显示 IMultipleResults 与使用相同结构的 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 语句:
这与马克的建议 https://stackoverflow.com/questions/2037280/sqlmetal-wrongly-generates-the-return-type-of-my-stored-proc-linq/2150955#2150955.
SELECT TOP 1 CASE WHEN @Input = 1 THEN OrderId ELSE ProductId END FROM OrderDetails
使用 UDF 代替 SPROC
你可以使用标量值 UDF http://msdn.microsoft.com/en-us/library/bb386973.aspx并调整您的查询以使用 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 更改回其原始形式,但对返回的结果使用相同的别名。例如,我将返回both OrderId
and 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
就我而言)。该类将使用代码中步骤 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
to FakeId
and _OrderId
to _FakeId
。完成后,您可以像平常一样使用上面的 ISingleResult,例如:
int fakeId = dc.FakeODIds(i).Single().FakeId;
这总结了我在该主题上使用过并能够找到的内容。