原文 Contoso 大学 - 9 - 实现仓储和工作单元模式

By Tom Dykstra, Tom Dykstra is a Senior Programming Writer on Microsoft's Web Platform & Tools Content Team.

原文地址:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
全文目录:Contoso 大学 - 使用 EF Code First 创建 MVC 应用

在上一次的教程中,你已经使用继承来消除在 Student 和 Instructor 实体之间的重复代码。在这个教程中,你将要看到使用仓储和工作单元模式进行增、删、改、查的一些方法。像前面的教程一样,你将要修改已经创建的页面中代码的工作方式,而不是新创建的页面。

9-1  仓储和工作单元模式

仓储和工作单元模式用来在数据访问层和业务逻辑层之间创建抽象层。实现这些模式有助于隔离数据存储的变化,便于自动化的单元测试或者测试驱动的开发 ( TDD )。
在这个教程中,你将要为每个实体类型实现一个仓储类。对于 Student 实体来说,你需要创建一个仓储接口和一个仓储类。当在控制器中实例化仓储对象的时候。你将会通过接口来使用它,当控制器在 Web 服务器上运行的时候,控制器将会接受任何实现仓储接口的对象引用。通过接收仓储对象进行数据的存储管理,使得你可以容易地控制测试,就像使用内存中的集合一样。
在教程的最后,你将要在 Course 控制器中对 Course 和 Department 实体使用多个仓储和一个工作单元类。工作单元类则通过创建一个所有仓储共享的数据库上下文对象,来组织多个仓储对象。如果你希望执行自动化的单元测试,你也应该对 Student类通过相同的方式创建和使用接口。不管怎样,为了保持教程的简单,你将不会通过接口创建和使用这些类。
下面的截图展示了在控制器和上下文之间的概念图,用来比较与不使用仓储或工作单元模式的区别。

在这个教程中不会创建单元测试,在 MVC 应用中使用仓储模式进行 TDD 的相关信息,可以查看 MSDN 网站中的 Walkthrough: Using TDD with ASP.NET MVC ,EF 团队博客中的 Using Repository and Unit of Work patterns with Entity Framework 4.0 ,以及 Julie Lerman 的博客 Agile Entity Framework 4 Repository  系列。
注意:有多种方式可以实现仓储和工作单元模式。配合工作单元类可以使用也可以不使用仓储类。可以对所有的实体类型实现一个简单的仓储,或者每种类型一个。如果为每种类型实现一个仓储,还可以通过分离的类,或者泛型的基类然后派生,或者抽象基类然后派生。可以将业务逻辑包含在仓储中,或者限制只有数据访问逻辑。也可以通过在实体中使用 IDbSet 接口代替 DbSet 类为数据库上下文类创建一个抽象层。在这个教程中展示的目标实现了抽象层,只是其中一种考虑,并不是针对所有的场景和环境都适用。

9-2  创建 Student 仓储类

在 DAL 文件夹中,创建一个文件名为 IStudentRepository.cs 的文件,将当前的代码使用如下代码替换。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5. using ContosoUniversity.Models;
  6.  
  7. namespace ContosoUniversity.DAL
  8. {
  9. public interface IStudentRepository : IDisposable
  10. {
  11. IEnumerable<Student> GetStudents();
  12. Student GetStudentByID(int studentId);
  13. void InsertStudent(Student student);
  14. void DeleteStudent(int studentID);
  15. void UpdateStudent(Student student);
  16. void Save();
  17. }
  18. }

