在Transaction中运行C#代码

时间:2014-01-07 08:18:09

标签: c# transactions

我在asp.net的按钮点击中调用了三个方法

  1. 第一种方法是在应用程序中保存文本文件
  2. 第二种方法是创建和保存PdF文件。
  3. 第三种方法是在asp.net发送电子邮件
  4. 我想要的是,如果上述任何方法发生任何错误,那么应该回滚所有被调用的方法。

    这怎么可能。??

3 个答案:

答案 0 :(得分:0)

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

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

最有可能最好的解决方案,但一个简单的解决方案是创建一个包含您已成功编写的文件的List<string>,并在catch中删除该列表中的所有文件。

还有很多其他解决方案,例如TemporaryFile类,用于删除Dispose()方法中的文件。当你遇到尝试的问题时,试一试并再次询问。

答案 1 :(得分:0)

在这样简单的程序中,您不需要简单的事务处理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的不同之处在于,其他一些代码可以回滚事务而不是引发异常。

答案 2 :(得分:0)

以下是使用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");
}

以下是实施细节:

<强> SendEmail

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

<强> PDFFileSave

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

<强> AbstractFileSave

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实施的一件值得一提的事情是:如果资源在ForceRollback()方法中调用/投票Prepare(),则该资源的Rollback()方法不会被触发。因此,Rollback()中应该进行的任何清理可能需要在Prepare() 中手动调用。