• 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.改造学生控制器

    View Code
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.Entity;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using ContosoUniversity.Models;
    using ContosoUniversity.DAL;
    using PagedList;

    namespace ContosoUniversity.Controllers
    {
    public class StudentController : Controller
    {
    private IStudentRepository studentRepository;


    public StudentController()
    {
    this.studentRepository = new StudentRepository(new SchoolContext());
    }

    public StudentController(IStudentRepository studentRepository)
    {
    this.studentRepository = studentRepository;
    }


    //
    // GET: /Student/

    public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
    {
    ViewBag.CurrentSort
    = sortOrder;
    ViewBag.NameSortParm
    = String.IsNullOrEmpty(sortOrder) ? "Name desc" : "";
    ViewBag.DateSortParm
    = sortOrder == "Date" ? "Date desc" : "Date";

    if (Request.HttpMethod == "GET")
    {
    searchString
    = currentFilter;
    }
    else
    {
    page
    = 1;
    }
    ViewBag.CurrentFilter
    = searchString;

    var students
    = from s in studentRepository.GetStudents()
    select s;
    if (!String.IsNullOrEmpty(searchString))
    {
    students
    = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
    || s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));
    }
    switch (sortOrder)
    {
    case "Name desc":
    students
    = students.OrderByDescending(s => s.LastName);
    break;
    case "Date":
    students
    = students.OrderBy(s => s.EnrollmentDate);
    break;
    case "Date desc":
    students
    = students.OrderByDescending(s => s.EnrollmentDate);
    break;
    default:
    students
    = students.OrderBy(s => s.LastName);
    break;
    }

    int pageSize = 3;
    int pageIndex = (page ?? 1) - 1;
    return View(students.ToPagedList(pageIndex, pageSize));
    }


    //
    // GET: /Student/Details/5

    public ViewResult Details(int id)
    {
    Student student
    = studentRepository.GetStudentByID(id);
    return View(student);
    }

    //
    // GET: /Student/Create

    public ActionResult Create()
    {
    return View();
    }

    //
    // POST: /Student/Create

    [HttpPost]
    public ActionResult Create(Student student)
    {
    try
    {
    if (ModelState.IsValid)
    {
    studentRepository.InsertStudent(student);
    studentRepository.Save();
    return RedirectToAction("Index");
    }
    }
    catch (DataException)
    {
    //Log the error (add a variable name after DataException)
    ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
    }
    return View(student);
    }

    //
    // GET: /Student/Edit/5

    public ActionResult Edit(int id)
    {
    Student student
    = studentRepository.GetStudentByID(id);
    return View(student);
    }

    //
    // POST: /Student/Edit/5

    [HttpPost]
    public ActionResult Edit(Student student)
    {
    try
    {
    if (ModelState.IsValid)
    {
    studentRepository.UpdateStudent(student);
    studentRepository.Save();
    return RedirectToAction("Index");
    }
    }
    catch (DataException)
    {
    //Log the error (add a variable name after DataException)
    ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
    }
    return View(student);
    }

    //
    // GET: /Student/Delete/5

    public ActionResult Delete(int id, bool? saveChangesError)
    {
    if (saveChangesError.GetValueOrDefault())
    {
    ViewBag.ErrorMessage
    = "Unable to save changes. Try again, and if the problem persists see your system administrator.";
    }
    Student student
    = studentRepository.GetStudentByID(id);
    return View(student);
    }


    //
    // POST: /Student/Delete/5

    [HttpPost, ActionName(
    "Delete")]
    public ActionResult DeleteConfirmed(int id)
    {
    try
    {
    Student student
    = studentRepository.GetStudentByID(id);
    studentRepository.DeleteStudent(id);
    studentRepository.Save();
    }
    catch (DataException)
    {
    //Log the error (add a variable name after DataException)
    return RedirectToAction("Delete",
    new System.Web.Routing.RouteValueDictionary {
    {
    "id", id },
    {
    "saveChangesError", true } });
    }
    return RedirectToAction("Index");
    }

    protected override void Dispose(bool disposing)
    {
    studentRepository.Dispose();
    base.Dispose(disposing);
    }
    }
    }

    通过上面的操作  现在我们的控制器  针对的是一个接口 而不再是直接通过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;
    }
    }

    最后是保存和资源的释放

    好 现在看下怎么用这个~

    五.改变课程控制器

    原文控制器代码
    namespace ContosoUniversity.Controllers
    {
    public class CourseController : Controller
    {
    private UnitOfWork unitOfWork = new UnitOfWork();

    //
    // GET: /Course/

    public ViewResult Index()
    {
    var courses
    = unitOfWork.CourseRepository.Get(includeProperties: "Department");
    return View(courses.ToList());
    }

    //
    // GET: /Course/Details/5

    public ViewResult Details(int id)
    {
    Course course
    = unitOfWork.CourseRepository.GetByID(id);
    return View(course);
    }

    //
    // GET: /Course/Create

    public ActionResult Create()
    {
    PopulateDepartmentsDropDownList();
    return View();
    }

    [HttpPost]
    public ActionResult Create(Course course)
    {
    try
    {
    if (ModelState.IsValid)
    {
    unitOfWork.CourseRepository.Insert(course);
    unitOfWork.Save();
    return RedirectToAction("Index");
    }
    }
    catch (DataException)
    {
    //Log the error (add a variable name after DataException)
    ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
    }

    public ActionResult Edit(int id)
    {
    Course course
    = unitOfWork.CourseRepository.GetByID(id);
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
    }

    [HttpPost]
    public ActionResult Edit(Course course)
    {
    try
    {
    if (ModelState.IsValid)
    {
    unitOfWork.CourseRepository.Update(course);
    unitOfWork.Save();
    return RedirectToAction("Index");
    }
    }
    catch (DataException)
    {
    //Log the error (add a variable name after DataException)
    ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
    }

    private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
    {
    var departmentsQuery
    = unitOfWork.DepartmentRepository.Get(
    orderBy: q
    => q.OrderBy(d => d.Name));
    ViewBag.DepartmentID
    = new SelectList(departmentsQuery, "DepartmentID", "Name", selectedDepartment);
    }

    //
    // GET: /Course/Delete/5

    public ActionResult Delete(int id)
    {
    Course course
    = unitOfWork.CourseRepository.GetByID(id);
    return View(course);
    }

    //
    // POST: /Course/Delete/5

    [HttpPost, ActionName(
    "Delete")]
    public ActionResult DeleteConfirmed(int id)
    {
    Course course
    = unitOfWork.CourseRepository.GetByID(id);
    unitOfWork.CourseRepository.Delete(id);
    unitOfWork.Save();
    return RedirectToAction("Index");
    }

    protected override void Dispose(bool disposing)
    {
    unitOfWork.Dispose();
    base.Dispose(disposing);
    }
    }
    }

    原文的这个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语句,关闭跟踪状态这些~~

  • 相关阅读:
    Linux环境变量$PATH
    grep
    echo命令
    ip命令
    浅析Linux下的/etc/profile、/etc/bashrc、~/.bash_profile、~/.bashrc文件
    shell脚本4种执行方式
    /proc路径
    tr命令
    Linux命令cut
    前端论坛网站知识
  • 原文地址:https://www.cnblogs.com/wlflovenet/p/EFandMvc9.html
Copyright © 2020-2023  润新知