在 ASP.NET MVC 应用程序中实现存储库和工作单元模式 (9(共 10 个) )

作者 :Tom Dykstra

Contoso University 示例 Web 应用程序演示如何使用 Entity Framework 5 Code First 和 Visual Studio 2012 创建 ASP.NET MVC 4 应用程序。 若要了解教程系列,请参阅本系列中的第一个教程

注意

如果遇到无法解决的问题, 请下载已完成的章节 并尝试重现问题。 通常可以通过将代码与已完成的代码进行比较来找到问题的解决方案。 有关一些常见错误及其解决方法,请参阅 错误和解决方法。

在上一教程中,你使用了继承来减少 和 Instructor 实体类中的Student冗余代码。 在本教程中,你将了解将存储库和工作单元模式用于 CRUD 操作的一些方法。 与上一教程一样,在本教程中,你将更改代码处理已创建页面的方式,而不是创建新页面。

存储库和工作单元模式

存储库和工作单元模式旨在创建应用程序的数据访问层和业务逻辑层之间的抽象层。 实现这些模式可让你的应用程序对数据存储介质的更改不敏感,而且很容易进行自动化单元测试和进行测试驱动开发 (TDD)。

在本教程中,你将为每个实体类型实现一个存储库类。 对于实体类型, Student 你将创建存储库接口和存储库类。 在控制器中实例化存储库时,将使用 接口,以便控制器接受对实现存储库接口的任何对象的引用。 当控制器在 Web 服务器下运行时,它会收到一个适用于实体框架的存储库。 当控制器在单元测试类下运行时,它会收到一个存储库,该存储库处理存储的数据的方式你可以轻松操作以进行测试,例如内存中集合。

在本教程的后面部分,你将对Course控制器中的 Course 和 实体类型使用多个存储库和Department一个工作类单元。 工作类单元通过创建由所有存储库共享的单个数据库上下文类来协调多个存储库的工作。 如果希望能够执行自动单元测试,可以像对存储库一样 Student 为这些类创建和使用接口。 但是,为简单起见,你将创建并使用这些没有接口的类。

下图显示了一种概念化控制器类和上下文类之间的关系的方法,而根本不使用存储库或工作单元模式。

Repository_pattern_diagram

在本系列教程中,不会创建单元测试。 有关使用存储库模式的 MVC 应用程序的 TDD 简介,请参阅 演练:将 TDD 与 ASP.NET MVC 配合使用。 有关存储库模式的详细信息,请参阅以下资源:

注意

有多种方法可以实现存储库和工作单元模式。 可以使用带或不带工作类单元的存储库类。 可以为所有实体类型实现单个存储库,或为每个类型实现一个存储库。 如果为每个类型实现一个,则可以使用单独的类、泛型基类和派生类,或者抽象基类和派生类。 可以在存储库中包含业务逻辑,或将其限制为数据访问逻辑。 还可以使用数据库上下文类中的 IDbSet 接口(而不是实体集的 DbSet 类型)将抽象层构建到数据库上下文类中。 实现本教程中所示的抽象层的方法是一个可供你考虑的选项,而不是针对所有方案和环境的建议。

创建学生存储库类

DAL 文件夹中,创建名为 IStudentRepository.cs 的类文件,并将现有代码替换为以下代码:

using System;
using System.Collections.Generic;
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();
    }
}

此代码声明一组典型的 CRUD 方法,包括两个读取方法,一个返回所有 Student 实体,另一个按 ID 查找单个 Student 实体。

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 并像前面在控制器中看到的那样释放数据库上下文,其 CRUD 方法以前面看到的相同方式调用数据库上下文。

将学生控制器更改为使用存储库

StudentController.cs 中,将当前类中的代码替换为以下代码。 突出显示所作更改。

