在MVC程式中,使用泛型倉儲模式和工作單元實現增刪查改
在這片文章中,我將自己動手為所有的實體:寫一個泛型倉儲類,還有一個工作單元。
工作單元的職責就是:為每一個實體,建立倉儲例項。倉儲(倉庫)的職責:增刪查改的功能實現。
我們將會在控制器中,建立工作單元類(UnitOfWork)的例項,然後根據實體,建立倉儲例項,再就是使用倉儲裡面的方法,做操作了。
下面的圖中,解釋了,倉儲和EF 資料上文的關係,在這個圖裡面,MVC控制器和倉儲之間的互動,是通過工作單元來進行的,而不是直接和EF接觸。
那麼你可能就要問了,為什麼要使用工作單元???
工作單元,就像其名稱一樣,做某些事情。在這篇文章中,工作單元主要是,建立例項:它例項化資料上下文,然後使用同樣的資料上下文物件,例項化每一個倉儲物件,用來做資料庫操作。所以: 工作單元是一種模式,它確保我們所有的倉儲類,使用同樣資料庫上下文。
實現一個泛型倉儲類和一個工作單元
請注意:在這篇文章中,你的使用者介面,使用具體的類,而不是介面,原因,我後面的一篇文章中,會說!
好了,現在開始實施:
在這篇文章中,我將會搭建4個專案。
MVC.Core---->>>類庫專案【這裡面:主要是實體的宣告】
MVC.Data---->>>類庫專案【這裡面主要是資料庫的操作,新增引用類庫專案(MVC.Core)】
MVC.Repository--->>>類庫專案【這裡主要是定義泛型倉儲類,新增引用MVC.Core和MVC.Data兩個專案】
MVC.Web----->>>MVC WEB程式【Web程式UI,新增引用MVC.Core和MVC.Data還有MVC.Repository三個專案】
框架的介面:
首先來寫實體層:在MVC.Core專案下,新增一個BaseEntity實體,新增如下程式碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MVC.Core { public class BaseEntity { /// <summary> /// 編號 /// </summary> publicInt64 ID { get; set; } /// <summary> /// 新增時間 /// </summary> public DateTime AddedTime { get; set; } /// <summary> /// 修改時間 /// </summary> public DateTime ModifiedTime { get; set; } /// <summary> /// IP地址 /// </summary> public string IP { get; set; } } }
然後,在MVC.Core專案下,新建一個資料夾【Data】,在【Data】資料夾裡面新增Book實體,Book繼承BaseEntity實體。
下面是Book實體裡面的程式碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MVC.Core.Data { 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 PublishedTime { get; set; } } }
然後,我們看到MVC.Data這個類庫專案,這個專案中,我們將會包含資料上下文類,Book實體的對映。
ADO.NET Entity Framework 要求我們建立的資料上下文類必須繼承DbContext類。我們在上下文類中,將會重寫OnModelCreating方法,這個方法是用來使用Code-First方式,配置實體類的。
使用EF,需要安裝EF,這裡就不介紹了,要了解的話,可以去看我前面的文章中的介紹。
我建立一個EFDbContextClass類,下面是資料上下文類中的程式碼:
using MVC.Core; 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 MVC.Data { public class EFDBContextClass:DbContext { public EFDBContextClass() : base("name=ConnectionStrings") { } public new IDbSet<TEntity> Set<TEntity>() where TEntity:BaseEntity { return base.Set<TEntity>(); } 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); } } }
在上面的程式碼中,使用反射來對映每個實體。然後資料上下文的建構函式中,我傳遞了連線字串名字ConnectionStrings,在配置檔案中
<connectionStrings> <add name="ConnectionStrings" connectionString="server=.;database=MyRepositoryDB;uid=sa;pwd=Password_1" providerName="System.Data.SqlClient"/> </connectionStrings>
好了,資料上下文也寫好了,現在開始實體對映吧,在MVC.Data專案中,新增一個資料夾--【Mapping】,然後在Mapping資料夾下面,新增一個類--【BookMap】,下面是BookMap的程式碼:
using MVC.Core.Data; using System; using System.Collections.Generic; using System.Data.Entity.ModelConfiguration; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ComponentModel.DataAnnotations.Schema; namespace MVC.Data.Mapping { public class BookMap:EntityTypeConfiguration<Book> { public BookMap() { //配置主鍵 HasKey(s => s.ID); //配置列 Property(s => s.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); Property(p => p.Title).IsRequired(); Property(p => p.ISBN).IsRequired(); Property(p => p.Author).IsRequired(); Property(p => p.AddedTime).IsRequired(); Property(p => p.ModifiedTime).IsRequired(); Property(p => p.PublishedTime).IsRequired(); Property(p => p.IP); //配置表名稱 ToTable("Books"); } } }
好了,現在開始寫我們的泛型倉儲類,在我們的MVC.Repository專案中,新增一個類Repository。這裡沒有新增介面倉儲,是為了更好的理解。泛型倉儲類擁有增刪查改的方法,
這個倉儲類,擁有一個帶資料上下文類的引數的建構函式,所以當我們建立建立倉儲例項的時候,只需要傳遞一個數據上下文物件過來就行,這就一樣,每個實體的倉儲都有一樣的資料上下文物件了。下面的程式碼是,泛型倉儲模式程式碼了:
using MVC.Core; using MVC.Data; using System; using System.Collections.Generic; using System.Data.Entity; using System.Data.Entity.Validation; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MVC.Repository { public class Repository<T> where T:BaseEntity { private EFDBContextClass context; private IDbSet<T> entities; string errorMessage = string.Empty; public Repository(EFDBContextClass context) { this.context = context; } public T GetById(object id) { return this.Entities.Find(id); } public void Insert(T entity) { try { if (entity == null) { throw new ArgumentNullException("entity"); } this.Entities.Add(entity); this.context.SaveChanges(); } catch (DbEntityValidationException ex) { //錯誤處理機制 foreach (var validationErros in ex.EntityValidationErrors) { foreach (var errorInfo in validationErros.ValidationErrors) { errorMessage += string.Format("屬性:{0} 錯誤訊息:{1}", errorInfo.PropertyName, errorInfo.ErrorMessage) + Environment.NewLine; } } throw new Exception(errorMessage, ex); } } public void Update(T entity) { try { if (entity == null) { throw new ArgumentNullException("entity"); } this.context.SaveChanges(); } catch (DbEntityValidationException ex) { foreach (var errorItems in ex.EntityValidationErrors) { foreach (var errorinfo in errorItems.ValidationErrors) { errorMessage += string.Format("屬性名:{0},錯誤訊息:{1}", errorinfo.PropertyName, errorinfo.ErrorMessage) + Environment.NewLine; } } throw new Exception(errorMessage, ex); } } public void Delete(T entity) { try { if (entity == null) { throw new ArgumentNullException("entity"); } this.Entities.Remove(entity); this.context.SaveChanges(); } catch (DbEntityValidationException ex) { foreach (var errorItems in ex.EntityValidationErrors) { foreach (var errorinfo in errorItems.ValidationErrors) { errorMessage += string.Format("屬性名:{0},錯誤訊息:{1}", errorinfo.PropertyName, errorinfo.ErrorMessage) + Environment.NewLine; } } throw new Exception(errorMessage, ex); } } private IDbSet<T> Entities { get { if (entities == null) { entities = context.Set<T>(); } return entities; } } public virtual IQueryable<T> Table { get { return this.Entities; } } } }
在MVC.Repository專案中新增一個類:UnitOfWork
現在來為工作單元建立一個類,UnitOfWork,這個類繼承IDisposible介面,所以它的每個例項將會在控制器中銷燬,這個工作單元,初始化程式的資料上下文類【EFDBContextClass】.這個工作單元類的核心就是Repository方法,這個方法,為每一個繼承自BaseEntity的實體,返回一個倉儲【repository】物件,下面是工作單元類的程式碼:
using MVC.Core; using MVC.Data; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MVC.Repository { public class UnitOfWork:IDisposable { private readonly EFDBContextClass context; private bool disposed; private Dictionary<string, object> repositories; public UnitOfWork(EFDBContextClass context) { this.context = context; } public UnitOfWork() { context = new EFDBContextClass(); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public void Save() { context.SaveChanges(); } public virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { context.Dispose(); } } disposed = true; } 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)) { var repositoryType = typeof(Repository<>); var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), context); repositories.Add(type, repositoryInstance); } return (Repository<T>)repositories[type]; } } }
到此,現在底層的程式碼,基本都寫好了。
現在開始MVC.Web專案裡面的程式碼:
首先,我們在控制器資料夾下面,新建一個控制器類,BookController。然後,我們建立工作單元的例項。
下面是BookController控制器的程式碼:
using MVC.Core.Data; using MVC.Data; using MVC.Repository; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace MVC.Web.Controllers { public class BookController : Controller { private UnitOfWork unitOfWork = new UnitOfWork(); private Repository<Book> bookRepository; public BookController() { //通過工作單元來初始化倉儲 bookRepository = unitOfWork.Repository<Book>(); } // GET: Book public ActionResult Index() { List<Book> listBooks= bookRepository.Table.ToList(); return View(listBooks); } public ActionResult CreateEditBook(int? id) { Book bookModel = new Book(); if (id.HasValue) { bookModel = bookRepository.GetById(id.Value); } return View(bookModel); } [HttpPost] public ActionResult CreateEditBook(Book model) { if (model.ID == 0) { model.ModifiedTime = DateTime.Now; model.AddedTime = DateTime.Now; model.IP = Request.UserHostAddress; bookRepository.Insert(model); } else { var editModel = bookRepository.GetById(model.ID); editModel.Title = model.Title; editModel.Author = model.Author; editModel.ISBN = model.ISBN; editModel.PublishedTime = model.PublishedTime; editModel.ModifiedTime = System.DateTime.Now; editModel.IP = Request.UserHostAddress; bookRepository.Update(editModel); } if (model.ID > 0) { return RedirectToAction("Index"); } return View(model); } public ActionResult DeleteBook(int id) { Book model= bookRepository.GetById(id); return View(model); } [HttpPost,ActionName("DeleteBook")] public ActionResult ConfirmDeleteBook(int id) { Book model= bookRepository.GetById(id); bookRepository.Delete(model); return RedirectToAction("Index"); } public ActionResult DetailBook(int id) { Book model= bookRepository.GetById(id); return View(model); } protected override void Dispose(bool disposing) { unitOfWork.Dispose(); base.Dispose(disposing); } } }
現在已經完成了控制器的方法,開始新增檢視了:
CreateEdit:
@model MVC.Core.Data.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.PublishedTime, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.TextBoxFor(model => model.PublishedTime, 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 { <script src="~/Scripts/bootstrap-datepicker.js" type="text/javascript"></script> <script src="~/Scripts/book-create-edit.js" type="text/javascript"></script> }
DeleteBook:
@model MVC.Core.Data.Book @{ ViewBag.Title = "Delete Book"; } <div class="book-example panel panel-primary"> <div class="panel-heading panel-head">Delete Book</div> <div class="panel-body"> <h3>Are you sure you want to delete this?</h3> <h1>@ViewBag.ErrorMessage</h1> <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.DisplayFor(model => model.Title, 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.DisplayFor(model => model.Author, 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.DisplayFor(model => model.ISBN, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.PublishedTime, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.PublishedTime, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.AddedTime, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.AddedTime, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.ModifiedTime, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.ModifiedTime, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.IP, new { @class = "col-lg-1 control-label" }) <div class="col-lg-9"> @Html.DisplayFor(model => model.IP, new { @class = "form-control" }) </div> </div> @using (Html.BeginForm()) { <div class="form-group"> <div class="col-lg-1"></div> <div class="col-lg-9"> <input type="submit" value="Delete" class="btn btn-danger" /> @Html.ActionLink("Back to List", "Index", null, new { @class = "btn btn-success" }) </div> </div> } </div> </div> </div