演练5-8:Contoso大学校园管理系统(实现存储池和工作单元模式) 一、存储池和工作单元模式 二、创建 Student 仓储类 三、修改 Student 控制器使用仓储
在上一次的教程中,你已经使用继承来消除在 Student 和 Instructor 实体之间的重复代码。在这个教程中,你将要看到使用存储池和工作单元模式进行增、删、改、查的一些方法。像前面的教程一样,你将要修改已经创建的页面中代码的工作方式,而不是新创建的页面。
存储池和工作单元模式用来在数据访问层和业务逻辑层之间创建抽象层。实现这些模式有助于隔离数据存储的变化,便于自动化的单元测试或者测试驱动的开发 ( 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 类为数据库上下文类创建一个抽象层。在这个教程中展示的目标实现了抽象层,只是其中一种考虑,并不是针对所有的场景和环境都适用。
二、创建 Student 仓储类
在 DAL 文件夹中,创建一个文件名为 IStudentRepository.cs 的文件,将当前的代码使用如下代码替换。
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();
}
}
代码定义了一套典型的增、删、改、查方法。包括两个读取方法 – 一个返回所有的学生实体,一个通过 ID 查询单个实体。 在 DAL 文件夹中,创建名为 StudentRepository.cs 的类文件,使用下面的代码替换原有的代码,这个类实现了 IStudentRepository 接口。
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);
}
}
}
数据库上下文是类中定义的一个成员变量,构造函数期望传递一个数据库上下文对象实例。
private SchoolContext context;
public StudentRepository(SchoolContext context)
{
this.context = context;
}
你需要创建一个新的数据库上下文实例,但是如果在控制器中需要使用多个仓储类,每一个会得到一个不同的数据库上下文对象。后面在 Course 控制器中,你将要使用多个仓储,会看到如何使用工作单元类来保证所有的仓储使用相同的数据库上下文对象。 仓储类还实现了 IDisposable 接口,如同在前面控制器中所见,释放数据库上下文,仓储的增删改查方法也如前所见调用数据库上下文的方法。
三、修改 Student 控制器使用仓储
在 StudentController.cs 中,使用下面的代码替换现有的代码。
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 pageNumber = (page ?? 1);
return View(students.ToPagedList(pageNumber, 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