在事务中运行C#代码

在事务中运行C#代码,c#,transactions,C#,Transactions,我在asp.net中调用了三种方法 第一种方法是在应用程序上保存文本文件 第二种方法是创建和保存PdF文件 第三种方法是在asp.net中发送电子邮件 我希望,如果上面的任何一个方法发生了任何错误,那么之前调用的所有方法都应该回滚 这是如何实现的。?无论操作是否成功,您都应该始终清理您创建的文件。如果您可以绕过文件系统,使用MemoryStream存储数据并将其包含在电子邮件中,那么这当然可以解决您的问题,而且速度更快 正如其他人所提到的,没有神奇的方法可以自动回滚您单击该按钮后创建的任何内容-

我在asp.net中调用了三种方法

  • 第一种方法是在应用程序上保存文本文件
  • 第二种方法是创建和保存PdF文件
  • 第三种方法是在asp.net中发送电子邮件
  • 我希望,如果上面的任何一个方法发生了任何错误,那么之前调用的所有方法都应该回滚


    这是如何实现的。?

    无论操作是否成功,您都应该始终清理您创建的文件。如果您可以绕过文件系统,使用
    MemoryStream
    存储数据并将其包含在电子邮件中,那么这当然可以解决您的问题,而且速度更快

    正如其他人所提到的,没有神奇的方法可以自动回滚您单击该按钮后创建的任何内容-您必须自己思考解决方案

    最有可能的不是最佳解决方案,而是一个简单的解决方案,即创建一个包含您已成功编写的文件的
    列表
    ,然后在
    catch
    中删除该列表中的所有文件


    还有很多其他的解决方案,比如
    TemporaryFile
    类,它在
    Dispose()
    方法中删除文件。当您的尝试遇到问题时,请尝试并再次询问。

    在这样简单的过程中,您不需要像简单的Try/Catch/Finally那样进行事务处理

    FileInfo localFile;
    FileInfo pdfFile;
    
    try{
        SaveTextFile(localFile);
        SavePDFFile(pdfFile);
    
        SendEmail();
    }catch{
       // something went wrong...
       // you can remove extra try catch
       // but you might get security related
       // exceptions
       try{
          if(localFile.Exists) localFile.Delete();
          if(pdfFile.Exists) pdfFile.Delete();
       }catch{}
    }
    
    下面是详细的事务实现

    这是一个很长的过程,但这里有一个简单的实现(没有锁定的单线程方法等)。记住,这是最简单的事务形式,没有双重锁定,没有多版本并发

    using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
    {
    
        FileInfo localFile = new FileInfo("localFile.txt");
        FileInfo pdfFile = new FileInfo("localFile.pdf");
    
        SimpleTransaction.EnlistTransaction(
    
            // prepare
            () =>
            {
                CreateTextFile(localFile);
                CreatePDFFile(pdfFile);
    
                // prepare mail should throw an error
                // if something is missing as sending email
                // is network operation, it cannot be rolled back
                // so email should be sent in commit
                PrepareMail();
            },
    
            // commit
            () =>
            {
                SendEmail();
            },
    
            // rollback
            () =>
            {
                try
                {
                    if (localFile.Exists)
                        localFile.Delete();
                    if (pdfFile.Exists)
                        pdfFile.Delete();
                }
                catch { }
            },
    
            // in doubt...
            () => { }
        );
    
    }
    
    public class SimpleTransaction : IEnlistmentNotification
    {
    
        public static void EnlistTransaction(Action prepare, Action commit, Action rollback, Action inDoubt)
        {
    
            var st = new SimpleTransaction(prepare, commit, rollback, inDoubt);
            Transaction.Current.EnlistVolatile(st, EnlistmentOptions.None);
    
        }
    
    
        Action CommitAction;
        Action PrepareAction;
        Action RollbackAction;
        Action InDoubtAction;
    
        private SimpleTransaction(Action prepare, Action commit, Action rollback, Action inDoubt)
        {
            this.CommitAction = commit;
            this.PrepareAction = prepare;
            this.RollbackAction = rollback;
            this.InDoubtAction = inDoubt  ?? (Action)(() => {});
        }
    
        public void Prepare(PreparingEnlistment preparingEnlistment)
        {
            try
            {
                PrepareAction();
                preparingEnlistment.Prepared();
            }
            catch
            {
                preparingEnlistment.ForceRollback();
            }
    
        }
    
        public void Commit(Enlistment enlistment)
        {
            CommitAction();
            enlistment.Done();
        }
    
        public void Rollback(Enlistment enlistment)
        {
            RollbackAction();
            enlistment.Done();
        }
    
        public void InDoubt(Enlistment enlistment)
        {
            InDoubtAction();
            enlistment.Done();
        }
    }
    

    这与Try-Catch不同的原因是,其他一些代码可以回滚事务,而不是引发异常。

    下面是使用
    IEnlistmentNotification实现OP所需的另一种方法。
    但是,与在一个实现类中编写所有操作(保存文本、保存pdf和发送电子邮件)不同,该类使用单独的
    IEnlistmentNotification
    实现,并在电子邮件发送操作失败时支持回滚

    var textPath = "somefile.txt";
    var pdfPath = "somefile.pdf";
    
    try {
      using (var scope = new TransactionScope()) {
        var textFileSave = new TextFileSave(textPath);
        var pdfFileSave = new PDFFileSave(pdfPath);
    
        Transaction.Current.TransactionCompleted += (sender, eventArgs) => {
          try {
            var sendEmail = new SendEmail();
            sendEmail.Send();
          }
          catch (Exception ex) {
            // Console.WriteLine(ex);
            textFileSave.CleanUp();
            pdfFileSave.CleanUp();
          }
        };
    
        Transaction.Current.EnlistVolatile(textFileSave, EnlistmentOptions.None);
        Transaction.Current.EnlistVolatile(pdfFileSave, EnlistmentOptions.None);
    
        scope.Complete();
      }
    }
    catch (Exception ex) {
      // Console.WriteLine(ex);
    }
    catch {
      // Console.WriteLine("Cannot complete transaction");
    }
    
    以下是实施细节:

    发送电子邮件

    public class SendEmail {
      public void Send() {
        // uncomment to simulate error in sending email
        // throw new Exception();
    
        // write email sending operation here
        // Console.WriteLine("Email Sent");
      }
    }
    
    TextFileSave

    public class TextFileSave : AbstractFileSave {
       public TextFileSave(string filePath) : base(filePath) { }
    
       protected override bool OnSaveFile(string filePath) {        
         // write save text file operation here
         File.WriteAllText(filePath, "Some TXT contents");                
    
         return File.Exists(filePath);
       }
    }
    
    public class PDFFileSave : AbstractFileSave {
      public PDFFileSave(string filePath) : base(filePath) {}
    
      protected override bool OnSaveFile(string filePath) {
        // for simulating a long running process
        // Thread.Sleep(5000);
    
        // write save pdf file operation here
        File.WriteAllText(filePath, "Some PDF contents");
    
        // try returning false instead to simulate an error in saving file
        // return false;
        return File.Exists(filePath);
      }
    }
    
    public abstract class AbstractFileSave : IEnlistmentNotification {
      protected AbstractFileSave(string filePath) {
        FilePath = filePath;
      }
    
      public string FilePath { get; private set; }
    
      public void Prepare(PreparingEnlistment preparingEnlistment) {
        try {
          var success = OnSaveFile(FilePath);
          if (success) {
            // Console.WriteLine("[Prepared] {0}", FilePath);
            preparingEnlistment.Prepared();
          }
          else {
            throw new Exception("Error saving file");
          }
        }
        catch (Exception ex) {
          // we vote to rollback, so clean-up must be done manually here
          OnDeleteFile(FilePath);
          preparingEnlistment.ForceRollback(ex);
        }
      }
    
      public void Commit(Enlistment enlistment) {
        // Console.WriteLine("[Commit] {0}", FilePath);
        enlistment.Done();
      }
    
      public void Rollback(Enlistment enlistment) {
        // Console.WriteLine("[Rollback] {0}", FilePath);
        OnDeleteFile(FilePath);
        enlistment.Done();
      }
    
      public void InDoubt(Enlistment enlistment) {
        // in doubt operation here
        enlistment.Done();
      }
    
      // for manual clean up
      public void CleanUp() {
        // Console.WriteLine("[Manual CleanUp] {0}", FilePath);
        OnDeleteFile(FilePath);
      }
    
      protected abstract bool OnSaveFile(string filePath);
    
      protected virtual void OnDeleteFile(string filePath) {
        if (File.Exists(FilePath)) {
          File.Delete(FilePath);
        }
      }
    }
    
    PDFFileSave

    public class TextFileSave : AbstractFileSave {
       public TextFileSave(string filePath) : base(filePath) { }
    
       protected override bool OnSaveFile(string filePath) {        
         // write save text file operation here
         File.WriteAllText(filePath, "Some TXT contents");                
    
         return File.Exists(filePath);
       }
    }
    
    public class PDFFileSave : AbstractFileSave {
      public PDFFileSave(string filePath) : base(filePath) {}
    
      protected override bool OnSaveFile(string filePath) {
        // for simulating a long running process
        // Thread.Sleep(5000);
    
        // write save pdf file operation here
        File.WriteAllText(filePath, "Some PDF contents");
    
        // try returning false instead to simulate an error in saving file
        // return false;
        return File.Exists(filePath);
      }
    }
    
    public abstract class AbstractFileSave : IEnlistmentNotification {
      protected AbstractFileSave(string filePath) {
        FilePath = filePath;
      }
    
      public string FilePath { get; private set; }
    
      public void Prepare(PreparingEnlistment preparingEnlistment) {
        try {
          var success = OnSaveFile(FilePath);
          if (success) {
            // Console.WriteLine("[Prepared] {0}", FilePath);
            preparingEnlistment.Prepared();
          }
          else {
            throw new Exception("Error saving file");
          }
        }
        catch (Exception ex) {
          // we vote to rollback, so clean-up must be done manually here
          OnDeleteFile(FilePath);
          preparingEnlistment.ForceRollback(ex);
        }
      }
    
      public void Commit(Enlistment enlistment) {
        // Console.WriteLine("[Commit] {0}", FilePath);
        enlistment.Done();
      }
    
      public void Rollback(Enlistment enlistment) {
        // Console.WriteLine("[Rollback] {0}", FilePath);
        OnDeleteFile(FilePath);
        enlistment.Done();
      }
    
      public void InDoubt(Enlistment enlistment) {
        // in doubt operation here
        enlistment.Done();
      }
    
      // for manual clean up
      public void CleanUp() {
        // Console.WriteLine("[Manual CleanUp] {0}", FilePath);
        OnDeleteFile(FilePath);
      }
    
      protected abstract bool OnSaveFile(string filePath);
    
      protected virtual void OnDeleteFile(string filePath) {
        if (File.Exists(FilePath)) {
          File.Delete(FilePath);
        }
      }
    }
    
    抽象文件保存

    public class TextFileSave : AbstractFileSave {
       public TextFileSave(string filePath) : base(filePath) { }
    
       protected override bool OnSaveFile(string filePath) {        
         // write save text file operation here
         File.WriteAllText(filePath, "Some TXT contents");                
    
         return File.Exists(filePath);
       }
    }
    
    public class PDFFileSave : AbstractFileSave {
      public PDFFileSave(string filePath) : base(filePath) {}
    
      protected override bool OnSaveFile(string filePath) {
        // for simulating a long running process
        // Thread.Sleep(5000);
    
        // write save pdf file operation here
        File.WriteAllText(filePath, "Some PDF contents");
    
        // try returning false instead to simulate an error in saving file
        // return false;
        return File.Exists(filePath);
      }
    }
    
    public abstract class AbstractFileSave : IEnlistmentNotification {
      protected AbstractFileSave(string filePath) {
        FilePath = filePath;
      }
    
      public string FilePath { get; private set; }
    
      public void Prepare(PreparingEnlistment preparingEnlistment) {
        try {
          var success = OnSaveFile(FilePath);
          if (success) {
            // Console.WriteLine("[Prepared] {0}", FilePath);
            preparingEnlistment.Prepared();
          }
          else {
            throw new Exception("Error saving file");
          }
        }
        catch (Exception ex) {
          // we vote to rollback, so clean-up must be done manually here
          OnDeleteFile(FilePath);
          preparingEnlistment.ForceRollback(ex);
        }
      }
    
      public void Commit(Enlistment enlistment) {
        // Console.WriteLine("[Commit] {0}", FilePath);
        enlistment.Done();
      }
    
      public void Rollback(Enlistment enlistment) {
        // Console.WriteLine("[Rollback] {0}", FilePath);
        OnDeleteFile(FilePath);
        enlistment.Done();
      }
    
      public void InDoubt(Enlistment enlistment) {
        // in doubt operation here
        enlistment.Done();
      }
    
      // for manual clean up
      public void CleanUp() {
        // Console.WriteLine("[Manual CleanUp] {0}", FilePath);
        OnDeleteFile(FilePath);
      }
    
      protected abstract bool OnSaveFile(string filePath);
    
      protected virtual void OnDeleteFile(string filePath) {
        if (File.Exists(FilePath)) {
          File.Delete(FilePath);
        }
      }
    }
    

    关于
    IEnlistmentNotification
    实现,值得一提的是:如果资源在
    Prepare()
    方法中调用/投票了
    ForceRollback()
    ,则不会触发该资源的
    Rollback()
    方法。因此,在
    Rollback()
    中应该进行的任何清理都可能需要在
    Prepare()

    @Maarten中手动调用。为什么不搜索google&post结果作为答案,您将获得一些投票。C#没有任何“STM”(软件事务内存)支持。一般来说,.NET中的“事务”只适用于支持这类事务的源,例如数据库。如果需要“回滚”副作用/作用,则必须手动对其进行说明,例如在
    捕获
    或使用
    保护的处置中。上述场景中的某些操作(例如创建和保存PDF)可能是单独的原子操作(例如创建文件),但不在.NET环境本身的范围内(必须使用反向操作回滚或在设计中考虑其他操作)。因此,这意味着,我们无法回滚我在第一个方法中创建的文件,如果第二个方法将进入catch??实现
    IEnlistmentNotification
    真的值得吗?而不是用简单的try/catch/finally来完成这项工作?在处理DB时,使用
    TransactionScope
    是有意义的,因为它知道如何为您执行回滚,否则您只是过度复杂了代码,没有任何实际好处?@Darius如果您知道所调用代码的内部工作细节,当然,使用
    try
    /
    catch
    /
    最终
    回滚一切都会起作用,但如果您调用的代码对调用方来说是一个黑盒,则不会起作用(因为OP的示例提到了不同的方法,我假设这里就是这种情况)
    IEnlistmentNotification
    为其调用者提供了某种程度的保证,即在回滚发生时,一切都会恢复到以前的状态。使用
    IEnlistmentNotification
    发生回滚时应该做什么是调用的代码的责任,使用
    try
    /
    catch
    它是我们的。。。