• 分享我们项目中基于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了。

  • 相关阅读:
    poj 2584 T-Shirt Gumbo (二分匹配)
    hdu 1757 A Simple Math Problem (乘法矩阵)
    矩阵之矩阵乘法(转载)
    poj 2239 Selecting Courses (二分匹配)
    hdu 3661 Assignments (贪心)
    hdu 1348 Wall (凸包)
    poj 2060 Taxi Cab Scheme (二分匹配)
    hdu 2202 最大三角形 (凸包)
    hdu 1577 WisKey的眼神 (数学几何)
    poj 1719 Shooting Contest (二分匹配)
  • 原文地址:https://www.cnblogs.com/powerzhang/p/3186806.html
Copyright © 2020-2023  润新知