上個月,同事問我 DAL 裡的 CUD 方法若需要共用,交易要怎麼寫?
首先,要思考處理資料庫的 Data Access Layer 裡的 CUD方法,該不該共用?
- 以使用者案例的角度切入看 DAL,它不應該有機會共用,因為每一個作業流程的資料異動方式不會一樣。
- 設計 DAL 方法時,不應該用 Table 的 CRUD 作為 DAO 的 Method,相信我,那只會讓事情變得更複雜而已。
- 共用 DAL 方法,就得把檢查機制放在 DAL 方法。
我的 DAL 是依作業流程設計,比如請假作業,就會對應到請假作業的 DAL,該 DAL 就會提供請假作業的查詢、新增、更新、刪除等功能
架構如下圖:
萬一,你檢視使用者案例後,發現真的要共用方法,那該怎麼辦?
開始之前:
- 在 DAL 裡每一個方法裡的 DbConnection 應該是獨立的私有等級,用完就立即放掉,如果你的 DbConnection 是屬於全域變數等級,就要用解構子關閉物件;若是屬於靜態全域等級,我只能為你念佛超渡。
- 以下的範例,是使用 ADO.NET Entity Framework 的 DbContext,這跟 ADO.NET 的 DbConnection 是一樣的意思
先來看看不好的寫法:
在同一個資料庫連線裡,使用TransactionScope是最無腦,也是最耗資源的,處理不好就會有一大堆的 lock 等著你去處理
public int FLowA()
{
var result = -1;
using (var db = new FlowDbContext())//←處理資料用完就關掉
{
//.....TODO:處理資料
result = db.SaveChanges();//←儲存資料,db.SaveChanges 具有交易的功能
}
return result;
}
FlowB 跟 A一樣
當需要合併兩個 Flow 最無腦的做法就是用 TransactionScope 包起來
public int CombineFlowAB()
{
var result = -1;
using (var scope = new TransactionScope())//←不好的寫法,不要學
{
var resultA = FLowA();
var resultB = FLowB();
result = resultA + resultB;
scope.Complete();
}
return result;
}
改善方法:
首先,把連線物件抽出來變成參數,讓 isDispose 決定是否要調用 DbContext.Dispose 方法
public int FLowA(FlowDbContext db = null)//← 首先,把連線物件抽出來變成參數,我要由外面決定交易、關閉
{
var result = -1;
var isDispose = false;
if (db == null)//← 若外面沒有傳東西進來,我就自己建立連線,並開啟 isDispose 旗標
{
db = new FlowDbContext();
isDispose = true;
}
try
{
//.....TODO:處理資料
result = db.SaveChanges();//儲存資料
return result;
}
finally
{
if (isDispose)//← 自己挖的洞自己跳
{
db.Dispose();
}
}
}
由外部方法調用 DbContext.Database.BeginTransaction
public int CombineFlowAB()
{
var result = -1;
using (var db = new FlowDbContext())
using (var trans = db.Database.BeginTransaction())←宣告交易物件,此時SaveChanges就不具有交易機制
{
try
{
var resultA = FLowA(db);
var resultB = FLowB(db);
result = resultA + resultB;
trans.Commit();
return result;
}
catch (Exception)
{
trans.Rollback();
throw;
}
}
}
這樣一來 FlowA、B 能擁有自己的交易權杖,也能適時地將交易權杖釋放出去
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET