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