仓储(Repository)和工作单元模式(UnitOfWork)
仓储和工作单元模式
仓储模式
为什么要用仓储模式
通常不建议在业务逻辑层直接访问数据库。因为这样可能会导致如下结果:
- 重复的代码
- 编程错误的可能性更高
- 业务数据的弱类型
- 更难集中处理数据,比如缓存
- 无法轻松地从外部依赖项测试业务逻辑
在业务逻辑层通过仓库模式访问数据则可以实现如下特点:
- 最大化可以用自动化测试的代码量,并隔离数据层以支持单元测试。
- 对数据集中管理、提供一致的访问规则和逻辑。
- 通过将业务逻辑与数据或服务访问逻辑分隔开,从而提高代码的可维护性和可读性。
- 使用强类型的Entity以便在编译时识别问题而不是在运行时
实现仓储模式
使用仓储模式是为了分离业务层和数据源层,并实现业务层的Model和数据源层的Model映射。(ViewModel和Entity之间的映射)。即业务逻辑层应该和数据源层无关,业务层只关心结果,数据源层关心细节。
数据源层和业务层之间的分离有三个好处:
- 集中了数据逻辑或Web服务访问逻辑。
- 为单元测试提供了一个替代点。
- 提供了一种灵活的体系结构,可以作为应用程序的整体设计进行调整。
一、定义仓储接口
所有的仓储要实现该接口。该接口定义了对数据的基本操作。
public interface IRepository<TEntity> where TEntity : class
{
    #region 属性
    //IQueryable Entities { get; }
    #endregion
    #region 公共方法
    void Insert(TEntity entity);
    void Insert(IEnumerable<TEntity> entities);
    void Delete(object id);
    void Delete(TEntity entity);
    void Delete(IEnumerable<TEntity> entities);
    void Update(TEntity entity);
    TEntity GetByKey(object key);
    #endregion
    }
