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語句,關閉跟蹤狀態這些~~

浙公網安備 33010602011771號