我试图通过实现IEnlistmentNotification接口来创建自定义的“资源管理器”。这个接口有以下方法:执行IEnlistmentNotification时应该在哪里执行操作?
- 准备()
- 提交()
- 回滚()
- 的不确定()
虽然很明显,回滚代码应在回滚去( )方法我不知道应该在哪种方法中实现执行实际操作的代码?是否应该在Prepare()或Commit()中进行,或者在类中使用一些其他自定义方法,这些自定义方法将从TransactionScope块内部的外部代码中调用?
我试图通过实现IEnlistmentNotification接口来创建自定义的“资源管理器”。这个接口有以下方法:执行IEnlistmentNotification时应该在哪里执行操作?
虽然很明显,回滚代码应在回滚去( )方法我不知道应该在哪种方法中实现执行实际操作的代码?是否应该在Prepare()或Commit()中进行,或者在类中使用一些其他自定义方法,这些自定义方法将从TransactionScope块内部的外部代码中调用?
实际工作应该用另一种方法进行。 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的应该重命名文件,表示要处理它,然后读取和处理文件。如果任一操作失败,它应该抛出一个异常导致回调被调用。 回滚应该将文件重命名为其原始名称。 准备可能会重命名该文件以表明它应该被删除并检查是否允许删除该文件。如果任一操作失败,则应该抛出异常。 提交然后会实际删除该文件。这不会失败,因为我们已经检查过安全性,但即使这样做,也不应该抛出异常。
你实际上可以删除Prepare方法中的文件,并调用enlistment.Done();
。这将表明不再需要对Commit的调用。但是问题在于,在删除文件后,其他资源管理器可能会在Prepare中引发异常。因为你表示你已经完成了,所以你的回滚方法将不会被调用。并且即使是所谓的,就没有办法让你恢复你的行动...
我希望这解释了事情有点...
这里是有一些实施和单位一些示例代码试验。 创建基类将使我专注于我需要做的操作,而不是处理任何地方的事务。
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实现我们:
- 测试预期流动
-Mock 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-method中恢复该事务。 (我不知道为什么...) –
我需要的'TransactionScope'实例传递给管理者,并争取在交易经理或者我怎么注册'ResourceManager'如此Prepare,Commit或Rollback被调用? –
在我上面的例子中,ResouceManager本身可以检查'Transaction.Current!= null',如果是这种情况,它可以使用'Transaction.Current.EnlistDurable'或'Transaction.Current.EnlistVolatile'登记自己,无论最合适的。 –