简体   繁体   English

在交易中运行C#代码

[英]Run C# code in Transaction

I called three methods on button click in asp.net 我在asp.net中点击按钮时调用了三种方法

  1. The First Method is to save a text file on the application 第一种方法是在应用程序上保存文本文件
  2. The Second Method is to create and save PdF file. 第二种方法是创建并保存PdF文件。
  3. The Third Method is to send email in asp.net 第三种方法是在asp.net中发送电子邮件

I want that , If any of the above method has any error occured, then all the methods that are prevsouly called should be rollbacked. 我想要,如果上述任何方法发生任何错误,那么应该回滚所有预先调用的方法。

How this is possible.?? 这怎么可能。?

Whether or not the operation succeeds, you should always be cleaning up files you create. 无论操作是否成功,您都应该始终清理创建的文件。 If you can bypass the file system, and use a MemoryStream to store the data and include it in the email, that would of course both solve your problem and be alot faster. 如果您可以绕过文件系统,并使用MemoryStream来存储数据并将其包含在电子邮件中,那么这当然可以解决您的问题,并且可以更快地完成。

As mentioned by others, there is no magic method to automatically rollback whatever you created since you clicked that button - you'll have to think of a solution yourself. 正如其他人所提到的那样,自单击按钮以来,没有一种神奇的方法可以自动回滚您创建的任何内容-您必须自己考虑解决方案。

Most likely not the best solution, but a simple one, is to create a List<string> containing the files you have successfully written, and in the catch you simply delete all files from that list. 可能不是最好的解决方案,而是一个简单的解决方案,是创建一个包含成功写入文件的List<string> ,然后在catch仅删除该列表中的所有文件。

There are tons of other solutions, like a TemporaryFile class that deletes files in its Dispose() method. 还有很多其他解决方案,例如TemporaryFile类,该类删除其Dispose()方法中的文件。 Give it a go and ask again when you run into issues with your attempt. 尝试一下,遇到问题时再试一下。

In such simpler procedure, you do not need transaction as simple Try/Catch/Finally should do the job. 在这样简单的过程中,您不需要事务,因为简单的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{}
}

Here is detailed Transaction Implementation. 这是详细的交易实施。

This is little long process, but here is a simple implementation (single threaded approach with no locking etc). 这是一个很少的过程,但是这里是一个简单的实现(没有锁定的单线程方法等)。 Remember this is simplest form of transaction with no double locking, no multi version concurrency. 请记住,这是最简单的事务形式,没有双重锁定,没有多版本并发。

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

The reason this is different from Try Catch is that some other code can rollback transaction instead of raising exception. 这与Try Catch不同的原因是其他一些代码可以回滚事务,而不引发异常。

Here's another take for achieving what the OP wanted using IEnlistmentNotification . 这是使用IEnlistmentNotification实现OP所需功能的另一种方法。 But instead of writing all the operation (save text, save pdf, and send email) in one implementation class, this one use separate IEnlistmentNotification implementation and support for rollback in case of email sending operation failed. 但是, IEnlistmentNotification将所有操作(保存文本,保存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");
}

Here's the implementation details: 以下是实施细节:

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 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 PDF文件保存

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

One thing worth mentioning about IEnlistmentNotification implementation is: if a resource called/ voted a ForceRollback() within the Prepare() method, the Rollback() method for that resource will not be triggered. 值得一提的有关IEnlistmentNotification实现的一件事是: 如果在Prepare()方法中调用/投票的资源为ForceRollback() ,则不会触发该资源的Rollback()方法。 So any cleanup that should have happen in Rollback() may need to be manually called in Prepare() . 因此,应该在Prepare()手动调用Rollback()中应该发生的任何清理操作

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM