仓储和工作单元模式

仓储模式

为什么要用仓储模式

通常不建议在业务逻辑层直接访问数据库。因为这样可能会导致如下结果:

  • 重复的代码
  • 编程错误的可能性更高
  • 业务数据的弱类型
  • 更难集中处理数据,比如缓存
  • 无法轻松地从外部依赖项测试业务逻辑

在业务逻辑层通过仓库模式访问数据则可以实现如下特点:

  • 最大化可以用自动化测试的代码量,并隔离数据层以支持单元测试。
  • 对数据集中管理、提供一致的访问规则和逻辑。
  • 通过将业务逻辑与数据或服务访问逻辑分隔开,从而提高代码的可维护性和可读性。
  • 使用强类型的Entity以便在编译时识别问题而不是在运行时

实现仓储模式

使用仓储模式是为了分离业务层和数据源层,并实现业务层的Model和数据源层的Model映射。(ViewModel和Entity之间的映射)。即业务逻辑层应该和数据源层无关,业务层只关心结果,数据源层关心细节。

数据源层和业务层之间的分离有三个好处:

  • 集中了数据逻辑或Web服务访问逻辑。
  • 为单元测试提供了一个替代点。
  • 提供了一种灵活的体系结构,可以作为应用程序的整体设计进行调整。

一、定义仓储接口

所有的仓储要实现该接口。该接口定义了对数据的基本操作。

  1. public interface IRepository<TEntity> where TEntity : class
  2. {
  3. #region 属性
  4. //IQueryable Entities { get; }
  5. #endregion
  6. #region 公共方法
  7. void Insert(TEntity entity);
  8. void Insert(IEnumerable<TEntity> entities);
  9. void Delete(object id);
  10. void Delete(TEntity entity);
  11. void Delete(IEnumerable<TEntity> entities);
  12. void Update(TEntity entity);
  13. TEntity GetByKey(object key);
  14. #endregion
  15. }

二、实现泛型仓储基类

该类为仓储的泛型基类,实现之前定义的仓储接口(IRepository),并包含数据上下文(DbContext),数据集(DataSet)。

每个表都会对应一个实体(Entity)。每个实体(Entity)对应一个仓储。把实体作为泛型仓储基类的参数,来实现每个实体对应的仓储。

