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

发布于 2024-08-17 07:53:39 字数 359 浏览 1 评论 0原文

您好,有一个存储过程,它总是根据参数返回一行:

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

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

如果我删除 if(进行单个 SELECT 调用),则会生成一个 ISingleResult 返回类型。

有什么想法吗?

Hi have a stored proc that always returns a single row depending of a parameter:

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

I must use SqlMetal to generate the DataContext but this stored procedure returns a IMultipleResults, which is an error. Instead it should return a ISingleResult...

If I remove the if (putting a single SELECT call), an ISingleResult return type is generated.

Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

大海や 2024-08-24 07:53:39

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

  • SqlMetalIMultipleResults
  • LINQ To SQL Designer (在 VS IDE 中拖放):ISingleResult

这是 由 Microsoft 的 Matt Warren 支持:

设计者无法识别存储的
具有多个返回值的过程和
将把它们全部映射到返回
单个整数。

SQLMetal 命令行工具可以
识别多个结果并
将键入方法的返回值
正确地作为 IMultipleResults。你
可以使用 SQLMetal 或修改
手动 DBML 或添加方法
将此存储过程的签名发送给您
为你自己的部分课程
数据上下文。

这篇博文中 Dinesh Kulkarni 评论了相反的情况,即设计者不添加 IMultipleResults 而是使用 ISingleResult。他指出(强调是后加的):

不,设计者不支持
这个功能。所以你必须添加
部分类中的方法。 Sql金属
但是会提取存储过程。
原因是一个实施
细节:两者使用相同的代码
生成器但数据库不同
模式提取器。

此外,Scott Gu 的帖子这篇 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 更改回其原始形式,但对返回的结果使用相同的别名。例如,我会将 OrderIdProductId 都返回为 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 中的语句以别名开头,则可以避免这整个步骤,即: 从 OrderDetails 中选择前 1 个 OrderId 作为 FakeId。如果你没有,你需要进去并调整一些东西。

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;

这总结了我在该主题上使用过并能够找到的内容。

The scenario you're describing is by design. I've tested with both .NET 3.5 and .NET 4.0 Beta 2 and got the same results. Given a SPROC using an IF/ELSE structure as yours does, the generated results and tools used are:

  • SqlMetal: IMultipleResults
  • LINQ To SQL Designer (drag & drop in the VS IDE): ISingleResult

This is supported by Matt Warren at Microsoft:

The designer does not recognize stored
procs with multiple return values and
will map them all to returning a
single integer.

SQLMetal command line tool does
recognize the multiple results and
will type the return of the method
correctly as IMultipleResults. You
can either use SQLMetal or modify the
DBML by hand or add the method
signature for this stored proc to your
own partial class for your
DataContext.

In this blog post Dinesh Kulkarni comments on the opposite scenario where the designer doesn't add IMultipleResults and uses ISingleResult instead. He states (emphasis added):

And no, the designer does not support
this feature. So you have to add the
method in your partial class. SqlMetal
does however extract the sproc. The
reason for that is an implementation
detail: the two use the same code
generator but different database
schema extractors.

In addition, the section titled "Handling Multiple Result Shapes from SPROCs" in Scott Gu's post and this MSDN article both show IMultipleResults being used with SPROCs that use the same structure.

Great, now what? There are a few workarounds, some are nicer than others.


Rewrite the SPROC

You can rewrite the SPROC so that SqlMetal generates the function using ISingleResult. This can be achieved by

Rewrite #1 - Storing the result in a variable:

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

Obviously the types will need to be similar or something that can be cast to the other. For example, if one was an INT and the other was a DECIMAL(8, 2) you would use the decimal to retain precision.

Rewrite #2 - Use a case statement:

This is identical to Mark's suggestion.

SELECT TOP 1 CASE WHEN @Input = 1 THEN OrderId ELSE ProductId END FROM OrderDetails

Use a UDF instead of a SPROC

You could use a scalar-valued UDF and adjust your query to use the UDF format (identical to the variable approach mentioned above). SqlMetal will generate an ISingleResult for it since only one value is returned.

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

Fake the SPROC & switch it out

This works but is more tedious than the previous options. Also, future use of SqlMetal would overwrite these changes and require the process to be repeated. Using a partial class and moving the relative code there would help prevent this.

1) Change your SPROC to return a single SELECT statement (comment out your actual code), such as SELECT TOP 1 OrderId FROM OrderDetails

2) Use SqlMetal. It will generate an 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) Change your SPROC back to its original form but use the same alias for the returned result. For example, I will return 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

Notice I am not using a variable here but using the format you originally started with directly.

4) Since we're using the FakeId alias we need to tweak the generated code. If you navigate to the mapped class that was generated for you in step 2 (FakeODIdsResult in my case). The class will be using the original column name from step 1 in the code, OrderId in my case. In fact, this whole step could be avoided if the statement in step 1 was aliased to start with, ie. SELECT TOP 1 OrderId As FakeId FROM OrderDetails. If you didn't though, you need to go in and tweak things.

FakeODIdsResult will be using OrderId, which will return nothing since it aliases FakeId. It will look similar to this:

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;
            }
        }
    }
}

What you need to do is rename OrderId to FakeId and _OrderId to _FakeId. Once that's done, you can use the ISingleResult above as you normally would, for example:

int fakeId = dc.FakeODIds(i).Single().FakeId;

This concludes what I've used and was able to find on the topic.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文