这是我的输出字符串参数的存储过程。
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手动捕获错误并将错误作为输出参数发送。 当我尝试从管理工作室执行它时,该过程正常,但是当我从我的应用程序执行时,实体框架会抛出这个错误 :
SqlException: Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.
但如果我删除了RAISERROR,它可以正常工作。
我正在使用实体框架版本6的C#MVC应用程序。 这是我的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;
现在工作正常。我认为与RAISEERROR
THROW
必须始终由EntityFramework
语句处理
答案 0 :(得分:1)
你做在C#代码中有交易,你只是不知道它们而没有明确地创建它们。 EF正在为某些操作创建自己的TransactionScope。
在此存储过程中存在事务时,您的错误处理将不起作用。你已经发现了一个原因。您还将遇到其他问题,例如在没有事务时尝试回滚。 CATCH块必须检查块内的XACT_STATE()
值并采取相应措施。
如果您想在有交易的情况下使用正确的错误处理模式,请参阅Exception handling and nested transactions:
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 :(得分: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 Level 16 State 2 Line 0
EXECUTE之后的事务计数表示BEGIN和COMMIT语句的数量不匹配。先前的计数= 1,当前计数= 0。
Msg 3902 Level 16 State 1 Line 4
COMMIT TRANSACTION请求没有相应的BEGIN TRANSACTION。
使用C#/ EF,您已经打开了交易并且它很重要。现在在SQL Server中没有嵌套事务这样的东西。我强烈建议您阅读A SQL Server DBA myth a day: (26/30) nested transactions are real
嵌套事务的回滚会回滚整个事务集 - 因为没有嵌套事务。
您应该做的是正确处理存储过程中的事务。例如,使用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