Почему EntityFramework бросает ошибку счетчика транзакций для хранимой процедуры с транзакцией и RAISERROR?

Ответов: 2


1

Вы делаете есть операции в C # код, вы просто не знают о них и явно не создавать их. EF создает собственный TransactionScope для определенных операций.

Ваша обработка ошибок при наличии транзакций в этой хранимой процедуре не будет работать. Вы уже обнаружили одну причину. Вы также столкнетесь с другими проблемами, такими как попытка отката при отсутствии транзакции. Блок CATCH должен проверять XACT_STATE()значение внутри блока и действовать соответствующим образом.

Если вы хотите получить правильный шаблон обработки ошибок при наличии транзакций, см. Обработка исключений и вложенные транзакции :

create procedure [usp_my_procedure_name]
as
begin
    set nocount on;
    declare @trancount int;
    set @trancount = @@trancount;
    begin try
        if @trancount = 0
            begin transaction
        else
            save transaction usp_my_procedure_name;

        -- Do the actual work here

lbexit:
        if @trancount = 0   
            commit;
    end try
    begin catch
        declare @error int, @message varchar(4000), @xstate int;
        select @error = ERROR_NUMBER(), @message = ERROR_MESSAGE(), @xstate = XACT_STATE();
        if @xstate = -1
            rollback;
        if @xstate = 1 and @trancount = 0
            rollback
        if @xstate = 1 and @trancount > 0
            rollback transaction usp_my_procedure_name;

        raiserror ('usp_my_procedure_name: %d: %s', 16, 1, @error, @message) ;
    end catch   
end
go

1

Эта процедура отлично работает, когда я пытаюсь выполнить ее из студии управления

Он работает, потому что вы не находитесь внутри активной транзакции. Давайте эмулировать то же поведение, что и код C #:

Create PROCEDURE [dbo].[SpTest]    (@ReturnMessage varchar(50) output)
AS
    BEGIN
        SET NOCOUNT ON;
        BEGIN TRY
            BEGIN TRANSACTION;
            RAISERROR('asdf',16,1)
            COMMIT TRANSACTION;
        END TRY
        BEGIN CATCH
            ROLLBACK TRANSACTION;
            DECLARE @ErrorMessage VARCHAR(50)= ERROR_MESSAGE();

            IF ( @ErrorMessage = 'asdf' )
                BEGIN
                    SET @ReturnMessage = @ErrorMessage;
                    RETURN;
                END;
            ELSE


            THROW;
        END CATCH;
    END;

BEGIN TRANSACTION
EXEC dbo.[spTest] 'a'
COMMIT;

DBFiddle

Msg 266 Уровень 16 Состояние 2 Строка 0

Счет транзакции после EXECUTE указывает несоответствующее число операторов BEGIN и COMMIT. Предыдущий счетчик = 1, текущий счетчик = 0.

Msg 3902 Уровень 16 Состояние 1 Строка 4

Запрос COMMIT TRANSACTION не имеет соответствующей BEGIN TRANSACTION.


Используя C # / EF, вы открыли транзакцию, и она подсчитывается. Теперь в SQL Server нет такой вещи, как вложенные транзакции. Я настоятельно рекомендую прочитать миф о DBA SQL Server в день: (26/30) вложенные транзакции являются реальными

Откат вложенной транзакции откатывает весь набор транзакций - так как нет такой вещи, как вложенная транзакция.


Вы должны правильно обработать транзакцию внутри хранимой процедуры. Например, используя SAVE TRANSACTION

В следующем примере показано, как использовать точку сохранения транзакции, чтобы откатить только изменения, внесенные хранимой процедурой, если активная транзакция запускается до выполнения хранимой процедуры.

CREATE PROCEDURE SaveTranExample  
    @InputCandidateID INT  
AS  
    -- Detect whether the procedure was called  
    -- from an active transaction and save  
    -- that for later use.  
    -- In the procedure, @TranCounter = 0  
    -- means there was no active transaction  
    -- and the procedure started one.  
    -- @TranCounter > 0 means an active  
    -- transaction was started before the   
    -- procedure was called.  
    DECLARE @TranCounter INT;  
    SET @TranCounter = @@TRANCOUNT;  
    IF @TranCounter > 0  
        -- Procedure called when there is  
        -- an active transaction.  
        -- Create a savepoint to be able  
        -- to roll back only the work done  
        -- in the procedure if there is an  
        -- error.  
        SAVE TRANSACTION ProcedureSave;  
    ELSE  
        -- Procedure must start its own  
        -- transaction.  
        BEGIN TRANSACTION;  
    -- Modify database.  
    BEGIN TRY  
        DELETE HumanResources.JobCandidate  
            WHERE JobCandidateID = @InputCandidateID;  
        -- Get here if no errors; must commit  
        -- any transaction started in the  
        -- procedure, but not commit a transaction  
        -- started before the transaction was called.  
        IF @TranCounter = 0  
            -- @TranCounter = 0 means no transaction was  
            -- started before the procedure was called.  
            -- The procedure must commit the transaction  
            -- it started.  
            COMMIT TRANSACTION;  
    END TRY  
    BEGIN CATCH  
        -- An error occurred; must determine  
        -- which type of rollback will roll  
        -- back only the work done in the  
        -- procedure.  
        IF @TranCounter = 0  
            -- Transaction started in procedure.  
            -- Roll back complete transaction.  
            ROLLBACK TRANSACTION;  
        ELSE  
            -- Transaction started before procedure  
            -- called, do not roll back modifications  
            -- made before the procedure was called.  
            IF XACT_STATE() <> -1  
                -- If the transaction is still valid, just  
                -- roll back to the savepoint set at the  
                -- start of the stored procedure.  
                ROLLBACK TRANSACTION ProcedureSave;  
                -- If the transaction is uncommitable, a  
                -- rollback to the savepoint is not allowed  
                -- because the savepoint rollback writes to  
                -- the log. Just return to the caller, which  
                -- should roll back the outer transaction.  

        -- After the appropriate rollback, echo error  
        -- information to the caller.  
        DECLARE @ErrorMessage NVARCHAR(4000);  
        DECLARE @ErrorSeverity INT;  
        DECLARE @ErrorState INT;  

        SELECT @ErrorMessage = ERROR_MESSAGE();  
        SELECT @ErrorSeverity = ERROR_SEVERITY();  
        SELECT @ErrorState = ERROR_STATE();  

        RAISERROR (@ErrorMessage, -- Message text.  
                   @ErrorSeverity, -- Severity.  
                   @ErrorState -- State.  
                   );  
    END CATCH  
GO 
C #, SQL-сервер, сущность-рамка, TSQL,
Похожие вопросы