学习 MVC 第 6 部分:在带有 Entity Framework 的 MVC3 应用程序中使用通用存储库模式






4.90/5 (50投票s)
工作单元模式和存储库模式,以及如何在 MVC 应用程序中执行 CRUD 操作。
- 下载 PDF_Article.zip - 606.8 KB
- 下载 SqlScriptToCreateTable.zip - 537 B
- 下载 SqlScriptToCreateDatabase.zip - 872 B
- 下载 MVCWithEntityFrameworkGenericRepPattern.zip - 2.3 MB
- 下载 LearningMVCRepoPattern.zip - 2.3 MB
引言
在带有 Entity Framework 的 MVC3 应用程序中创建通用存储库模式是我们学习 MVC 之旅中要介绍的最后一个主题。
本文将重点介绍工作单元模式和存储库模式,并展示如何在 MVC 应用程序中执行 CRUD 操作,当可能需要创建多个存储库类时。为了克服这种可能性和开销,我们为所有其他存储库创建了一个通用存储库类,并实现了一个工作单元模式来提供抽象。
我们学习 MVC 的路线图
只是为了提醒我们学习 MVC 的完整路线图,
- 第一部分:MVC 架构和关注点分离简介。
- 第二部分:从头开始创建 MVC 应用程序并使用 LINQ to SQL 将其连接到数据库。
- 第三部分:借助 EntityFramework DB-First 方法连接 MVC 应用程序。
- 第四部分:借助 EntityFramework Code-First 方法连接 MVC 应用程序。
- 第五部分:在带有 EntityFramework 的 MVC 应用程序中实现 Repository 模式。
- 第六部分:在带有 EntityFramework 的 MVC 应用程序中实现通用 Repository 模式和 Unit Of Work 模式。
先决条件
在开始阅读本文之前,有一些先决条件,
- 我们有一个在文章系列的第五部分中创建的正在运行的示例应用程序。
- 我们的本地文件系统上安装了 Entity Framework 4.1 包或 DLL。
- 我们了解 MVC 应用程序的创建方法(请参阅系列的第二部分)。
为什么需要通用存储库
我们在上一篇文章中已经讨论过什么是存储库模式以及为什么我们需要存储库模式。我们创建了一个用户存储库来执行 CRUD 操作,但请考虑我们需要 10 个这样的存储库的情况。
我们要创建这些类吗?这不好,这会导致大量重复代码。因此,为了克服这种情况,我们将创建一个通用存储库类,该类将通过属性调用以创建新的存储库,这样我们就不会产生大量类,并且还可以避免重复代码。此外,我们节省了大量可能浪费在创建这些类上的时间。
工作单元模式
根据 Martin Fowler 的工作单元模式,“维护一个受业务事务影响的对象列表,并协调更改的写入和并发问题的解决。”
根据 MSDN 的说法,工作单元模式不一定是你自己显式构建的东西,但该模式几乎出现在每个持久化工具中。NHibernate 中的 ITransaction
接口、LINQ to SQL 中的 DataContext
类以及 Entity Framework 中的 ObjectContext
类都是工作单元的示例。就此而言,古老的 DataSet 也可以用作工作单元。
其他时候,你可能想编写自己的应用程序特定工作单元接口或类,它包装了你持久化工具的内部工作单元。你可能出于多种原因这样做。你可能想为事务管理添加应用程序特定的日志记录、跟踪或错误处理。也许你想将持久化工具的细节封装起来,使其与应用程序的其余部分分离。你可能希望这种额外的封装更容易以后更换持久化技术。或者你可能希望提高系统的可测试性。许多常见持久化工具的内置工作单元实现很难在自动化单元测试场景中处理。
工作单元类可以包含标记实体为已修改、新创建或已删除的方法。工作单元还将包含提交或回滚所有更改的方法。
工作单元的重要职责是,
- 管理事务。
- 排序数据库插入、删除和更新。
- 防止重复更新。在一个工作单元对象的单个使用过程中,代码的不同部分可能会将同一个发票对象标记为已更改,但工作单元类只会向数据库发出一个 UPDATE 命令。
使用工作单元模式的价值在于将这些关注点从我们代码的其余部分中解放出来,以便你可以专注于业务逻辑。
为什么要使用工作单元?
再次引用 Martin Fowler 的话,“当你将数据从数据库中读取和写入时,跟踪你所做的更改很重要;否则,这些数据将不会写回数据库。同样,你必须插入新创建的对象并删除任何删除的对象。
你可以每次更改对象模型时更改数据库,但这会导致大量非常小的数据库调用,这最终会非常慢。此外,这需要你为整个交互打开一个事务,如果你的业务事务跨越多个请求,这是不切实际的。如果你需要跟踪你已读取的对象以避免不一致的读取,情况会更糟。
工作单元会跟踪并负责你在业务事务期间对数据库产生影响的所有操作。当你完成后,它会计算出为了响应你的工作而需要对数据库进行的所有更改。”
你看,我不需要过多地关注理论,我们已经有了很好的定义,我们只需要将它们以正确的格式堆叠起来。
使用工作单元
使用工作单元模式的最佳方法之一是允许不同的类和服务参与单个逻辑事务。这里的关键点是,你希望不同的类和服务彼此无关,但又能参与到单个事务中。传统上,你可以通过使用事务协调器,如 MTS/COM+ 或较新的 System.Transactions 命名空间来实现。我个人更喜欢使用工作单元模式来允许不相关的类和服务参与逻辑事务,因为我认为这使得代码更明确、更容易理解,并且更易于进行单元测试(来自 MSDN)。
创建通用存储库
步骤 1:在 Visual Studio 中打开我们在第 5 部分创建的现有 MVC3 应用程序。
步骤 2:右键单击“学习 MVC”项目文件夹,创建一个名为 GenericRepository 的文件夹,并将一个名为 GenericRepository.cs 的类添加到该文件夹。
GenericRepository.cs 类的代码如下:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
namespace LearningMVC.GenericRepository
{
public class GenericRepository<TEntity> where TEntity : class
{
internal MVCEntities context;
internal DbSet<TEntity> dbSet;
public GenericRepository(MVCEntities context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get()
{
IQueryable<TEntity> query = dbSet;
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;
}
}
}
我们可以看到,我们已经创建了通用方法,并且类本身也是通用的,在实例化此类时,我们可以传入任何模型,该类将在此模型上运行并达到目的。
TEntity
是任何模型/域/实体类。MVCEntities
是我们前面讨论过的 DBContext
。
步骤 3:实现 UnitOfWork:在 LearningMVC 项目下创建一个名为 UnitOfWork 的文件夹,并将一个名为 UnitOfWork.cs 的类添加到该文件夹。
该类的代码如下:
using System;
using LearningMVC.GenericRepository;
namespace LearningMVC.UnitOfWork
{
public class UnitOfWork : IDisposable
{
private MVCEntities context = new MVCEntities();
private GenericRepository<User> userRepository;
public GenericRepository<User> UserRepository
{
get
{
if (this.userRepository == null)
this.userRepository = new GenericRepository<User>(context);
return userRepository;
}
}
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);
}
}
}
我们看到该类实现了 IDisposable
接口,以便此类的对象可以被释放。
我们在该类中创建 DBContext
的对象,请注意,之前是从控制器将它传递到 Repository 类。
现在是创建我们的 User Repository 的时候了。我们在代码本身中看到,只是声明了一个名为 userRepository
的变量,类型为 private GenericRepository<User> userRepository;
,它将 User 实体作为 TEntity
模板。
然后以非常简化的方式为此 userRepository
变量创建了一个属性,
public GenericRepository<User> UserRepository
{
get
{
if (this.userRepository == null)
this.userRepository = new GenericRepository<User>(context);
return userRepository;
}
}
即,仅仅 6-7 行代码。猜猜怎么着?我们的 UserRepository 已经创建好了。
(取自 Google)
你看,就是这么简单,你只需要创建简单的属性就可以创建任意数量的存储库,而无需创建单独的类。现在你可以自己完成剩下的故事了,迷惑了吗?是的,这是 DBOperations
,我们来做吧。
步骤 4:在 MyController
中,声明一个名为 unitOfWork
的变量,如下所示:
private UnitOfWork.UnitOfWork unitOfWork = new UnitOfWork.UnitOfWork();
现在这个 UnitOfWork 类的实例 unitOfWork
包含了所有的存储库属性,如果我们输入“.”,它会显示存储库。所以我们可以选择任何已创建的存储库并对它们执行 CRUD 操作。
例如,我们的 Index 操作
public ActionResult Index()
{
var userList = from user in unitOfWork.UserRepository.Get() select user;
var users = new List<LearningMVC.Models.UserList>();
if (userList.Any())
{
foreach (var user in userList)
{
users.Add(new LearningMVC.Models.UserList() { UserId = user.UserId,
Address = user.Address, Company = user.Company,
FirstName = user.FirstName, LastName = user.LastName,
Designation = user.Designation, EMail = user.EMail, PhoneNo = user.PhoneNo });
}
}
ViewBag.FirstName = "My First Name";
ViewData["FirstName"] = "My First Name";
if(TempData.Any())
{
var tempData = TempData["TempData Name"];
}
return View(users);
}
这里,
unitOfWork.UserRepository
> 访问UserRepository
。unitOfWork.UserRepository.Get()
-> 访问通用Get()
方法以获取所有用户。
以前,我们的 MyController
构造函数是这样的:
public MyController()
{
this.userRepository = new UserRepository(new MVCEntities());
}
现在,无需编写该构造函数,事实上,你可以删除我们在学习 MVC 第 5 部分中创建的 UserRepository
类和接口。
我希望你也能写出其余 CRUD 操作的 Action。
详细说明
public ActionResult Details(int id)
{
var userDetails = unitOfWork.UserRepository.GetByID(id);
var user = new LearningMVC.Models.UserList();
if (userDetails != null)
{
user.UserId = userDetails.UserId;
user.FirstName = userDetails.FirstName;
user.LastName = userDetails.LastName;
user.Address = userDetails.Address;
user.PhoneNo = userDetails.PhoneNo;
user.EMail = userDetails.EMail;
user.Company = userDetails.Company;
user.Designation = userDetails.Designation;
}
return View(user);
}
Create:
[HttpPost]
public ActionResult Create(LearningMVC.Models.UserList userDetails)
{
try
{
var user = new User();
if (userDetails != null)
{
user.UserId = userDetails.UserId;
user.FirstName = userDetails.FirstName;
user.LastName = userDetails.LastName;
user.Address = userDetails.Address;
user.PhoneNo = userDetails.PhoneNo;
user.EMail = userDetails.EMail;
user.Company = userDetails.Company;
user.Designation = userDetails.Designation;
}
unitOfWork.UserRepository.Insert(user);
unitOfWork.Save();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
未使用。:
public ActionResult Edit(int id)
{
var userDetails = unitOfWork.UserRepository.GetByID(id);
var user = new LearningMVC.Models.UserList();
if (userDetails != null)
{
user.UserId = userDetails.UserId;
user.FirstName = userDetails.FirstName;
user.LastName = userDetails.LastName;
user.Address = userDetails.Address;
user.PhoneNo = userDetails.PhoneNo;
user.EMail = userDetails.EMail;
user.Company = userDetails.Company;
user.Designation = userDetails.Designation;
}
return View(user);
}
[HttpPost]
public ActionResult Edit(int id, User userDetails)
{
TempData["TempData Name"] = "Akhil";
try
{
var user = unitOfWork.UserRepository.GetByID(id);
user.FirstName = userDetails.FirstName;
user.LastName = userDetails.LastName;
user.Address = userDetails.Address;
user.PhoneNo = userDetails.PhoneNo;
user.EMail = userDetails.EMail;
user.Company = userDetails.Company;
user.Designation = userDetails.Designation;
unitOfWork.UserRepository.Update(user);
unitOfWork.Save();
return RedirectToAction("Index");
}
删除:
public ActionResult Delete(int id)
{
var user = new LearningMVC.Models.UserList();
var userDetails = unitOfWork.UserRepository.GetByID(id);
if (userDetails != null)
{
user.FirstName = userDetails.FirstName;
user.LastName = userDetails.LastName;
user.Address = userDetails.Address;
user.PhoneNo = userDetails.PhoneNo;
user.EMail = userDetails.EMail;
user.Company = userDetails.Company;
user.Designation = userDetails.Designation;
}
return View(user);
}
[HttpPost]
public ActionResult Delete(int id, LearningMVC.Models.UserList userDetails)
{
try
{
var user = unitOfWork.UserRepository.GetByID(id);
if (user != null)
{
unitOfWork.UserRepository.Delete(id);
unitOfWork.Save();
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
注意:图片取自 Google 图片。
结论
现在我们知道了如何创建通用存储库,以及如何使用它执行 CRUD 操作。
我们还详细学习了工作单元模式。现在你已经具备了在企业应用程序中应用这些概念的资格和信心。这是本 MVC 系列的最后一篇,如果你觉得需要讨论任何特定主题,请告诉我,或者我们也可以开始其他系列。
编程愉快 :)