Repository模式与UnitOfWorks模式的运用
軟件開發(fā)就像是一個江湖,而設(shè)計模式就是一本高深的秘籍每讀一次、用一次、想一次都能得到新的領(lǐng)悟,讓我們的設(shè)計技能有所提高。開始時我們可能會“為了模式而模式”,讓代碼變得亂78糟甚至難以讓人理解,但隨著設(shè)計能力的提高與模式的運用和理解,慢慢地我們可能會忘掉模式隨心所欲,此時再讀讀代碼或者你已經(jīng)發(fā)現(xiàn)自己的工程已融合模式之美—"模式為設(shè)計而生,設(shè)計為需求而活"。在開篇突然想分享一下這10幾年用模式的一點小小的領(lǐng)悟。
IRepository 與 IUnitOfWork
在2011年我曾在Codeproject 發(fā)表過題為 "The Repository Pattern with EF Code First & Dependency Injection in ASP.NET MVC3" 的文章,當(dāng)時講述的是Repository模式和Ioc如何結(jié)合EF在ASP.NET MVC3上應(yīng)用。歷時3年多對Repository的使用,最近又有新的感悟。 Repository 是這幾年我用得最多的模式之一,而且也是我認(rèn)為最常用和最容易掌握的一種模式,我使用Repository的目的有二:
- 將數(shù)據(jù)的實例化解耦,通過Ioc或是工廠方法構(gòu)造數(shù)據(jù)對象而不是用 "new"
- 將數(shù)據(jù)的邏輯行為(CURD)解耦,這樣會便于我更換任何形式的數(shù)據(jù)庫,無論底層數(shù)據(jù)庫使用的是MSSQL還是MongoDB我都可以采用相同的訪問邏輯。
以下是我期望在代碼中看到的效果
public class PostController:Contrller {private IRepository repository;public MyController(IRepository repository){this.repository=repository;}public Action Get(int id) {return View(repository.Find(id));}[HttpPost]public Action Create(Post post) {repository.Add(post);repository.Submit();return this.Get(post.ID);}
... }
這里使用了構(gòu)造注入,由MVC向Controller注入Repository的實例,這樣我就不需要在使Repository的時候去創(chuàng)建它了。(這種例子你Google會發(fā)現(xiàn)很多,包括在asp.net上也不少)
單一地使用Repository很容易理解,但運用在項目中的實例情況可以這樣嗎?大多數(shù)答案是否定的,如果當(dāng)前的Controller控制的不單純是一個實體,而是多個實體時,如果使用純Repository的話代碼會變糟:
public class PostController:Contrller {private IRepository posts;private IRepository blogs;private IRepository owners;public MyController(IRepository blogs,IRepository posts,IRepository owners){this.posts=posts;this.blogs=blogs;this.owners=owners;}public Action Get(int id) {var post=posts.Find(id)var blog=blogs.Find(post.BlogID);var owner=posts.Find(post.Owner);...return View(new { Post=post, Blog=blog, Owner=owner });}... }一看上去似乎沒什么大問題,只是在構(gòu)造時多了兩個Repository的注入,但是我們的目只有一個Controller嗎? 而每個實體都需要去實現(xiàn)一個Repository? 這樣的寫法在項目中散播會怎么樣呢?
最后你會得到一大堆雞肋式的"Repository",他們的相似度非常大。或是你使用一個Repository實現(xiàn)處理所有的實體,但你需要在構(gòu)造時進行配置 (注:構(gòu)造注入并不代表不構(gòu)造,而是將構(gòu)造代碼放在了一個統(tǒng)一的地方),更糟的情況是,如果使用的是繼承于IRepository的子接口,那么Controller就會與IRepository的耦合度加大。
很明顯?Repository 不是一種獨立的模式,它自身和其它模式有很強的相關(guān)度,如果只是拿它來獨立使用局限性會很大。我們需要其它的接口去統(tǒng)一“管理”Repository和避免在Controller內(nèi)顯式的出現(xiàn)Repository的類型聲明。將上面的代碼改一下:
public class PostController:Contrller {private IUnitOfWorks works;public PostController(IUnitOfWorks works){this.works=works;}public Action Get(int id) {var post=works.Posts.Find(id)var blog=works.Blogs.Find(post.BlogID);var owner=works.Posts.Find(post.Owner);...return View(new { Post=post, Blog=blog, Owner=owner });}[HttpPost]public Action Create(Post post){works.Add(post);...} }這里就引入了 UnitOfWorks 模式,將對所有的Repository的耦合消除(把所有的Repository變量的聲明去掉了),
IRepository與IUnitOfWorks兩個模式是最佳組合,我們可以通過對UnitOfWorks一個類進行IoC處理,而調(diào)用方代碼則集中從IUnitOfWorks對象獲取所需的Repository,接下來看看他們的定義吧
IRepository 接口
/// <summary>/// 定義通用的Repository接口/// </summary>/// <typeparam name="T"></typeparam>public interface IRepository<T>: IDisposablewhere T : class{/// <summary>/// 獲取所有的實體對象/// </summary>/// <returns></returns>IQueryable<T> All();/// <summary>/// 通過Lamda表達(dá)式過濾符合條件的實體對象/// </summary>IQueryable<T> Filter(Expression<Func<T, bool>> predicate); /// <summary>/// Gets the object(s) is exists in database by specified filter./// </summary>bool Contains(Expression<Func<T, bool>> predicate);/// <summary>/// 獲取實體總數(shù)/// </summary>int Count();int Count(Expression<Func<T, bool>> predicate);/// <summary>/// 通過鍵值查找并返回單個實體/// </summary>T Find(params object[] keys);/// <summary>/// 通過表達(dá)式查找復(fù)合條件的單個實體/// </summary>/// <param name="predicate"></param>T Find(Expression<Func<T, bool>> predicate);/// <summary>/// 創(chuàng)建實體對象/// </summary> T Create(T t);/// <summary>/// 刪除實體對象/// </summary>void Delete(T t);/// <summary>/// 刪除符合條件的多個實體對象/// </summary>int Delete(Expression<Func<T, bool>> predicate);/// <summary>/// Update object changes and save to database./// </summary>/// <param name="t">Specified the object to save.</param> T Update(T t);/// <summary>/// Clear all data items./// </summary>/// <returns>Total clear item count</returns>void Clear();/// <summary>/// Save all changes./// </summary>/// <returns></returns>int Submit();}?IUnitOfWorks 接口
public interface IUnitOfWorks{IQueryable<T> Where<T>(Expression<Func<T, bool>> predicate) where T : class;IQueryable<T> All<T>() where T : class;int Count<T>() where T : class;int Count<T>(Expression<Func<T, bool>> predicate) where T : class; T Find<T>(object id) where T : class;T Find<T>(Expression<Func<T, bool>> predicate) where T : class; T Add<T>(T t) where T : class; IEnumerable<T> Add<T>(IEnumerable<T> items) where T : class; void Update<T>(T t) where T : class; void Delete<T>(T t) where T : class;void Delete<T>(Expression<Func<T, bool>> predicate) where T : class; void Clear<T>() where T : class;int SaveChanges();void Config(IConfiguration settings);}
?
仔細(xì)一看你會發(fā)現(xiàn)IRepository和IUnitOfWorks的定義非常相似,在使用的角度是UnitOfWorks就是所有實例化的Repository的一個統(tǒng)一“包裝”, 這是我在發(fā)表?"The Repository Pattern with EF Code First & Dependency Injection in ASP.NET MVC3" 后對IUnitOfWorks在實用上的一種擴展。以前的方式是直接在UnitOfWorks的屬性的中暴露一個Repository實例給外部使用,但這樣做的話會降低IUnitOfWorks的通用性。所以我讓IUnitOfWorks使用起來更像是一個IRepository.看一段代碼的比較:
//之前的做法,Posts是一個IRepository的實現(xiàn),是不是很像EF var post=works.Posts.Add(new Post()); //C works.Posts.Update(post);//U var post=works.Posts.Get(id); //R works.Posts.Delete(post);//D//優(yōu)化后的IUnitOfWorks var post=works.Add(new Post());//C works.Update(post);//U var post=works.Get(id); //R works.Delete(post);//D如果將IRepository通過屬性的方式暴露給調(diào)用方,IUnitOfWorks的擴展性就會下降,而且會令I(lǐng)UnitOfWorks的實現(xiàn)類與調(diào)用方建立很緊密的耦合。我對IUnitOfWorks優(yōu)化后以泛型決定使用哪一個Repository,這樣可以將IUnitOfWorks與調(diào)用方進行解耦。所有的實體通過一個通用Repository實現(xiàn),這樣可以避免為每一個實體寫一個Repository。而對于具有特殊處理邏輯的Repository才通過屬性暴露給調(diào)用方。
?
IRepository 與 IUnitOfWorks的實現(xiàn)
在這里我會先實現(xiàn)一套使用EF訪問數(shù)據(jù)庫的通用 Repository 和 UnitOfWorks
EntityRepository?
public class EntityRepository<TContext, TObject> : IRepository<TObject>where TContext : DbContextwhere TObject : class{protected TContext context;protected DbSet<TObject> dbSet;protected bool IsOwnContext = false;/// <summary>/// Gets the data context object./// </summary>protected virtual TContext Context { get { return context; } }/// <summary>/// Gets the current DbSet object./// </summary>protected virtual DbSet<TObject> DbSet { get { return dbSet; } }/// <summary>/// Dispose the class./// </summary>public void Dispose(){if ((IsOwnContext) && (Context != null))Context.Dispose();GC.SuppressFinalize(this);}/// <summary>/// Get all objects./// </summary>/// <returns></returns>public virtual IQueryable<TObject> All(){return DbSet.AsQueryable();}/// <summary>/// Gets objects by specified predicate./// </summary>/// <param name="predicate">The predicate object.</param>/// <returns>return an object collection result.</returns>public virtual IQueryable<TObject> Filter(Expression<Func<TObject, bool>> predicate){return DbSet.Where(predicate).AsQueryable<TObject>();}public bool Contains(Expression<Func<TObject, bool>> predicate){return DbSet.Count(predicate) > 0;}/// <summary>/// Find object by keys./// </summary>/// <param name="keys"></param>/// <returns></returns>public virtual TObject Find(params object[] keys){return DbSet.Find(keys);}public virtual TObject Find(Expression<Func<TObject, bool>> predicate){return DbSet.FirstOrDefault(predicate); }public virtual TObject Create(TObject TObject){var newEntry = DbSet.Add(TObject);if (IsOwnContext)Context.SaveChanges();return newEntry;}public virtual void Delete(TObject TObject){var entry = Context.Entry(TObject);DbSet.Remove(TObject);if (IsOwnContext)Context.SaveChanges();}public virtual TObject Update(TObject TObject){var entry = Context.Entry(TObject);DbSet.Attach(TObject);entry.State = EntityState.Modified;if (IsOwnContext)Context.SaveChanges();return TObject;}public virtual int Delete(Expression<Func<TObject, bool>> predicate){var objects = DbSet.Where(predicate).ToList();foreach (var obj in objects)DbSet.Remove(obj);if (IsOwnContext)return Context.SaveChanges();return objects.Count();}public virtual int Count(){return DbSet.Count();}public virtual int Count(Expression<Func<TObject, bool>> predicate){return DbSet.Count(predicate);}public int Submit(){return Context.SaveChanges();} public virtual void Clear(){}}?
?UnitOfWorks
public class UnitOfWorks<TDBContext> : IUnitOfWorkswhere TDBContext :DbContext{protected TDBContext dbContext;public UnitOfWorks<TDBContext>(TDBContext context){dbContext=context;}//構(gòu)造通用的Repositoryprivate IDictionary<Type,object> repositoryTable = new Dictionary<Type,object>();//注冊其它的Repositorypublic void Register<T>(IRepository<T> repository){var key=typeof(T);if (repositoryTable.ContainsKey(key))repositoryTable[key].Add(repository);}private IRepository<T> GetRepository<T>()where T:class{IRepository<T> repository = null;var key=typeof(T);if (repositoryTable.ContainsKey(key))repository = (IRepository<T>)repositoryTable[key];else{repository = GenericRepository<T>();repositoryTable.Add(key, repository);}return repository;}protected virtual IRepository<T> GenericRepository<T>() where T : class{return new EntityRepository<T>(dbContext);}public T Find<T>(object id) where T : class{return GetRepository<T>().Find(id);}public T Add<T>(T t) where T : class{return GetRepository<T>().Create(t);}public IEnumerable<T> Add<T>(IEnumerable<T> items) where T : class{var list = new List<T>();foreach (var item in items)list.Add(Add(item));return list;}public void Update<T>(T t) where T : class{GetRepository<T>().Update(t);}public void Delete<T>(T t) where T : class{GetRepository<T>().Delete(t);}public void Delete<T>(Expression<Func<T, bool>> predicate) where T : class{GetRepository<T>().Delete(predicate);}public int SaveChanges(bool validateOnSave = true){if (!validateOnSave)dbContext.Configuration.ValidateOnSaveEnabled = false;return dbContext.SaveChanges();}public void Dispose(){if (dbContext != null)dbContext.Dispose();GC.SuppressFinalize(this);}public System.Linq.IQueryable<T> Where<T>(System.Linq.Expressions.Expression<Func<T, bool>> predicate)where T:class{return GetRepository<T>().Filter(predicate);}public T Find<T>(System.Linq.Expressions.Expression<Func<T, bool>> predicate) where T : class{return GetRepository<T>().Find(predicate);}public System.Linq.IQueryable<T> All<T>() where T : class{return GetRepository<T>().All();}public int Count<T>() where T : class{return GetRepository<T>().Count();}public int Count<T>(System.Linq.Expressions.Expression<Func<T, bool>> predicate) where T : class{return GetRepository<T>().Count(predicate);}public void Config(IConfiguration settings){ var configuration=settings as DbConfiguration ;if (configuration != null){this.dbContext.Configuration.AutoDetectChangesEnabled = configuration.AutoDetectChangesEnabled;this.dbContext.Configuration.LazyLoadingEnabled = configuration.LazyLoadingEnabled;this.dbContext.Configuration.ProxyCreationEnabled = configuration.ProxyCreationEnabled;this.dbContext.Configuration.ValidateOnSaveEnabled = configuration.ValidateOnSaveEnabled;}}public void Clear<T>() where T : class{GetRepository<T>().Clear();}int IUnitOfWorks.SaveChanges(){return this.SaveChanges();} }
?
?接下來看看如何使用這套基于EF的實現(xiàn),首先是對Model的定義
public class Category{[Key]public int ID { get; set; }public virtual string Name { get; set; }public virtual string Title { get; set; }public virtual ICollection<Product> Products { get; set; }}public class Product{[Key]public int ID { get; set; }public int CategoryID { get; set; }[ForeignKey("CategoryID")]public virtual Category Category {get;set;}public string Name { get; set; }public string Title { get; set; }public string Description{get;set;}public decimal Price { get; set; }}public class DB : DbContext{public DB() : base("DemoDB") { }public DbSet<Category> Categories { get; set; }public DbSet<Product> Products { get; set; }}?
調(diào)用方:使用UnitOfWorks和Repository
var works=new UnitOfWorks(new DB());var pc=works.Add(new Category() { Name="PC",Title="電腦" });workds.Add(new Product(){Category=pc,Name="iMac",Title="iMac"Price=9980 })works.SaveChanges();?注:如果需要使用IoC方式構(gòu)造UnitOfWorks 可參考我在?"The Repository Pattern with EF Code First & Dependency Injection in ASP.NET MVC3" 一文中提及如何在MVC內(nèi)通過Unity 實現(xiàn)DI.
小結(jié)
?以上述例子為例,如果我們想將Category存儲于文本文件而不想改動調(diào)用方的代碼。我們可以實現(xiàn)一個 FileBaseCategoryRepository,然后在UnitOfWorks在構(gòu)造后調(diào)用Register方法將默認(rèn)的Category Repository替換掉,同理,這就可以建立不同的Repository去配置UnitOfWork
?
var works=new UnitOfWorks(new DB()); works.Register(new FileBaseCategoryRepository());var pc=works.Add(new Category() { Name="PC",Title="電腦" });workds.Add(new Product(){Category=pc,Name="iMac",Title="iMac"Price=9980 })works.SaveChanges();使用IUnitOfWorks+IRepository模式你就可以靈活地配置你的數(shù)據(jù)訪問方式,可以通過Repository極大地提通實體存儲邏輯代碼的重用性。
?
?相關(guān)參考
- Repository?Pattern?- by Martin fowler
- The Repository Pattern with EF Code First & Dependency Injection in ASP.NET MVC3?- by Ray
?
轉(zhuǎn)載于:https://www.cnblogs.com/Ray-liang/p/3809822.html
總結(jié)
以上是生活随笔為你收集整理的Repository模式与UnitOfWorks模式的运用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在Eclipse上搭建Android C
- 下一篇: WCF 第五章 控制并发调用的数量