第七節: EF的三種事務的應用場景和各自注意的問題(SaveChanges、DBContextTransaction、TransactionScope)


一. 什么是事務

   我們通俗的理解事務就是一系列操作要么全部成功、要么全部失敗(不可能存在部分成功,部分失敗的情況)。

   舉一個事務在我們日常生活中的經典例子:兩張銀行卡(甲、乙),甲向乙轉錢,整個過程需要執行兩個操作,甲賬戶錢減少,乙賬戶錢增加,這是轉賬成功的情況;轉賬失敗時候,二者均不執行,甲乙賬戶錢都不變。

1. SQLServer中的事務

  SQLServer中的事務具體封裝方式有多種,但無論是哪種,都需要依賴於這三句代碼:begin transaction 、commit transaction、rollback transaction。 

   具體怎么使用,詳見數據庫事務章節:去復習

2. EF中的事務

   EF中的事務主要分為三類,分別是SaveChanges、DbContextTransaction、TransactionScope。

   需要掌握的是這三種事務各自的使用方式和適用場景。

二. SaveChanges

   SaveChanges是EF中最常見的事務了,在前面章節,多次提到,SaveChanges一次性將本地緩存中所有的狀態變化一次性提交到數據庫,這就是一個事務,要么統一成功,要么統一回滾。

   應用場景:該種事務模式在實際開發中非常常用,在處理模塊業務同時需要對一張表(或多張表)進行一系列增刪改操作,但這些操作都是要么都成功,要么都失敗,這種情況下可以在流程的最后 調用SaveChanges,一次性保存本地屬性狀態的全部變化。

  該模式作用於一個DBContext,即一個數據庫的EF的上下文,不能控制多個數據庫。

    比如:注冊場景,可能需要向用戶表、日志表、賬戶表等多張表中插入數據,其中一張表插入失敗,所有的都需要回滾,這種情況,就可以在該業務的最后,統一調用SaveChanges一次性的事務提交。

1. 事務成功

 1      private static void TestSaveChanges()
 2         {
 3             using (DbContext db = new CodeFirstModel())
 4             {
 5                 //增加
 6                 TestInfor t1 = new TestInfor()
 7                 {
 8                     id = Guid.NewGuid().ToString("N"),
 9                     txt1 = "txt1",
10                     txt2 = "txt2"
11                 };
12                 db.Set<TestInfor>().Add(t1);
13                 //刪除
14                 TestInfor t2 = db.Set<TestInfor>().Where(u => u.id == "1").FirstOrDefault();
15                 if (t2 != null)
16                 {
17                     db.Set<TestInfor>().Remove(t2);
18                 }
19                 //修改
20                 TestInfor t3 = db.Set<TestInfor>().Where(u => u.id == "3").FirstOrDefault();
21                 t3.txt2 = "我是李馬茹23";
22 
23                 //SaveChanges事務提交
24                 int n = db.SaveChanges();
25                 Console.WriteLine("數據作用條數:" + n);
26             }
27         }    

2. 事務回滾

 1       private static void TestSaveChangesFailure()
 2         {
 3             using (DbContext db = new CodeFirstModel())
 4             {
 5                 //增加
 6                 TestInfor t1 = new TestInfor()
 7                 {
 8                     id = Guid.NewGuid().ToString("N"),
 9                     txt1 = "txt1",
10                     txt2 = "txt2"
11                 };
12                 db.Set<TestInfor>().Add(t1);
13                 //刪除
14                 TestInfor t2 = db.Set<TestInfor>().Where(u => u.id == "1").FirstOrDefault();
15                 if (t2 != null)
16                 {
17                     db.Set<TestInfor>().Remove(t2);
18                 }
19                 //修改
20                 TestInfor t3 = db.Set<TestInfor>().Where(u => u.id == "3").FirstOrDefault();
21                 t3.txt2 = "我是李馬茹23";
22                 //增加(制造錯誤)
23                 TestInfor t4 = new TestInfor()
24                 {
25                     id = Guid.NewGuid().ToString("N") + "123",
26                     txt1 = "txt1",
27                     txt2 = "txt2"
28                 };
29                 db.Set<TestInfor>().Add(t4);
30 
31                 //SaveChanges事務提交
32                 int n = db.SaveChanges();
33                 Console.WriteLine("數據作用條數:" + n);
34             }
35         }

