如何确保系统级操作是原子的?任何模式?

时间:2014-01-22 13:58:58

标签: c# design-patterns

我有一个方法,它按顺序在内部执行不同的子操作,并且在我想要回滚整个操作的任何子操作失败时。

我的问题是子操作不是所有的数据库操作。这些主要是系统级更改,例如在Windows注册表中添加内容,在指定路径创建文件夹以及设置权限等。子操作可能不止于此。

想要做这样的事情;

CreateUser(){

     CreateUserFtpAccount();

     CreateUserFolder();

     SetUserPermission();

     CreateVirtualDirectoryForUser();


     ....
     ....
     ....
     and many more

}

如果上次操作失败,我想回滚以前的所有操作。

那么,这样做的标准方法是什么?是否有设计模式可以处理这个?

注意:我正在使用C#.net

4 个答案:

答案 0 :(得分:4)

这是一种方法:

使用command pattern,您可以创建可撤消的操作。每次操作都会注册相关命令,以便在发生故障时撤消执行的命令。

例如,这可能都属于类似事务的上下文对象,它实现IDisposable并放入using块。可撤消的操作将注册到此上下文对象。在处置时,如果未提交,则对所有已注册的命令执行“撤消”。希望能帮助到你。缺点是你可能需要将一些方法转换为类。这可能是一个必要的邪恶。

代码示例:

using(var txn = new MyTransaction()) {
  txn.RegisterCommand(new CreateUserFtpAccountCommand());
  txn.RegisterCommand(new CreateUserFolderCommand());
  txn.RegisterCommand(new SetUserPermissionCommand());
  txn.RegisterCommand(new CreateVirtualDirectoryForUserCommand());
  txn.Commit();
}

class MyTransaction : IDisposable {
  public void RegisterCommand(Command command){ /**/ }
  public void Commit(){ /* Runs all registered commands */ }
  public void Dispose(){ /* Executes undo for all registered commands */ }
}

class UndoableCommand {
  public Command(Action action) { /**/ }
  public void Execute() { /**/ }
  public void Undo{ /**/ }
}

<强>更新

你提到你有数百个这样的可逆操作。在这种情况下,您可以采用更实用的方法并彻底摆脱UndoableCommand。您可以改为注册代表,如下所示:

using(var txn = new MyTransaction()) {
  txn.Register(() => ftpManager.CreateUserAccount(user),
               () => ftpManager.DeleteUserAccount(user));
  txn.Register(() => ftpManager.CreateUserFolder(user, folder),
               () => ftpManager.DeleteUserFolder(user, folder));
  /* ... */
  txn.Commit();
}

class MyTransaction : IDisposable {
  public void Register(Action operation, Action undoOperation){ /**/ }
  public void Commit(){ /* Runs all registered operations */ }
  public void Dispose(){ /* Executes undo for all registered and attempted operations */ }
}

请注意,使用此方法时,您需要谨慎使用closures

答案 1 :(得分:3)

我认为最好的办法是封装流程每一步的执行和撤销。与嵌套的try-catch块相比,它将更容易读取代码。类似的东西:

public interface IReversableStep
{
    void DoWork();
    void ReverseWork();
}

public void DoEverything()
{
    var steps = new List<IReversableStep>()
    {
         new CreateUserFTPAccount(),
         new CreateUserFolder(),
         ...
    }
    var completed = new List<IReversableStep>();
    try 
    {
         foreach (var step in steps)
         {
              step.DoWork();
              completed.Add(step);
         }
    }
    catch (Exception)
    {
         //if it is necessary to undo the most recent actions first, 
         //just reverse the list:
         completed.Reverse(); 
         completed.ForEach(x => x.ReverseWork());
    }
}

答案 2 :(得分:1)

NTFS和注册管理机构都支持KTM和MS DTC交易(以及TransactionScope)的注册。但是,由于复杂性,事务文件系统已被弃用,并且可能不会出现在某些未来版本的Windows中。

如果不是所有事情都适合交易,我当然会关注这个问题的其他答案中提供的命令历史模式。

答案 3 :(得分:0)

我不知道这类事情的任何标准模式,但我可能会自己使用嵌套的try / catch块 - 使用适当的代码来回滚catch块中的非数据库操作。使用TransactionScope确保所有数据库操作都是事务性的。

例如:

using (TransactionScope scope)
{
  try
  {
     DoOperationOne();

     try
     {
       DoOperationTwo();

       DoDataBaseOperationOne(); // no need for try/catch surrounding as using transactionscope

       try
       {
         DoOperationThree();
       }
       catch
       {
         RollBackOperationThree();
         throw;
       }
     }
     catch
     {
       RollBackOperationTwo();
       throw;
     } 
  }
  catch
  {
    RollbackOperationOne();
    throw;
  }

  scope.Complete(); // the last thing that happens, and only if there are no errors!
}