ASP.NET MVC 中实现存储库和工作单元模式

用简洁明了的方式在 ASP.NET MVC 应用程序中实现存储库和工作单元模式

下载完成的项目

Contoso 大学示例 web 应用程序演示了如何使用实体框架 5 Code First 和 Visual Studio 2012 创建 ASP.NET MVC 4 应用程序。 若要了解系列教程,请参阅本系列中的第一个教程。 你可以从头开始学习本系列教程,也可以从此处下载入门项目

 备注

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

在上一教程中,你使用了继承来减少 Student 和 Instructor 实体类中的冗余代码。 在本教程中,你将看到一些使用存储库和适用于 CRUD 操作的工作单元模式的方法。 如前面的教程中所述,在这种情况下,你将使用已创建的页面而不是创建新页面来更改代码的工作方式。

存储库和工作单元模式

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

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

稍后在本教程中,你将使用多个存储库和 Course 的工作单元,并 Department Course 控制器中的实体类型。 工作单元通过创建所有存储库的工作方式来协调多个存储库的工作。 如果希望能够执行自动单元测试,则可以使用与 Student 存储库相同的方式为这些类创建和使用接口。 但是,为了简化本教程,你将创建并使用这些类,而无需使用接口。

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

Repository_pattern_diagram

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

 备注

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

创建 Student Repository

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

页面的外观和工作方式与您将代码更改为使用存储库之前的效果相同,并且其他学生页面也工作正常。 但是,控制器的 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.Students 更改为 studentRepository.GetStudents(),因此该语句后的 students 变量是包含数据库中所有学生的 IEnumerable 集合。 应用 Where 方法的最终结果是相同的,但现在该工作是在 web 服务器上的内存中完成的,而不是在数据库中完成。 对于返回大量数据的查询,这可能会降低效率。

 提示

IQueryable 与 IEnumerable

实现此存储库后,即使在搜索框中输入了内容,查询发送到 SQL Server 也会返回所有学生行,因为它不包含搜索条件:

SQL复制

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'

此查询将返回所有学生数据,因为存储库执行查询时未了解搜索条件。 在 IEnumerable 集合上调用 ToPagedList 方法时,排序、应用搜索条件以及选择用于分页的数据子集(在本例中仅显示3行)在内存中执行的过程。

在之前版本的代码中(在实现存储库之前),如果在对 IQueryable 对象调用 ToPagedList,则查询不会发送到数据库。

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

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 的查询。)

以下部分演示如何实现存储库方法,使用这些方法可以指定数据库应执行此操作。

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

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

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

在本教程的此部分中,你将创建一个 GenericRepository 类和一个 UnitOfWork 类,并在 Course 控制器中使用它们来访问 Department 和 Course 实体集。 如前文所述,若要使本教程的这一部分简单,你不会为这些类创建接口。 但是,如果您打算使用它们来促进 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 表达式。 但在这种情况下,表达式的输入是 TEntity 类型的 IQueryable 对象。 表达式将返回 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 方法,如 GetStudentsInNameOrder 或 GetStudentsByName。 但是,在复杂应用程序中,这可能会导致大量的此类派生类和专用方法,这可能更适合维护。

GetByIDInsert和 Update 方法中的代码与在非泛型存储库中看到的代码类似。 (你不能在 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 存储库执行的操作一样。)

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

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

后记

本人不是大佬,只是道路先行者,在落河后,向后来的人大喊一声,这里有坑,不要过来啊!

纵然如此,依旧有人重复着落河,重复着呐喊······

个人博客网站 Blog

文章后续会在公众号更新,微信搜索 OneByOneDotNet 即可关注。

你的一分鼓励,我的十分动力,点赞免费,感恩回馈。喜欢就点赞评论吧,双击6666~

技术交流Q群: 1012481075 群内有各种流行书籍资料
原文链接:https://docs.microsoft.com/zh-cn/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

发表评论

您的电子邮箱地址不会被公开。

WeChat
WeChat
QQ
QQ
返回顶部