zoukankan      html  css  js  c++  java
  • 分享我们项目中基于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更深层的机制,我了解的也不多。欢迎大家讨论!

  • 相关阅读:
    C# UserControl集合属性使用
    类属性的几个特性的含义
    C# 绘图时使用抗锯齿会多出一个像素
    Tooltip导致的无法访问已释放对象
    C#窗口闪烁问题解决
    窗口扩展风格
    动态字段列表实现及List<T>排序
    比较好用的Copy代码到博客VS扩展工具
    Dictionary与SortedDictionary
    VS2017 15.6之后支持直接反编译了
  • 原文地址:https://www.cnblogs.com/leotsai/p/how-to-use-entity-framework-transaction-scope.html
Copyright © 2011-2022 走看看