也都有CRUD(新增、修改、讀取、刪除)的操作,也有Transaction的操作
那為什麼要再給它包一層Repository來操作EF呢?
然後再用Unit of Work來做Transaction呢?
目前我想到把EF包成Repository的最大優點應該是
日後變換ORM或其他資料庫時無需變更上層的程式,也就不跟EF綁得死死的
在單元測試的時候來源資料切換容易
但是仔細想想,一個專案要變更資料庫的機會大嗎?
我目前是還沒遇到過,所以EF是否在套上一個Repository就見人見智了
那回歸正題,Entity Framework 如何使用 Repository & Unit of Work
首先先設計Repository的CRUD類別
Repository介面的定義
IRepository.csusing System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; namespace enjoy01coding.Repository { public interface IRepository<TEntity> where TEntity : class { IEnumerable<TEntity> Get( Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = ""); TEntity GetByID(object id); void Insert(TEntity entity); void Delete(object id); void Delete(TEntity entityToDelete); void Update(TEntity entityToUpdate); } }
Repository介面的實作
using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Linq.Expressions; namespace enjoy01coding.Repository { public class Repository<TEntity> : IRepository<TEntity> where TEntity : class { internal DbContext context; internal DbSet<TEntity> dbSet; public Repository(DbContext context) { this.context = context; this.dbSet = context.Set<TEntity>(); } public virtual IEnumerable<TEntity> Get( Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "") { IQueryable<TEntity> query = dbSet; if (filter != null) { query = query.Where(filter); } foreach (var includeProperty in includeProperties.Split (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { query = query.Include(includeProperty); } if (orderBy != null) { return orderBy(query).ToList(); } else { return query.ToList(); } } public virtual TEntity GetByID(params object[] id) { return dbSet.Find(id); } 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; } } }
但是呢Repository一次只能針對一個Table來進行操作
如果我有兩個或多個Table要操作
而且只要有一個Table操作失敗,此次操作就應該要算全部失敗
因為有時候不允許A Table已經成功寫入資料,但是B C Table都寫入失敗
就是要寫一種Transaction的概念,它就是Unit of work
這時候要在建立UnitOfWork類別
UnitOfWork介面的定義
using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Text; using System.Threading.Tasks; namespace enjoy01coding.Repository { public interface IUnitOfWork : IDisposable { IRepository<T> GetRepository<T>() where T : class; void Save(); void Dispose(); } }
UnitOfWork介面的實作
using System; using System.Collections; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Web; namespace enjoy01coding.Repository { public class UnitOfWork<C> : IDisposable, IUnitOfWork where C : DbContext, new() { private C context = new C(); private Hashtable repositories = new Hashtable(); public IRepository<T> GetRepository<T>() where T : class { if (!repositories.Contains(typeof(T))) { repositories.Add(typeof(T), new Repository<T>(context)); } return (IRepository<T>)repositories[typeof(T)]; } 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); } } }
該如何使用呢?我這裡直接寫在Controllers展示
using enjoy01coding.NorthwindDB; using enjoy01coding.Repository; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Web; using System.Web.Mvc; namespace enjoy01coding.Controllers { public class HomeController : Controller { IUnitOfWork unitOfWork; //Autofac DI 注入 public HomeController(IUnitOfWork inunitOfWork) { this.unitOfWork = inunitOfWork; } public ActionResult Index() { ViewBag.Message = ""; //新增Categories資料測試 NorthwindDB.Categories CategoriesData = new NorthwindDB.Categories { CategoryName = "Categories TEST", Description = "Categories TEST" }; //新增Region資料測試 NorthwindDB.Region RegionData = new NorthwindDB.Region { RegionID = 1, RegionDescription = "Region TEST" }; try { var categories = unitOfWork.GetRepository<NorthwindDB.Categories>(); categories.Insert(CategoriesData); var region = unitOfWork.GetRepository<NorthwindDB.Region>(); region.Insert(RegionData); unitOfWork.Save(); ViewBag.Message = "OK."; } catch(Exception ex) { ViewBag.Message = "ERROR. " + ex.Message; } return View(); } public ActionResult About() { ViewBag.Message = ""; return View(); } public ActionResult Contact() { ViewBag.Message = ""; return View(); } } }
以上就是Entity Framework 使用 Repository & Unit of Work
若對分層式架構有興趣可參考以下連結
Alan Tsai 的學習筆記
http://blog.alantsai.net/2014/10/BuildYourOwnApplicationFrameworkOnMvc-30-Conclusion.html#WizKMOutline_1414590577032885
mrkt 的程式學習筆記
http://kevintsengtw.blogspot.tw/2015/04/aspnet-mvc-twmvc18.html
為什麼UnitOfWork要繼承IDisposable呢,我看你的程式碼裡面也有沒用到釋放資源的方法,挺好奇的
回覆刪除