系列目录:
CRUD Operations Using Entity Framework 5.0 Code First Approach in MVC【在MVC中使用EF 5.0做增删查改】
CRUD Operations Using the Repository Pattern in MVC【在MVC中使用仓储模式,来做增删查改】
CRUD Operations Using the Generic Repository Pattern and Unit of Work in MVC【在MVC中使用泛型仓储模式和工作单元来做增删查改】
源代码下载:https://github.com/caofangsheng93/GenericRepositoryCURD
这篇文章,我将会介绍在ASP.NET MVC应用程序中使用泛型仓储模式和工作单元。我将开发一个程序,对Book实体进行增删查改,为了保证这篇文章简单,便于大家理解泛型仓储模式和工作单元,在这篇文章中,我将只会使用一个Book实体。
在上篇文章中“4.CRUD Operations Using the Repository Pattern in MVC【在MVC中使用仓储模式进行增删查改】“讲到了仓储模式,里面我们为Book实体,创建了一个仓储类,但是这个仓储仅仅是只能为一个实体服务的。试想一下,如果是真正的企业级开发,我们会有很多实体,难道,我们要为每一个实体都创建一个仓储类么???显然是不现实的。对于这个问题,我们需要创建一个可以为所有实体公用的仓储,所以这里引入泛型仓储。这样就避免了重复编码。
下面的图中,讲到了两个开发者,讨论一个问题:"是否需要创建一个新的零件或者是利用已经存在的零件?" ------如果你选择第一种,那么就是这篇文章讲到的,"4.CRUD Operations Using the Repository Pattern in MVC【在MVC中使用仓储模式进行增删查改】"
但我在这里选择第二种,也就是这篇文章,我将要讲到的。--使用泛型仓储模式来重用代码,减少冗余代码。
既然说到仓储,那么什么是仓储模式呢?
仓储模式旨在数据访问层和业务逻辑层之间创建一个抽象层。仓储模式是数据访问模式,旨在达到数据访问的更松散的耦合性。我们在单独的类,或者类库中创建数据访问的逻辑,这就是仓储。仓储的职责就是和业务逻辑层进行通信。
在这篇文章中,我将会为所有的实体设计一个公共的泛型仓储,另外还有一个工作单元类。这个工作单元类,为每个实体创建仓储的实例,然后仓储实例用来做增删查改操作。我在控制器中创建工作单元类的实例,然后依据具体的实体,创建仓储的实例,然后就可以使用仓储中的方法进行每个操作了。
下面的图表显示了仓储和EF数据上下文之间的关系。在图中,MVC控制器直接通过工作单元和仓储进行交互,而不是直接和EF进行交互。
讲到这里,大家可能就会有疑问了,为什么要使用工作单元呢???
工作单元就像它的名字一样,做某件事情。在这篇文章中,工作单元主要做的是:我们创建工作单元的实例,然后工作单元为我们初始化EF数据上下文,然后每个仓储的实例都使用同一个数据上下文实例进行数据库操作。因此工作单元就是,用来确保所有的仓储实例都使用同一个数据上下文实例。
好了理论到此为止,讲的差不多了。现在我们开始进入正题:
先看看项目的结构:
这三个项目就不用做过多介绍了吧,前面的文章已经说了很多次了...
EF.Entity类库中,添加BaseEntity实体:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace EF.Entity { public abstract class BaseEntity { /// <summary> /// ID编号 /// </summary> public int ID { get; set; } /// <summary> /// 添加时间 /// </summary> public DateTime AddedDate { get; set; } /// <summary> /// 修改时间 /// </summary> public DateTime ModifiedDate { get; set; } /// <summary> /// IP地址 /// </summary> public string IP { get; set; } } }
Book实体:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace EF.Entity { public class Book:BaseEntity { /// <summary> /// 书名 /// </summary> public string Title { get; set; } /// <summary> /// 作者 /// </summary> public string Author { get; set; } /// <summary> /// ISBN编号 /// </summary> public string ISBN { get; set; } /// <summary> /// 出版时间 /// </summary> public DateTime PublishedDate { get; set; } } }
Entity.Data类库中BookMap类:
using EF.Entity;using System;using System.Collections.Generic;using System.ComponentModel.DataAnnotations.Schema;using System.Data.Entity.ModelConfiguration;using System.Linq;using System.Text;using System.Threading.Tasks;namespace EF.Data { public class BookMap:EntityTypeConfiguration<Book> { public BookMap() { //配置主键 this.HasKey(s => s.ID); //配置字段 this.Property(s => s.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); this.Property(s => s.Author).HasColumnType("nvarchar").HasMaxLength(50).IsRequired(); this.Property(s => s.AddedDate).IsRequired(); this.Property(s => s.IP).IsOptional(); this.Property(s => s.ISBN).HasColumnType("nvarchar").HasMaxLength(50).IsRequired(); this.Property(s => s.ModifiedDate).IsOptional(); this.Property(s => s.PublishedDate).IsRequired(); this.Property(s => s.Title).HasColumnType("nvarchar").HasMaxLength(50).IsRequired(); //配置表名 this.ToTable("Books"); } } }
EF数据上下文类:
using System;using System.Collections.Generic;using System.Data.Entity;using System.Data.Entity.ModelConfiguration;using System.Linq;using System.Reflection;using System.Text;using System.Threading.Tasks;namespace EF.Data { public class EFDbContext:DbContext { public EFDbContext() : base("name=DbConnectionString") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { var typesToRegister = Assembly.GetExecutingAssembly().GetTypes() .Where(type => !String.IsNullOrEmpty(type.Namespace)) .Where(type => type.BaseType != null && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>)); foreach (var type in typesToRegister) { dynamic configurationInstance = Activator.CreateInstance(type); modelBuilder.Configurations.Add(configurationInstance); } //base.OnModelCreating(modelBuilder); } } }
然后启用数据库迁移,自动生成数据库,这里的步骤就省略了,因为前面的文章已经说了。
接着,在EF.Data项目中创建泛型仓储类,里面提供了增删查改的方法,这个泛型的仓储类中,有一个带DbContext参数的构造函数,所以当我们实例化仓储的时候,传递一个数据上下文对象给仓储,所有的实体就可以使用同一个数据上下文对象了。我们使用了数据上下文的SaveChange方法,但是你同样可以使用工作单元类的Save方法,因为这两者使用的是同一个数据上下文对象,下面是泛型的仓储类代码:【为了使文章更容易理解,这里我不创建泛型的仓储接口】。
View Code
下面创建工作单元类,UnitOfWork,这个工作单元类继承自IDisposable接口,所以它的实例将会在每个控制器中被 释放掉。工作单元类初始化了程序的上下文,工作单元类的核心就是Repository<T>() 类型的泛型方法,这个方法为继承自BaseEntity的每个实体返回了仓储实例。下面是工作单元类的代码:
using EF.Entity;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace EF.Data { public class UnitOfWork : IDisposable { private readonly EFDbContext db; private bool disposed; private Dictionary<string, object> repositories; public UnitOfWork(EFDbContext context) { this.db = context; //构造函数中初始化上下文对象 } public UnitOfWork() { db = new EFDbContext(); //构造函数中初始化上下文对象 } #region Dispose public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { db.Dispose(); } } disposed = true; } #endregion #region Save public void Save() { db.SaveChanges(); } #endregion #region Repository<T>() public Repository<T> Repository<T>() where T : BaseEntity { if (repositories == null) { repositories = new Dictionary<string, object>(); } var type = typeof(T).Name;//获取当前成员名称 if (!repositories.ContainsKey(type))//如果repositories中不包含Name { var repositoryType = typeof(Repository<>);//获取Repository<>类型 var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), db); repositories.Add(type, repositoryInstance); } return (Repository<T>)repositories[type]; } #endregion } }
好了,底层的代码,写完了,现在开始写控制器的代码:我们创建一个Book控制器,进行增删查改。
View Code
Index 视图代码:
@model IEnumerable<EF.Entity.Book> <div class="book-example panel panel-primary"> <div class="panel-heading panel-head">Books Listing</div> <div class="panel-body"> <a id="createEditBookModal" href="@Url.Action("CreateEditBook")" class="btn btn-success"> <span class="glyphicon glyphicon-plus"></span>Book </a> <table class="table" style="margin: 4px"> <tr> <th> @Html.DisplayNameFor(model => model.Title) </th> <th> @Html.DisplayNameFor(model => model.Author) </th> <th> @Html.DisplayNameFor(model => model.ISBN) </th> <th> Action </th> <th></th> </tr> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Title) </td> <td> @Html.DisplayFor(modelItem => item.Author) </td> <td> @Html.DisplayFor(modelItem => item.ISBN) </td> <td> @Html.ActionLink("Edit", "CreateEditBook", new { id = item.ID }, new { @class = "btn btn-success" }) | @Html.ActionLink("Details", "DetailBook", new { id = item.ID }, new { @class = "btn btn-primary" }) | @Html.ActionLink("Delete", "DeleteBook", new { id = item.ID }, new { @class = "btn btn-danger" }) </td> </tr> } </table> </div> </div>
CraeteEdit视图代码:
@model EF.Entity.Book @{ ViewBag.Title = "Create Edit Book"; }<div class="book-example panel panel-primary"> <div class="panel-heading panel-head">Add / Edit Book</div> <div class="panel-body"> @using (Html.BeginForm()) { <div class="form-horizontal"> <div class="form-group"> @Html.LabelFor(model => model.Title, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.TextBoxFor(model => model.Title, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.ISBN, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.TextBoxFor(model => model.ISBN, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Author, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.TextBoxFor(model => model.Author, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Published, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.TextBoxFor(model => model.Published, new { @class = "form-control datepicker" }) </div> </div> <div class="form-group"> <div class="col-lg-8"></div> <div class="col-lg-3"> @Html.ActionLink("Back to List", "Index", null, new { @class = "btn btn-default" }) <button class="btn btn-success" id="btnSubmit" type="submit"> Submit </button> </div> </div> </div> } </div> </div>@section scripts 这里的话,在布局页中要添加这个块: @RenderSection("scripts", required: false){ <script src="~/Scripts/bootstrap-datepicker.js" type="text/javascript"></script> <script src="~/Scripts/book-create-edit.js" type="text/javascript"></script>}
DeleteBook视图:
View Code
Detail视图:
View Code
修改一下默认路由为Book控制器,Index方法,然后运行项目》》》
后记:
用到的两个js文件
book-create-edit.js
View Code
bootstrap-datepicker.js
View Code
总结:使用了标准清理模式,每次操作之后,工作单元对象都被销毁了,再次进行其他操作的时候,又会重新创建对象的实例。
完成之后项目的结构是:
每天学一点,每天积累一天,进步就不止一点点!PS:好记性不如烂笔头,学会总结,学会思考~~~ ----要飞翔,必须靠自己!