SQL Server 2008 存储过程 return 值
SQL Server 2008 stored procedure return value
这是我的存储过程
ALTER PROCEDURE Delete
@ID nvarchar(64),
@value int = 0 output
AS
BEGIN
IF(EXISTS(SELECT * FROM A where Ap = @ID))
BEGIN
set @value = 1
END
ELSE IF(EXISTS(SELECT * FROM B where Bp = @ID))
BEGIN
set @value = 2
END
ELSE
BEGIN
select *
from Table_D
END
END
RETURN @value
问题是当我执行它时,这 return 没有任何值
return 似乎超出了程序的范围。尝试:
ALTER PROCEDURE Delete
@ID nvarchar(64),
@value int=0 output
AS
BEGIN
IF(EXISTS(SELECT * FROM A where Ap=@ID))
BEGIN
set @value=1
END
ELSE IF(EXISTS(SELECT * FROM B where Bp=@ID))
BEGIN
set @value=2
END
ELSE
BEGIN
set @value=5
end --end if
RETURN @value
end --end procedure
这就是正确使用制表符使代码更具可读性的地方,这些问题也更加明显
不要使用输出参数。相反,使用这个:
ALTER PROCEDURE Delete
@ID nvarchar(64)
AS
BEGIN
DECLARE @value int
SET @value = 0
IF(EXISTS(SELECT 1 FROM A where Ap=@ID))
BEGIN
set @value=1
END
ELSE IF(EXISTS(SELECT 1 FROM B where Bp=@ID))
BEGIN
set @value=2
END
ELSE
BEGIN
set @value=5
end
Select @value as Value, * from Table_D
end
你能试试运行 SP 作为下面的脚本吗?
declare @pID as nvarchar(64)
declare @pValue as int
set @pID = 1 -- Your ID filter
exec Delete @pID, @pValue OUTPUT
select @pValue
有多种方法可以将状态信息从存储过程return发送到应用程序。各有利弊;没有一种技术可以绝对地说在所有情况下都是正确的。即便如此,我还是要开始:
TL;DR:建议
如果您的存储过程遇到问题并且无法 return 正常 return 存储数据,请使用 RAISERROR
。使用 OUTPUT
参数获取客户不能随意忽略的信息,但从逻辑上讲,这不是您的结果的一部分。如果您有客户端可以随意忽略的信息状态代码,请使用 return 值。仅当您知道自己在做什么时才使用其他结果集。
RAISERROR
如果您的存储过程遇到错误并且无法return任何数据,您可以使用RAISERROR
终止执行并在客户端引发异常。
CREATE PROCEDURE [Delete]
@ID nvarchar(64)
AS BEGIN
IF(EXISTS(SELECT * FROM A where Ap = @ID))
BEGIN
RAISERROR('Wrong. Try again.', 11, 1);
RETURN;
END
ELSE IF(EXISTS(SELECT * FROM B where Bp = @ID))
BEGIN
RAISERROR('Wrong in a different way. Try again.', 11, 2);
RETURN;
END
ELSE
BEGIN
select *
from Table_D
END
END
第二个参数(严重性)必须至少设置为 11 才能使错误作为异常传播,否则它只是一条信息性消息。这些也可以被捕获,但这超出了这个答案的范围。第三个参数(状态)可以是任何你喜欢的,并且可以用来传递错误的代码,例如,如果你需要本地化它。用户生成的消息总是有SQL错误代码50000,所以不能用来区分不同的错误,并且解析消息是脆弱的。
处理结果的C#代码:
try {
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
...
}
}
} catch (SqlException e) {
Console.WriteLine(
"Database error executing [Delete] (code {0}): {1}", e.State, e.Message
);
}
这很适合错误,因为实际处理数据的代码保持原样,您可以在正确的位置处理异常(而不是到处传播状态代码)。但是,如果存储过程是 expected 到 return 信息性而非错误的状态,则此方法不合适,因为即使什么都没有,您也会一直捕获异常错了。
输出参数
存储过程可以设置参数值以及接收它们,方法是声明它们 OUTPUT
:
CREATE PROCEDURE [Delete]
@ID nvarchar(64),
@StatusCode INT OUTPUT
AS BEGIN
IF(EXISTS(SELECT * FROM A where Ap = @ID))
BEGIN
SET @StatusCode = 1;
END
ELSE IF(EXISTS(SELECT * FROM B where Bp = @ID))
BEGIN
SET @StatusCode = 2;
END
ELSE
BEGIN
SET @StatusCode = 0;
select *
from Table_D
END
END
在 C# 中,这是在标记为输出参数的参数中捕获的:
SqlParameter statusCodeParameter = command.Parameters.Add(
new SqlParameter {
ParameterName = "@StatusCode",
SqlDbType = SqlDbType.Int,
Direction = ParameterDirection.Output
}
);
using (var reader = command.ExecuteReader()) {
int statusCode = (int) statusCodeParameter.Value;
if (statusCode != 0) {
// show alert
return;
}
while (reader.Read()) {
...
}
}
这里的好处是客户端不能忘记声明参数(必须提供),你不局限于单个INT
,你可以根据参数的值来决定你想用结果集做什么。 Return以这种方式处理结构化数据很麻烦(很多 OUTPUT
参数),但您可以在单个 XML
参数中捕获它。
Return值
每个存储过程都有一个 return 值,它是一个 INT
。如果您没有使用 RETURN
显式设置它,它将保持为 0。
CREATE PROCEDURE [Delete]
@ID nvarchar(64)
AS BEGIN
IF(EXISTS(SELECT * FROM A where Ap = @ID))
BEGIN
RETURN 1
END
ELSE IF(EXISTS(SELECT * FROM B where Bp = @ID))
BEGIN
RETURN 2
END
ELSE
BEGIN
select *
from Table_D
END
END
在 C# 中,return 值必须在标记为 return 值的单个特殊参数中捕获:
SqlParameter returnValueParameter = command.Parameters.Add(
new SqlParameter { Direction = ParameterDirection.ReturnValue }
);
using (var reader = command.ExecuteReader()) {
// this could be empty
while (reader.Read()) {
...
}
}
int returnValue = (int) returnValueParameter.Value;
请务必注意,在您处理完存储过程生成的所有其他结果集(如果有)之前,return 值将不可用,因此如果您将它用于状态指示没有行的代码,在获得状态代码之前,您仍然必须先处理空结果集。你不能 return 除了 INT
以外的任何东西。 Frameworks/OR 映射器通常不支持 return 值。最后,请注意客户端不需要对 return 值执行任何操作,因此您必须仔细记录其预期用途。
结果集
存储过程可以简单地 return 它想要的结果集,就像它 return 其他数据一样。存储过程允许 return 多个结果集,因此即使您的状态在逻辑上与其他数据分开,您也可以 return 它作为一行。
CREATE PROCEDURE [Delete]
@ID nvarchar(64)
AS BEGIN
DECLARE @StatusCode INT = 0;
IF(EXISTS(SELECT * FROM A where Ap = @ID))
BEGIN
SET @StatusCode = 1;
END
ELSE IF(EXISTS(SELECT * FROM B where Bp = @ID))
BEGIN
SET @StatusCode = 2;
END
SELECT @StatusCode AS StatusCode;
IF @StatusCode = 0
BEGIN
select *
from Table_D
END
END
要用 C# 处理这个,我们需要 SqlDataReader.NextResult
:
using (var reader = command.ExecuteReader()) {
if (!reader.Read()) throw new MyException("Expected result from stored procedure.");
statusCode = reader.GetInt32(reader.GetOrdinal("StatusCode"));
if (statusCode != 0) {
// show alert
return;
}
reader.NextResult();
while (reader.Read()) {
// use the actual result set
}
}
这里的主要缺点是存储过程 return 可变数量的结果集并不直观,而且很少有数据 frameworks/OR 映射器支持它,所以你几乎总是最终编写这样的手动代码。 Returning 多个结果集并不真正适合 returning 像状态代码这样的单个数据,但它可能是 returning 结构化数据的替代方法XML
输出参数(特别是如果有很多的话)。
这是我的存储过程
ALTER PROCEDURE Delete
@ID nvarchar(64),
@value int = 0 output
AS
BEGIN
IF(EXISTS(SELECT * FROM A where Ap = @ID))
BEGIN
set @value = 1
END
ELSE IF(EXISTS(SELECT * FROM B where Bp = @ID))
BEGIN
set @value = 2
END
ELSE
BEGIN
select *
from Table_D
END
END
RETURN @value
问题是当我执行它时,这 return 没有任何值
return 似乎超出了程序的范围。尝试:
ALTER PROCEDURE Delete
@ID nvarchar(64),
@value int=0 output
AS
BEGIN
IF(EXISTS(SELECT * FROM A where Ap=@ID))
BEGIN
set @value=1
END
ELSE IF(EXISTS(SELECT * FROM B where Bp=@ID))
BEGIN
set @value=2
END
ELSE
BEGIN
set @value=5
end --end if
RETURN @value
end --end procedure
这就是正确使用制表符使代码更具可读性的地方,这些问题也更加明显
不要使用输出参数。相反,使用这个:
ALTER PROCEDURE Delete
@ID nvarchar(64)
AS
BEGIN
DECLARE @value int
SET @value = 0
IF(EXISTS(SELECT 1 FROM A where Ap=@ID))
BEGIN
set @value=1
END
ELSE IF(EXISTS(SELECT 1 FROM B where Bp=@ID))
BEGIN
set @value=2
END
ELSE
BEGIN
set @value=5
end
Select @value as Value, * from Table_D
end
你能试试运行 SP 作为下面的脚本吗?
declare @pID as nvarchar(64)
declare @pValue as int
set @pID = 1 -- Your ID filter
exec Delete @pID, @pValue OUTPUT
select @pValue
有多种方法可以将状态信息从存储过程return发送到应用程序。各有利弊;没有一种技术可以绝对地说在所有情况下都是正确的。即便如此,我还是要开始:
TL;DR:建议
如果您的存储过程遇到问题并且无法 return 正常 return 存储数据,请使用 RAISERROR
。使用 OUTPUT
参数获取客户不能随意忽略的信息,但从逻辑上讲,这不是您的结果的一部分。如果您有客户端可以随意忽略的信息状态代码,请使用 return 值。仅当您知道自己在做什么时才使用其他结果集。
RAISERROR
如果您的存储过程遇到错误并且无法return任何数据,您可以使用RAISERROR
终止执行并在客户端引发异常。
CREATE PROCEDURE [Delete]
@ID nvarchar(64)
AS BEGIN
IF(EXISTS(SELECT * FROM A where Ap = @ID))
BEGIN
RAISERROR('Wrong. Try again.', 11, 1);
RETURN;
END
ELSE IF(EXISTS(SELECT * FROM B where Bp = @ID))
BEGIN
RAISERROR('Wrong in a different way. Try again.', 11, 2);
RETURN;
END
ELSE
BEGIN
select *
from Table_D
END
END
第二个参数(严重性)必须至少设置为 11 才能使错误作为异常传播,否则它只是一条信息性消息。这些也可以被捕获,但这超出了这个答案的范围。第三个参数(状态)可以是任何你喜欢的,并且可以用来传递错误的代码,例如,如果你需要本地化它。用户生成的消息总是有SQL错误代码50000,所以不能用来区分不同的错误,并且解析消息是脆弱的。
处理结果的C#代码:
try {
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
...
}
}
} catch (SqlException e) {
Console.WriteLine(
"Database error executing [Delete] (code {0}): {1}", e.State, e.Message
);
}
这很适合错误,因为实际处理数据的代码保持原样,您可以在正确的位置处理异常(而不是到处传播状态代码)。但是,如果存储过程是 expected 到 return 信息性而非错误的状态,则此方法不合适,因为即使什么都没有,您也会一直捕获异常错了。
输出参数
存储过程可以设置参数值以及接收它们,方法是声明它们 OUTPUT
:
CREATE PROCEDURE [Delete]
@ID nvarchar(64),
@StatusCode INT OUTPUT
AS BEGIN
IF(EXISTS(SELECT * FROM A where Ap = @ID))
BEGIN
SET @StatusCode = 1;
END
ELSE IF(EXISTS(SELECT * FROM B where Bp = @ID))
BEGIN
SET @StatusCode = 2;
END
ELSE
BEGIN
SET @StatusCode = 0;
select *
from Table_D
END
END
在 C# 中,这是在标记为输出参数的参数中捕获的:
SqlParameter statusCodeParameter = command.Parameters.Add(
new SqlParameter {
ParameterName = "@StatusCode",
SqlDbType = SqlDbType.Int,
Direction = ParameterDirection.Output
}
);
using (var reader = command.ExecuteReader()) {
int statusCode = (int) statusCodeParameter.Value;
if (statusCode != 0) {
// show alert
return;
}
while (reader.Read()) {
...
}
}
这里的好处是客户端不能忘记声明参数(必须提供),你不局限于单个INT
,你可以根据参数的值来决定你想用结果集做什么。 Return以这种方式处理结构化数据很麻烦(很多 OUTPUT
参数),但您可以在单个 XML
参数中捕获它。
Return值
每个存储过程都有一个 return 值,它是一个 INT
。如果您没有使用 RETURN
显式设置它,它将保持为 0。
CREATE PROCEDURE [Delete]
@ID nvarchar(64)
AS BEGIN
IF(EXISTS(SELECT * FROM A where Ap = @ID))
BEGIN
RETURN 1
END
ELSE IF(EXISTS(SELECT * FROM B where Bp = @ID))
BEGIN
RETURN 2
END
ELSE
BEGIN
select *
from Table_D
END
END
在 C# 中,return 值必须在标记为 return 值的单个特殊参数中捕获:
SqlParameter returnValueParameter = command.Parameters.Add(
new SqlParameter { Direction = ParameterDirection.ReturnValue }
);
using (var reader = command.ExecuteReader()) {
// this could be empty
while (reader.Read()) {
...
}
}
int returnValue = (int) returnValueParameter.Value;
请务必注意,在您处理完存储过程生成的所有其他结果集(如果有)之前,return 值将不可用,因此如果您将它用于状态指示没有行的代码,在获得状态代码之前,您仍然必须先处理空结果集。你不能 return 除了 INT
以外的任何东西。 Frameworks/OR 映射器通常不支持 return 值。最后,请注意客户端不需要对 return 值执行任何操作,因此您必须仔细记录其预期用途。
结果集
存储过程可以简单地 return 它想要的结果集,就像它 return 其他数据一样。存储过程允许 return 多个结果集,因此即使您的状态在逻辑上与其他数据分开,您也可以 return 它作为一行。
CREATE PROCEDURE [Delete]
@ID nvarchar(64)
AS BEGIN
DECLARE @StatusCode INT = 0;
IF(EXISTS(SELECT * FROM A where Ap = @ID))
BEGIN
SET @StatusCode = 1;
END
ELSE IF(EXISTS(SELECT * FROM B where Bp = @ID))
BEGIN
SET @StatusCode = 2;
END
SELECT @StatusCode AS StatusCode;
IF @StatusCode = 0
BEGIN
select *
from Table_D
END
END
要用 C# 处理这个,我们需要 SqlDataReader.NextResult
:
using (var reader = command.ExecuteReader()) {
if (!reader.Read()) throw new MyException("Expected result from stored procedure.");
statusCode = reader.GetInt32(reader.GetOrdinal("StatusCode"));
if (statusCode != 0) {
// show alert
return;
}
reader.NextResult();
while (reader.Read()) {
// use the actual result set
}
}
这里的主要缺点是存储过程 return 可变数量的结果集并不直观,而且很少有数据 frameworks/OR 映射器支持它,所以你几乎总是最终编写这样的手动代码。 Returning 多个结果集并不真正适合 returning 像状态代码这样的单个数据,但它可能是 returning 结构化数据的替代方法XML
输出参数(特别是如果有很多的话)。