MVC3+EF4.1学习系列(八)-----利用Repository and Unit of Work重构项目

项目最基础的东西已经结束了,但是现在我们的项目还不健全  不利于测试 重复性代码多   层与层之间耦合性高  不利于扩展等问题.今天的这章 主要就是解决这些问题的。再解决这些问题时,自己也产生了很多疑问,理解的也并不是很透彻 ,希望我的疑问能在这里得到解答~~

一.模式介绍

1.Repository

在《企业架构模式》中,通过用来访问领域对象的一个类似集合的接口,在领域与数据映射层之间进行协调。还有请大家参考这个  P OF EAA详细介绍

然后说下我对这个的感觉和疑问   怎么都觉得这个Repository就是以前的dao(dal)层~~  不过就是通过接口 泛型 与ORM结合 实现了更好的复用 不知道对不~~

2.Unit of Work

先上介绍  Unit Of Work 定义和解释  。主要是解决当有多个操作时,数据的变更 存储 以及事务的处理等 。unit of work是一个记录所有对象模型修改过的信息,在提交的时候,一次性修改,并把结果同步到数据库。 其实我们可以发现 DbContext 已经具备了这样的功能~~ 很大程度实现了Unit of work~  因为我们savechange()

时 才提交数据的

3.整体概述

运用Repository 和Unit of Work 在 数据层  和业务逻辑层之间 再创建一个抽象层 。它将帮我们隔离变化,并且更利于测试.

下面借用下原文的图 说明下使用Repository 和Unit of Work和不使用的区别

二.改造开始

让我们回到最早的学生控制器上 忘了讲啥的朋友可以看下这节-------学生控制器    我们把

private SchoolContext db = new SchoolContext();

写在了每个控制器里面  声明周期与控制器完全耦合在了一起

1.创建学生资源库接口

using System;using System.Collections.Generic;using System.Linq;using System.Web;using ContosoUniversity.Models;

namespace ContosoUniversity.DAL{    public interface IStudentRepository : IDisposable    {        IEnumerable<Student> GetStudents();        Student GetStudentByID(int studentId);        void InsertStudent(Student student);        void DeleteStudent(int studentID);        void UpdateStudent(Student student);        void Save();    }}

2.实现接口

using System;using System.Collections.Generic;using System.Linq;using System.Data;using ContosoUniversity.Models;

namespace ContosoUniversity.DAL{    public class StudentRepository : IStudentRepository, IDisposable    {        private SchoolContext context;

public StudentRepository(SchoolContext context)        {            this.context = context;        }

public IEnumerable<Student> GetStudents()        {            return context.Students.ToList();        }

public Student GetStudentByID(int id)        {            return context.Students.Find(id);        }

public void InsertStudent(Student student)        {            context.Students.Add(student);        }

public void DeleteStudent(int studentID)        {            Student student = context.Students.Find(studentID);            context.Students.Remove(student);        }

public void UpdateStudent(Student student)        {            context.Entry(student).State = EntityState.Modified;        }

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);        }    }}

3.改造学生控制器

通过上面的操作  现在我们的控制器  针对的是一个接口 而不再是直接通过dbContext   当然 如果我们这里使用IOC容器  将能实现更好的解耦 大家可以看下

桀骜的灵魂的 这篇文章

4.改造后出现的问题

1.我们本来在控制器里实现的dbContext,  现在这个被放在了Repository里   因为项目会有多个Repository  所以会有多个dbContext!  这是个时候 当我们有

一个复杂的逻辑   要用到多个Repository时  比如 我随便说个例子~~  比如 添加个订单 更新个详细订单 再删除个客户 假设这一系列的逻辑 是在一起的  这里就会用到三个 Repository    我们要调用不同的Repository  里的 SAVE()    这时 我们就要做多次提交 而且不再是统一的事务了  效率 完整性 都大大的下降了。反而失去了

dbContext 自带 Unit of Work的好处~~   没关系  后面我们会再实现Unit of Work 来改造这个问题

2.第二个问题 也是个很严重的问题 我们以前的时候  再查找学生  搜索 排序 以及分页时  是这样的

var students = from s in context.Students               select s;if (!String.IsNullOrEmpty(searchString)){    students = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())                           || s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));}

而现在变成了

var students = from s in studentRepository.GetStudents()               select s;

