[C#.NET][Entity Framework] 實作 DAL 共用方法的交易

上個月,同事問我 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

Image result for microsoft+mvp+logo