SQL 当插入与输出子句一起使用时,ExecuteScalar 不会抛出异常
SQL ExecuteScalar doesn't throw exception when insert used with output clause
我在 SqlCommand 对象的 "ExecuteScalar()" 方法中遇到了一个有趣的问题。
我有一个存储过程,它在 table 中创建一条新记录。它包含一个 "insert" 语句,其中有一个 "output" 子句 (因为创建的记录使用标识列和一些默认值).
这很好用,"ExecuteScalar" return 是标识列值 (第一行,第一列)。
然而,在测试中,我故意调用了我的存储过程两次,而第二次预计会在数据中遇到唯一约束 table 并失败 (有一个 "try/catch/throw" 在 SP).
到目前为止一切顺利,除了第二次调用 return 是一个空行集 (因为我可以验证我是否 运行 来自 SQLMgr) 和ExecuteScalar return 为 null - 但 不会引发异常。
这不是预期的结果,给我留下了一个有趣的问题。我如何利用 ExecuteScalar - 为了在成功的情况下 return id 列值 - 但如果发生错误它会失败?
我知道如果我从我的 "insert" 中删除 "output" 子句,就会按预期抛出异常。
如果这是预期的行为,我应该如何调用我的 SP,以便 return id 值,并在我的约束被命中时引发异常?
显然,我可以在存储过程 中进行所有形式的猜谜游戏(例如使用 scope_identity 插入然后 select),并且 - 作为我已经在我的测试中完成了——我可以检查一个 null return 并进行后续的 ExecuteNonQuery 调用 (确实会抛出异常)。但是,考虑到 "output" 子句的有用性,这一切似乎都有些捏造。
我做了一些 "Googling",并找到了对 ExecuteScalar "eating" 异常的引用,但它只是一个传递引用。
想法?
--编辑1--
我的意思是,经过进一步调查,在下面写下 "simple case",(显然)如果我有一个存储过程,它有一个 "try catch",并且一个插入违反了唯一约束,当通过 ExecuteScalar 调用时,我没有得到异常。
如果我删除 "try/catch/throw" - 我得到异常。
如果我删除 "output" - 我得到异常
--编辑2--
这里有一些示例(这是 非常 削减...是的,我使用 "try/catch" 跳过其他功能:
Table 看起来像这样:
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
ExecuteScalar
returns 第一行的第一列并丢弃剩余的结果,其中可能包括异常。在 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();
根据 Dan 的建议,我编写了一个强类型扩展方法,虽然它不是 ExecuteScalar() 的直接替代品,但足以满足我的需要。
此扩展的两个主要优点是生成的标量值是强类型的,并且如果在执行命令时发生异常 - 导致一些数据或没有数据 - 遍历所有结果为了检测异常。一个缺点是,如果未返回任何结果,标量结果现在可能不为空 (因为泛型类型可能不可为空(即 Int32)).
就我而言,这很好,因为我要么得到结果,要么得到异常。我不会得到 (通过设计) 的是单个值,它位于大量结果集的第一行,后面是一个异常。如果我们认为 ExecuteScalar 只应该对第一行的第一列感兴趣,我的扩展将 (我怀疑) 强制遍历所有结果 (可能非常大) 直到读取所有数据 and/or 遇到异常。如果您没有预料到这一点,则性能影响可能会令人担忧。在我的例子中,我插入一行并返回该行,我从中取出第一列。
不完美,但我要解决的问题是,如果命令引发异常,我想知道 - 无论它是否已经决定要返回的结果。
我列出了扩展名以备使用:
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()" 方法中遇到了一个有趣的问题。
我有一个存储过程,它在 table 中创建一条新记录。它包含一个 "insert" 语句,其中有一个 "output" 子句 (因为创建的记录使用标识列和一些默认值).
这很好用,"ExecuteScalar" return 是标识列值 (第一行,第一列)。
然而,在测试中,我故意调用了我的存储过程两次,而第二次预计会在数据中遇到唯一约束 table 并失败 (有一个 "try/catch/throw" 在 SP).
到目前为止一切顺利,除了第二次调用 return 是一个空行集 (因为我可以验证我是否 运行 来自 SQLMgr) 和ExecuteScalar return 为 null - 但 不会引发异常。
这不是预期的结果,给我留下了一个有趣的问题。我如何利用 ExecuteScalar - 为了在成功的情况下 return id 列值 - 但如果发生错误它会失败?
我知道如果我从我的 "insert" 中删除 "output" 子句,就会按预期抛出异常。
如果这是预期的行为,我应该如何调用我的 SP,以便 return id 值,并在我的约束被命中时引发异常?
显然,我可以在存储过程 中进行所有形式的猜谜游戏(例如使用 scope_identity 插入然后 select),并且 - 作为我已经在我的测试中完成了——我可以检查一个 null return 并进行后续的 ExecuteNonQuery 调用 (确实会抛出异常)。但是,考虑到 "output" 子句的有用性,这一切似乎都有些捏造。
我做了一些 "Googling",并找到了对 ExecuteScalar "eating" 异常的引用,但它只是一个传递引用。
想法?
--编辑1--
我的意思是,经过进一步调查,在下面写下 "simple case",(显然)如果我有一个存储过程,它有一个 "try catch",并且一个插入违反了唯一约束,当通过 ExecuteScalar 调用时,我没有得到异常。
如果我删除 "try/catch/throw" - 我得到异常。
如果我删除 "output" - 我得到异常
--编辑2--
这里有一些示例(这是 非常 削减...是的,我使用 "try/catch" 跳过其他功能:
Table 看起来像这样:
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
ExecuteScalar
returns 第一行的第一列并丢弃剩余的结果,其中可能包括异常。在 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();
根据 Dan 的建议,我编写了一个强类型扩展方法,虽然它不是 ExecuteScalar() 的直接替代品,但足以满足我的需要。
此扩展的两个主要优点是生成的标量值是强类型的,并且如果在执行命令时发生异常 - 导致一些数据或没有数据 - 遍历所有结果为了检测异常。一个缺点是,如果未返回任何结果,标量结果现在可能不为空 (因为泛型类型可能不可为空(即 Int32)).
就我而言,这很好,因为我要么得到结果,要么得到异常。我不会得到 (通过设计) 的是单个值,它位于大量结果集的第一行,后面是一个异常。如果我们认为 ExecuteScalar 只应该对第一行的第一列感兴趣,我的扩展将 (我怀疑) 强制遍历所有结果 (可能非常大) 直到读取所有数据 and/or 遇到异常。如果您没有预料到这一点,则性能影响可能会令人担忧。在我的例子中,我插入一行并返回该行,我从中取出第一列。
不完美,但我要解决的问题是,如果命令引发异常,我想知道 - 无论它是否已经决定要返回的结果。
我列出了扩展名以备使用:
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;
}
}