SqlMetal错误地生成我的存储过程的返回类型(LINQ)

Mik*_*ier 6 .net linq sql-server stored-procedures sqlmetal

嗨有一个存储过程总是返回一行取决于一个参数:

IF @bleh = 1
  SELECT TOP 1 Xyz FROM Abc
ELSE
  SELECT TOP 1 Def FROM Abc
Run Code Online (Sandbox Code Playgroud)

我必须使用SqlMetal生成DataContext但该存储过程返回IMultipleResults一个错误.相反它应该返回ISingleResult...

如果我删除if(发出单个SELECT调用),ISingleResult则会生成返回类型.

有任何想法吗?

Ahm*_*eed 10

您描述的场景是设计的.我已经使用.NET 3.5和.NET 4.0 Beta 2进行了测试,并得到了相同的结果.给定一个使用IF/ELSE结构的SPROC,生成的结果和使用的工具是:

  • SqlMetal:IMul​​tipleResults
  • LINQ to SQL Designer(在VS IDE中拖放):ISingleResult

得到了微软Matt Warren的支持:

设计器无法识别具有多个返回值的存储过程,并将它们全部映射为返回单个整数.

SQLMetal命令行工具确实识别多个结果,并将正确输入方法作为IMultipleResults.您可以使用SQLMetal或手动修改DBML,也可以将此存储过程的方法签名添加到您自己的DataContext的分部类中.

这篇博文中, Dinesh Kulkarni评论了相反的情况,即设计师不添加IMultipleResults并使用ISingleResult.他说(重点补充):

不,设计师不支持此功能.因此,您必须在分部类中添加该方法.但是,SqlMetal会提取sproc.原因是实现细节:两者使用相同的代码生成器但使用不同的数据库模式提取器.

此外,Scott Gu的文章MSDN文章中标题为"处理来自SPROC的多个结果形状"的部分都显示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
Run Code Online (Sandbox Code Playgroud)

显然,类型需要相似或者可以投射到另一个类型.例如,如果一个是a INT而另一个是a DECIMAL(8, 2),则使用小数来保持精度.

重写#2 - 使用案例陈述:

这与马克的建议完全相同.

SELECT TOP 1 CASE WHEN @Input = 1 THEN OrderId ELSE ProductId END FROM OrderDetails
Run Code Online (Sandbox Code Playgroud)

使用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
Run Code Online (Sandbox Code Playgroud)

伪造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));
}
Run Code Online (Sandbox Code Playgroud)

3)将您的SPROC更改回其原始格式,但对返回的结果使用相同的别名.例如,我将返回两者 OrderIdProductIdas 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
Run Code Online (Sandbox Code Playgroud)

注意我这里没有使用变量,而是直接使用您最初使用的格式.

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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

你需要做的是重新命名OrderId,以FakeId_OrderId_FakeId.完成后,您可以像往常一样使用上面的ISingleResult,例如:

int fakeId = dc.FakeODIds(i).Single().FakeId;
Run Code Online (Sandbox Code Playgroud)

这就结束了我已经使用过并且能够在主题上找到的内容.