二、实现泛型仓储基类
该类为仓储的泛型基类,实现之前定义的仓储接口(IRepository),并包含数据上下文(DbContext),数据集(DataSet)。
每个表都会对应一个实体(Entity)。每个实体(Entity)对应一个仓储。把实体作为泛型仓储基类的参数,来实现每个实体对应的仓储。
(使用泛型仓储基类可以把实体作为泛型参数来创建对应的仓储。)
//泛型仓储基类
public class EFBaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
    //数据上下文
    internal DbContext context;
    //数据集
    internal DbSet<TEntity> dbSet;
    public EFBaseRepository(DbContext context)
    {
        this.context = context;
        this.dbSet = context.Set<TEntity>();
    }
    //public IQueryable Entities => context.Set<TEntity>();
    public void Delete(object id)
    {
        TEntity entityToDelete = dbSet.Find(id);
        Delete(entityToDelete);
    }
    public void Delete(IEnumerable<TEntity> entities)
    {
        dbSet.RemoveRange(entities);
    }
    public void Delete(TEntity entityToDelete)
    {
        if (context.Entry(entityToDelete).State == EntityState.Detached)
        {
            dbSet.Attach(entityToDelete);
        }
        dbSet.Remove(entityToDelete);
    }
    public TEntity GetByKey(object key)
    {
        return dbSet.Find(key);
    }
    public void Insert(TEntity entity)
    {
        dbSet.Add(entity);
    }
    public void Insert(IEnumerable<TEntity> entities)
    {
        dbSet.AddRange(entities);
    }
    public void Update(TEntity entity)
    {
        dbSet.Attach(entity);
        context.Entry(entity).State = EntityState.Modified;
    }
    public virtual IEnumerable<TEntity> Get(
        Expression<Func<TEntity, bool>> filter = null,
        Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
        string includeProperties = "", int topNum = 0)
    {
        IQueryable<TEntity> query = dbSet;
        if (filter != null)
        {
            query = query.Where(filter);
        }
        foreach (var includeProperty in includeProperties.Split
            (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }
        if (orderBy != null)
        {
            query = orderBy(query);
        }
        if (topNum != 0)
        {
            return query.Take(topNum);
        }
        else
        {
            return query.ToList();
        }
    }
}
三、访问数据
可以把对Person的相关操作封装到一个类中。在该类中实现PersonRepository(Person仓储),操作PersonRepository来操作数据。
(数据库有一个Person表,代码中有一个TPerson实体)
(该类提供与业务逻辑无关的仓储操作)
public class PersonService
{
    private EFBaseRepository<TPerson> _personRepository;
    public PersonService(DbContext dbContext)
    {
        var context = dbContext;
        //实现Person仓储,TPerson为对应的Entity
        _personRepository = new EFBaseRepository<TPerson>(context);
    }
    public IEnumerable<TPerson> Get()
    {
        return _personRepository.Get();
    }
    public bool AddPerson(TPerson p)
    {
        try
        {
            _personRepository.Insert(p);
        }
        catch (Exception ex)
        {
            return false;
        }
        return true;
    }
    public bool EditPerson(TPerson p)
    {
        try
        {
            _personRepository.Update(p);
        }
        catch (Exception ex)
        {
            return false;
        }
        return true;
    }
    public bool DeletePerson(TPerson p)
    {
        try
        {
            _personRepository.Delete(p);
        }
        catch (Exception)
        {
            return false;
        }
        return true;
    }
}
四、ViewModel和Entity的映射
该类是对PersonService的封装,是为了提供同一数据上下文,和对数据上下文的释放,及ViewModle和Entity的映射。
该类中每个方法对应一个数据上下文。如果有需要对多个表操作,将这些操作封装到一个数据上下文中。数据上下文的释放在每个方法中实现。
(所有与业务逻辑相关的操作在该类实现)
public class PersonManage
{
    public IList<PersonVM> GetPersons()
    {
        using (var context = new RepositoryDemoEntities())
        {
            var list = new PersonService(context).Get();
            var result = new List<PersonVM>();
            foreach (var item in list)
            {
                result.Add(new PersonVM { Name = item.Name, Age = item.Age, Home = item.Home, PersonID = item.Id });
            }
            return result;
        }
    }
    public bool AddPerson(PersonVM p)
    {
        using (var context = new RepositoryDemoEntities())
        {
            var result = new PersonService(context).AddPerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
            context.SaveChanges();
            return result;
        }
    }
    public bool DeletePerson(PersonVM p)
    {
        using (var context = new RepositoryDemoEntities())
        {
            var result = new PersonService(context).DeletePerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
            context.SaveChanges();
            return result;
        }
    }
    public bool EditPerson(PersonVM p)
    {
        using (var context = new RepositoryDemoEntities())
        {
            var result = new PersonService(context).EditPerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
            context.SaveChanges();
            return result;
        }
    }
}
五、在Test中测试
仓储模式使得更容易实现单元测试
- 添加项目引用
- 设置数据库连接字符串
- 添加EntityFramework包即可对每个方法测试
[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestShowPerson()
    {
        var res = new PersonManage().GetPersons();
        Assert.AreNotEqual(0, res.Count);
    }
    [TestMethod]
    public void TestAddPerson()
    {
        var p = new PersonVM { Home = "zhengzhou", Age = 22, Name = "Jessica", PersonID = 3 };
        var res = new PersonManage().AddPerson(p);
        Assert.IsTrue(res);
    }
    [TestMethod]
    public void TestEditPerson()
    {
        var persons = new PersonManage().GetPersons();
        var p = persons[0];
        p.Name = "fixed";
        var res = new PersonManage().EditPerson(p);
        Assert.IsTrue(res);
    }
    [TestMethod]
    public void TestDeletePerson()
    {
        var persons = new PersonManage().GetPersons();
        var p = persons[0];
        var res = new PersonManage().DeletePerson(p);
        Assert.IsTrue(res);
    }
}
小结:
仓储模式通过对数据库操作的封装使数据访问有一致性和对应用层和数据层的隔离,降低代码的耦合性,更加容易实现单元测试。
工作单元模式
工作单元模式是“维护一个被业务事务影响的对象列表,协调变化的写入和并发问题的解决”
比如:新入校一个同学,需要在班级,学校,学生,课程等多个表里同时操作。这些表要么都完成,要么都不完成。具有一致性。
在仓储模式中使用工作单元模式是为了当你操作多个仓储时,共用一个数据上下文(DbContext)使得这些仓储具有一致性。
在Entity Framework中可以把DbContext当作是一个工作单元。在同一个DbContext对多个仓储操作。所以工作单元模式并不是一定要自己实现,通过Entity Framework也可以实现。
上面的仓储模式其实通过对DbContext的使用了也实现了工作单元模式。
还是简单说下如何实现自定义的工作单元 (如果要对每个操作都产生记录的话,可以扩展自定义工作单元来实现)
自定义工作单元
一、定义IUnitOfWork接口
/// <summary>
/// 工作单元接口
/// </summary>
public interface IUnitOfWork
{
    /// <summary>
    /// 保存当前单元操作的结果
    /// </summary>
    /// <returns></returns>
    void Save();
}
二、定义UnitOfWork类
UnitOfWork包含了所有的仓储,及一个数据上下文,该类实现IDisposable接口(该接口的方法中释放数据上下文)。
public class UnitOfWork : IUnitOfWork, IDisposable
{
    private RepositoryDemoEntities1 context = new RepositoryDemoEntities1();
    private EFBaseRepository<TPerson> _personRepository;
    public EFBaseRepository<TPerson> PersonRepository
    {
        get
        {
            return _personRepository ?? new EFBaseRepository<TPerson>(context);
        }
    }
    public void Save()
    {
        context.SaveChanges();
    }
    private bool disposed = false;
    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                context.Dispose();
            }
        }
        this.disposed = true;
    }
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}
三、实现UnitOfWork实例。通过该实例访问仓储。
定义一个UnitOfWork的字段,通过构造函数实例化该UnitOfWork
(该类提供与业务逻辑无关的仓储操作)
public class PersonService
{
    private UnitOfWork unit;
    public PersonService(UnitOfWork unitOfWork)
    {
        unit = unitOfWork;
    }
    public IEnumerable<TPerson> Get()
    {
        return unit.PersonRepository.Get();
    }
    public bool AddPerson(TPerson p)
    {
        try
        {
            unit.PersonRepository.Insert(p);
        }
        catch (Exception ex)
        {
            return false;
        }
        return true;
    }
    public bool EditPerson(TPerson p)
    {
        try
        {
            unit.PersonRepository.Update(p);
        }
        catch (Exception ex)
        {
            return false;
        }
        return true;
    }
    public bool DeletePerson(TPerson p)
    {
        try
        {
            unit.PersonRepository.Delete(p);
        }
        catch (Exception)
        {
            return false;
        }
        return true;
    }
}
四、通过工作单元,保持操作一致性,手动释放数据上下文
在此将PersonService封装,如果有对多个仓储的操作,封装在一个工作单元中。
(所有与业务逻辑相关的操作在该类实现)
public class PersonManage
{
    public IList<PersonVM> GetPersons()
    {
        using (var unit = new UnitOfWork())
        {
            var list = new PersonService(unit).Get();
            var result = new List<PersonVM>();
            foreach (var item in list)
            {
                result.Add(new PersonVM { Name = item.Name, Age = item.Age, Home = item.Home, PersonID = item.Id });
            }
            return result;
        }
    }
    public bool AddPerson(PersonVM p)
    {
        using (var unit = new UnitOfWork())
        {
            var result = new PersonService(unit).AddPerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
            unit.Save();
            return result;
        }
    }
    public bool DeletePerson(PersonVM p)
    {
        using (var unit = new UnitOfWork())
        {
            var result = new PersonService(unit).DeletePerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
            unit.Save();
            return result;
        }
    }
    public bool EditPerson(PersonVM p)
    {
        using (var unit = new UnitOfWork())
        {
            var result = new PersonService(unit).EditPerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
            unit.Save();
            return result;
        }
    }
}
五、单元测试
  [TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestShow()
    {
        var res = new PersonManage().GetPersons();
        Console.WriteLine(res.Count);
        Assert.AreNotEqual(0, res.Count);
    }
    [TestMethod]
    public void TestAdd()
    {
        var res = new PersonManage().AddPerson(new PersonVM { Home = "meiguo", Age = 11, Name = "tidy" });
        Assert.IsTrue(res);
    }
    [TestMethod]
    public void TestEdit()
    {
        var pmanage = new PersonManage();
        var p = pmanage.GetPersons()[0];
        p.Name = "fixed";
        var res = pmanage.EditPerson(p);
        Assert.IsTrue(res);
    }
    [TestMethod]
    public void TestDelete()
    {
        var pmanage = new PersonManage();
        var p = pmanage.GetPersons()[0];
        var res = pmanage.DeletePerson(p);
        Assert.IsTrue(res);
    }
}
小结:
工作单元模式是为了实现业务的事务功能。通过一个数据上下文对相关的仓储操作。但是也不是必须要自己实现模式,通过ORM也可以实现。
如有不对,请多多指教。
仓储(Repository)和工作单元模式(UnitOfWork)的更多相关文章
- 工作单元模式(UnitOfWork)学习总结
		工作单元的目标是维护变化的对象列表.使用IUnitOfWorkRepository负责对象的持久化,使用IUnitOfWork收集变化的对象,并将变化的对象放到各自的增删改列表中, 最后Commit, ... 
