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