这有个很严重的问题 以前是 IQueryable 而现在的是  IEnumerable  变成了数据直接全部查找出来  所以再这里 我觉得Repository的查找 应该是返回IQueryable

而不是 IEnumerable 

要不然 就出现了我最早在文中的疑问  这不就是普通的CRUD 一个普通的数据访问层而已

Repository用法我觉得应该是 返回IQueryable 参数的接受应该是一个表达式树  不知道大家是否认同?希望大家帮我解决下疑惑 谢谢~

再下面的 公共泛型 Repository 会体现这个

三.创建一个公共的Repository

看看我们上面的学生Repository   如果我们再写 课程 院系 等等 这些代码 会非常类似  所以我们利用泛型注入 来实现复用  这里应该实现一个泛型接口 和泛型类

但是原文没有实现接口~ 只有个类

让我来看下这个类

namespace ContosoUniversity.DAL{    public class GenericRepository<TEntity> where TEntity : class    {        internal SchoolContext context;        internal DbSet<TEntity> dbSet;

public GenericRepository(SchoolContext context)        {            this.context = context;            this.dbSet = context.Set<TEntity>();        }

public virtual IEnumerable<TEntity> Get(            Expression<Func<TEntity, bool>> filter = null,            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,            string includeProperties = "")        {            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)            {                return orderBy(query).ToList();            }            else            {                return query.ToList();            }        }

public virtual TEntity GetByID(object id)        {            return dbSet.Find(id);        }

public virtual void Insert(TEntity entity)        {            dbSet.Add(entity);        }

public virtual void Delete(object id)        {            TEntity entityToDelete = dbSet.Find(id);            Delete(entityToDelete);        }

public virtual void Delete(TEntity entityToDelete)        {            if (context.Entry(entityToDelete).State == EntityState.Detached)            {                dbSet.Attach(entityToDelete);            }            dbSet.Remove(entityToDelete);        }

public virtual void Update(TEntity entityToUpdate)        {            dbSet.Attach(entityToUpdate);            context.Entry(entityToUpdate).State = EntityState.Modified;        }    }}

这里重点讲这个方法

public virtual IEnumerable<TEntity> Get(            Expression<Func<TEntity, bool>> filter = null,            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,            string includeProperties = "")        {            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)            {                return orderBy(query).ToList();            }            else            {                return query.ToList();            }        }

还是如上面所说 我觉得这里应该返回的是 IQueryable 所以我觉得应该 去掉最后的 .ToList  并且返回IQueryable

然后 来看这个方法  第一个接受一个表达式树   其实就是过滤条件  第二个 是个委托 主要是用来排序的   第三个接受要贪婪加载哪些导航属性 可以用逗号隔开

并且 这里利用了下4.0的功能 可以给参数个默认值  都是空  怎么用这个方法 后面会写的有~~

还有个说的地方 以前我没有提到过   这里稍带的说下

     public virtual void Delete(TEntity entityToDelete)        {            if (context.Entry(entityToDelete).State == EntityState.Detached)            {                dbSet.Attach(entityToDelete);            }            dbSet.Remove(entityToDelete);        }

这个 dbSet.Attach(entityToDelete);  表示将对象添加到数据库上下文中   受dbcontext管理  这里我有个小疑问  不加这个判断 是否也可以 加这个的好处是?

四.创建 Unit of Work Class

创建这个类的主要目的  就是为了确保 多个Repository可以共享一个 database context  让我们看下这个类

namespace ContosoUniversity.DAL{    public class UnitOfWork : IDisposable    {        private SchoolContext context = new SchoolContext();        private GenericRepository<Department> departmentRepository;        private GenericRepository<Course> courseRepository;

public GenericRepository<Department> DepartmentRepository        {            get            {

if (this.departmentRepository == null)                {                    this.departmentRepository = new GenericRepository<Department>(context);                }                return departmentRepository;            }        }

public GenericRepository<Course> CourseRepository        {            get            {

if (this.courseRepository == null)                {                    this.courseRepository = new GenericRepository<Course>(context);                }                return courseRepository;            }        }

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);        }    }}

把想让unit of work 帮你整体控制的类写到里面 这里 我们只写了两个

private SchoolContext context = new SchoolContext();private GenericRepository<Department> departmentRepository;private GenericRepository<Course> courseRepository;

实现只读属性

