系列目錄:
Code First Migrations with Entity Framework【使用EF 做數(shù)據(jù)庫(kù)遷移】
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中使用倉(cāng)儲(chǔ)模式,來(lái)做增刪查改】
CRUD Operations Using the Generic Repository Pattern and Unit of Work in MVC【在MVC中使用泛型倉(cāng)儲(chǔ)模式和工作單元來(lái)做增刪查改】
源代碼下載:https://github.com/caofangsheng93/GenericRepositoryCURD
這篇文章,我將會(huì)介紹在ASP.NET MVC應(yīng)用程序中使用泛型倉(cāng)儲(chǔ)模式和工作單元。我將開(kāi)發(fā)一個(gè)程序,對(duì)Book實(shí)體進(jìn)行增刪查改,為了保證這篇文章簡(jiǎn)單,便于大家理解泛型倉(cāng)儲(chǔ)模式和工作單元,在這篇文章中,我將只會(huì)使用一個(gè)Book實(shí)體。
在上篇文章中“4.CRUD Operations Using the Repository Pattern in MVC【在MVC中使用倉(cāng)儲(chǔ)模式進(jìn)行增刪查改】“講到了倉(cāng)儲(chǔ)模式,里面我們?yōu)锽ook實(shí)體,創(chuàng)建了一個(gè)倉(cāng)儲(chǔ)類(lèi),但是這個(gè)倉(cāng)儲(chǔ)僅僅是只能為一個(gè)實(shí)體服務(wù)的。試想一下,如果是真正的企業(yè)級(jí)開(kāi)發(fā),我們會(huì)有很多實(shí)體,難道,我們要為每一個(gè)實(shí)體都創(chuàng)建一個(gè)倉(cāng)儲(chǔ)類(lèi)么???顯然是不現(xiàn)實(shí)的。對(duì)于這個(gè)問(wèn)題,我們需要?jiǎng)?chuàng)建一個(gè)可以為所有實(shí)體公用的倉(cāng)儲(chǔ),所以這里引入泛型倉(cāng)儲(chǔ)。這樣就避免了重復(fù)編碼。
下面的圖中,講到了兩個(gè)開(kāi)發(fā)者,討論一個(gè)問(wèn)題:"是否需要?jiǎng)?chuàng)建一個(gè)新的零件或者是利用已經(jīng)存在的零件?" ------如果你選擇第一種,那么就是這篇文章講到的,"4.CRUD Operations Using the Repository Pattern in MVC【在MVC中使用倉(cāng)儲(chǔ)模式進(jìn)行增刪查改】"
但我在這里選擇第二種,也就是這篇文章,我將要講到的。--使用泛型倉(cāng)儲(chǔ)模式來(lái)重用代碼,減少冗余代碼。
既然說(shuō)到倉(cāng)儲(chǔ),那么什么是倉(cāng)儲(chǔ)模式呢?
倉(cāng)儲(chǔ)模式旨在數(shù)據(jù)訪問(wèn)層和業(yè)務(wù)邏輯層之間創(chuàng)建一個(gè)抽象層。倉(cāng)儲(chǔ)模式是數(shù)據(jù)訪問(wèn)模式,旨在達(dá)到數(shù)據(jù)訪問(wèn)的更松散的耦合性。我們?cè)趩为?dú)的類(lèi),或者類(lèi)庫(kù)中創(chuàng)建數(shù)據(jù)訪問(wèn)的邏輯,這就是倉(cāng)儲(chǔ)。倉(cāng)儲(chǔ)的職責(zé)就是和業(yè)務(wù)邏輯層進(jìn)行通信。
在這篇文章中,我將會(huì)為所有的實(shí)體設(shè)計(jì)一個(gè)公共的泛型倉(cāng)儲(chǔ),另外還有一個(gè)工作單元類(lèi)。這個(gè)工作單元類(lèi),為每個(gè)實(shí)體創(chuàng)建倉(cāng)儲(chǔ)的實(shí)例,然后倉(cāng)儲(chǔ)實(shí)例用來(lái)做增刪查改操作。我在控制器中創(chuàng)建工作單元類(lèi)的實(shí)例,然后依據(jù)具體的實(shí)體,創(chuàng)建倉(cāng)儲(chǔ)的實(shí)例,然后就可以使用倉(cāng)儲(chǔ)中的方法進(jìn)行每個(gè)操作了。
下面的圖表顯示了倉(cāng)儲(chǔ)和EF數(shù)據(jù)上下文之間的關(guān)系。在圖中,MVC控制器直接通過(guò)工作單元和倉(cāng)儲(chǔ)進(jìn)行交互,而不是直接和EF進(jìn)行交互。
講到這里,大家可能就會(huì)有疑問(wèn)了,為什么要使用工作單元呢???
工作單元就像它的名字一樣,做某件事情。在這篇文章中,工作單元主要做的是:我們創(chuàng)建工作單元的實(shí)例,然后工作單元為我們初始化EF數(shù)據(jù)上下文,然后每個(gè)倉(cāng)儲(chǔ)的實(shí)例都使用同一個(gè)數(shù)據(jù)上下文實(shí)例進(jìn)行數(shù)據(jù)庫(kù)操作。因此工作單元就是,用來(lái)確保所有的倉(cāng)儲(chǔ)實(shí)例都使用同一個(gè)數(shù)據(jù)上下文實(shí)例。
好了理論到此為止,講的差不多了?,F(xiàn)在我們開(kāi)始進(jìn)入正題:
先看看項(xiàng)目的結(jié)構(gòu):
這三個(gè)項(xiàng)目就不用做過(guò)多介紹了吧,前面的文章已經(jīng)說(shuō)了很多次了...
EF.Entity類(lèi)庫(kù)中,添加BaseEntity實(shí)體:
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編號(hào) /// </summary> public int ID { get; set; } /// <summary> /// 添加時(shí)間 /// </summary> public DateTime AddedDate { get; set; } /// <summary> /// 修改時(shí)間 /// </summary> public DateTime ModifiedDate { get; set; } /// <summary> /// IP地址 /// </summary> public string IP { get; set; } } }
Book實(shí)體:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace EF.Entity { public class Book:BaseEntity { /// <summary> /// 書(shū)名 /// </summary> public string Title { get; set; } /// <summary> /// 作者 /// </summary> public string Author { get; set; } /// <summary> /// ISBN編號(hào) /// </summary> public string ISBN { get; set; } /// <summary> /// 出版時(shí)間 /// </summary> public DateTime PublishedDate { get; set; } } }
Entity.Data類(lèi)庫(kù)中BookMap類(lèi):
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數(shù)據(jù)上下文類(lèi):
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); } } }
然后啟用數(shù)據(jù)庫(kù)遷移,自動(dòng)生成數(shù)據(jù)庫(kù),這里的步驟就省略了,因?yàn)榍懊娴奈恼乱呀?jīng)說(shuō)了。
接著,在EF.Data項(xiàng)目中創(chuàng)建泛型倉(cāng)儲(chǔ)類(lèi),里面提供了增刪查改的方法,這個(gè)泛型的倉(cāng)儲(chǔ)類(lèi)中,有一個(gè)帶DbContext參數(shù)的構(gòu)造函數(shù),所以當(dāng)我們實(shí)例化倉(cāng)儲(chǔ)的時(shí)候,傳遞一個(gè)數(shù)據(jù)上下文對(duì)象給倉(cāng)儲(chǔ),所有的實(shí)體就可以使用同一個(gè)數(shù)據(jù)上下文對(duì)象了。我們使用了數(shù)據(jù)上下文的SaveChange方法,但是你同樣可以使用工作單元類(lèi)的Save方法,因?yàn)檫@兩者使用的是同一個(gè)數(shù)據(jù)上下文對(duì)象,下面是泛型的倉(cāng)儲(chǔ)類(lèi)代碼:【為了使文章更容易理解,這里我不創(chuàng)建泛型的倉(cāng)儲(chǔ)接口】。
View Code
下面創(chuàng)建工作單元類(lèi),UnitOfWork,這個(gè)工作單元類(lèi)繼承自IDisposable接口,所以它的實(shí)例將會(huì)在每個(gè)控制器中被 釋放掉。工作單元類(lèi)初始化了程序的上下文,工作單元類(lèi)的核心就是Repository<T>() 類(lèi)型的泛型方法,這個(gè)方法為繼承自BaseEntity的每個(gè)實(shí)體返回了倉(cāng)儲(chǔ)實(shí)例。下面是工作單元類(lèi)的代碼:
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; //構(gòu)造函數(shù)中初始化上下文對(duì)象 } public UnitOfWork() { db = new EFDbContext(); //構(gòu)造函數(shù)中初始化上下文對(duì)象 } #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;//獲取當(dāng)前成員名稱(chēng) if (!repositories.ContainsKey(type))//如果repositories中不包含Name { var repositoryType = typeof(Repository<>);//獲取Repository<>類(lèi)型 var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), db); repositories.Add(type, repositoryInstance); } return (Repository<T>)repositories[type]; } #endregion } }
好了,底層的代碼,寫(xiě)完了,現(xiàn)在開(kāi)始寫(xiě)控制器的代碼:我們創(chuàng)建一個(gè)Book控制器,進(jìn)行增刪查改。
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 這里的話,在布局頁(yè)中要添加這個(gè)塊: @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
修改一下默認(rèn)路由為Book控制器,Index方法,然后運(yùn)行項(xiàng)目》》》
后記:
用到的兩個(gè)js文件
book-create-edit.js
View Code
bootstrap-datepicker.js
View Code
總結(jié):使用了標(biāo)準(zhǔn)清理模式,每次操作之后,工作單元對(duì)象都被銷(xiāo)毀了,再次進(jìn)行其他操作的時(shí)候,又會(huì)重新創(chuàng)建對(duì)象的實(shí)例。
完成之后項(xiàng)目的結(jié)構(gòu)是:
每天學(xué)一點(diǎn),每天積累一天,進(jìn)步就不止一點(diǎn)點(diǎn)!PS:好記性不如爛筆頭,學(xué)會(huì)總結(jié),學(xué)會(huì)思考~~~ ----要飛翔,必須靠自己!