EntityFramework transaction Sample Code
第一種寫法,只需一個db.SaveChanges()即可。
NorthwindEntities db = new NorthwindEntities();
try
{
Product p1 = db.Products.FirstOrDefault(m => m.ProductID == 1);
p1.UnitPrice -= 1;//修改資料
//假裝這裡意外發生
throw new Exception("Crash");
Product p2 = db.Products.FirstOrDefault(m => m.ProductID == 2);
p2.UnitPrice += 1;//修改資料
db.SaveChanges(); //都成功才SaveChanges()
}catch(Exception ex)
{
}
第二種寫法,已呼叫過db.SaveChanges()儲存DB,想Rollback復原資料
須事先加入System.Transactions的參考
NorthwindEntities db = new NorthwindEntities();
//想要交易的範圍
using (System.Transactions.TransactionScope scope=new System.Transactions.TransactionScope())
{
try
{
Product p1 = db.Products.FirstOrDefault(m => m.ProductID == 1);
p1.UnitPrice -= 1;//修改資料
db.SaveChanges();//提交變更
//假裝意外發生,但之前已執行 db.SaveChanges();
throw new Exception("Crash");
Product p2 = db.Products.FirstOrDefault(m => m.ProductID == 2);
p2.UnitPrice += 1;//修改資料
db.SaveChanges();
scope.Complete();//都成功,才提交此筆交易
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}//end using
上述TransactionScope建構式預設值↓ (最嚴格等級,只要其中一筆交易失敗,就整個失敗,其它連線要讀取異動資料,都必須等待此交易完成)
NorthwindEntities db = new NorthwindEntities();
//想要交易的範圍
using(TransactionScope scope=new TransactionScope( TransactionScopeOption.Required,
new TransactionOptions() { IsolationLevel=IsolationLevel.Serializable}))
{
try
{
Product p1 = db.Products.FirstOrDefault(m => m.ProductID == 1);
p1.UnitPrice -= 1;//修改資料
db.SaveChanges();//提交變更
//假裝意外發生,但之前已執行 db.SaveChanges();
throw new Exception("Crash");
Product p2 = db.Products.FirstOrDefault(m => m.ProductID == 2);
p2.UnitPrice += 1;//修改資料
db.SaveChanges();
scope.Complete();//都成功
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}//end using
※第二種寫法也支援在TransactionScope的using區塊裡透過ADO.net異動資料庫
要留意scope.Complete();之後的同一using區塊裡,不可以再異動&查詢DB資料,否則會出現錯誤:“The current TransactionScope is already complete”
//想要交易的範圍
using (System.Transactions.TransactionScope scope=new System.Transactions.TransactionScope())
{
try
{
Product p1 = db.Products.FirstOrDefault(m => m.ProductID == 1);
p1.UnitPrice -= 1;//修改資料
db.SaveChanges();//提交變更
scope.Complete();//都成功
//↓在scope.Complete()之後異動DB資料會報錯
Product p2 = db.Products.FirstOrDefault(m => m.ProductID == 2);
p2.UnitPrice += 1;//修改資料
db.SaveChanges();
//↑另一解套方法:把此段異動DB資料動作移至scope範圍(using)區域外面 或 全部DB資料異動完才呼叫.Complete();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}//end using
2023-10-06追記:
TransactionScope 除了ADO.net有支援、Dapper套件也支援;但不支援檔案IO、寄信的Rollback。
留意在.Complete();之前的DB查詢資料雖然可以查DB異動後的資料,但假如最終沒呼叫.Complete();提交交易的話,之前的異動在交易結束時都會被Rollback。
以Dapper套件的SQL範例如下:
namespace ConsoleApp_TransTest
{
/// <summary>
/// DB資料表
/// </summary>
public class MyTable
{
/// <summary>
/// DB資料行
/// </summary>
public string sys_id { get; set; } = "";//PK
public int mySeq { get; set; } = 0;
}
public class Program
{
/// <summary>
/// DB連線字串
/// </summary>
static string connStr = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString;
static void Main(string[] args)
{
//DB連線物件
var conn = new SqlConnection(connStr);
//DB Transaction未提交前的讀取
using (TransactionScope trans=new TransactionScope())
{
try
{
//先異動資料
conn.Execute(@"Update MyTable set mySeq=1111 Where sys_id='myKey' ");conn.Close();
var obj1 = conn.QueryFirstOrDefault<MyTable>(@"Select top 1 mySeq from MyTable Where sys_id='myKey' "); conn.Close();
Console.WriteLine(obj1.mySeq);//※值為「1111」
throw new Exception("模擬出錯,不執行下述的.Complete()");
trans.Complete();//提交此筆交易
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);//顯示錯誤訊息
}
}//end using 結束交易&結束鎖定
var obj = conn.QueryFirstOrDefault<MyTable>(@"Select top 1 mySeq from MyTable Where sys_id='myKey' "); conn.Close();
Console.WriteLine("Transaction結束後的查詢:" + obj.mySeq);//值會被rollback復原原狀
Console.ReadKey();
}//end Main
}
}