三. DbContextTransaction事務

  該事務為EF6新增的事務,通常用於手動接管事務,某些操作是一個事務,某些操作是另外一個事務。

  使用場景:EF調用SQL語句的時候使用該事務、 多個SaveChanges的情況(解決自增ID另一個業務中使用的場景)。

  核心代碼:BeginTransaction、Commit、Rollback、Dispose. 如果放到using塊中,就不需要手動Dispose了。

  該種事務與數據庫中的transaction事務原理一致,在EF中,由於每次調用 db.Database.ExecuteSqlCommand(sql1, pars1)的時候,即刻執行了該SQL語句,所以要把他放到一個大的事務中,整體提交、回滾.(EF6新增的)

1. 事務成功

 1      private static void TestDbContextTransaction()
 2         {
 3             using (DbContext db = new CodeFirstModel())
 4             {
 5                 DbContextTransaction trans = null;
 6                 try
 7                 {
 8                     //開啟事務
 9                     trans = db.Database.BeginTransaction();
10                     //增加
11                     string sql1 = @"insert into TestInfor  values(@id,@txt1,@txt2)";
12                     SqlParameter[] pars1 ={
13                                                  new SqlParameter("@id",Guid.NewGuid().ToString("N")),
14                                                  new SqlParameter("@txt1","txt11"),
15                                                  new SqlParameter("@txt2","txt22")
16                                             };
17                     db.Database.ExecuteSqlCommand(sql1, pars1);
18                     //刪除
19                     string sql2 = @"delete from TestInfor where id=@id";
20                     SqlParameter[] pars2 ={
21                                                  new SqlParameter("@id","22")
22                                             };
23                     db.Database.ExecuteSqlCommand(sql2, pars2);
24                     //修改
25                     string sql3 = @"update TestInfor set txt1=@txt1 where id=@id";
26                     SqlParameter[] pars3 ={
27                                                  new SqlParameter("@id","3"),
28                                                  new SqlParameter("@txt1","二狗子")
29                                             };
30                     db.Database.ExecuteSqlCommand(sql3, pars3);
31 
32                     //提交事務
33                     trans.Commit();
34                     Console.WriteLine("事務成功了");
35                 }
36                 catch (Exception ex)
37                 {
38                     Console.WriteLine(ex.Message);
39                     trans.Rollback();
40 
41                 }
42                 finally
43                 {
44                     trans.Dispose();     //也可以把該事務寫到using塊中,讓其自己托管,就不需要手動釋放了
45                 }
46             }
47         }

2. 事務回滾

 1      private static void TestDbContextTransactionFailure()
 2         {
 3             using (DbContext db = new CodeFirstModel())
 4             {
 5                 DbContextTransaction trans = null;
 6                 try
 7                 {
 8                     //開啟事務
 9                     trans = db.Database.BeginTransaction();
10                     //增加
11                     string sql1 = @"insert into TestInfor  values(@id,@txt1,@txt2)";
12                     SqlParameter[] pars1 ={
13                                                  new SqlParameter("@id",Guid.NewGuid().ToString("N")),
14                                                  new SqlParameter("@txt1","txt11"),
15                                                  new SqlParameter("@txt2","txt22")
16                                             };
17                     db.Database.ExecuteSqlCommand(sql1, pars1);
18                     //刪除
19                     string sql2 = @"delete from TestInfor where id=@id";
20                     SqlParameter[] pars2 ={
21                                                  new SqlParameter("@id","22")
22                                             };
23                     db.Database.ExecuteSqlCommand(sql2, pars2);
24                     //修改
25                     string sql3 = @"update TestInfor set txt1=@txt1 where id=@id";
26                     SqlParameter[] pars3 ={
27                                                  new SqlParameter("@id","3"),
28                                                  new SqlParameter("@txt1","二狗子222")
29                                             };
30                     db.Database.ExecuteSqlCommand(sql3, pars3);
31 
32 
33                     //增加(制造錯誤)
34                     string sql4 = @"insert into TestInfor  values(@id,@txt1,@txt2)";
35                     SqlParameter[] pars4 ={
36                                                  new SqlParameter("@id",Guid.NewGuid().ToString("N")+"123"),
37                                                  new SqlParameter("@txt1","txt11"),
38                                                  new SqlParameter("@txt2","txt22")
39                                             };
40                     db.Database.ExecuteSqlCommand(sql4, pars4);
41 
42                     //提交事務
43                     trans.Commit();
44                     Console.WriteLine("事務成功了");
45                 }
46                 catch (Exception ex)
47                 {
48                     Console.WriteLine(ex.Message);
49                     trans.Rollback();
50 
51                 }
52                 finally
53                 {
54                     trans.Dispose();
55                 }
56             }
57         }

