在 MVC 中使用存储库模式进行 CRUD 操作






4.67/5 (67投票s)
本文介绍了在 MVC 应用程序中使用仓库模式。
引言
本文介绍了在 MVC 应用程序中使用仓库模式。我们正在开发一个用于图书实体的应用程序,我们可以在该应用程序上执行创建、读取、更新和删除操作。为了保持文章的简洁性,并使仓库模式易于理解,我们在应用程序中使用了单个图书实体。
仓库模式概述
仓库模式旨在为应用程序的数据访问层和业务逻辑层之间创建一个抽象层。它是一种数据访问模式,提倡更松耦合的数据访问方法。我们将数据访问逻辑创建在一个单独的类或一组类中,称为仓库,其职责是持久化应用程序的业务模型。
在本文中,我们将采用“每个业务模型一个仓库”的方法来设计仓库,即为每种实体类型都有一个仓库类。对于图书实体类型,我们将创建一个仓库接口和一个仓库类。当我们在控制器中实例化仓库时,我们将使用接口,以便控制器接受任何实现该仓库接口的对象的引用。当控制器在 Web 服务器上运行时,它会接收一个使用 Entity Framework 的仓库。
MVC 控制器与仓库交互以加载和持久化应用程序业务模型。通过利用依赖注入 (DI),可以将仓库注入到控制器的构造函数中。下图显示了仓库与 Entity Framework 数据上下文之间的关系,其中 MVC 控制器与仓库交互,而不是直接与 Entity Framework 交互。
学习使用 Entity Framework 进行 MVC 的路线图
- 使用 Code First 方法和 Fluent API 在 Entity Framework 中建立关系
- Entity Framework 的 Code First 迁移
- 使用 MVC 中的 Entity Framework 5.0 Code First 方法进行 CRUD 操作
- 在 MVC 中使用存储库模式进行 CRUD 操作
- 在 MVC 中使用通用存储库模式和工作单元进行 CRUD 操作
- 在 MVC 中使用通用存储库模式和依赖注入进行 CRUD 操作
Entity Framework 概述
.NET Framework 中的 ADO.NET Entity Framework 是一个对象关系映射器 (ORM)。它基本上根据数据库表生成业务对象和实体。它提供基本的 CRUD 操作,能够轻松管理实体之间的关系,并具有实体之间继承关系的能力。
使用 EF 时,我们操作的是实体模型而不是应用程序的关系数据库模型。这种抽象使我们能够专注于业务行为和实体之间的关系。我们使用 Entity Framework 数据上下文执行查询。当调用 CRUD 操作之一时,Entity Framework 将生成执行操作所需的 SQL。
在 Entity Framework 中处理数据
ADO.NET Entity Framework 允许开发人员在三种可能的方法中选择一种:数据库优先 (Database First)、模型优先 (Model First) 和代码优先 (Code First)。
数据库优先:这是一种更以数据为中心的设计,基于现有数据库。Entity Framework 能够根据关系数据库中的表和列生成业务模型。关于数据库结构(存储模式)、数据模型(概念模型)以及它们之间映射的信息存储在 .edmx 文件中的 XML 中。
模型优先:在这种方法中,我们没有现有数据库,Entity Framework 提供了一个可以创建概念数据模型的设计器。它还使用 .edmx 文件来存储模型和映射信息。创建模型后,Entity Framework 设计器可以生成用于创建数据库的数据库模式。
代码优先:无论您是否有现有数据库,都可以编写自己的对应于表和列的类和属性,并在没有 .edmx 文件的情况下与 Entity Framework 一起使用。在此方法中,Entity Framework 不利用任何配置文件(.edmx 文件)来存储数据库模式,因为映射 API 使用这些约定在运行时动态生成数据库模式。
目前,Entity Framework 的 Code First 方法不支持映射到存储过程。可以使用 ExecuteSqlCommand()
和 SqlQuery()
方法来执行存储过程。
在本文中,我们将使用 Entity Framework 的 Code First 方法在 MVC 应用程序中开发数据访问层。Code First 方法的驱动力在于能够使用 POCO(纯粹的 CLR 对象)类。Code First 使用一组约定来映射 POCO 类,但可以使用 Code First 数据注解进行更改。
- 主键基于属性名称
Id
或ClassNameId
。换句话说,假设我们有一个 Book 实体,它有一个属性Id
或BookId
,它将是生成的 Books 表中的主键。 - 表名使用实体类名称的复数形式定义。换句话说,假设我们有一个 Book 实体,该实体将生成一个数据库表,该表的名称将是 Books。
- 表的列名源自实体的属性名。可以使用 Code First 数据注解更改列名。
- 默认连接字符串与
DataContext
类的名称匹配。
Code First 数据注解
Entity Framework 包含一些数据注解属性,我们可以使用它们来控制框架如何处理实体映射。这里我们有一个将用于 Book 实体的基本数据注解。
序号 | 属性 | 描述 |
---|---|---|
1 | 表格 | 用于定义用于实体的表名。 |
2 | Column | 数据库表列名、序位位置以及要映射到属性的数据类型 |
3 | 键 | 一个或多个属性,用于唯一标识一个实体。 |
4 | 必需 | 将属性标记为必需(非可空)。 |
5 | MaxLength | 属性(列)的最大长度。 |
6 | MinLength | 属性(列)的最小长度。 |
7 | StringLength | 定义字段的最小和最大长度。 |
使用仓库模式的 MVC 应用程序
现在我们有了足够的理论。现在让我们开始在 MVC 应用程序中实现它的乐趣。我们使用 Visual Studio 2010、MVC 4 和 Entity Framework 5 创建一个 MVC 应用程序(BookStore 应用程序)。
步骤 1:从 Visual Studio 开始页,单击“新建项目”
步骤 2:选择“MVC 4 项目模板”
我们得到“新建项目”窗口,我们在其中选择“MVC 4 项目模板”,并为项目和解决方案提供合适的名称,然后单击“确定”按钮。
然后我们得到另一个窗口来选择 MVC 应用程序模板。我们从模板中选择“Internet 应用程序”,并选择“Razor”作为视图引擎。
单击“确定”,我们的默认应用程序就准备好了。
我们正在使用带有 Razor 视图引擎的 MVC 4 开发 MVC 应用程序,因此我们的默认 MVC Internet 应用程序包含 EntityFramework 引用,无需为 Entity Framework 添加引用或安装 Nuget 包。
步骤 3:创建模型
我们在 Models 文件夹下为 Book 创建一个模型。这个模型实际上是一个使用实体和实体集的类。我们在 Models 下创建 Book 类,并为它将创建的数据库表实现 Code First 数据注解。
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace BookStore.Models
{
public class Book
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(30)]
public string Title { get; set; }
public string Authers { get; set; }
[Column("Year")]
[Display(Name = "Publish Year")]
public string publishYear { get; set; }
[Column("Price")]
[Display(Name = "Price")]
public decimal BasePrice { get; set; }
}
}
我们的 Book 模型已准备好,现在我们继续处理数据上下文。
步骤 4:创建数据上下文类
ADO.NET Entity Framework Code First 数据访问方法要求我们创建一个继承自 DbContext
类的数据库访问上下文类。这个类必须为领域模型中的每个实体都包含属性。
这是一个包含一个实体 Book 的 Entity Framework Code First 数据上下文。我们在新的 DAL 文件夹下创建这个上下文类(BookContext
)。在数据上下文类定义下方有一个构造函数,用于传递在 web.config 文件中定义的连接字符串。默认情况下,连接字符串的名称与数据上下文类的名称相同,但我们可以为连接字符串使用不同的名称,以便所有数据上下文都可以使用单个连接字符串。
using System.Data.Entity;
using BookStore.Models;
namespace BookStore.DAL
{
public class BookContext : DbContext
{
public BookContext()
: base("name=BookStoreConnectionString")
{
}
public DbSet Books { get; set; }
}
}
web.config 文件中的连接字符串是
<connectionStrings>
<add name="BookStoreConnectionString" connectionString="Data Source=sandeepss-PC;Initial
Catalog=BookStore;User ID=shekhawat; Password=******" providerName="System.Data.SqlClient" />
</connectionStrings>
步骤 5:创建仓库
在 DAL 文件夹中创建一个 IBookRepository
接口,文件名是 IBookRepository.cs。这个接口代码声明了一组典型的 CRUD 方法,包括两个读取方法:一个返回所有 Book 实体集,另一个按 ID 查找单个 Book 实体。
using System;
using System.Collections.Generic;
using BookStore.Models;
namespace BookStore.DAL
{
public interface IBookRepository : IDisposable
{
IEnumerable<Book> GetBooks();
Book GetBookByID(int bookId);
void InsertBook(Book book);
void DeleteBook(int bookID);
void UpdateBook(Book book);
void Save();
}
}
在 DAL 文件夹中,创建一个名为 BookRepository.cs 的类文件。类文件实现了 IBookRepository
接口,并且 IBookRepository
继承了 IDisposable
接口,因此 IDisposable
接口被 BookRespository
类间接实现。数据库上下文定义在一个类变量中,构造函数期望调用对象传入上下文的实例。这里我们将 BookContext
实例传递给构造函数。
using System;
using System.Collections.Generic;
using System.Linq;
using BookStore.Models;
using System.Data;
namespace BookStore.DAL
{
public class BookRepository : IBookRepository
{
private BookContext _context;
public BookRepository(BookContext bookContext)
{
this._context = bookContext;
}
public IEnumerable GetBooks()
{
return _context.Books.ToList();
}
public Book GetBookByID(int id)
{
return _context.Books.Find(id);
}
public void InsertBook(Book book)
{
_context.Books.Add(book);
}
public void DeleteBook(int bookID)
{
Book book = _context.Books.Find(bookID);
_context.Books.Remove(book);
}
public void UpdateBook(Book book)
{
_context.Entry(book).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);
}
}
}
步骤 6:为 CRUD 操作创建控制器和视图。
现在我们的模型、数据上下文和仓库都已准备就绪,因此现在继续进行控制器。所以我们在 Controllers 文件夹下为 Book 创建一个控制器(BookController
)。
控制器中需要以下“using”语句才能执行 CRUD 操作
using System.Data;
using System.Linq;
using System.Web.Mvc;
using BookStore.DAL;
using BookStore.Models;
我们在 Book 控制器中创建 Book 仓库接口的实例,并在 Book 控制器的构造函数(BookController.cs)中初始化 book 仓库,如下所示。
private IBookRepository _bookRepository;
public BookController()
{
this._bookRepository = new BookRepository(new BookContext());
}
我们将使用脚手架模板为 CRUD 操作创建视图。我们使用五个脚手架模板:List、Create、Edit、Delete 和 Details。因此,创建一个控制器,它具有取决于操作的 post 和 get 操作结果。
操作 1:显示所有图书列表
在名为 Index 的控制器中创建一个操作。Index 操作返回图书列表。
public ActionResult Index()
{
var books = from book in _bookRepository.GetBooks()
select book;
return View(books);
}
现在我们创建视图。要创建视图,请使用以下过程
- 成功编译源代码
- 右键单击操作方法 Index。
- 视图名称已填写,请勿更改。
- 视图引擎已选择 Razor,请勿更改。
- 勾选“创建强类型视图”复选框,因为我们正在创建强类型视图。
- 选择模型类“Book”,以便它可以与视图绑定。
- 从脚手架模板中选择“List”,以便可以进行快速开发,并获得显示图书列表的代码视图。
- 勾选“引用脚本库”和“使用布局或主页”复选框。
这些是每个操作要遵循的通用步骤,唯一的变化将是脚手架模板。下图显示了带有 List 脚手架模板的 Index 视图。
操作 2:显示图书详情
在名为 Details 的控制器中创建一个操作。Details 操作返回图书的详细信息。
public ViewResult Details(int id)
{
Book student = _bookRepository.GetBookByID(id);
return View(student);
}
现在我们创建视图。要创建视图,请使用以下过程
- 右键单击操作方法 Details。
- 视图名称已填写,请勿更改。
- 视图引擎已选择 Razor,请勿更改。
- 勾选“创建强类型视图”复选框,因为我们正在创建强类型视图。
- 选择模型类“Book”,以便它可以与视图绑定。
- 从脚手架模板中选择“Details”,以便我们可以进行快速开发,并获得显示图书详细信息的代码视图。
- 勾选“引用脚本库”和“使用布局或主页”复选框。
操作 3:创建新图书
在控制器中创建两个操作,一个用于创建新图书视图(GET 操作),另一个用于将新图书详细信息提交到仓库(POST 操作)。它们具有相同的名称,Create。
public ActionResult Create()
{
return View(new Book());
}
[HttpPost]
public ActionResult Create(Book book)
{
try
{
if (ModelState.IsValid)
{
_bookRepository.InsertBook(book);
_bookRepository.Save();
return RedirectToAction("Index");
}
}
catch (DataException)
{
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists see your system administrator.");
}
return View(book);
}
现在我们创建视图。要创建视图,请使用以下过程
- 右键单击操作方法 Create (GET)。
- 视图名称已填写,请勿更改。
- 视图引擎已选择 Razor,请勿更改。
- 勾选“创建强类型视图”复选框,因为我们正在创建强类型视图。
- 选择模型类“Book”,以便它可以与视图绑定。
- 从脚手架模板中选择“Create”,以便我们可以进行快速开发,并获得创建新图书的视图。
- 勾选“引用脚本库”和“使用布局或主页”复选框。
操作 4:更新图书详情
在控制器中创建两个操作,一个用于现有图书的编辑视图(GET 操作),另一个用于将更新后的图书详细信息提交到仓库(POST 操作)。它们具有相同的名称 Create。GET 操作通过图书的 ID 在表单中填充图书详细信息,因此我们将 ID 传递给操作。
public ActionResult Edit(int id)
{
Book book = _bookRepository.GetBookByID(id);
return View(book);
}
[HttpPost]
public ActionResult Edit(Book book)
{
try
{
if (ModelState.IsValid)
{
_bookRepository.UpdateBook(book);
_bookRepository.Save();
return RedirectToAction("Index");
}
}
catch (DataException)
{
ModelState.AddModelError("", "Unable to save changes. Try again, " +
"and if the problem persists see your system administrator.");
}
return View(book);
}
现在我们创建视图。要创建视图,请使用以下过程
- 右键单击操作方法 Edit (GET)。
- 视图名称已填写,请勿更改。
- 视图引擎已选择 Razor,请勿更改。
- 勾选“创建强类型视图”复选框,因为我们正在创建强类型视图。
- 选择模型类“Book”,以便它可以与视图绑定。
- 从脚手架模板中选择“Edit”,以便我们可以进行快速开发,并获得更新现有图书的视图。
- 勾选“引用脚本库”和“使用布局或主页”复选框。
操作 5:删除图书
在控制器中创建两个操作,一个用于在单击 Delete 链接后显示图书的详细信息(GET 操作),另一个用于删除图书(POST 操作)。一个 Delete 操作,但另一个覆盖了 Delete 操作,而 Delete 操作又覆盖了 DeleteConfirmed 方法。GET 操作通过图书的 ID 在表单中填充图书详细信息,然后对其执行 POST 操作。
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.";
}
Book book = _bookRepository.GetBookByID(id);
return View(book);
}
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
try
{
Book book = _bookRepository.GetBookByID(id);
_bookRepository.DeleteBook(id);
_bookRepository.Save();
}
catch (DataException)
{
return RedirectToAction("Delete",
new System.Web.Routing.RouteValueDictionary {
{ "id", id },
{ "saveChangesError", true } });
}
return RedirectToAction("Index");
}
现在我们创建视图。要创建视图,请使用以下过程
- 右键单击操作方法 Delete。
- 视图名称已填写,请勿更改。
- 视图引擎已选择 Razor,请勿更改。
- 勾选“创建强类型视图”复选框,因为我们正在创建强类型视图。
- 选择模型类“Book”,以便它可以与视图绑定。
- 从脚手架模板中选择“Delete”,以便我们可以进行快速开发,并获得现有图书的删除视图。
- 勾选“引用脚本库”和“使用布局或主页”复选框。
现在视图和操作已准备好执行 CRUD 操作
步骤 7:运行应用程序
从浏览器调用 Book 控制器 https://:4736/Book,我们会看到默认的空图书列表。
检查我们的数据库是否由数据上下文在应用程序调用图书实体时创建。
创建新的图书。
单击“创建”按钮,即创建了一本新书。
现在单击一行上的“Edit”链接按钮,并在编辑表单中显示图书详情。
单击“Save”按钮,然后查看更新后的图书列表。
现在单击“Details”按钮,我们将获得图书的详细信息。
单击“Back to List”然后单击“Delete”链接按钮,我们将看到删除确认窗口。
单击“Delete”按钮,图书将被删除。
我们已成功执行所有 CRUD 操作。我没有在此处使用许多基本截图来创建控制器、模型和视图,因为我知道您已经熟悉它们了。如果您想了解更多关于 MVC Razor 和 MVC 中 Code First 方法的基础知识,这里是我的其他文章
我已附上此图书商店应用程序的源代码(Models、Views、Controllers 文件夹和 web.config 文件),以便您可以尝试开发它。如果您在此应用程序的开发过程中遇到任何问题或有任何疑问,请随时发表评论。