分享我們項目中基於EF事務機制的架構


寫在前面:

1. 本文中單元測試用到的數據庫,在執行測試之前,會被清空,即使用空數據庫。

2. 本文中的單元測試都是正確通過的。

要理解EF的事務機制,首先要理解這2個類:TransactionScope和DbContext。

DbContext是我們的數據庫,通常我們會建一個類MyProjectDbContext繼承自DbContext,里面包含所有的數據庫表。這個類相當於定義了一個完整的數據庫。

下面通過一些單元測試來看看這2個類是如何工作的。

 1 [Test]
 2 public void Can_Rollback_On_Errors_In_Different_Context()
 3 {
 4     var user1 = Mock.Users.Random();
 5     var user2 = Mock.Users.Random();
 6     user2.FirstName = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
 7     var userCount = 0;
 8     try
 9     {
10         using (var scope = new TransactionScope())
11         {
12             using (var db = new MyProjectDbContext())
13             {
14                 db.Users.Add(user1);
15                 db.SaveChanges();
16                 userCount = db.Users.Count();
17             }
18             using (var db = new MyProjectDbContext())
19             {
20                 db.Users.Add(user2);
21                 db.SaveChanges();//will throw exception
22             }
23             scope.Complete();
24         }
25     }
26     catch(Exception)
27     {
28                 
29     }
30     Assert.AreEqual(1, userCount);
31     using (var db = new MyProjectDbContext())
32     {
33         Assert.AreEqual(0, db.Users.Count());
34     }
35 }

注意第一個assert,userCount是等於1的,也就是說第一個db.SaveChanges()是順利執行了的。但是看看第二個assert,數據庫里面卻沒有user記錄。這就是使用TransactionScope得到的真正的事務機制。

再看一個測試:

 1 [Test]
 2 public void Cannot_Rollback_Without_Scope()
 3 {
 4     var user1 = Mock.Users.Random();
 5     var user2 = Mock.Users.Random();
 6     user2.FirstName = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
 7     var userCount = 0;
 8     try
 9     {
10         using (var db = new MyProjectDbContext())
11         {
12             db.Users.Add(user1);
13             db.SaveChanges();
14             userCount = db.Users.Count();
15         }
16         using (var db = new MyProjectDbContext())
17         {
18             db.Users.Add(user2);
19             db.SaveChanges();//will throw exception
20         }
21     }
22     catch (Exception)
23     {
24 
25     }
26     Assert.AreEqual(1, userCount);
27     using (var db = new MyProjectDbContext())
28     {
29         Assert.AreEqual(1, db.Users.Count());
30     }
31 }

這個測試跟上面的測試差不多,唯一的區別就是沒有使用TransactionScope把兩個DbContext包起來。於是每個DbContext成為獨立的事務。

再來看一個測試:

 1 [Test]
 2 public void Shouldnot_SaveToDB_As_ScopeNotComitted()
 3 {
 4     var user1 = Mock.Users.Random();
 5     var userCount = 0;
 6     try
 7     {
 8         using (var scope = new TransactionScope())
 9         {
10             using (var db = new MyProjectDbContext())
11             {
12                 db.Users.Add(user1);
13                 db.SaveChanges();
14                 userCount = db.Users.Count();
15             }
16             //scope.Complete();
17         }
18     }
19     catch (Exception)
20     {
21 
22     }
23     Assert.AreEqual(1, userCount);
24     using (var db = new MyProjectDbContext())
25     {
26         Assert.AreEqual(0, db.Users.Count());
27     }
28 }

}

這個測試表明,一旦DbContext被TransactionScope包起來之后,那么scope必須要調用scope.Complete()才能將數據更新到數據庫。

基於上面的這些知識,我們可以很容易為EF搭建支持真正事務的框架。下面我簡單介紹下我們的項目架構(EF CodeFirst, MVC)。

基於EF事務機制的架構

Domain層:

定義數據實體類,即數據庫中的表。定義繼承自DbContext的MyProjectDbContext。

Service層:

主要用於封裝所有對數據庫的訪問。例子代碼如下:

1 public List<User> GetAllUsers()
2 {
3     using (var db = new MyProjectDbContext())
4     {
5         return db.Users.ToList();
6     }
7 }

上面這段代碼中注意要使用using,否則DbContext的延遲加載功能會在controller層被調用。加了using之后,可以避免在controller層對數據庫的直接訪問。

Controller層:

調用service層的代碼從數據庫中得到數據,返回給UI。例子:

1 public ActionResult GetAllUsers()
2 {
3     var users = IoC.GetService<IUserService>().GetAll();
4     return View(users);
5 }

同時將UI傳回來的數據更新到數據庫,這時如果需要調用多個service來更新數據庫,那么就需要用到事務。例子:

 1 public ActionResult DeleteUser(int userId)
 2 {
 3     try
 4     {
 5         using (var scope = new TransactionScope())
 6         {
 7             IoC.GetService<IUserService>().DeleteLogs(userId);
 8             IoC.GetService<IUserService>().DeleteUser(userId);
 9             scope.Complete();
10             return View();
11         }
12     }
13     catch(Exception)
14     {
15         
16     }
17     return View();
18 }

通常情況下,我們會在MyControllerBase里面加一個 ActionResult TryScope(Action action)的方法,這樣在子類里面就可以不用寫try-catch了。

對於EF更深層的機制,我了解的也不多。歡迎大家討論!


免責聲明!

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



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