zoukankan      html  css  js  c++  java
  • Repository模式与UnitOfWorks模式的运用

    软件开发就像是一个江湖,而设计模式就是一本高深的秘籍每读一次、用一次、想一次都能得到新的领悟,让我们的设计技能有所提高。开始时我们可能会“为了模式而模式”,让代码变得乱78糟甚至难以让人理解,但随着设计能力的提高与模式的运用和理解,慢慢地我们可能会忘掉模式随心所欲,此时再读读代码或者你已经发现自己的工程已融合模式之美—"模式为设计而生,设计为需求而活"。在开篇突然想分享一下这10几年用模式的一点小小的领悟。

    IRepository 与 IUnitOfWork

    在2011年我曾在Codeproject 发表过题为 "The Repository Pattern with EF Code First & Dependency Injection in ASP.NET MVC3" 的文章,当时讲述的是Repository模式和Ioc如何结合EF在ASP.NET MVC3上应用。历时3年多对Repository的使用,最近又有新的感悟。 Repository 是这几年我用得最多的模式之一,而且也是我认为最常用和最容易掌握的一种模式,我使用Repository的目的有二:

    • 将数据的实例化解耦,通过Ioc或是工厂方法构造数据对象而不是用 "new"
    • 将数据的逻辑行为(CURD)解耦,这样会便于我更换任何形式的数据库,无论底层数据库使用的是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); }
    ... }

    这里使用了构造注入,由MVC向Controller注入Repository的实例,这样我就不需要在使Repository的时候去创建它了。(这种例子你Google会发现很多,包括在asp.net上也不少)

    单一地使用Repository很容易理解,但运用在项目中的实例情况可以这样吗?大多数答案是否定的,如果当前的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 });
       }
    
    
       ...
    }

    一看上去似乎没什么大问题,只是在构造时多了两个Repository的注入,但是我们的目只有一个Controller吗? 而每个实体都需要去实现一个Repository? 这样的写法在项目中散播会怎么样呢?

    最后你会得到一大堆鸡肋式的"Repository",他们的相似度非常大。或是你使用一个Repository实现处理所有的实体,但你需要在构造时进行配置 (注:构造注入并不代表不构造,而是将构造代码放在了一个统一的地方),更糟的情况是,如果使用的是继承于IRepository的子接口,那么Controller就会与IRepository的耦合度加大。

    很明显 Repository 不是一种独立的模式,它自身和其它模式有很强的相关度,如果只是拿它来独立使用局限性会很大。我们需要其它的接口去统一“管理”Repository和避免在Controller内显式的出现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处理,而调用方代码则集中从IUnitOfWorks对象获取所需的Repository,接下来看看他们的定义吧

    IRepository 接口

        /// <summary>
        /// 定义通用的Repository接口
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public interface IRepository<T>: IDisposable
            where T : class
        {
            /// <summary>
            /// 获取所有的实体对象
            /// </summary>
            /// <returns></returns>
            IQueryable<T> All();
    
    /// <summary>
            /// 通过Lamda表达式过滤符合条件的实体对象
            /// </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>
            /// 获取实体总数
            /// </summary>
            int Count();
    
            int Count(Expression<Func<T, bool>> predicate);
    
            /// <summary>
            /// 通过键值查找并返回单个实体
            /// </summary>
            T Find(params object[] keys);
    
            /// <summary>
            /// 通过表达式查找复合条件的单个实体
            /// </summary>
            /// <param name="predicate"></param>
            T Find(Expression<Func<T, bool>> predicate);
    
            /// <summary>
            /// 创建实体对象
            /// </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); }

    仔细一看你会发现IRepository和IUnitOfWorks的定义非常相似,在使用的角度是UnitOfWorks就是所有实例化的Repository的一个统一“包装”, 这是我在发表 "The Repository Pattern with EF Code First & Dependency Injection in ASP.NET MVC3" 后对IUnitOfWorks在实用上的一种扩展。以前的方式是直接在UnitOfWorks的属性的中暴露一个Repository实例给外部使用,但这样做的话会降低IUnitOfWorks的通用性。所以我让IUnitOfWorks使用起来更像是一个IRepository.看一段代码的比较:

    //之前的做法,Posts是一个IRepository的实现,是不是很像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
    
    //优化后的IUnitOfWorks
    var post=works.Add(new Post());//C
    works.Update(post);//U
    var post=works.Get(id); //R
    works.Delete(post);//D

    如果将IRepository通过属性的方式暴露给调用方,IUnitOfWorks的扩展性就会下降,而且会令IUnitOfWorks的实现类与调用方建立很紧密的耦合。我对IUnitOfWorks优化后以泛型决定使用哪一个Repository,这样可以将IUnitOfWorks与调用方进行解耦。所有的实体通过一个通用Repository实现,这样可以避免为每一个实体写一个Repository。而对于具有特殊处理逻辑的Repository才通过属性暴露给调用方。

    IRepository 与 IUnitOfWorks的实现

    在这里我会先实现一套使用EF访问数据库的通用 Repository 和 UnitOfWorks

    EntityRepository 

        public class EntityRepository<TContext, TObject> : IRepository<TObject>
            where TContext : DbContext
            where 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> : IUnitOfWorks
            where TDBContext :DbContext
        {
            protected TDBContext dbContext;
    
            public UnitOfWorks<TDBContext>(TDBContext context)
            {
                 dbContext=context;
            }
    
            //构造通用的Repository
            private IDictionary<Type,object> repositoryTable = new Dictionary<Type,object>();
            
    //注册其它的Repository
    public 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的实现,首先是对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; }
        }

    调用方:使用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方式构造UnitOfWorks 可参考我在 "The Repository Pattern with EF Code First & Dependency Injection in ASP.NET MVC3" 一文中提及如何在MVC内通过Unity 实现DI.

    小结

     以上述例子为例,如果我们想将Category存储于文本文件而不想改动调用方的代码。我们可以实现一个 FileBaseCategoryRepository,然后在UnitOfWorks在构造后调用Register方法将默认的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模式你就可以灵活地配置你的数据访问方式,可以通过Repository极大地提通实体存储逻辑代码的重用性。

     相关参考

  • 相关阅读:
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    程序员,你最重要的选择是和谁结婚,你最重要的能力是赚钱,钱和女友两手抓...
    计算机网络基础 — Linux 流量控制
    Oracle 20c 新特性:持久化内存数据库
    数据库每日一题 2020.04.30
  • 原文地址:https://www.cnblogs.com/Ray-liang/p/3809822.html
Copyright © 2011-2022 走看看