考慮以下DataHelper靜態型別,有AddWorkItem及AddDataGroup兩個方法透過Entity Framework新増資料:

    public class DataHelper
    {
        //使用統一的靜態函式建立DbContext物件,避免自行建構
        public static BBDPEntities CreateDbContext()
        {
            //正式應用時,設定檔之連線字串應加密
            //在此進行讀取設定並解密以建構DbContext,細節待日後介紹
            return new BBDPEntities();
        }
        public static void AddWorkItem(Guid wuid, string subject)
        {
            using (var ctx = CreateDbContext())
            {
                var wi = new WorkItem()
                {
                    Wuid = wuid,
                    Subject = subject
                };
                ctx.WorkItem.Add(wi);
                ctx.SaveChanges();
            }
        }
        public static void AddDataGroup(string costCenter, string jobType)
        {
            using (var ctx = CreateDbContext())
            {
                var dg = new DataGroup()
                {
                    CostCenter = costCenter,
                    JobType = jobType
                };
                ctx.DataGroup.Add(dg);
                ctx.SaveChanges();
            }
        }
    }

在兩個方法裡各自產生DbContext物件(實務上多使用統一的靜態方法,不要自行用new建構,以集中連線字串解密以及其他初始化邏輯),建立Entities物件,使用Add()加入,呼叫SaveChanges()存入。如果我們想將AddWorkItem()及AddDataGroup()兩個新増動作包成交易(Transaction)要怎麼做?

跟LINQ to SQL一樣,要將EF的多個資料庫操作包成交易也有三種選擇:

    1. Implicit Transaction:
      使用同一個DbContext進行新增/更新/刪除等動作,SaveChanges()時EF會自動將資料庫異動包成交易,這是最單純最有效率的做法。
    2. Explicit Local Transaction:
      使用DbContext.Database.BeginTransaction()取得DbContextTransaction物件,自行呼叫Commit()、Rollback()控制交易成立或回滾。這個做法還有一個好處-透過SqlQuery()及ExecuteSqlCommand()直接執行的指令也能納入交易範圍。
    3. Explicit Distributable Transaction:
      使用TransactionScope將資料庫動作包起來,範圍內的資料庫操作都會參與交易。Transaction的彈性最大,甚至能支援異種資料庫的分散式交易,但要注意包在TransactionScope中的資料庫動作會啟用LTM或OleTx,成本較為昂貴(LTM較輕巧,但有些限制,如果涉及一台以上DB或對遠端SQL開了兩條不同連線,就一定要用貴森森的OleTx),必要時要將不必參與Transaction的部分隔離開以增進效能。(延伸閱讀: .NET分散式交易程式開發FAQ )

TransactionScope做法最無腦最好寫,將資料異動方法當成黑盒子用TransactionScope包起來就搞定。

        public static void Test()
        {
            using (var tx = new TransactionScope())
            {
                DataHelper.AddWorkItem(Guid.NewGuid(), "SUBJECT");
                DataHelper.AddDataGroup("12345", "A");
            }
        }

TransactoinScope固然方便,但不管走LTM或OleTx,效能都不如在同一條資料庫連線進行的Implicit Transaction。實務上我偏好的做法是由Test()建立DbContext,讓AddWorkItem與AddDataGroup共用一個DbContext,AddWorkItem及AddDataGroup新増資料後不執行SaveChanges(),待全部動作完成再由Test()呼叫SaveChanges()。

如此,AddWorkItem跟AddDataGroup需增加一個參數接收外部傳入的DbContext。為了方便使用,我常用一個小技巧讓AddWorkItem跟AddDataGroup變聰明,遇到外部傳入DbContext就共用,外部沒有傳入時自己生一個。如以下範例:

       public static void Test()
        {
            using (var ctx = DataHelper.CreateDbContext())
            {
                DataHelper.AddWorkItem(Guid.NewGuid(), "SUBJECT", ctx);
                DataHelper.AddDataGroup("12345", "A", ctx);
            }
        }
    //...略...
 
    public class DataHelper
    {
        //使用統一的靜態函式建立DbContext物件,避免自行建構
        public static BBDPEntities CreateDbContext()
        {
            //正式應用時,設定檔之連線字串應加密
            //在此進行讀取設定並解密以建構DbContext,細節日後介紹
            return new BBDPEntities();
        }
        public static void AddWorkItem(Guid wuid, string subject, 
            BBDPEntities exCtx = null)
        {
            var ctx = exCtx ?? CreateDbContext())
            
            ctx.Database.BeginTransaction();
            var wi = new WorkItem()
            {
                Wuid = wuid,
                Subject = subject
            };
            ctx.WorkItem.Add(wi);
            if (exCtx == null)
            {
                ctx.SaveChanges();
                ctx.Dispose(); 
            }
        }
        public static void AddDataGroup(string costCenter, string jobType, 
            BBDPEntities exCtx = null)
        {
            var ctx = exCtx ?? CreateDbContext())
            
            var dg = new DataGroup()
            {
                CostCenter = costCenter,
                JobType = jobType
            };
            ctx.DataGroup.Add(dg);
            if (exCtx == null) 
            {
                ctx.SaveChanges();
                ctx.Dispose();
            }
        }
    }

以上是我處理EF交易常用的小技巧,分享大家參考。


Comments

Be the first to post a comment

Post a comment