在实施IEnlistmentNotification时我应该在哪里执行操作?

时间:2014-01-13 15:00:56

标签: c# .net transactions transactionscope

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

  • 准备()
  • 提交()
  • 回滚()
  • INDOUBT()

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

2 个答案:

答案 0 :(得分:8)

实际工作应该用另一种方法进行。准备和提交是为了实现两阶段提交机制。

模式如下:

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方法中删除该文件并致电enlistment.Done();。这表明不再需要对Commit的调用。但问题是,在删除文件后,另一个资源管理器可能会在其Prepare中抛出异常。因为您表示您已完成,所以不会调用您的回滚方法。即使被召唤,你也无法恢复行动......

我希望这能解释一下......

答案 1 :(得分:2)

以下是一些具有一些实现和单元测试的示例代码。 创建基类将让我专注于我需要做的操作,而不是处理各地的事务。

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();
    }
}