(使用泛型仓储基类可以把实体作为泛型参数来创建对应的仓储。)

  1. //泛型仓储基类
  2. public class EFBaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
  3. {
  4. //数据上下文
  5. internal DbContext context;
  6. //数据集
  7. internal DbSet<TEntity> dbSet;
  8. public EFBaseRepository(DbContext context)
  9. {
  10. this.context = context;
  11. this.dbSet = context.Set<TEntity>();
  12. }
  13. //public IQueryable Entities => context.Set<TEntity>();
  14. public void Delete(object id)
  15. {
  16. TEntity entityToDelete = dbSet.Find(id);
  17. Delete(entityToDelete);
  18. }
  19. public void Delete(IEnumerable<TEntity> entities)
  20. {
  21. dbSet.RemoveRange(entities);
  22. }
  23. public void Delete(TEntity entityToDelete)
  24. {
  25. if (context.Entry(entityToDelete).State == EntityState.Detached)
  26. {
  27. dbSet.Attach(entityToDelete);
  28. }
  29. dbSet.Remove(entityToDelete);
  30. }
  31. public TEntity GetByKey(object key)
  32. {
  33. return dbSet.Find(key);
  34. }
  35. public void Insert(TEntity entity)
  36. {
  37. dbSet.Add(entity);
  38. }
  39. public void Insert(IEnumerable<TEntity> entities)
  40. {
  41. dbSet.AddRange(entities);
  42. }
  43. public void Update(TEntity entity)
  44. {
  45. dbSet.Attach(entity);
  46. context.Entry(entity).State = EntityState.Modified;
  47. }
  48. public virtual IEnumerable<TEntity> Get(
  49. Expression<Func<TEntity, bool>> filter = null,
  50. Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
  51. string includeProperties = "", int topNum = 0)
  52. {
  53. IQueryable<TEntity> query = dbSet;
  54. if (filter != null)
  55. {
  56. query = query.Where(filter);
  57. }
  58. foreach (var includeProperty in includeProperties.Split
  59. (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
  60. {
  61. query = query.Include(includeProperty);
  62. }
  63. if (orderBy != null)
  64. {
  65. query = orderBy(query);
  66. }
  67. if (topNum != 0)
  68. {
  69. return query.Take(topNum);
  70. }
  71. else
  72. {
  73. return query.ToList();
  74. }
  75. }
  76. }

三、访问数据

可以把对Person的相关操作封装到一个类中。在该类中实现PersonRepository(Person仓储),操作PersonRepository来操作数据。

(数据库有一个Person表,代码中有一个TPerson实体)

(该类提供与业务逻辑无关的仓储操作)

  1. public class PersonService
  2. {
  3. private EFBaseRepository<TPerson> _personRepository;
  4. public PersonService(DbContext dbContext)
  5. {
  6. var context = dbContext;
  7. //实现Person仓储,TPerson为对应的Entity
  8. _personRepository = new EFBaseRepository<TPerson>(context);
  9. }
  10. public IEnumerable<TPerson> Get()
  11. {
  12. return _personRepository.Get();
  13. }
  14. public bool AddPerson(TPerson p)
  15. {
  16. try
  17. {
  18. _personRepository.Insert(p);
  19. }
  20. catch (Exception ex)
  21. {
  22. return false;
  23. }
  24. return true;
  25. }
  26. public bool EditPerson(TPerson p)
  27. {
  28. try
  29. {
  30. _personRepository.Update(p);
  31. }
  32. catch (Exception ex)
  33. {
  34. return false;
  35. }
  36. return true;
  37. }
  38. public bool DeletePerson(TPerson p)
  39. {
  40. try
  41. {
  42. _personRepository.Delete(p);
  43. }
  44. catch (Exception)
  45. {
  46. return false;
  47. }
  48. return true;
  49. }
  50. }

四、ViewModel和Entity的映射

该类是对PersonService的封装,是为了提供同一数据上下文,和对数据上下文的释放,及ViewModle和Entity的映射。

该类中每个方法对应一个数据上下文。如果有需要对多个表操作,将这些操作封装到一个数据上下文中。数据上下文的释放在每个方法中实现。

(所有与业务逻辑相关的操作在该类实现)


  1. public class PersonManage
  2. {
  3. public IList<PersonVM> GetPersons()
  4. {
  5. using (var context = new RepositoryDemoEntities())
  6. {
  7. var list = new PersonService(context).Get();
  8. var result = new List<PersonVM>();
  9. foreach (var item in list)
  10. {
  11. result.Add(new PersonVM { Name = item.Name, Age = item.Age, Home = item.Home, PersonID = item.Id });
  12. }
  13. return result;
  14. }
  15. }
  16. public bool AddPerson(PersonVM p)
  17. {
  18. using (var context = new RepositoryDemoEntities())
  19. {
  20. var result = new PersonService(context).AddPerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
  21. context.SaveChanges();
  22. return result;
  23. }
  24. }
  25. public bool DeletePerson(PersonVM p)
  26. {
  27. using (var context = new RepositoryDemoEntities())
  28. {
  29. var result = new PersonService(context).DeletePerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
  30. context.SaveChanges();
  31. return result;
  32. }
  33. }
  34. public bool EditPerson(PersonVM p)
  35. {
  36. using (var context = new RepositoryDemoEntities())
  37. {
  38. var result = new PersonService(context).EditPerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
  39. context.SaveChanges();
  40. return result;
  41. }
  42. }
  43. }

五、在Test中测试

仓储模式使得更容易实现单元测试

  1. 添加项目引用
  2. 设置数据库连接字符串
  3. 添加EntityFramework包即可对每个方法测试
  1. [TestClass]
  2. public class UnitTest1
  3. {
  4. [TestMethod]
  5. public void TestShowPerson()
  6. {
  7. var res = new PersonManage().GetPersons();
  8. Assert.AreNotEqual(0, res.Count);
  9. }
  10. [TestMethod]
  11. public void TestAddPerson()
  12. {
  13. var p = new PersonVM { Home = "zhengzhou", Age = 22, Name = "Jessica", PersonID = 3 };
  14. var res = new PersonManage().AddPerson(p);
  15. Assert.IsTrue(res);
  16. }
  17. [TestMethod]
  18. public void TestEditPerson()
  19. {
  20. var persons = new PersonManage().GetPersons();
  21. var p = persons[0];
  22. p.Name = "fixed";
  23. var res = new PersonManage().EditPerson(p);
  24. Assert.IsTrue(res);
  25. }
  26. [TestMethod]
  27. public void TestDeletePerson()
  28. {
  29. var persons = new PersonManage().GetPersons();
  30. var p = persons[0];
  31. var res = new PersonManage().DeletePerson(p);
  32. Assert.IsTrue(res);
  33. }
  34. }

小结:

仓储模式通过对数据库操作的封装使数据访问有一致性和对应用层和数据层的隔离,降低代码的耦合性,更加容易实现单元测试。


工作单元模式

工作单元模式是“维护一个被业务事务影响的对象列表,协调变化的写入和并发问题的解决”

比如:新入校一个同学,需要在班级,学校,学生,课程等多个表里同时操作。这些表要么都完成,要么都不完成。具有一致性。

在仓储模式中使用工作单元模式是为了当你操作多个仓储时,共用一个数据上下文(DbContext)使得这些仓储具有一致性。

在Entity Framework中可以把DbContext当作是一个工作单元。在同一个DbContext对多个仓储操作。所以工作单元模式并不是一定要自己实现,通过Entity Framework也可以实现。

上面的仓储模式其实通过对DbContext的使用了也实现了工作单元模式。


还是简单说下如何实现自定义的工作单元 (如果要对每个操作都产生记录的话,可以扩展自定义工作单元来实现)

自定义工作单元

一、定义IUnitOfWork接口

  1. /// <summary>
  2. /// 工作单元接口
  3. /// </summary>
  4. public interface IUnitOfWork
  5. {
  6. /// <summary>
  7. /// 保存当前单元操作的结果
  8. /// </summary>
  9. /// <returns></returns>
  10. void Save();
  11. }

二、定义UnitOfWork类

UnitOfWork包含了所有的仓储,及一个数据上下文,该类实现IDisposable接口(该接口的方法中释放数据上下文)。


  1. public class UnitOfWork : IUnitOfWork, IDisposable
  2. {
  3. private RepositoryDemoEntities1 context = new RepositoryDemoEntities1();
  4. private EFBaseRepository<TPerson> _personRepository;
  5. public EFBaseRepository<TPerson> PersonRepository
  6. {
  7. get
  8. {
  9. return _personRepository ?? new EFBaseRepository<TPerson>(context);
  10. }
  11. }
  12. public void Save()
  13. {
  14. context.SaveChanges();
  15. }
  16. private bool disposed = false;
  17. protected virtual void Dispose(bool disposing)
  18. {
  19. if (!this.disposed)
  20. {
  21. if (disposing)
  22. {
  23. context.Dispose();
  24. }
  25. }
  26. this.disposed = true;
  27. }
  28. public void Dispose()
  29. {
  30. Dispose(true);
  31. GC.SuppressFinalize(this);
  32. }
  33. }

三、实现UnitOfWork实例。通过该实例访问仓储。

定义一个UnitOfWork的字段,通过构造函数实例化该UnitOfWork

(该类提供与业务逻辑无关的仓储操作)

  1. public class PersonService
  2. {
  3. private UnitOfWork unit;
  4. public PersonService(UnitOfWork unitOfWork)
  5. {
  6. unit = unitOfWork;
  7. }
  8. public IEnumerable<TPerson> Get()
  9. {
  10. return unit.PersonRepository.Get();
  11. }
  12. public bool AddPerson(TPerson p)
  13. {
  14. try
  15. {
  16. unit.PersonRepository.Insert(p);
  17. }
  18. catch (Exception ex)
  19. {
  20. return false;
  21. }
  22. return true;
  23. }
  24. public bool EditPerson(TPerson p)
  25. {
  26. try
  27. {
  28. unit.PersonRepository.Update(p);
  29. }
  30. catch (Exception ex)
  31. {
  32. return false;
  33. }
  34. return true;
  35. }
  36. public bool DeletePerson(TPerson p)
  37. {
  38. try
  39. {
  40. unit.PersonRepository.Delete(p);
  41. }
  42. catch (Exception)
  43. {
  44. return false;
  45. }
  46. return true;
  47. }
  48. }

四、通过工作单元,保持操作一致性,手动释放数据上下文

在此将PersonService封装,如果有对多个仓储的操作,封装在一个工作单元中。

(所有与业务逻辑相关的操作在该类实现)

  1. public class PersonManage
  2. {
  3. public IList<PersonVM> GetPersons()
  4. {
  5. using (var unit = new UnitOfWork())
  6. {
  7. var list = new PersonService(unit).Get();
  8. var result = new List<PersonVM>();
  9. foreach (var item in list)
  10. {
  11. result.Add(new PersonVM { Name = item.Name, Age = item.Age, Home = item.Home, PersonID = item.Id });
  12. }
  13. return result;
  14. }
  15. }
  16. public bool AddPerson(PersonVM p)
  17. {
  18. using (var unit = new UnitOfWork())
  19. {
  20. var result = new PersonService(unit).AddPerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
  21. unit.Save();
  22. return result;
  23. }
  24. }
  25. public bool DeletePerson(PersonVM p)
  26. {
  27. using (var unit = new UnitOfWork())
  28. {
  29. var result = new PersonService(unit).DeletePerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
  30. unit.Save();
  31. return result;
  32. }
  33. }
  34. public bool EditPerson(PersonVM p)
  35. {
  36. using (var unit = new UnitOfWork())
  37. {
  38. var result = new PersonService(unit).EditPerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
  39. unit.Save();
  40. return result;
  41. }
  42. }
  43. }

五、单元测试

  1. [TestClass]
  2. public class UnitTest1
  3. {
  4. [TestMethod]
  5. public void TestShow()
  6. {
  7. var res = new PersonManage().GetPersons();
  8. Console.WriteLine(res.Count);
  9. Assert.AreNotEqual(0, res.Count);
  10. }
  11. [TestMethod]
  12. public void TestAdd()
  13. {
  14. var res = new PersonManage().AddPerson(new PersonVM { Home = "meiguo", Age = 11, Name = "tidy" });
  15. Assert.IsTrue(res);
  16. }
  17. [TestMethod]
  18. public void TestEdit()
  19. {
  20. var pmanage = new PersonManage();
  21. var p = pmanage.GetPersons()[0];
  22. p.Name = "fixed";
  23. var res = pmanage.EditPerson(p);
  24. Assert.IsTrue(res);
  25. }
  26. [TestMethod]
  27. public void TestDelete()
  28. {
  29. var pmanage = new PersonManage();
  30. var p = pmanage.GetPersons()[0];
  31. var res = pmanage.DeletePerson(p);
  32. Assert.IsTrue(res);
  33. }
  34. }

小结:

工作单元模式是为了实现业务的事务功能。通过一个数据上下文对相关的仓储操作。但是也不是必须要自己实现模式,通过ORM也可以实现。


代码下载

如有不对,请多多指教。


仓储(Repository)和工作单元模式(UnitOfWork)的更多相关文章

  1. 工作单元模式(UnitOfWork)学习总结

    工作单元的目标是维护变化的对象列表.使用IUnitOfWorkRepository负责对象的持久化,使用IUnitOfWork收集变化的对象,并将变化的对象放到各自的增删改列表中, 最后Commit, ...

  2. MVC+EF 理解和实现仓储模式和工作单元模式

    MVC+EF 理解和实现仓储模式和工作单元模式 原文:Understanding Repository and Unit of Work Pattern and Implementing Generi ...

  3. Contoso 大学 - 9 - 实现仓储和工作单元模式

    原文 Contoso 大学 - 9 - 实现仓储和工作单元模式 By Tom Dykstra, Tom Dykstra is a Senior Programming Writer on Micros ...

  4. 演练5-8:Contoso大学校园管理系统(实现存储池和工作单元模式)

    在上一次的教程中,你已经使用继承来消除在 Student 和 Instructor 实体之间的重复代码.在这个教程中,你将要看到使用存储池和工作单元模式进行增.删.改.查的一些方法.像前面的教程一样, ...

  5. [.NET领域驱动设计实战系列]专题四:前期准备之工作单元模式(Unit Of Work)

    一.前言 在前一专题中介绍了规约模式的实现,然后在仓储实现中,经常会涉及工作单元模式的实现.然而,在我的网上书店案例中也将引入工作单元模式,所以本专题将详细介绍下该模式,为后面案例的实现做一个铺垫. ...

  6. 关于工作单元模式——工作单元模式与EF结合的使用

    工作单元模式往往和仓储模式一起使用,本篇文章讲到的是工作单元模式和仓储模式一起用来在ef外面包一层,其实EF本身就是工作单元模式和仓储模式使用的经典例子,其中DbContext就是工作单元,而每个Db ...

  7. .NET应用架构设计—工作单元模式(摆脱过程式代码的重要思想,代替DDD实现轻量级业务)

    阅读目录: 1.背景介绍 2.过程式代码的真正困境 3.工作单元模式的简单示例 4.总结 1.背景介绍 一直都在谈论面向对象开发,但是开发企业应用系统时,使用面向对象开发最大的问题就是在于,多个对象之 ...

  8. [.NET领域驱动设计实战系列]专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现

    一.前言 在前面2篇博文中,我分别介绍了规约模式和工作单元模式,有了前面2篇博文的铺垫之后,下面就具体看看如何把这两种模式引入到之前的网上书店案例里. 二.规约模式的引入 在第三专题我们已经详细介绍了 ...

  9. 重新整理 .net core 实践篇—————工作单元模式[二十六]

    前言 简单整理一下工作单元模式. 正文 工作单元模式有3个特性,也算是其功能: 使用同一上下文 跟踪实体的状态 保障事务一致性 工作单元模式 主要关注事务,所以重点在事务上. 在共享层的基础建设类库中 ...

随机推荐

  1. mybatis foreach标签的解释 与常用之处

    情景:查询数据库中文章的相关文章   文章为一个表 字段tags为相关文章字符串中间用','逗号进行啦分割 查询完一个文章后可以把tags字段构造为一个List<String> 然后利用这 ...

  2. 界面主窗体,子窗体的InitializeComponent(构造函数)、Load事件执行顺序

    主窗体,子窗体的InitializeComponent(构造函数).Load事件执行顺序1.执行主窗体定义事件 new函数时,同时执行主窗体构造函数,默认就一个InitializeComponent函 ...

  3. Vulkan Tutorial 04 理解Validation layers

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 What are validation layers? Vulkan API的设计核 ...

  4. 第一篇&nbsp;UCOS介绍

    第一篇 UCOS介绍 这个大家都知道.呵呵.考虑到咱们学习的完整性还是在这里唠叨一下.让大家再熟悉一下.高手们忍耐一下吧! uC/OS II(Micro Control Operation Syste ...

  5. java基础之JDBC一:概述及步骤详解

    1. JDBC的简介 概述: 就是Java用来操作不同数据库(DBMS)的类库(技术), 本质就是一些类和接口. /* 类: DriverManager 接口: Driver, Connection, ...

  6. 使用composer安装laravel5.4

    composer create-project --prefer-dist laravel/laravel blog 后面的是文件目录

  7. shell cut 用法

    cut -f   提取第几列 -d  按指定的分隔符割列 cut -f 1 xxx.txt   提取第1列 cut -f 1,3 xxx.txt   提取第1,3列 cut -d ":&qu ...

  8. IDEA03 连接数据库、自动生成实体类

    1 版本说明 JDK:1.8 MAVEN:3.5 SpringBoot:2.0.4 IDEA:旗舰版207.2 MySQL:5.5 2 利用IDEA连接数据库 说明:本案例以MySQL为例 2.1 打 ...

  9. 【转】http 缓存

    原文地址:https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-c ...

  10. 面试题:Java多线程必须掌握的十个问题 背1

    一.进程与线程?并行与并发? 进程代表一个运行中的程序,是资源分配与调度的基本单位.进程有三大特性: 1.独立性:独立的资源,私有的地址空间,进程间互不影响. 2.动态性:进程具有生命周期. 3.并发 ...