代码定义了一套典型的增、删、改、查方法。包括两个读取方法 – 一个返回所有的学生实体,一个通过 ID 查询单个实体。
在 DAL 文件夹中,创建名为 StudentRepository.cs 的类文件,使用下面的代码替换原有的代码,这个类实现了 IStudentRepository 接口。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Data;
  5. using ContosoUniversity.Models;
  6.  
  7. namespace ContosoUniversity.DAL
  8. {
  9. public class StudentRepository : IStudentRepository, IDisposable
  10. {
  11. private SchoolContext context;
  12.  
  13. public StudentRepository(SchoolContext context)
  14. {
  15. this.context = context;
  16. }
  17.  
  18. public IEnumerable<Student> GetStudents()
  19. {
  20. return context.Students.ToList();
  21. }
  22.  
  23. public Student GetStudentByID(int id)
  24. {
  25. return context.Students.Find(id);
  26. }
  27.  
  28. public void InsertStudent(Student student)
  29. {
  30. context.Students.Add(student);
  31. }
  32.  
  33. public void DeleteStudent(int studentID)
  34. {
  35. Student student = context.Students.Find(studentID);
  36. context.Students.Remove(student);
  37. }
  38.  
  39. public void UpdateStudent(Student student)
  40. {
  41. context.Entry(student).State = EntityState.Modified;
  42. }
  43.  
  44. public void Save()
  45. {
  46. context.SaveChanges();
  47. }
  48.  
  49. private bool disposed = false;
  50.  
  51. protected virtual void Dispose(bool disposing)
  52. {
  53. if (!this.disposed)
  54. {
  55. if (disposing)
  56. {
  57. context.Dispose();
  58. }
  59. }
  60. this.disposed = true;
  61. }
  62.  
  63. public void Dispose()
  64. {
  65. Dispose(true);
  66. GC.SuppressFinalize(this);
  67. }
  68. }
  69. }

数据库上下文是类中定义的一个成员变量,构造函数期望传递一个数据库上下文对象实例。

  1. private SchoolContext context;
  2.  
  3. public StudentRepository(SchoolContext context)
  4. {
  5. this.context = context;
  6. }

你需要创建一个新的数据库上下文实例,但是如果在控制器中需要使用多个仓储类,每一个会得到一个不同的数据库上下文对象。后面在 Course 控制器中,你将要使用多个仓储,会看到如何使用工作单元类来保证所有的仓储使用相同的数据库上下文对象。
仓储类还实现了 IDisposable 接口,如同在前面控制器中所见,释放数据库上下文,仓储的增删改查方法也如前所见调用数据库上下文的方法。

9-3  修改 Student 控制器使用仓储

