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语句,关闭跟踪状态这些~~
MVC3+EF4.1学习系列(八)-----利用Repository and Unit of Work重构项目的更多相关文章
- MVC3+EF4.1学习系列(十一)----EF4.1常见的问题解决
博客写了10篇了~有很多朋友私信问了一些问题,而且很多问题 大家问的都一样 这里说说这些常见问题的解决办法.如果大家有更好的解决办法~也希望分享出来 问题大概为这几个 一.ef4.1 codeFirs ...
- MVC3+EF4.1学习系列(四)----- ORM关系的处理
上篇文章 终于把基础的一些操作写完了 但是这些都是单表的处理 而EF做为一个ORM框架 就必须点说说对于关系的处理 处理好关系 才能灵活的运用EF 关于关系的处理 一般就是 一对一 一对多 ...
- MVC3+EF4.1学习系列(一)-------创建EF4.1 code first的第一个实例
基于EF4.1 code first 简单的CRUD 园子中已经有很多了 ~~ 真不想再写这个了 可是为了做一个完整的小demo 从开始 到后面的一些简单重构 还是决定认真把这个写出来 争取写些别人 ...
- MVC3+EF4.1学习系列(五)----- EF查找导航属性的几种方式
文章索引和简介 通过上一篇的学习 我们把demo的各种关系终于搭建里起来 以及处理好了如何映射到数据库等问题 但是 只是搭建好了关系 问题还远没有解决 这篇就来写如何查找导航属性 和查找导航属性的几种 ...
- MVC3+EF4.1学习系列(九)-----EF4.1其他的一些技巧的使用
上节通过一系列重构 简单的项目就实现了 不过还有些EF的功能没有讲 这节就通过项目 讲讲EF其他的功能与技巧 一.直接执行SQL语句 通常来讲 EF 不用写SQL语句的 但是 在有些场合 比如对生 ...
- MVC3+EF4.1学习系列(十)----MVC+EF处理树形结构
通过前几篇文章 我们处理了 一对一, 一对多,多对多关系 很好的发挥了ORM框架的做用 但是 少说了一种 树形结构的处理, 而这种树形关系 我们也经常遇到,常见的N级类别的处理, 以及经常有数据与类别 ...
- MVC3+EF4.1学习系列(六)-----导航属性数据更新的处理
通过上一篇的学习 我们已经知道怎么查询关系 这篇就来说说怎么导航属性数据更新时的处理 以及EF又会为我们生成哪些SQL~ 老规矩 先看下今天的图 添加和修改页面基本就是这样 这节的内容相对简单~~ 主 ...
- MVC3+EF4.1学习系列(三)-----排序 刷选 以及分页
上篇文章 已经做出了基本的增删改查 但这远远不足以应付实际的项目 今天讲下实际项目中 肯定会有的 排序 刷选 以及分页. 重点想多写点分页的 毕竟这个是任何时候都要有的 而且 我会尽量把这个 ...
- MVC3+EF4.1学习系列(二)-------基础的增删改查和持久对象的生命周期变化
上篇文章中 我们已经创建了EF4.1基于code first的例子 有了数据库 并初始化了一些数据 今天这里写基础的增删改查和持久对象的生命周期变化 学习下原文先把运行好的原图贴来上~~ 一.创建 ...
随机推荐
- linux看代码方法和建议
http://blog.csdn.net/lxl584685501/article/details/46803077
- 如何运用xp_cmdshell 执行多行cmd命令
因为需要利用sql 脚本调用外部程序,所以尝试xp_cmdshell 命令,发现其默认只能执行一行命令,最后找到了下面方法: 将多行cmd 命令处理成bat文件,然后直接利用所在路径+文件来执行: ...
- swift3 UIColor扩展
//返回一个RGB颜色 class func getColor(r:CGFloat,g:CGFloat,b:CGFloat,l:CGFloat = 1) ->UIColor{ let color ...
- ntopng源码分析
参数初始化以及ntop主流程启动 #ifndef WIN32 ) && (argv[][] != '-')) rc = prefs->loadFromFile(argv[]); ...
- AndroidPullToRefresh拉动效果配置
最近用了 开源的 AndroidPullToRefresh 库,但是发现拉动时的效果有个很奇怪的地方,无论上下拉动,当列表滚动到顶部或底部时,会瞬间弹出半个列表高度的拉动提示,感觉很不舒服,这种提示根 ...
- ssh框架的小实例(用户登录)
刚学SSH框架写一个小实例,以便以后查看: 本案例简单的实现一个用户登录: 数据库方面就不写了,自己领悟吧!哈哈(根据user.hbm.xml文件就知道了) 我们一般可以创建下面几个包,什么意思呢,自 ...
- 查看Oracle正在执行的任务
select a.program, b.spid, c.sql_text,c.SQL_ID from v$session a, v$process b, v$sqlarea c where a.pad ...
- 移动互联网app
移动互联网app 2 App测试点 2.1安全测试 2.1.1软件权限 1隐私泄露风险:包括访问手机信息.访问联系人信息等 2对App的输入有效性校验.认证.授权.敏感数据存储.数据加密等方面进行检测 ...
- iOS 加载本地 HTML 文件 CSS 样式图片无效果
在开发的过程中,有时候需要加载一些 HTML 页面,对于不太复杂的界面,基本上都可以放到本地用 UIWebview 来加载,但是在开发过程中会碰到 UIWebview 加载出来的 HTML 页面没有图 ...
- wall time
"showing elapsed time and wall clock time correspondingly." what's difference between elap ...