- MVC+EF 理解和实现仓储模式和工作单元模式
		MVC+EF 理解和实现仓储模式和工作单元模式 原文:Understanding Repository and Unit of Work Pattern and Implementing Generi ... 
- Contoso 大学 - 9 - 实现仓储和工作单元模式
		原文 Contoso 大学 - 9 - 实现仓储和工作单元模式 By Tom Dykstra, Tom Dykstra is a Senior Programming Writer on Micros ... 
- 演练5-8:Contoso大学校园管理系统(实现存储池和工作单元模式)
		在上一次的教程中,你已经使用继承来消除在 Student 和 Instructor 实体之间的重复代码.在这个教程中,你将要看到使用存储池和工作单元模式进行增.删.改.查的一些方法.像前面的教程一样, ... 
- [.NET领域驱动设计实战系列]专题四:前期准备之工作单元模式(Unit Of Work)
		一.前言 在前一专题中介绍了规约模式的实现,然后在仓储实现中,经常会涉及工作单元模式的实现.然而,在我的网上书店案例中也将引入工作单元模式,所以本专题将详细介绍下该模式,为后面案例的实现做一个铺垫. ... 
- 关于工作单元模式——工作单元模式与EF结合的使用
		工作单元模式往往和仓储模式一起使用,本篇文章讲到的是工作单元模式和仓储模式一起用来在ef外面包一层,其实EF本身就是工作单元模式和仓储模式使用的经典例子,其中DbContext就是工作单元,而每个Db ... 