public GenericRepository<Department> DepartmentRepository{    get    {

if (this.departmentRepository == null)        {            this.departmentRepository = new GenericRepository<Department>(context);        }        return departmentRepository;    }}

最后是保存和资源的释放

好 现在看下怎么用这个~

五.改变课程控制器

原文控制器代码

原文的这个demo 并没有很好的体现出 使用 unit of work 的好处  因为他的这个例子没有出现一个逻辑用到多个资源库的  希望大家明白这点~~ 等以后的文章

我会写个完整的demo  来说明这点 这里大家先看下 明白怎么回事就行~

六.EF+MVC框架的疑问

1.是否有必要实现 IUnitOfWork 接口?代码大概这样

public interface IUnitOfWork

{

void Save();

IStudentRepository StudentRepository { get; }

}

实现接口

public class UnitOfWork : IUnitOfWork

{

private SchoolEntities context = new SchoolEntities();

private IStudentRepository studentRepository;

public IStudentRepository StudentRepository

{

get

{

if (this.studentRepository == null)

{

this.studentRepository = new StudentRepository(context);

}

return studentRepository;

}

}

public void Save()

{

context.SaveChanges();

}

}

控制器

private IUnitOfWork unitOfWork;

public StudentController () : this (new UnitOfWork())

{

}

public StudentController (IUnitOfWork unitOfWork)

{

this.unitOfWork = unitOfWork;

}

2. 是否有必要再  加上一个 服务层 service  这个层 在 控制器和dal 之间   也就是 通过 unitofwork 调用的东西等 都写在servie上  这样控制器里的代码会变得非常少, 个人觉得应该加上service层的,但是是否需要加Iservice 接口    加这个接口 能获得哪些好处 ? 我一直觉得 只用 给数据访问层 实现接口 就行  ~~

3. 这里我们是用的unit of work 完成了事务的一致性  以前我是使用

using (TransactionScope transaction = new TransactionScope()){   ....

transaction.Complete();   } 

用这个来实现 事务一致性  不知道大家觉得 这个和 unit of work 比怎么样? 我暂时还没研究这个~     但是 小城岁月对这个做了很好的介绍 感谢小城~ 大家可以参考他的这篇文章

4. 我们的缓存 比如用的 mongodb 这个写到哪层比较好呢?

六.总结

经过重构 代码终于有些项目的样子了~~   下节讲讲EF的其他一些功能 如 直接执行SQL语句,关闭跟踪状态这些~~

时间: 2024-10-06 06:23:49

MVC3+EF4.1学习系列(八)-----利用Repository and Unit of Work重构项目的相关文章

MVC3+EF4.1学习系列(十一)----EF4.1常见的问题解决

博客写了10篇了~有很多朋友私信问了一些问题,而且很多问题 大家问的都一样 这里说说这些常见问题的解决办法.如果大家有更好的解决办法~也希望分享出来 问题大概为这几个 一.ef4.1 codeFirst 修改表结构 增加字段等 EF code first需要重新生成库导致数据丢失的问题. 二.ef4.1 没有了edmx等复杂的东西 变得简单 干净  但如何使用存储过程,存储过程可以返回表 可以返回数值 也有可能是执行修改 删除 增加等  该怎么做? 三.ef4.1 如何使用数据库视图?每个视图都

MVC3+EF4.1学习系列(一)-------创建EF4.1 code first的第一个实例

基于EF4.1 code first 简单的CRUD  园子中已经有很多了 ~~ 真不想再写这个了 可是为了做一个完整的小demo 从开始 到后面的一些简单重构 还是决定认真把这个写出来 争取写些别人没写到的东西~~ 好了 开始~~ 这次要做的是个学校管理的demo(通俗些) 先建一个MVC3的应用程序  因为我们是code first 所以 开始创建实体类 一.创建Model 学生和学生成绩登记表是一对多的关系  一个学生可以有多次登记 (因为有多个课程)  一个课程也可以有多个登记   可以

MVC3+EF4.1学习系列(二)-------基础的增删改查和持久对象的生命周期变化

上篇文章中 我们已经创建了EF4.1基于code first的例子  有了数据库 并初始化了一些数据  今天这里写基础的增删改查和持久对象的生命周期变化 学习下原文先把运行好的原图贴来上~~ 一.创建详细页 首先 我们先在控制器下 添加详细页的方法 因为这篇文章后面要介绍持久对象声明周期的变化 所以在这里先看下有哪些状态 EF里一共有这五中生命状态类型 其实 看名字我们可以大概猜测出个一二三来~~  游离的 未改变的  新添加的  已删除的 修改的  但是是怎么变化的能 我们在后面的代码中实践与