using System;
using System.Data;
using System.Linq;
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 (searchString != null)
         {
            page = 1;
         }
         else
         {
            searchString = currentFilter;
         }
         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:  // Name ascending 
               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]
      [ValidateAntiForgeryToken]
      public ActionResult Create(
         [Bind(Include = "LastName, FirstMidName, EnrollmentDate")]
           Student student)
      {
         try
         {
            if (ModelState.IsValid)
            {
               studentRepository.InsertStudent(student);
               studentRepository.Save();
               return RedirectToAction("Index");
            }
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
            ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact 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]
      [ValidateAntiForgeryToken]
      public ActionResult Edit(
         [Bind(Include = "LastName, FirstMidName, EnrollmentDate")]
         Student student)
      {
         try
         {
            if (ModelState.IsValid)
            {
               studentRepository.UpdateStudent(student);
               studentRepository.Save();
               return RedirectToAction("Index");
            }
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
            ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
         }
         return View(student);
      }

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

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

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

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Delete(int id)
      {
         try
         {
            Student student = studentRepository.GetStudentByID(id);
            studentRepository.DeleteStudent(id);
            studentRepository.Save();
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
            return RedirectToAction("Delete", new { id = id, saveChangesError = true });
         }
         return RedirectToAction("Index");
      }

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

控制器现在为实现 IStudentRepository 接口而不是上下文类的对象声明类变量:

private IStudentRepository studentRepository;

默认 (无参数) 构造函数创建新的上下文实例,而可选的构造函数允许调用方传入上下文实例。

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

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

(如果使用 依赖项注入或 DI,则不需要默认构造函数,因为 DI 软件将确保始终提供正确的存储库对象。)

在 CRUD 方法中,现在调用存储库而不是上下文:

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

Dispose方法现在释放存储库而不是上下文:

studentRepository.Dispose();

运行站点并单击“ 学生 ”选项卡。

Students_Index_page

页面的外观和工作方式与你更改代码以使用存储库之前相同,其他 Student 页面的工作方式也相同。 但是,控制器的 方法执行筛选和排序的方式 Index 有一个重要差异。 此方法的原始版本包含以下代码:

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()));
}

更新 Index 的 方法包含以下代码:

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()));
}

只有突出显示的代码已更改。

在代码的原始版本中, students 类型为 对象 IQueryable 。 查询不会发送到数据库,直到使用 等 ToList方法将其转换为集合,直到索引视图访问学生模型才会发生。 Where上述原始代码中的 方法将成为发送到数据库的 SQL 查询中的子WHERE句。 这反过来意味着数据库仅返回所选实体。 但是,由于将 更改为 context.StudentsstudentRepository.GetStudents()students 此语句后面的变量是包含 IEnumerable 数据库中所有学生的集合。 应用 Where 方法的最终结果是相同的,但现在工作是在 Web 服务器上的内存中完成的,而不是由数据库完成的。 对于返回大量数据的查询,这可能效率低下。

提示

IQueryable vs.IEnumerable

实现存储库后,如此处所示,即使在“搜索”框中输入某些内容,发送到 SQL Server的查询也会返回所有 Student 行,因为它不包含搜索条件:

代码的屏幕截图,其中显示了已实现并突出显示的新学生存储库。

SELECT 
'0X0X' AS [C1], 
[Extent1].[PersonID] AS [PersonID], 
[Extent1].[LastName] AS [LastName], 
[Extent1].[FirstName] AS [FirstName], 
[Extent1].[EnrollmentDate] AS [EnrollmentDate]
FROM [dbo].[Person] AS [Extent1]
WHERE [Extent1].[Discriminator] = N'Student'

此查询返回所有学生数据,因为存储库在不知道搜索条件的情况下执行了查询。 排序、应用搜索条件和选择数据子集以分页 (在这种情况下仅显示 3 行的过程) 稍后在集合上IEnumerable调用 方法时ToPagedList在内存中完成。