在 StudentController.cs 中,使用下面的代码替换现有的代码。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Data.Entity;
  5. using System.Linq;
  6. using System.Web;
  7. using System.Web.Mvc;
  8. using ContosoUniversity.Models;
  9. using ContosoUniversity.DAL;
  10. using PagedList;
  11.  
  12. namespace ContosoUniversity.Controllers
  13. {
  14. public class StudentController : Controller
  15. {
  16. private IStudentRepository studentRepository;
  17.  
  18. public StudentController()
  19. {
  20. this.studentRepository = new StudentRepository(new SchoolContext());
  21. }
  22.  
  23. public StudentController(IStudentRepository studentRepository)
  24. {
  25. this.studentRepository = studentRepository;
  26. }
  27.  
  28. //
  29. // GET: /Student/
  30.  
  31. public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
  32. {
  33. ViewBag.CurrentSort = sortOrder;
  34. ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "Name desc" : "";
  35. ViewBag.DateSortParm = sortOrder == "Date" ? "Date desc" : "Date";
  36.  
  37. if (Request.HttpMethod == "GET")
  38. {
  39. searchString = currentFilter;
  40. }
  41. else
  42. {
  43. page = 1;
  44. }
  45. ViewBag.CurrentFilter = searchString;
  46.  
  47. var students = from s in studentRepository.GetStudents()
  48. select s;
  49. if (!String.IsNullOrEmpty(searchString))
  50. {
  51. students = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
  52. || s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));
  53. }
  54. switch (sortOrder)
  55. {
  56. case "Name desc":
  57. students = students.OrderByDescending(s => s.LastName);
  58. break;
  59. case "Date":
  60. students = students.OrderBy(s => s.EnrollmentDate);
  61. break;
  62. case "Date desc":
  63. students = students.OrderByDescending(s => s.EnrollmentDate);
  64. break;
  65. default:
  66. students = students.OrderBy(s => s.LastName);
  67. break;
  68. }
  69.  
  70. int pageSize = 3;
  71. int pageNumber = (page ?? 1);
  72. return View(students.ToPagedList(pageNumber, pageSize));
  73. }
  74.  
  75. //
  76. // GET: /Student/Details/5
  77.  
  78. public ViewResult Details(int id)
  79. {
  80. Student student = studentRepository.GetStudentByID(id);
  81. return View(student);
  82. }
  83.  
  84. //
  85. // GET: /Student/Create
  86.  
  87. public ActionResult Create()
  88. {
  89. return View();
  90. }
  91.  
  92. //
  93. // POST: /Student/Create
  94.  
  95. [HttpPost]
  96. public ActionResult Create(Student student)
  97. {
  98. try
  99. {
  100. if (ModelState.IsValid)
  101. {
  102. studentRepository.InsertStudent(student);
  103. studentRepository.Save();
  104. return RedirectToAction("Index");
  105. }
  106. }
  107. catch (DataException)
  108. {
  109. //Log the error (add a variable name after DataException)
  110. ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
  111. }
  112. return View(student);
  113. }
  114.  
  115. //
  116. // GET: /Student/Edit/5
  117.  
  118. public ActionResult Edit(int id)
  119. {
  120. Student student = studentRepository.GetStudentByID(id);
  121. return View(student);
  122. }
  123.  
  124. //
  125. // POST: /Student/Edit/5
  126.  
  127. [HttpPost]
  128. public ActionResult Edit(Student student)
  129. {
  130. try
  131. {
  132. if (ModelState.IsValid)
  133. {
  134. studentRepository.UpdateStudent(student);
  135. studentRepository.Save();
  136. return RedirectToAction("Index");
  137. }
  138. }
  139. catch (DataException)
  140. {
  141. //Log the error (add a variable name after DataException)
  142. ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
  143. }
  144. return View(student);
  145. }
  146.  
  147. //
  148. // GET: /Student/Delete/5
  149.  
  150. public ActionResult Delete(int id, bool? saveChangesError)
  151. {
  152. if (saveChangesError.GetValueOrDefault())
  153. {
  154. ViewBag.ErrorMessage = "Unable to save changes. Try again, and if the problem persists see your system administrator.";
  155. }
  156. Student student = studentRepository.GetStudentByID(id);
  157. return View(student);
  158. }
  159.  
  160. //
  161. // POST: /Student/Delete/5
  162.  
  163. [HttpPost, ActionName("Delete")]
  164. public ActionResult DeleteConfirmed(int id)
  165. {
  166. try
  167. {
  168. Student student = studentRepository.GetStudentByID(id);
  169. studentRepository.DeleteStudent(id);
  170. studentRepository.Save();
  171. }
  172. catch (DataException)
  173. {
  174. //Log the error (add a variable name after DataException)
  175. return RedirectToAction("Delete",
  176. new System.Web.Routing.RouteValueDictionary {
  177. { "id", id },
  178. { "saveChangesError", true } });
  179. }
  180. return RedirectToAction("Index");
  181. }
  182.  
  183. protected override void Dispose(bool disposing)
  184. {
  185. studentRepository.Dispose();
  186. base.Dispose(disposing);
  187. }
  188. }
  189. }

在控制器中定义了一个 IStudentRepository 接口的类变量,而不是直接的数据库上下文。

  1. private IStudentRepository studentRepository;

默认的构造函数创建一个新的上下文接口,可选的构造函数允许调用者传递一个数据库上下文实例。

  1. public StudentController()
  2. {
  3. this.studentRepository = new StudentRepository(new SchoolContext());
  4. }
  5.  
  6. public StudentController(IStudentRepository studentRepository)
  7. {
  8. this.studentRepository = studentRepository;
  9. }

( 如果使用依赖注入,或者 DI,就不需要默认构造函数,因为 DI 容器会为你创建正确的仓储对象 )
在 CRUD 方法中,调用仓储方法来而不是数据库上下文的方法。

  1. var students = from s in studentRepository.GetStudents()
  2. select s;
  1. Student student = studentRepository.GetStudentByID(id);
  1. studentRepository.InsertStudent(student);
  2. studentRepository.Save();
  1. studentRepository.UpdateStudent(student);
  2. studentRepository.Save();
  1. studentRepository.DeleteStudent(id);
  2. studentRepository.Save();

现在的 Dispose 方法释放仓储而不是数据库上下文。

  1. studentRepository.Dispose();

运行程序,点击 Students 窗格。

现在的页面显示与使用仓储之前完全相同。其他的学生页面也一样。实际上,在 Index 控制器方法的过滤和排序中,存在一个重要的不同,原来版本的代码如下:

  1. var students = from s in context.Students
  2. select s;
  3. if (!String.IsNullOrEmpty(searchString))
  4. {
  5. students = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
  6. || s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));
  7. }

