C# 使用@TRANCOUNT有用吗?

C# 使用@TRANCOUNT有用吗?,c#,sql-server,C#,Sql Server,我有一个简单的SP,它可以根据表中是否存在数据来执行插入或更新 CREATE PROCEDURE [dbo].spUpsert -- Parameters to Update / Insert a StudentSet @StudentSetId nvarchar(128), @Status_Id int AS BEGIN BEGIN TRY BEGIN TRANSACTION SET XACT_ABORT ON;

我有一个简单的SP,它可以根据表中是否存在数据来执行插入或更新

CREATE PROCEDURE [dbo].spUpsert 
    -- Parameters to Update / Insert a StudentSet
    @StudentSetId nvarchar(128),
    @Status_Id int

AS
BEGIN
    BEGIN TRY
        BEGIN TRANSACTION
            SET XACT_ABORT ON;
            SET NOCOUNT ON;

            IF EXISTS(SELECT StudentSetId FROM StudentSet WHERE StudentSetId = @StudentSetId)
                BEGIN
                    UPDATE StudentSet SET ModifiedDate = GETDATE(), Status_Id = @Status_Id
                    WHERE StudentSetId = @StudentSetId;
                END
            ELSE
                BEGIN

                    INSERT INTO StudentSet
                                (StudentSetId, Status_Id)
                     VALUES
                           (
                                @StudentSetId,
                                @Status_Id
                           )
                END
        COMMIT TRANSACTION
    END TRY

    BEGIN CATCH
        ROLLBACK TRANSACTION
    END CATCH

END
写了一个这样的方法:

public void Upsert(string studentSetId, int statusId)
{
    this.DatabaseJobs.ExecuteSqlCommand(@"exec spUpsert 
                                     @StudentSetId = {0}, 
                                     @Status_Id = {10} ",
                                        studentSetId,
                                        statusId);
}
这是如何使用的: 学生有一个文件,确切地说是xml,它被发送到一个处理器,作为进程的一部分,处理器调用这个SP。可以上载多个文件,处理器设计用于处理5个文件,生成5个线程

对于一批5个文件,它抛出以下错误:

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

数字5不是一个完美的数字,当上传超过5个文件时可能会出现。比这个小,我还没试过

因此,我搜索并找到了一个解决方案,该解决方案实现了@TRANCOUNT detailed&

@@TRANCOUNT是一个全局变量,文章中建议的用法似乎是会话的局部变量。我的意思是,SQL Server中的任何进程都可以增加@TRANCOUNT,依赖它可能不会产生预期的结果

我的问题是什么是处理这种情况的好方法


提前感谢。

首先,
@@TRANCOUNT
是信息性的-它告诉您当前线程中有多少嵌套事务正在进行中。在您的情况下,调用存储过程时事务已经在进行中,因此事务计数为1

您的问题是
ROLLBACK
回滚所有事务,包括任何嵌套事务。如果您希望中止整个批处理,这正是您想要的,错误只是告诉您已经发生了

但是,如果您只想回滚您在本地创建的事务,那么您必须做一些稍微不同的事情。您必须在一开始就保存事务,然后在出现错误时可以回滚到该点(在完成任何工作之前),然后提交它(没有完成任何工作)


好吧,如果事务是在.NET代码中启动的,那么最好是在相同的代码中回滚。但是,如果不可能,则应选中@TRANCOUNT

然而,您遗漏了一件重要的事情:若事务根本并没有启动怎么办?您的代码的构造方式使您需要事务。若您(或其他人)执行SSMS中的过程,该怎么办

我建议你做以下几点:

  • 在代码存储的开始处@trancount本地(声明@mytrancount)
  • 在开始处理之前,请检查@mytrancount,如果没有事务,请启动一个事务
  • 在最后提交事务,但在提交之前可以再次检查@mytrancount
编辑

当然,正如Ben在回答中所说,您可以保存事务,而不是在代码中开始它。例如,如果存在事务,则保存该事务,以便能够仅从“保存”回滚到“回滚”。如果没有事务,则在您的过程中启动它


Remus Rusanu对此有好处。

您应该在启动try-catch块之前启动事务。
this.DatabaseJobs.ExecuteSqlCommand必须自己启动事务,过程中的
ROLLBACK TRANSACTION
导致此事务回滚。这可能会帮助您:@CeOnSql在结束TRY之前在BEGIN TRY中调用COMMIT TRAN是否正确?难道不会有一个不匹配的方式,我现在有吗?提交和回滚是正确的。但我总是在try块之前启动事务-如果在
begintransaction
发生了什么事情,我不想进入catch块(因为有一个回滚事务->如果begintransaction失败,就没有什么可回滚的)
BEGIN TRAN
DECLARE @savepoint varbinary(16) set @savepoint = newid()
SAVE TRAN @savepoint
BEGIN TRY
    -- Do some stuff here
    select 1/0; -- divide by zero error
    COMMIT TRAN
END TRY
BEGIN CATCH
    ROLLBACK TRAN @savepoint;
    COMMIT TRAN -- important!!!
    --re-raise the error if you want (or recover in some other way)

    RAISERROR('Rethrowing error', ERROR_SEVERITY(), ERROR_STATE()    );
END CATCH