在实现存储库) 之前 (的以前版本的代码中,在应用搜索条件之后(对对象调用 IQueryableToPagedList)不会将查询发送到数据库。

显示学生控制器代码的屏幕截图。突出显示了代码的搜索字符串行和“已分页列表”代码行。

IQueryable对象调用 ToPagedList 时,发送到 SQL Server 的查询将指定搜索字符串,因此仅返回满足搜索条件的行,无需在内存中执行筛选。

exec sp_executesql N'SELECT TOP (3) 
[Project1].[StudentID] AS [StudentID], 
[Project1].[LastName] AS [LastName], 
[Project1].[FirstName] AS [FirstName], 
[Project1].[EnrollmentDate] AS [EnrollmentDate]
FROM ( SELECT [Project1].[StudentID] AS [StudentID], [Project1].[LastName] AS [LastName], [Project1].[FirstName] AS [FirstName], [Project1].[EnrollmentDate] AS [EnrollmentDate], row_number() OVER (ORDER BY [Project1].[LastName] ASC) AS [row_number]
FROM ( SELECT 
    [Extent1].[StudentID] AS [StudentID], 
    [Extent1].[LastName] AS [LastName], 
    [Extent1].[FirstName] AS [FirstName], 
    [Extent1].[EnrollmentDate] AS [EnrollmentDate]
    FROM [dbo].[Student] AS [Extent1]
    WHERE (( CAST(CHARINDEX(UPPER(@p__linq__0), UPPER([Extent1].[LastName])) AS int)) > 0) OR (( CAST(CHARINDEX(UPPER(@p__linq__1), UPPER([Extent1].[FirstName])) AS int)) > 0)
)  AS [Project1]
)  AS [Project1]
WHERE [Project1].[row_number] > 0
ORDER BY [Project1].[LastName] ASC',N'@p__linq__0 nvarchar(4000),@p__linq__1 nvarchar(4000)',@p__linq__0=N'Alex',@p__linq__1=N'Alex'

(以下教程介绍如何检查发送到 SQL Server.)

以下部分演示如何实现存储库方法,使你能够指定此工作应由数据库完成。

现在,你已在控制器和 Entity Framework 数据库上下文之间创建了一个抽象层。 如果打算使用此应用程序执行自动单元测试,可以在实现 IStudentRepository 的单元测试项目中创建备用存储库类此模拟存储库类可以操作内存中的集合来测试控制器函数,而不是调用上下文来读取和写入数据。

实现通用存储库和工作单元类

为每个实体类型创建存储库类可能会导致大量冗余代码,并且可能会导致部分更新。 例如,假设必须更新两个不同的实体类型作为同一事务的一部分。 如果每个实例都使用单独的数据库上下文实例,则一个实例可能会成功,另一个可能会失败。 最小化冗余代码的一种方法是使用通用存储库,一种方法是确保所有存储库使用相同的数据库上下文 (从而协调所有更新) 是使用工作类单元。

在本教程的这一部分中,你将创建一个 GenericRepository 类和一个 UnitOfWork 类,并在控制器中使用 Course 它们来访问 DepartmentCourse 实体集。 如前所述,为了简化本教程的这一部分,你不会为这些类创建接口。 但是,如果打算使用它们来促进 TDD,通常使用接口实现它们的方式 Student 与执行存储库的方式相同。

创建通用存储库

DAL 文件夹中,创建 GenericRepository.cs 并将现有代码替换为以下代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using System.Data.Entity;
using ContosoUniversity.Models;
using System.Linq.Expressions;

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;
        }
    }
}

为数据库上下文和实例化存储库的实体集声明类变量:

internal SchoolContext context;
internal DbSet dbSet;

构造函数接受数据库上下文实例并初始化实体集变量:

public GenericRepository(SchoolContext context)
{
    this.context = context;
    this.dbSet = context.Set<TEntity>();
}

方法 Get 使用 lambda 表达式允许调用代码指定筛选条件和按其排序结果的列,字符串参数允许调用方提供用于预先加载的导航属性的逗号分隔列表:

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

代码 Expression<Func<TEntity, bool>> filter 表示调用方将基于 TEntity 类型提供 lambda 表达式,此表达式将返回布尔值。 例如,如果为Student实体类型实例化存储库,则调用方法中的代码可能会为 filter 参数指定 student => student.LastName == "Smith“ 。

Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy代码还意味着调用方将提供 lambda 表达式。 但在这种情况下,表达式的输入是 IQueryable 类型的对象 TEntity 。 表达式将返回该 IQueryable 对象的有序版本。 例如,如果为实体类型实例化Student存储库,则调用方法中的代码可能会为 orderBy 参数指定q => q.OrderBy(s => s.LastName)

方法中的 Get 代码创建一个 IQueryable 对象,然后应用筛选器表达式(如果有):

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); 
}

最后,如果存在表达式, orderBy 则应用表达式并返回结果;否则,它将返回无序查询的结果:

if (orderBy != null)
{
    return orderBy(query).ToList();
}
else
{
    return query.ToList();
}

调用 Get 方法时,可以对方法返回的 IEnumerable 集合执行筛选和排序,而不是为这些函数提供参数。 但是排序和筛选工作将在 Web 服务器上的内存中完成。 使用这些参数可确保工作由数据库而不是 Web 服务器完成。 另一种方法是为特定实体类型创建派生类,并添加专用 Get 方法,例如 GetStudentsInNameOrderGetStudentsByName。 但是,在复杂的应用程序中,这可能会导致大量此类派生类和专用方法,这可能需要维护更多工作。

InsertUpdate 方法中的GetByID代码与在非泛型存储库中看到的代码类似。 (未在签名中 GetByID 提供预先加载参数,因为无法使用 方法执行预先加载 Find 。)