在原来版本的代码中,students 变量的类型是 IQueryable ,查询在使用诸如 ToList 方法转换为集合之前并不会发送到数据库中。这意味着这里的 Where 方法在处理到数据库中的时候变成 SQL 中的 where 子句。同时意味着仅仅选中的实体从数据库中返回。从 context.Students 修改为 studentRepository.GetStudents() 之后,代码中的 students 变量类型成为 IEnumerable 集合,包括数据库中所有的学生。通过 Where 方法得到的结果是一样的,但是处理在 Web 服务器的内存中进行,而不是在数据库中。对于大量的数据来说,这样做是低效的。后继的段落展示如何通过仓储方法实现在数据库中完成。
现在你已经在控制器和 EF 数据库上下文之间创建了抽象层。如果你将在这个程序中执行自动化的单元测试,可以在单元测试项目中创建一个替代的实现接口 IStudentRepository 仓储类,来代替实际的上下文完成读写数据。这个模拟 ( Mock ) 的仓储类可以通过操作内存中的集合来测试控制器功能。

9-4  实现泛型的仓储和工作单元

对每一个实体类型创建一个仓储将会导致大量重复代码。还会带来部分更新的问题。例如,假设在一个事务中更新两个不同的实体。 如果每一个仓储使用不同的数据库上下文实例,一个可能成功了,另外一个失败了。一种减少冗余代码的方式是使用泛型仓储,另一种方式是使用工作单元类来确保所有的仓储都使用同样的数据库上下文 ( 来协调所有的更新 )。
在这一节中,你将要创建 GenericRepository 类和 UnitOfWork 类。在 Course 控制器中使用它们来访问 Department 和 Course 实体集。如前所述,为了保持教程的简单,不为这些类创建接口,但是为了以后使用它们进行 TDD 的便利,你应该像在 Student 仓储中一样通过接口实现。

9-4-1  创建泛型仓储

