SQL Server 从存储过程向.NET发出可恢复异常信号而不回滚事务的方法

yeotifhr  于 2022-12-22  发布在  .NET
关注(0)|答案(1)|浏览(113)

我正在编写一个使用ADO .NET连接到Azure SQL数据库的.NET 7应用程序。
某些代码路径要求打开一个事务,并将多个命令(对存储过程的调用)作为同一事务的一部分发送到数据库。
存储过程大多是简单的原子CRUD操作,但如果应用实体及其底层存储不同(例如,为了性能或抽象),它们也可能涉及多个语句。
我目前的需要是让存储过程向应用层返回一些值,以表示某些“部分出错但可恢复”的状态(例如,对不存在的行执行UPDATE)。
在这些情况下,我还可能希望存储过程将事务回滚到更早的保存点,但避免回滚整个事务。
应用程序将使用自定义异常(意外的、可恢复的错误)来管理这些状态,所以我的第一个方法是在存储过程中也使用THROW,因为异常将很好地Map到ExecuteNonQuery(),并且在Map到ExecuteReader()时也要小心。
问题是,在我的存储过程中,我默认使用XACT_ABORT ONTRY...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;
muk1a3rh

muk1a3rh1#

过了一段时间,我得出了这些结论:

  • “可恢复的错误”并不是例外,这样处理它们当然很方便,但事实上,你可以检测到它们并从中恢复,这意味着它们只是你所想到的罕见错误情况。
  • 除了C#异常之外,T-SQL异常还适用于事务需要回滚的不可恢复的异常情况。在过程内部设置XACT_ABORT ON的最佳实践就是证明。
  • 根据前面的两点,返回值(或输出参数)是正确的解决方案。我选择返回值是因为它足以满足我的用例,并允许我保持启用EnableOptimizedParameterBinding

相关问题