【问题标题】:Where should I perform action when implementing IEnlistmentNotification?实施 IEnlistmentNotification 时应该在哪里执行操作?
【发布时间】:2023-03-11 14:39:01
【问题描述】:

我正在尝试通过实现IEnlistmentNotification 接口来创建自定义“资源管理器”。该接口有以下方法:

  • 准备()
  • 提交()
  • 回滚()
  • 不确定()

虽然很明显回滚代码应该放在 Rollback() 方法中,但我不确定应该在哪个方法中实现执行实际操作的代码?它应该放在 Prepare() 或 Commit() 中,还是应该从 TransactionScope 块内部的外部代码调用的类中的其他一些自定义方法?

【问题讨论】:

    标签: c# .net transactions transactionscope


    【解决方案1】:

    这里是一些示例代码,其中包含一些实现和单元测试。 创建一个基类可以让我专注于我需要做的动作,而不是到处处理事务。

    public abstract class TransactionCreator : IEnlistmentNotification
    {
        protected TransactionCreator()
        {
            System.Transactions.Transaction.Current.EnlistVolatile(this, EnlistmentOptions.None);
        }
    
        public void Commit(Enlistment enlistment)
        {
            Complete();
            enlistment.Done();
        }
    
        public void InDoubt(Enlistment enlistment)
        {
            enlistment.Done();
        }
    
        //Don't throw an exception here. Instead call ForceRollback()
        public void Prepare(PreparingEnlistment preparingEnlistment)
        {
            try
            {
                Execute();
                preparingEnlistment.Prepared();
            }
            catch (Exception e)
            {
                preparingEnlistment.ForceRollback(e);
            }
        }
    
        public void Rollback(Enlistment enlistment)
        {
            Revert();
            enlistment.Done();
        }
    
        public abstract void Execute();
        public abstract void Complete();
        public abstract void Revert();
    }
    

    为了测试 IEnlistmentNotification 实现,我们将:

    -测试预期的流程

    - 模拟 Execute 方法以在一个对象中失败并查看在另一个对象中调用的回滚。

    (我正在使用 NSubstitute 进行模拟,但可以忽略它)

    [TestFixture]
    public class TransactionCreatorTest
    {
        [Test]
        public void Test_file_gets_created_on_transaction_complete()
        {
            TransactionCreator creator;
    
            using (var scope = new TransactionScope())
            {
                creator = Substitute.For<TransactionCreator>();
    
                scope.Complete();
            }
            creator.Received().Execute();
            creator.DidNotReceive().Revert();
        }
    
        [Test]
        public void Test_file_gets_does_not_get_created_on_rollback()
        {
            TransactionCreator creator = null;
            try
            {
                using (var scope = new TransactionScope())
                {
                    creator = Substitute.For<TransactionCreator>();
                    var failed = Substitute.For<TransactionCreator>();
                    failed.When(x => x.Execute()).Do(x => { throw new Exception(); });
                    scope.Complete();
                }
            }
            catch (TransactionAbortedException ex)
            {
                Console.Out.WriteLine(ex);
            }
    
    
            creator.Received().Execute();
            creator.Received().Revert();
        }
    }
    

    【讨论】:

    • 失败的事务不会调用 Rollback 或 Revert,因此您必须在调用 ForceRollback 之前在 Prepare 方法中还原该事务。 (我不知道为什么......)
    【解决方案2】:

    实际工作应该以另一种方法进行。 Prepare 和 Commit 用于实现两阶段提交机制。

    模式如下:

    using(var transaction = new TransactionScope())
    {
        var rc1 = new ResourceManager();
        rc1.DoWork();
        var rc2 = new ResourceManager();
        rc2.DoWork();
        transaction.Complete();
    }
    

    在这个例子中,DoWork 应该执行动作。 退出事务范围时,会调用两个资源管理器的 Prepare 方法。 如果他们都调用了enlistment.Prepared();,那么两个管理器的 Commit 方法都会被调用。 该提交永远不会失败!

    例如,在处理文件时, DoWork 应该重命名文件以表明您正在处理它,然后读取并处理该文件。如果任一操作失败,它应该抛出一个异常,导致调用 Rollback。 回滚 应将文件重命名为其原始名称。 Prepare可以重命名该文件以指示它应该被删除,并检查是否允许删除该文件。如果任一操作失败,它应该抛出异常。 Commit 将实际删除该文件。这不会失败,因为我们已经检查了安全性,但即使这样做了,也不应该抛出异常。

    您实际上可以在 Prepare 方法中删除文件并调用 enlistment.Done();。这表明不再需要对 Commit 的调用。但这样做的问题是,在您删除文件后,其他资源管理器可能会在其 Prepare 中抛出异常。因为您表明您已完成,所以不会调用您的回滚方法。而且即使被调用了,你也没有办法恢复你的行为......

    我希望这能解释一些事情......

    【讨论】:

    • 我是否需要将TransactionScope 实例传递给管理人员并让管理人员参与事务或如何注册ResourceManager 以便调用Prepare、Commit 或Rollback?
    • 在我上面的例子中,ResouceManager 本身可以检查Transaction.Current!=null,如果是这种情况,它可以使用Transaction.Current.EnlistDurableTransaction.Current.EnlistVolatile 最合适的方式来登记自己。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-02
    • 2021-07-19
    • 1970-01-01
    • 1970-01-01
    • 2010-12-24
    相关资源
    最近更新 更多