3. 自DbContextTransaction事務測試多個SaveChanges的情況統一回滾

 1   private static void DbContextTransSaveChanges()
 2         {
 3             using (DbContext db = new CodeFirstModel())
 4             {
 5                 //自動脫管,不需要手動釋放
 6                 using (DbContextTransaction trans = db.Database.BeginTransaction())
 7                 {
 8                     try
 9                     {
10                         TestInfor t1 = new TestInfor()
11                         {
12                             id = Guid.NewGuid().ToString("N"),
13                             txt1 = "111111111",
14                             txt2 = "222222222222"
15                         };
16                         db.Entry(t1).State = EntityState.Added;
17                         db.SaveChanges();
18 
19                         //人為制造失敗情況
20                         TestInfor t2 = new TestInfor()
21                         {
22                             id = Guid.NewGuid().ToString("N") + "123",
23                             txt1 = "111111111",
24                             txt2 = "222222222222"
25                         };
26                         db.Entry(t2).State = EntityState.Added;
27                         db.SaveChanges();
28 
29                         trans.Commit();
30                     }
31                     catch (Exception)
32                     {
33                         trans.Rollback();
34                     }
35 
36                 }
37             }
38         }

四. TransactionScope事務

1. 該事務用來處理多個SaveChanges的事務(特殊情況的業務)或者多個DBContext(每個DBContext是一個實例,代表不同的數據庫連接).

2. 核心代碼:(一個Complete函數走天下,異常的話,自動回滾 ,也可以結合try-catch Transaction.Current.Rollback();實現回滾)

         需要引入程序集:using System.Transactions;

3. 適用場景:

  ①該種事務適用於多數據庫連接的情況

  特別注意:如果使用該事務來處理多個數據庫(多個DBContex)時,必須手動開啟msdtc服務,這樣才可以將多個DB的SaveChange給放到一個事務中,如果失敗, 則多個數據庫的數據統一回滾.

     開啟msdtc服務的步驟: cmd命令→net start msdtc

    ②主鍵id自增的情況,同一個業務線中,需要拿到新增加的數據的主鍵id,進行操作。

      ③多線程帶鎖的情況,同一條業務線前半部分必須先SaveChanges,才能保證數據准確性(測試簡單版本,實際的業務場景待補充!!!)

4. 主鍵自增被使用的情況的解決方案

 1     private static void Test3()
 2         {
 3             using (DbContext db = new CodeFirstModel())
 4             {
 5                 using (TransactionScope trans = new TransactionScope())
 6                 {
 7                     try
 8                     {
 9                         TestInfor2 t1 = new TestInfor2()
10                         {
11                             txt11 = Guid.NewGuid().ToString("N"),
12                             txt22 = Guid.NewGuid().ToString("N")
13                         };
14                         db.Set<TestInfor2>().Add(t1);
15                         //如果這里不寫SaveChanges的話,t1.id永遠為0
16                         db.SaveChanges();
17 
18 
19                         TestInfor2 t2 = new TestInfor2()
20                         {
21                             txt11 = (t1.id + 1).ToString(),
22                             txt22 = Guid.NewGuid().ToString("N")
23                         };
24                         db.Entry(t2).State = EntityState.Added;
25                         db.SaveChanges();
26 
27                         //事務統一提交(若失敗則統一回滾)
28                         trans.Complete();
29                         Console.WriteLine("自增提交成功");
30                     }
31                     catch (Exception)
32                     {
33                         Transaction.Current.Rollback();
34                     }
35                 }    
36             }
37         }

5. 多個DBContext、多個數據庫的情況。

 1  private static void TransactionScopeTwoDB()
 2         {
 3             using (TransactionScope trans = new TransactionScope())
 4             {
 5                 try
 6                 {
 7                     DbContext db1 = new CodeFirstModel();
 8                     DbContext db2 = new CodeFirstModel2();
 9                     //數據庫1
10                     TestInfor t1 = new TestInfor()
11                     {
12                         id = Guid.NewGuid().ToString("N"),
13                         txt1 = "111111111",
14                         txt2 = "222222222222"
15                     };
16                     db1.Entry(t1).State = EntityState.Added;
17                     db1.SaveChanges();
18 
19                     //數據庫2
20                     TestOne t2 = new TestOne()
21                     {
22                         id = Guid.NewGuid().ToString("N"),    //可以在此處手動制造個錯誤,來測試多數據庫的回滾問題
23                         t1 = "222",
24                         t2 = "2222"
25                     };
26                     db2.Entry(t2).State = EntityState.Added;
27                     db2.SaveChanges();
28 
29                     trans.Complete();
30                     Console.WriteLine("多個數據庫添加成功");
31                 }
32                 catch (Exception)
33                 {
34                     Transaction.Current.Rollback();
35                 }
36 
37             }
38         }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM