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 Createory()
{
   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 Createory()
{
   Name="PC",
   Title="电脑"
});

workds.Add(new Product(){
   Category=pc,
   Name="iMac",
   Title="iMac"
   Price=9980
})

works.SaveChanges();

使用IUnitOfWorks+IRepository模式你就可以灵活地配置你的数据访问方式,可以通过Repository极大地提通实体存储逻辑代码的重用性。

相关参考

Repository模式与UnitOfWorks模式的运用

时间: 2024-08-11 07:43:33

Repository模式与UnitOfWorks模式的运用的相关文章

Go 中 ORM 的 Repository(仓储)模式

ORM 在业务开发中一直扮演着亦正亦邪的角色.很多人赞颂 ORM,认为 ORM 与面向对象的契合度让代码简洁有道.但是不少人厌恶它,因为 ORM 隐藏了太多的细节,埋下了超多的隐患.在 Go 中,我们也或多或少接触过 ORM,但是,在查阅不少业务代码后发现,ORM 使用起来颇为滑稽,并且"雷隐隐雾蒙蒙". 从 Entity Framework 谈起 Entity Framework 作为雄踞 Microsoft .NET Framework 以及 .NET Core 的杀手级 ORM

spring的配置模式与注解模式基础

“依赖注入”是spring的核心特征,在Web服务器(如Tomcat)加载时,它会根据Spring的配置文件中配置的bean或者是通过注解模式而扫描并装载的bean实例自动注入到ApplicationContext容器中(ApplicationContext容器管理了被注入的bean对象). 下面做两个简单测试以说明spring“依赖注入“的两种模式:配置模式与注解模式. 测试工具: 一.新建spring配置文件,放在类目录下 在“src”上右键点"new",选择"Other

PHP中的抽象类与抽象方法/静态属性和静态方法/PHP中的单利模式(单态模式)/串行化与反串行化(序列化与反序列化)/约束类型/魔术方法小结

  前  言  OOP  学习了好久的PHP,今天来总结一下PHP中的抽象类与抽象方法/静态属性和静态方法/PHP中的单利模式(单态模式)/串行化与反串行化(序列化与反序列化). 1  PHP中的抽象类与抽象方法 1.什么是抽象方法?              没有方法体 {} 的方法,必须使用abstract 关键字修饰.这样的方,我们叫做抽象方法.                    abstract function say(); //    抽象方法 2.什么是抽象类?        

2 结构型模式之 - 外观模式

外观模式的介绍:外观模式在开发运用中的频率非常高,尤其是现阶段各种第三方SDK充斥在我们的周边,而这些SDK很大概率会使用外观模式,通过一个外观类使得整个系统的接口只有一个统一的高层接口,这样就能够降低用户使用的复杂度,也对用户屏蔽了很多实现细节,当然 ,在我们的开发过程中,外观模式也是我们封装API的常用手段,例如网络模块,ImageLoader模块等.可能你已经在开发中运用过无数次外观模式,只是没有理论层面上认识它,本章我们就从理论与实践相结合的方式来理解外观模式 外观模式的定义: 要求一个

Java设计模式—工厂方法模式&amp;抽象工厂模式

工厂方法模式与抽象工厂模式都是设计模式中重要而且常见的模式.       工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类.工厂方法使一个类的实例化延迟到其子类. 通用类图如下: 在工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事物最抽象的定义:Creator为抽象创建 类,也就是抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator完成的. 工厂方法模式的扩展方式有很多种,下边是工厂方法模式一个比较实用的源代码: 抽象产品类: pub

第19章 行为型模式—中介者模式

1. 中介者模式(Mediator Pattern)的定义 (1)定义:用一个中介对象来封装一系统对象交互.中介者使得各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互.       ①中介者模式主要用来将同事类之间网状结构变为星状结构,使同事类之间的关系变的清晰一些. ②所有对象只跟中介者对象进行通信,相互之间不再有联系,这样也能够集中控制这些对象的交互关系. (2)中介者模式的结构和说明 ①Mediator: 中介者接口.在里面定义各个同事之间交互需要的方法,可以

设计模式之桥梁模式和策略模式的区别

桥接(Bridge)模式是结构型模式的一种,而策略(strategy)模式则属于行为模式.以下是它们的UML结构图. 桥梁模式: 策略模式: 在桥接模式中,Abstraction通过聚合的方式引用Implementor. 举一个例子: 策略模式:我要画圆,要实心圆,我可以用solidPen来配置,画虚线圆可以用dashedPen来配置.这是strategy模式. 桥接模式:同样是画圆,我是在windows下来画实心圆,就用windowPen+solidPen来配置,在unix下画实心圆就用uni

0.11之路(四):从实模式到保护模式

(一)关中断并将system移动到内存地址起始位置 0x00000 将CPU的标志寄存器(EFLAGS)中的中断允许标志(IF)置0.这样系统不会再响应中断,直到main函数中能够适应保护模式的中断服务体系重建完毕才会打开,那时候响应中断的服务程序将不再是BIOS提供的中断服务程序,而是系统自身提供的. 就是要完成实模式下的中断向量表和保护模式下的中断描述符表(IDT)的交接工作.借助关中断(cli)和开中断(sti)完成这个过程的创建,即在创建过程中不能去响应中断,否则没有对应的中断程序,系统

linux基础:4、linux运行级别、单用户模式、救援模式

linux运行级别 含义:指的是Unix或Linux等类Unix操作系统下不同的运行模式.运行级别通常分为7等,分别是从0到6 ================================================================================== [[email protected] ~]# tail /etc/inittab # Default runlevel. The runlevels used are: #   0 - halt (Do N