MVC3+EF4.1学习系列(五)----- EF查找导航属性的几种方式

通过上一篇的学习 我们把demo的各种关系终于搭建里起来 以及处理好了如何映射到数据库等问题 但是 只是搭建好了关系 问题还远没有解决 这篇就来写如何查找导航属性 和查找导航属性的几种方式 已经跟踪生成的SQL来检测是否满意 通过这节学习 来明白什么时候用哪个~~ 一.三种加载 1.延迟加载 这是原文中的图 大家可以去看下  我模仿上面的做了个测试  出现了  已有打开的与此 Command 相关联的 DataReader,必须首先将它关闭. 我的解决办法是    var departments

MVC3+EF4.1学习系列(九)-----EF4.1其他的一些技巧的使用

上节通过一系列重构 简单的项目就实现了 不过还有些EF的功能没有讲 这节就通过项目 讲讲EF其他的功能与技巧 一.直接执行SQL语句 通常来讲 EF 不用写SQL语句的  但是 在有些场合  比如对生成的SQL语句 觉得不满意 要做优化  或者做报表统计时 要写很变态的SQL语句 再或者 批量操作等   这个时候 使用ORM的弱点就显露了出来 但是 做为优秀的ORM框架  EF 是支持原生态的SQL的   这里面 提供了三种方法 1. DbSet.SqlQuery   有跟踪状态的查询  2. 

MVC3+EF4.1学习系列(十)----MVC+EF处理树形结构

通过前几篇文章 我们处理了 一对一, 一对多,多对多关系 很好的发挥了ORM框架的做用 但是 少说了一种 树形结构的处理, 而这种树形关系 我们也经常遇到,常见的N级类别的处理, 以及经常有数据与类别挂钩.今天主要写下EF处理树形结构以及 MVC如何展示树形结构. 前面几篇的例子 一直用的是一个例子,内容是连贯的.这篇是完全单独的~ 先来说下工作中会遇到的常见场景 针对这几个场景来处理~ 1.类别 a.类别可以有无限级别 b.类别的最末端 不确定是第几级 某个节点 可以到二级 其他的节点 有可能

MVC3+EF4.1学习系列(七)-----EF并发的处理

看这篇文章之前 推荐园子里的 这个文章已经有介绍了 而且写的很好~~ 可以先看下他的 再看我的 并发 1.悲观并发 简单的说 就是一个用户访问一条数据时 则把这个数据变为只读属性  把该数据变为独占 只有该用户释放了这条数据 其他用户才能修改 这期间如果该用户上个厕所 出去玩一圈 没有退出 则其他人都要等很久 很显然 这不是我们期望的效果  也不是这篇文章讨论的重点 2.乐观并发 乐观并发相对悲观并发,用户读取数据时不锁定数据.当一个用户更新数据时,系统将进行检查,查看该用户读取数据后其他用户是

Linux学习系列八:操作网口

一些相对高性能的单片机会带以太网接口,网口在MCU里算是比较复杂的外设了,因为它涉及到网络协议栈,通常情况下网络协议栈会运行在一个RTOS中,所以对普通单片机开发者来说网口使用起来相对难度较大一些.在Linux下网口是一个经常使用的接口,由于Linux具备成熟完备的网络通信协议栈,底层驱动厂家也都提供好了,所以使用起来相对方便的多.本篇对Linux下网口使用做个简单总结,希望对大家有所帮助. 内容主要包括使用buildroot来是实现ssh功能,UDP通信的例子,以及实际中容易犯的一个错误. 原

Git学习系列之如何正确且高效地将本地项目上传到Github(图文详解)

不多说,直接上干货! 首先你需要一个Github账号,所以还没有的话先去注册吧! https://github.com/ 见 Git的安装 见 Git学习系列之Windows上安装Git详细步骤(图文详解) 1.进入Github首页,点击New repository新建一个项目 点击Clone or dowload会出现一个地址,copy这个地址备用. 创建 ssh-keygen -t rsa -C "***@***.com" 欢迎大家,加入我的微信公众号:大数据躺过的坑