在 DAL 文件夹中,创建 GenericRepository.cs ,使用下面的代码替换原有代码。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Data;
  5. using System.Data.Entity;
  6. using ContosoUniversity.Models;
  7. using System.Linq.Expressions;
  8.  
  9. namespace ContosoUniversity.DAL
  10. {
  11. public class GenericRepository<TEntity> where TEntity : class
  12. {
  13. internal SchoolContext context;
  14. internal DbSet<TEntity> dbSet;
  15.  
  16. public GenericRepository(SchoolContext context)
  17. {
  18. this.context = context;
  19. this.dbSet = context.Set<TEntity>();
  20. }
  21.  
  22. public virtual IEnumerable<TEntity> Get(
  23. Expression<Func<TEntity, bool>> filter = null,
  24. Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
  25. string includeProperties = "")
  26. {
  27. IQueryable<TEntity> query = dbSet;
  28.  
  29. if (filter != null)
  30. {
  31. query = query.Where(filter);
  32. }
  33.  
  34. foreach (var includeProperty in includeProperties.Split
  35. (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
  36. {
  37. query = query.Include(includeProperty);
  38. }
  39.  
  40. if (orderBy != null)
  41. {
  42. return orderBy(query).ToList();
  43. }
  44. else
  45. {
  46. return query.ToList();
  47. }
  48. }
  49.  
  50. public virtual TEntity GetByID(object id)
  51. {
  52. return dbSet.Find(id);
  53. }
  54.  
  55. public virtual void Insert(TEntity entity)
  56. {
  57. dbSet.Add(entity);
  58. }
  59.  
  60. public virtual void Delete(object id)
  61. {
  62. TEntity entityToDelete = dbSet.Find(id);
  63. Delete(entityToDelete);
  64. }
  65.  
  66. public virtual void Delete(TEntity entityToDelete)
  67. {
  68. if (context.Entry(entityToDelete).State == EntityState.Detached)
  69. {
  70. dbSet.Attach(entityToDelete);
  71. }
  72. dbSet.Remove(entityToDelete);
  73. }
  74.  
  75. public virtual void Update(TEntity entityToUpdate)
  76. {
  77. dbSet.Attach(entityToUpdate);
  78. context.Entry(entityToUpdate).State = EntityState.Modified;
  79. }
  80. }
  81. }

为数据库上下文创建变量,以及仓储代表的实体集。

  1. internal SchoolContext context;
  2. internal DbSet dbSet;

构造函数接受一个数据库上下文实例,然后初始化实体集变量。

  1. public GenericRepository(SchoolContext context)
  2. {
  3. this.context = context;
  4. this.dbSet = context.Set();
  5. }

Get 方法接受 Lambda 表达式,允许调用代码通过 Lambda 表达式来传递过滤条件和排序列,字符串参数允许调用者传递一个逗号分隔的导航属性进行预先加载。

  1. public virtual IEnumerable<TEntity> Get(
  2. Expression<Func<TEntity, bool>> filter = null,
  3. Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
  4. string includeProperties = "")

代码 Expression<Func<TEntity, bool>> filter  表示调用方需要提供一个基于 TEntity 类型的 Lambda 表达式,表达式将会返回 bool 类型的值。例如,如果仓储实例化为 Student 类型,调用的方法可能为 filter 传递的参数为  student => student.LastName == "Smith"
代码 Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy  也表示调用方需要提供一个 Lambda 表达式,在这里,表达式是 TEntity 类型的 IQueryable 对象。返回排序版本的 IQueryable 对象。例如,如果仓储实例化为 Student 实体类型,代码为 orderBy 参数传递的参数可能为 q => q.OrderBy(s => s.LastName)  。
Get 方法创建一个 IQueryable 对象,如果存在过滤条件的话,再使用过滤条件。

  1. IQueryable<TEntity> query = dbSet;
  2.  
  3. if (filter != null)
  4. {
  5. query = query.Where(filter);
  6. }

然后,在解析逗号分隔的列表之后,应用预先加载。

  1. foreach (var includeProperty in includeProperties.Split
  2. (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
  3. {
  4. query = query.Include(includeProperty);
  5. }

最后,如果存在排序条件,应用 orderBy 表达式,否则它返回没有排序的查询。

  1. if (orderBy != null)
  2. {
  3. return orderBy(query).ToList();
  4. }
  5. else
  6. {
  7. return query.ToList();
  8. }

在调用 Get 方法的时候,你可以不提供这些参数,而通过方法返回的 IEnumerable 集合进行过滤和排序,但是排序和过滤将会在 Web 服务器的内存中进行。通过使用这些参数,可以使这些工作在数据库中进行而不是在 Web 服务器上进行。另外一种替代方式是为特定的实体类型创建派生类,增加特定的 Get 方法,诸如 GetStudentsInNameOrder 或者 GetStudentsByName。然而,在复杂的应用中,这会导致大量的派生类和特定方法,在维护的时候会导致大量的工作。
在 GetByID, Insert 和 Update 中的方法如同在非泛型方法中一样简单 ( 在 GetByID 方法扎没有提供预先加载参数,因为不能对 Find 方法进行预先加载 )。
Delete 方法有两个重载。

  1. public virtual void Delete(object id)
  2. {
  3. TEntity entityToDelete = dbSet.Find(id);
  4. dbSet.Remove(entityToDelete);
  5. }
  6.  
  7. public virtual void Delete(TEntity entityToDelete)
  8. {
  9. if (context.Entry(entityToDelete).State == EntityState.Detached)
  10. {
  11. dbSet.Attach(entityToDelete);
  12. }
  13. dbSet.Remove(entityToDelete);
  14.  
  15. }

一个允许仅仅传递实体的 ID 进行删除,另外一个使用实体实例。像在处理并发中所见,对于并发处理你需要 Delete 方法获取包含追踪属性原始值的实体实例。
泛型仓储可以处理典型的 CRUD 需求。当特定的实体有特定的需求时,例如更加复杂的过滤或者排序,可以通过创建派生类来增加额外的方法。

9-4-2  创建工作单元类

工作单元类服务于一个目的:当你使用多个仓储的时候,共享单个的数据库上下文实例。因此,当工作单元完成的时候,你可以通过在这个数据库上下文实例上调用 SaveChanges 方法来保证相关的所有操作被协调处理。所有这个类需要的就是一个 Save 方法和每个仓储一个的属性。每个仓储属性返回使用相同的数据库上下文对象创建的仓储对象实例。
在 DAL 文件夹中,创建名为 UnitOfWork.cs 的文件,使用下面的代码替换原有内容。

  1. using System;
  2. using ContosoUniversity.Models;
  3.  
  4. namespace ContosoUniversity.DAL
  5. {
  6. public class UnitOfWork : IDisposable
  7. {
  8. private SchoolContext context = new SchoolContext();
  9. private GenericRepository<Department> departmentRepository;
  10. private GenericRepository<Course> courseRepository;
  11.  
  12. public GenericRepository<Department> DepartmentRepository
  13. {
  14. get
  15. {
  16.  
  17. if (this.departmentRepository == null)
  18. {
  19. this.departmentRepository = new GenericRepository<Department>(context);
  20. }
  21. return departmentRepository;
  22. }
  23. }
  24.  
  25. public GenericRepository<Course> CourseRepository
  26. {
  27. get
  28. {
  29.  
  30. if (this.courseRepository == null)
  31. {
  32. this.courseRepository = new GenericRepository<Course>(context);
  33. }
  34. return courseRepository;
  35. }
  36. }
  37.  
  38. public void Save()
  39. {
  40. context.SaveChanges();
  41. }
  42.  
  43. private bool disposed = false;
  44.  
  45. protected virtual void Dispose(bool disposing)
  46. {
  47. if (!this.disposed)
  48. {
  49. if (disposing)
  50. {
  51. context.Dispose();
  52. }
  53. }
  54. this.disposed = true;
  55. }
  56.  
  57. public void Dispose()
  58. {
  59. Dispose(true);
  60. GC.SuppressFinalize(this);
  61. }
  62. }
  63. }

代码为数据库上下文以及每个仓储创建类级成员变量。对于 context 变量,新的上下文对象被实例化。

  1. private SchoolContext context = new SchoolContext();
  2. private GenericRepository<Department> departmentRepository;
  3. private GenericRepository<Course> courseRepository;

每个仓储属性检查仓储是否已经被创建了,如果没有,就传递数据库上下文对象,初始化仓储对象,因此,所有的仓储共享相同的数据库上下文。

  1. public GenericRepository<Department> DepartmentRepository
  2. {
  3. get
  4. {
  5.  
  6. if (this.departmentRepository == null)
  7. {
  8. this.departmentRepository = new GenericRepository<Department>(context);
  9. }
  10. return departmentRepository;
  11. }
  12. }

像在类中实例化数据库上下文的其他类一样, UnitOfWork 类也实现了 IDisposable 接口来释放数据库上下文。

9-4-3  修改 CourseController 使用工作单元类和仓储

使用如下代码替换当前的 CourseController.cs。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Data.Entity;
  5. using System.Linq;
  6. using System.Web;
  7. using System.Web.Mvc;
  8. using ContosoUniversity.Models;
  9. using ContosoUniversity.DAL;
  10.  
  11. namespace ContosoUniversity.Controllers
  12. {
  13. public class CourseController : Controller
  14. {
  15. private UnitOfWork unitOfWork = new UnitOfWork();
  16.  
  17. //
  18. // GET: /Course/
  19.  
  20. public ViewResult Index()
  21. {
  22. var courses = unitOfWork.CourseRepository.Get(includeProperties: "Department");
  23. return View(courses.ToList());
  24. }
  25.  
  26. //
  27. // GET: /Course/Details/5
  28.  
  29. public ViewResult Details(int id)
  30. {
  31. Course course = unitOfWork.CourseRepository.GetByID(id);
  32. return View(course);
  33. }
  34.  
  35. //
  36. // GET: /Course/Create
  37.  
  38. public ActionResult Create()
  39. {
  40. PopulateDepartmentsDropDownList();
  41. return View();
  42. }
  43.  
  44. [HttpPost]
  45. public ActionResult Create(Course course)
  46. {
  47. try
  48. {
  49. if (ModelState.IsValid)
  50. {
  51. unitOfWork.CourseRepository.Insert(course);
  52. unitOfWork.Save();
  53. return RedirectToAction("Index");
  54. }
  55. }
  56. catch (DataException)
  57. {
  58. //Log the error (add a variable name after DataException)
  59. ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
  60. }
  61. PopulateDepartmentsDropDownList(course.DepartmentID);
  62. return View(course);
  63. }
  64.  
  65. public ActionResult Edit(int id)
  66. {
  67. Course course = unitOfWork.CourseRepository.GetByID(id);
  68. PopulateDepartmentsDropDownList(course.DepartmentID);
  69. return View(course);
  70. }
  71.  
  72. [HttpPost]
  73. public ActionResult Edit(Course course)
  74. {
  75. try
  76. {
  77. if (ModelState.IsValid)
  78. {
  79. unitOfWork.CourseRepository.Update(course);
  80. unitOfWork.Save();
  81. return RedirectToAction("Index");
  82. }
  83. }
  84. catch (DataException)
  85. {
  86. //Log the error (add a variable name after DataException)
  87. ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
  88. }
  89. PopulateDepartmentsDropDownList(course.DepartmentID);
  90. return View(course);
  91. }
  92.  
  93. private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
  94. {
  95. var departmentsQuery = unitOfWork.DepartmentRepository.Get(
  96. orderBy: q => q.OrderBy(d => d.Name));
  97. ViewBag.DepartmentID = new SelectList(departmentsQuery, "DepartmentID", "Name", selectedDepartment);
  98. }
  99.  
  100. //
  101. // GET: /Course/Delete/5
  102.  
  103. public ActionResult Delete(int id)
  104. {
  105. Course course = unitOfWork.CourseRepository.GetByID(id);
  106. return View(course);
  107. }
  108.  
  109. //
  110. // POST: /Course/Delete/5
  111.  
  112. [HttpPost, ActionName("Delete")]
  113. public ActionResult DeleteConfirmed(int id)
  114. {
  115. Course course = unitOfWork.CourseRepository.GetByID(id);
  116. unitOfWork.CourseRepository.Delete(id);
  117. unitOfWork.Save();
  118. return RedirectToAction("Index");
  119. }
  120.  
  121. protected override void Dispose(bool disposing)
  122. {
  123. unitOfWork.Dispose();
  124. base.Dispose(disposing);
  125. }
  126. }
  127. }

代码中增加了 UnitOfWork 类级成员变量。( 如果在这里使用接口,就不需要在这里实例化对象,相反,应该实现类似前面 Student 仓储的两个构造函数 )

  1. private UnitOfWork unitOfWork = new UnitOfWork();

在类中的其他部分,所有引用的数据库上下文替换为适当的仓储。使用 UnitOfWork 属性来访问仓储。Dispose 方法用来释放 UnitOfWork 实例。

  1. var courses = unitOfWork.CourseRepository.Get(includeProperties: "Department");
  2. // ...
  3. Course course = unitOfWork.CourseRepository.GetByID(id);
  4. // ...
  5. unitOfWork.CourseRepository.Insert(course);
  6. unitOfWork.Save();
  7. // ...
  8. Course course = unitOfWork.CourseRepository.GetByID(id);
  9. // ...
  10. unitOfWork.CourseRepository.Update(course);
  11. unitOfWork.Save();
  12. // ...
  13. var departmentsQuery = unitOfWork.DepartmentRepository.Get(
  14. orderBy: q => q.OrderBy(d => d.Name));
  15. // ...
  16. Course course = unitOfWork.CourseRepository.GetByID(id);
  17. // ...
  18. unitOfWork.CourseRepository.Delete(id);
  19. unitOfWork.Save();
  20. // ...
  21. unitOfWork.Dispose();

运行程序,点击 Courses 窗格。

页面工作如同既往修改之前一样,Course 页面也同样工作。
你现在已经实现了仓储和工作单元模式。在泛型仓储中使用 Lambda 表达式作为参数。更多对 IQueryable 对象使用表达式的信息,可以参阅 MSDN 库中的 IQueryable(T) Interface (System.Linq) 。下一次,将学习如何处理一些高级场景。

Contoso 大学 - 9 - 实现仓储和工作单元模式的更多相关文章

  1. 演练5-8:Contoso大学校园管理系统(实现存储池和工作单元模式)

    在上一次的教程中,你已经使用继承来消除在 Student 和 Instructor 实体之间的重复代码.在这个教程中,你将要看到使用存储池和工作单元模式进行增.删.改.查的一些方法.像前面的教程一样, ...

  2. MVC+EF 理解和实现仓储模式和工作单元模式

    MVC+EF 理解和实现仓储模式和工作单元模式 原文:Understanding Repository and Unit of Work Pattern and Implementing Generi ...

  3. 仓储(Repository)和工作单元模式(UnitOfWork)

    仓储和工作单元模式 仓储模式 为什么要用仓储模式 通常不建议在业务逻辑层直接访问数据库.因为这样可能会导致如下结果: 重复的代码 编程错误的可能性更高 业务数据的弱类型 更难集中处理数据,比如缓存 无 ...

  4. [.NET领域驱动设计实战系列]专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现

    一.前言 在前面2篇博文中,我分别介绍了规约模式和工作单元模式,有了前面2篇博文的铺垫之后,下面就具体看看如何把这两种模式引入到之前的网上书店案例里. 二.规约模式的引入 在第三专题我们已经详细介绍了 ...

  5. [.NET领域驱动设计实战系列]专题四:前期准备之工作单元模式(Unit Of Work)

    一.前言 在前一专题中介绍了规约模式的实现,然后在仓储实现中,经常会涉及工作单元模式的实现.然而,在我的网上书店案例中也将引入工作单元模式,所以本专题将详细介绍下该模式,为后面案例的实现做一个铺垫. ...

  6. 关于工作单元模式——工作单元模式与EF结合的使用

    工作单元模式往往和仓储模式一起使用,本篇文章讲到的是工作单元模式和仓储模式一起用来在ef外面包一层,其实EF本身就是工作单元模式和仓储模式使用的经典例子,其中DbContext就是工作单元,而每个Db ...

  7. 重新整理 .net core 实践篇—————工作单元模式[二十六]

    前言 简单整理一下工作单元模式. 正文 工作单元模式有3个特性,也算是其功能: 使用同一上下文 跟踪实体的状态 保障事务一致性 工作单元模式 主要关注事务,所以重点在事务上. 在共享层的基础建设类库中 ...

  8. .NET应用架构设计—工作单元模式(摆脱过程式代码的重要思想,代替DDD实现轻量级业务)

    阅读目录: 1.背景介绍 2.过程式代码的真正困境 3.工作单元模式的简单示例 4.总结 1.背景介绍 一直都在谈论面向对象开发,但是开发企业应用系统时,使用面向对象开发最大的问题就是在于,多个对象之 ...

  9. 工作单元模式(UnitOfWork)学习总结

    工作单元的目标是维护变化的对象列表.使用IUnitOfWorkRepository负责对象的持久化,使用IUnitOfWork收集变化的对象,并将变化的对象放到各自的增删改列表中, 最后Commit, ...

随机推荐

  1. Redis学习_01 windows下的环境搭建

    一.Redis 简介 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset( ...

  2. 方法字段[C# 基础知识系列]专题二:委托的本质论

    首先声明,我是一个菜鸟.一下文章中出现技术误导情况盖不负责 引言: 上一个专题已和大家分享了我懂得的——C#中为什么须要委托,专题中简略介绍了下委托是什么以及委托简略的应用的,在这个专题中将对委托做进 ...

  3. Codeforces Beta Round #85 (Div. 1 Only) A. Petya and Inequiations 贪心

    A. Petya and Inequiations Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest ...

  4. POJ 1743 Musical Theme 后缀数组 最长重复不相交子串

    Musical ThemeTime Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://poj.org/problem?id=1743 Description ...

  5. Android游戏开发之主角的移动与地图的平滑滚动

    人物移动地图的平滑滚动处理 玩过rpg游戏的朋友应该都知道RPG的游戏地图一般都比较大 今天我和大家分享一下在RPG游戏中如何来处理超出手机屏幕大小的游戏地图. 如图所示为程序效果动画图 地图滚动的原 ...

  6. .Net枚举类型小结

    1.枚举类型的要点: (1)类型声明语法: enum 枚举名 (2)枚举体语法: a.成员名称 = 整数值,其他成员名称,或者其他成员与整数的表达式  b.成员之间需要用逗号隔开 (3)枚举可以继承的 ...

  7. Python2.7.3移除字符串中重复字符(一)

    移除重复字符很简单,这里是最笨,也是最简单的一种.问题关键是理解排序的意义: # coding=utf-8 #learning at jeapedu in 2013/10/26 #移除给定字符串中重复 ...

  8. [Angular 2] How To Debug An Angular 2 Application - Debugging via Augury or the Console

    In this lesson we will learn several ways to debug an Angular 2 application, including by using Augu ...

  9. Outlook2010 移动数据文件到其它地方

            您可以将数据文件移到计算机的其他文件夹中.移动文件的一个原因在于可使备份更容易并且可以让默认的outlook邮件文件不在存在C盘,导致装系统不见或者文件过大而撑死了C盘.例如,如果数据 ...

  10. 1081. Rational Sum (20)

    the problem is from PAT,which website is http://pat.zju.edu.cn/contests/pat-a-practise/1081 the code ...