Delete 方法提供了两个重载:

public virtual void Delete(object id)
{
    TEntity entityToDelete = dbSet.Find(id);
    dbSet.Remove(entityToDelete);
}

public virtual void Delete(TEntity entityToDelete)
{
    if (context.Entry(entityToDelete).State == EntityState.Detached)
    {
        dbSet.Attach(entityToDelete);
    }
    dbSet.Remove(entityToDelete);
}

其中一个允许你仅传入要删除的实体的 ID,一个使用实体实例。 正如 在处理并发 教程中看到的,对于并发处理,需要一个 Delete 方法,该方法采用包含跟踪属性的原始值的实体实例。

此通用存储库将处理典型的 CRUD 要求。 当特定实体类型具有特殊要求(例如更复杂的筛选或排序)时,可以创建一个派生类,该类具有该类型的其他方法。

创建工作单元类

工作类单元有一个用途:确保在使用多个存储库时,它们共享单个数据库上下文。 这样,当一个工作单元完成时,可以在该上下文实例上调用 SaveChanges 方法,并确保将协调所有相关更改。 类只需要一个 Save 方法和每个存储库的属性。 每个存储库属性返回一个存储库实例,该实例已使用与其他存储库实例相同的数据库上下文实例进行实例化。

DAL 文件夹中,创建名为 UnitOfWork.cs 的类文件,并将模板代码替换为以下代码:

using System;
using ContosoUniversity.Models;

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);
        }
    }
}

该代码为数据库上下文和每个存储库创建类变量。 context对于 变量,将实例化新的上下文:

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;
    }
}

方法 Save 对数据库上下文调用 SaveChanges

与在类变量中实例化数据库上下文的任何类一样, UnitOfWork 类实现 IDisposable 并释放上下文。

更改课程控制器以使用 UnitOfWork 课堂和存储库

将当前在 CourseController.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;

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]
      [ValidateAntiForgeryToken]
      public ActionResult Create(
          [Bind(Include = "CourseID,Title,Credits,DepartmentID")]
         Course course)
      {
         try
         {
            if (ModelState.IsValid)
            {
               unitOfWork.CourseRepository.Insert(course);
               unitOfWork.Save();
               return RedirectToAction("Index");
            }
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.)
            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]
      [ValidateAntiForgeryToken]
      public ActionResult Edit(
           [Bind(Include = "CourseID,Title,Credits,DepartmentID")]
         Course course)
      {
         try
         {
            if (ModelState.IsValid)
            {
               unitOfWork.CourseRepository.Update(course);
               unitOfWork.Save();
               return RedirectToAction("Index");
            }
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.)
            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")]
      [ValidateAntiForgeryToken]
      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);
      }
   }
}

此代码为 UnitOfWork 类添加类变量。 (如果在此处使用接口,则不会在此处初始化变量;相反,你将实现两个构造函数的模式,就像对 Student repository.)

private UnitOfWork unitOfWork = new UnitOfWork();

在 类的其余部分中,对数据库上下文的所有引用都替换为对相应存储库的引用,并使用 UnitOfWork 属性访问存储库。 方法 Dispose 释放 UnitOfWork 实例。

var courses = unitOfWork.CourseRepository.Get(includeProperties: "Department");
// ...
Course course = unitOfWork.CourseRepository.GetByID(id);
// ...
unitOfWork.CourseRepository.Insert(course);
unitOfWork.Save();
// ...
Course course = unitOfWork.CourseRepository.GetByID(id);
// ...
unitOfWork.CourseRepository.Update(course);
unitOfWork.Save();
// ...
var departmentsQuery = unitOfWork.DepartmentRepository.Get(
    orderBy: q => q.OrderBy(d => d.Name));
// ...
Course course = unitOfWork.CourseRepository.GetByID(id);
// ...
unitOfWork.CourseRepository.Delete(id);
unitOfWork.Save();
// ...
unitOfWork.Dispose();

运行网站并单击“ 课程 ”选项卡。

Courses_Index_page

页面的外观和工作方式与更改前相同,其他课程页面的工作方式也相同。

总结

现已实现存储库和工作单元模式。 已在泛型存储库中使用 lambda 表达式作为方法参数。 有关如何将这些表达式用于 IQueryable 对象的详细信息,请参阅 MSDN 库中的 IQueryable (T) 接口 (System.Linq) 。 在下一教程中,你将了解如何处理一些高级方案。

可以在 ASP.NET 数据访问内容映射中找到指向其他实体框架资源的链接。