我正在编写一个使用ADO .NET连接到Azure SQL数据库的.NET 7应用程序。
某些代码路径要求打开一个事务,并将多个命令(对存储过程的调用)作为同一事务的一部分发送到数据库。
存储过程大多是简单的原子CRUD操作,但如果应用实体及其底层存储不同(例如,为了性能或抽象),它们也可能涉及多个语句。
我目前的需要是让存储过程向应用层返回一些值,以表示某些“部分出错但可恢复”的状态(例如,对不存在的行执行UPDATE)。
在这些情况下,我还可能希望存储过程将事务回滚到更早的保存点,但避免回滚整个事务。
应用程序将使用自定义异常(意外的、可恢复的错误)来管理这些状态,所以我的第一个方法是在存储过程中也使用THROW
,因为异常将很好地Map到ExecuteNonQuery()
,并且在Map到ExecuteReader()
时也要小心。
问题是,在我的存储过程中,我默认使用XACT_ABORT ON
和TRY...CATCH
,因此如果我从TRY
块内部抛出一个自定义错误,事务将在批处理结束时回滚。
我考虑过使用RAISERROR
而不是THROW
(根据Microsoft文档,这不应该荣誉XACT_ABORT
),但是,在Azure SQL数据库上,我不能使用sp_addmessage
,所以我只能返回错误50000
;此外,微软建议将THROW
用于新的开发。
此外,我不想设置XACT_ABORT OFF
,因为如果抛出非自定义异常,我希望回滚整个事务,因为这实际上是一个意外和不可恢复的情况。
剩下的选项只有一个自定义的OUTPUT
参数(例如@State
)或RETURN
值,但这需要我确保输出/返回值始终Map到应用程序代码中,而不是默认抛出。
这些考虑是正确的吗?有没有我没有考虑的其他选择?
我还为这种情况编写了这个草稿存储过程模板,使用返回值。有什么需要注意的吗?
create or alter procedure [App].[SampleProcedure]
as
declare
@savepointName char(32),
@shouldCommit bit;
begin
set xact_abort, nocount on;
set transaction isolation level read committed;
set @savepointName = replace(newid(), '-', '');
if @@trancount > 0
begin
set @shouldCommit = 0;
save transaction @savepointName;
end;
else
begin
set @shoudlcommit = 1;
begin transaction @savepointName;
end;
begin try
if /* some condition */ 1 = 0
begin
rollback transaction @savepointName;
return -1; /* Precondition failed */
end;
-- update set ... where ...;
if /* some condition */ 1 = 0
begin
rollback transaction @savepointName;
return -2; /* Condition failed */
end;
-- insert into ... values ...;
if isnull(@shouldCommit, 0) = 1
begin
commit transaction @savepointName;
return 0; /* Everything is fine */
end;
end try
begin catch
if @@trancount > 0
begin
rollback transaction;
end;
throw;
end catch
end;
1条答案
按热度按时间muk1a3rh1#
过了一段时间,我得出了这些结论:
XACT_ABORT ON
的最佳实践就是证明。EnableOptimizedParameterBinding
。