.NET嵌套事务范围(Transactionscope)

4
我们认为我们理解了Transactionscope和嵌套(Transactionscope.Requires)-即
------------------------------------------------------- 内部 | 外部 | ------------------------------------------------------- 提交 | 回滚 | 不会提交任何更改 提交 | 提交 | 所有更改都将被提交 回滚 | 回滚 | 不会提交任何更改 回滚 | 提交 | ----无法正常工作----
然而,我想知道是否有一种创建Transactionscope、嵌套Transactionscope、依赖事务、自定义事务或其他方式的方法,其中回滚提交场景也可以工作?
例如,在库函数中有一个具有其自己事务的东西,出于某种原因,它位于父事务之下。如果内部成功,则外部事务可以访问任何更改,但如果内部回滚,则外部事务仍处于完全可用状态,并且不受内部事务的影响,就好像从未调用过一样?

1
在我看来,你在这里要求的内容应该得到直接支持,但显然并没有。 - 500 - Internal Server Error
我同意。我们已经解决了这个问题,但现在更多的是一种智力上的好奇心——也就是说,是否真的有可能让它像那样工作?我想到了使用继承范围的方法,并覆盖dispose方法,使其什么也不做,只是记住它,但接下来的代码会将数据库视为如果您提交了事务——或者以某种方式神奇地捕获它,并在此之后创建另一个事务范围——但然后你失去了之前发生的一切。这是一个非常有趣的问题! - Darren Oakey
1
我相信你把数据库事务(有助于强制执行数据一致性和原子更改)与其他东西混淆了,比如工作单元。从数据一致性的角度来看:如果外部依赖于内部(即内部嵌套在外部),您永远不会希望在内部回滚后继续进行。如果不是这种情况,外部不依赖于内部。在这种情况下,嵌套事务是您任务的错误工具。通过重构,您通常可以以更好的方式获得所需的结果:使用顺序事务(而不是嵌套事务),预先检查(如果内部将通过)等。 - Arkaine55
2个回答

5
不,我认为您的想法是错误的。所有操作仍然只是单个事务,只是嵌套允许感兴趣的代码块投票是否应该成功执行该事务(而无需传递事务对象),并不是创建嵌套事务。这就是为什么Transactionscope上的方法被称为Complete而不是Commit的原因。
编辑以回答OP的评论
要获得您想要的结果,我认为您需要创建两个TS对象,第二个对象使用RequiresNew,然后根据需要完成/回滚每个对象。我不知道第一个事务是否会看到第二个事务的更改,您需要自己进行实验并查看TS在此处是否有帮助。
我理解您尝试做什么,我不是说您尝试这样做是错误的;如果您的用例需要这样做,那么就需要这样做。
但是,我不认为TS是针对这种用例设计的,我认为文档中提到的嵌套事务是不幸的,因为它并不像通常讨论的嵌套事务那样(例如在TSQL中)。
TS旨在处理更常见的用例,其中组件A和B都执行事务性工作,A将B作为其工作的一部分使用,但B也可以独立使用。 TS允许B始终是事务性的,无论是独立使用还是作为A工作的一部分,并且可以启动事务或重用A的事务(因为A是一个UoW),而无需传递事务对象。

a) 我理论上明白,但我只想知道它是否可行 - 适当的嵌套事务 - b) 只因为某个组件失败,并不意味着我总是希望整个系统都失败, c) 主要问题在于它使事务处于不稳定状态,导致其后的所有操作都失败。我们认为某些事情“不应该”被做,并不是没有理由去尝试弄清楚如何做到它。 - Darren Oakey
@DarrenOakey,我已经添加了我的答案,希望能澄清事情。 - Andy
将内部TransactionScope设置为RequriesNew是实现目标的方法。但是根据文章http://msdn.microsoft.com/en-us/library/ms973865.aspx,“在使用TransactionScopeOption.RequiresNew值时应非常小心,并验证两个事务(环境事务和为您的范围创建的事务)是否会在其中一个中止并且另一个提交时引入不一致性。” - Aaron Hawkins
1
@AaronHawkins 我认为仅使用RequiresNew无法自动实现OP想要的功能;一旦内部事务提交,它就成为了一个独立的事务。如果外部事务失败,将无法回滚该事务,这就是TSQL嵌套事务的情况。 - Andy

1

不是作为事务范围。

如果你的所有事务都是针对数据库资源管理器(这是大多数托管TransactionScope的用途),那么你可以利用数据库的能力。数据库支持事务保存点。实际实现因数据库而异,让我们以SQL Server为例。

你可以直接在T-SQL中利用事务保存点,例如参见异常处理和嵌套事务

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

这个过程模板允许在异常情况下进行优雅的恢复,允许内部工作回滚,而外部工作继续并提交。
您可以在托管代码中执行相同的操作,使用 SqlTransaction.Save()SqlTransaction.Rollback Method (String)
然而,System.Transactions API 不支持这些操作。这并不奇怪,考虑到 System.Transactions 的主要作用之一是管理分布式事务(多个 RMs),但数据库事务保存点与分布式事务不兼容。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接