当insert与输出子句

时间:2015-06-18 11:53:27

标签: sql-server stored-procedures exception-handling

我在" ExecuteScalar()"中遇到了一个有趣的问题。 SqlCommand对象的方法。

我有一个存储过程,它在表中创建一个新记录。它包含一个"插入"声明有"输出"子句(因为创建的记录使用了标识列和一些默认值)

这很好用," ExecuteScalar"返回标识列值(第1行,第1列)

然而,在测试中,我故意调用我的存储过程两次,并且第二次在数据表中遇到一个唯一约束并且失败(那里' sa"尝试/在SP中捕获/抛出"

到目前为止一切顺利,除了第二次调用返回一个空行集(因为我可以验证我是否从SQLMgr运行它)并且ExecuteScalar返回null - 但是不是& #39;抛出异常。

这不是预期的结果,给我留下了一个有趣的问题。我如何利用ExecuteScalar - 为了在成功的情况下返回id列值 - 但是如果发生错误它会失败?

我知道如果我删除"输出"来自我的"插入"的子句,抛出了异常。

如果这是预期的行为,我应该如何调用我的SP,以便返回id值,并在我的约束被命中时引发异常?

显然,我可以在存储过程中做各种各样的jiggery-pokery(比如insert然后选择使用scope_identity),并且 - 正如我在测试中所做的那样 - 我可以检查null返回并进行后续的ExecuteNonQuery调用(确实抛出异常)。但考虑到"输出的有用性,这一切似乎都是一种软糖。子句。

我做了一些"谷歌搜索",并找到了对ExecuteScalar" eat"的引用。例外,但它只是一个传递参考。

想法?

- 编辑1 -

我所说的,经过进一步的调查,写下简单的案例"下面是(显然)是否有一个存储过程,它有一个" try catch"和一个违反唯一约束的插入,当通过ExecuteScalar调用时,我没有得到异常。

  • 如果我删除"尝试/ catch / throw" - 我得到例外。

  • 如果我删除"输出" - 我得到例外

- 编辑2 -

以下是一些示例(这是非常减少...是的,我使用" try / catch"跳过其他功能:

表格如下:

CREATE TABLE [dbo].[test]
(
    [id] [int] IDENTITY(1,1) NOT NULL,
    [value] [varchar](10) NOT NULL,
    [description] [varchar](100) NOT NULL,
    CONSTRAINT [PK_test_1] PRIMARY KEY CLUSTERED ([id] ASC) ON [PRIMARY]
) ON [PRIMARY]
END
GO
CREATE UNIQUE NONCLUSTERED INDEX [UNQ_test_description] ON [dbo].[test]([description] ASC) ON [PRIMARY]
GO
SP看起来像这样:

create procedure dbo.fred
(
    @Code           varchar(10),
    @Description    varchar(100)
)
as
begin
    begin try

        insert into dbo.test
            (value, [description])
        output
            inserted.ID
        values
            (@Code, @Description)

    end try
    begin catch
        throw
    end catch
end

2 个答案:

答案 0 :(得分:3)

根据Dan的建议,我编写了一个强类型的扩展方法,虽然不是ExecuteScalar()的直接替代方法,但已足够接近我的需求。

此扩展的两个主要优点是得到的标量值是强类型的,如果在执行命令时发生异常 - 导致某些数据或没有数据 - 所有结果都按顺序遍历检测异常。一个缺点是如果没有返回结果,标量结果现在可能不为null (因为泛型类型可能不可为空(即Int32))

在我的情况下,这很好,因为我要么得到一个结果,要么我会得到一个例外。我不会得到的(通过设计)是一个单值,在一个大型结果集的第一行,后跟一个异常。如果我们认为ExecuteScalar只应该对第一行的第一列感兴趣,我的扩展将(我怀疑)强制遍历所有结果(这可能是相当的)大)直到读取所有数据和/或遇到异常。如果您没有预料到这一点,性能影响可能会受到关注。在我的情况下,我正在插入一行并返回该行,我从第一列开始。

不完美,但我要解决的问题是,如果命令引发异常,我想知道 - 无论是否已经决定返回结果。

我列出了扩展名,以防其使用:

public static class Extensions
{
    /// <summary>
    /// Executes the query, and returns the first column of the first row in the result set returned by the query.
    /// </summary>
    /// <remarks>
    /// This is here because SqlCommand.ExecuteScalar() can, under some circumstances, fail to propagate an exception raised by the command.
    /// </remarks>
    static public T ExecuteScalar<T>(this SqlCommand command)
    {
        T result = default(T);

        using (IDataReader reader = command.ExecuteReader())
        {
            if (reader.Read())
            {
                var value = reader.GetValue(0);

                try
                {
                    result = (T)Convert.ChangeType(value, typeof(T));
                }
                catch (Exception ex)
                {
                    throw new FormatException(String.Format("Unable to convert scalar value of \"{0}\" to type {1}.", value, typeof(T)),ex);
                }
            }

            while (reader.NextResult()) ;

            return result;
        }
    }

答案 1 :(得分:2)

ExecuteScalar返回第一行的第一列并丢弃剩余的结果,其中可能包含异常。对于OUTPUT子句,INSERT和SQL TRY / CATCH上的错误,首先返回空的单列结果,然后返回丢弃的异常。

如果您想要更多控制,可以直接使用ExecuteReader方法。 ExecuteScalar及其ExecuteNonQuery表兄弟基本上只是围绕ExecuteReader的包装器。

var r = command.ExecuteReader();
if (r.Read())
    result = r.GetInt32(0);
else
    r.NextResult();
r.Close();