我在" 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
答案 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();