Sql server SQL ExecuteScalar不';insert与output子句一起使用时引发异常

Sql server SQL ExecuteScalar不';insert与output子句一起使用时引发异常,sql-server,stored-procedures,exception-handling,Sql Server,Stored Procedures,Exception Handling,我在SqlCommand对象的“ExecuteScalar()”方法中遇到了一个有趣的问题 我有一个存储过程,它在表中创建一个新记录。它包含一个“insert”语句,其中包含一个“output”子句(因为创建的记录使用标识列和一些默认值) 这工作正常,“ExecuteScalar”返回标识列值(第1行,第1列) 然而,在测试中,我故意调用了我的存储过程两次,在第二次调用时,预计它会遇到数据表中的唯一约束并失败(SP中有一个“try/catch/throw”) 到目前为止还不错,除了第二次调用返回

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

我有一个存储过程,它在表中创建一个新记录。它包含一个“insert”语句,其中包含一个“output”子句(因为创建的记录使用标识列和一些默认值)

这工作正常,“ExecuteScalar”返回标识列值(第1行,第1列)

然而,在测试中,我故意调用了我的存储过程两次,在第二次调用时,预计它会遇到数据表中的唯一约束并失败(SP中有一个“try/catch/throw”)

到目前为止还不错,除了第二次调用返回一个空行集(我可以验证是否从SQLMgr运行该行集)和ExecuteScalar返回一个null-,但是不会引发异常

这不是预期的结果,给我留下了一个有趣的问题。我如何使用ExecuteScalar(在成功的情况下,返回id列值),但如果出现错误,它将失败

我知道,如果我从“insert”中删除“output”子句,就会像预期的那样抛出异常

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

显然,我可以在存储过程中执行各种各样的jiggery扑克牌操作(比如使用scope_identity插入然后选择),并且——正如我在测试中所做的那样——我可以检查null返回并进行后续的ExecuteOnQuery调用(这会引发异常)。但考虑到“输出”条款的实用性,这一切似乎都有点胡说八道

我做了一些“谷歌搜索”,发现了ExecuteScalar“吃”异常的引用,但这只是一个间接引用

想法

--编辑1--

在写下面的“简单案例”进行进一步调查后,我想说的是(显然)如果我有一个存储过程,它有一个“try-catch”,并且有一个insert违反了唯一约束,当通过ExecuteScalar调用时,我不会得到异常

  • 如果我删除“try/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
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
SP如下所示:

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

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

如果你想要更多的控制,你可以直接使用ExecuteReader方法。ExecuteScalar和它的ExecuteOnQuery近亲基本上只是ExecuteReader的包装

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

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

如果你想要更多的控制,你可以直接使用ExecuteReader方法。ExecuteScalar和它的ExecuteOnQuery近亲基本上只是ExecuteReader的包装

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

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

此扩展的两个主要优点是,生成的标量值是强类型的,并且,如果在执行命令时发生异常(导致部分数据或没有数据),则遍历所有结果以检测异常。一个缺点是,如果没有重新生成,标量结果现在可能不会为空返回了sult(因为泛型类型可能不可为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;
        }
    }
公共静态类扩展
{
/// 
///执行查询,并返回查询返回的结果集中第一行的第一列。
/// 
/// 
///这是因为在某些情况下,SqlCommand.ExecuteScalar()可能无法传播该命令引发的异常。
/// 
静态公共T ExecuteScalar(此SqlCommand)
{
T结果=默认值(T);
使用(IDataReader=command.ExecuteReader())
{
if(reader.Read())
{
var值=reader.GetValue(0);
尝试
{
结果=(T)Convert.ChangeType(值,typeof(T));
}
捕获(例外情况除外)
{
抛出新的FormatException(String.Format(“无法将\“{0}\”的标量值转换为