我在asp.net的按钮点击中调用了三个方法
我想要的是,如果上述任何方法发生任何错误,那么应该回滚所有被调用的方法。
这怎么可能。??
答案 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()
中手动调用。