C# 为什么EntityFramework为带有Transaction和RAISERROR的StoredProcess引发事务计数错误?

C# 为什么EntityFramework为带有Transaction和RAISERROR的StoredProcess引发事务计数错误?,c#,sql-server,entity-framework,tsql,C#,Sql Server,Entity Framework,Tsql,这是带有输出字符串参数的存储过程 Create PROCEDURE [dbo].[SpTest] (@ReturnMessage varchar(50) output) AS BEGIN SET NOCOUNT ON; BEGIN TRY BEGIN TRANSACTION; RAISERROR('asdf',16,1) COMMIT TRANSACTION;

这是带有输出字符串参数的存储过程

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;
我试图用Raiserror手动捕获错误,并将错误作为输出参数发送。 当我尝试从ManagementStudio执行该过程时,该过程运行良好,但当我从应用程序执行该过程时,实体框架会抛出此错误 :

但是如果我删除了这个错误,它就可以正常工作了

我正在使用实体框架版本6的C#MVC应用程序。 这是我的C#代码:

更新:我在删除此代码时发现错误,事务计数错误已自动解决

IF ( @ErrorMessage = 'asdf' )
                BEGIN
                    SET @ReturnMessage = @ErrorMessage;
                    RETURN;
                END;
现在工作正常了。我认为当与
EntityFramework
一起使用时,
RAISEERROR
必须始终由
THROW
语句来处理
。EF正在为某些操作创建自己的TransactionScope

在此存储过程中存在事务时的错误处理将不起作用。你已经发现了一个原因。您还将遇到其他问题,如在没有事务时尝试回滚。捕捉块必须检查块内的值并相应地采取行动

如果希望在存在事务时使用正确的错误处理模式,请参阅:

当我尝试从ManagementStudio执行该过程时,该过程运行良好

它之所以有效,是因为您不在活动事务中。让我们模拟与C代码相同的行为:

Msg 266 16级状态2第0行

执行后的事务计数表示BEGIN和COMMIT语句的数量不匹配。上一次计数=1,当前计数=0。

Msg 3902 16级状态1第4行

提交事务请求没有相应的开始事务


使用C#/EF,您已经打开了事务,并且它很重要。现在在SQLServer中没有嵌套事务。我强烈推荐阅读

嵌套事务的回滚回滚整个事务集–因为没有嵌套事务。


您应该做的是正确处理存储过程中的事务。例如使用

下面的示例演示了如果在执行存储过程之前启动了活动事务,如何使用事务保存点仅回滚存储过程所做的修改

创建过程SaveTranExample
@InputCandidateID INT
作为
--检测是否调用了该过程
--从活动事务中删除并保存
--供以后使用。
--在该过程中,@TranCounter=0
--表示没有活动事务
--程序从一开始。
--@TranCounter>0表示活动
--事务在启动之前启动
--程序被调用。
声明@TranCounter INT;
设置@TranCounter=@@TRANCOUNT;
如果@TranCounter>0
--存在时调用的过程
--活跃的交易。
--创建一个保存点,以便
--仅回滚已完成的工作
--在程序中,如果存在
--错误。
保存事务过程保存;
其他的
--程序必须自己启动
--交易。
开始交易;
--修改数据库。
开始尝试
删除HumanResources.JobCandidate
其中JobCandidateID=@InputCandidateID;
--如果没有错误就到这里来;必须承诺
--在中启动的任何事务
--过程,但不提交事务
--在调用事务之前启动。
如果@TranCounter=0
--@TranCounter=0表示未处理任何事务
--在调用过程之前启动。
--过程必须提交事务
--它开始了。
提交事务;
结束尝试
开始捕捉
--发生错误;必须确定
--将回滚哪种类型的回滚
--仅支持在中完成的工作
--程序。
如果@TranCounter=0
--事务已在过程中启动。
--回滚完整事务。
回滚事务;
其他的
--事务在过程之前启动
--调用,不回滚修改
--在调用过程之前创建。
如果XACT_STATE()-1
--如果事务仍然有效,只需
--回滚到在上设置的保存点
--启动存储过程。
回滚事务过程保存;
--如果事务是不可模仿的,则
--不允许回滚到保存点
--因为保存点回滚写入
--日志。只要回到打电话的人那里就可以了
--应该回滚外部事务。
--在适当的回滚之后,返回错误
--向来电者提供信息。
声明@ErrorMessage NVARCHAR(4000);
声明@ErrorSeverity INT;
声明@ErrorState INT;
选择@ErrorMessage=ERROR_MESSAGE();
选择@ErrorSeverity=ERROR_SEVERITY();
选择@ErrorState=ERROR_STATE();
RAISERROR(@ErrorMessage,--消息文本)。
@错误严重性,--严重性。
@错误状态——状态。
);  
端接
去

您可能有C代码的交易记录<代码>回滚事务
将回滚所有事务。和
嵌套事务的回滚回滚
public ReturnMessageModel Test()
        {
            ReturnMessageModel result = new ReturnMessageModel();
            ObjectParameter returnMessage = new ObjectParameter("ReturnMessage", typeof(String));
            using (InsurestEntities db = new InsurestEntities())
            {
           db.SpTest(returnMessage);
            }
                result.ReturnMessage = returnMessage.Value.ToString();
            }
            return result;
        }
IF ( @ErrorMessage = 'asdf' )
                BEGIN
                    SET @ReturnMessage = @ErrorMessage;
                    RETURN;
                END;
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
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;
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