- .NET应用架构设计—工作单元模式(摆脱过程式代码的重要思想,代替DDD实现轻量级业务)
		阅读目录: 1.背景介绍 2.过程式代码的真正困境 3.工作单元模式的简单示例 4.总结 1.背景介绍 一直都在谈论面向对象开发,但是开发企业应用系统时,使用面向对象开发最大的问题就是在于,多个对象之 ... 
- [.NET领域驱动设计实战系列]专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
		一.前言 在前面2篇博文中,我分别介绍了规约模式和工作单元模式,有了前面2篇博文的铺垫之后,下面就具体看看如何把这两种模式引入到之前的网上书店案例里. 二.规约模式的引入 在第三专题我们已经详细介绍了 ... 
- 重新整理 .net core 实践篇—————工作单元模式[二十六]
		前言 简单整理一下工作单元模式. 正文 工作单元模式有3个特性,也算是其功能: 使用同一上下文 跟踪实体的状态 保障事务一致性 工作单元模式 主要关注事务,所以重点在事务上. 在共享层的基础建设类库中 ... 
随机推荐
- hdu 5706 GirlCat(BFS)
			As a cute girl, Kotori likes playing ``Hide and Seek'' with cats particularly. Under the influence o ... 
- Py修行路  python基础 (十五)面向对象编程 继承 组合 接口和抽象类
			一.前提回忆: 1.类是用来描述某一类的事物,类的对象就是这一类事物中的一个个体.是事物就要有属性,属性分为 1:数据属性:就是变量 2:函数属性:就是函数,在面向对象里通常称为方法 注意:类和对象均 ... 
- leetcode824
			class Solution { public: void SplitString(const string& s, vector<string>& v, const st ... 
- Java微信公众平台开发(十二)--微信用户信息的获取
			转自:http://www.cuiyongzhi.com/post/56.html 前面的文章有讲到微信的一系列开发文章,包括token获取.菜单创建等,在这一篇将讲述在微信公众平台开发中如何获取微信 ... 
- 【知识结构】最强Thymeleaf知识体系
			在开发一个小项目的时候,使用的是Spring Boot,Spring Boot 官方推荐的前端模板是thymeleaf, 花了两天时间将官方的文档看完并总结了下知识体系结构.转载请注明出处,https ... 
- [JBPM3.2]TaskNode的signal属性详解
			TaskNode节点的signal属性决定了任务完成时对流程执行继续的影响,共有六种取值:unsynchronized,never,first,first-wait,last,last-wait.默认 ... 
- C# XML 操作
			1 xml文件格式 <?xml version="1.0" encoding="utf-8"?> <userInfo> <user ... 
- oracle xe远程访问
			oracle xe其实监听了1521端口 netstat -ano|findstr 只是没请求防火墙权限而已. 手动打开防火墙1521端口 管理员运行下面的命令 本机环境win10 netsh adv ... 
- jmeter beanshell
			//获取返回数据 String json = prev.getResponseDataAsString(); ///加入变量vars.put("restr",json); //获取 ... 
- 场景中,并没有灯源的存在,但是cube却会有灯光照射的反应,这就是Light Probe Group的作用。
			http://blog.csdn.net/qq617119142/article/details/41674755 
