软件开发就像是一个江湖,而设计模式就是一本高深的秘籍每读一次、用一次、想一次都能得到新的领悟,让我们的设计技能有所提高。开始时我们可能会“为了模式而模式”,让代码变得乱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) > ;
} /// <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=
}) 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=
}) works.SaveChanges();

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

相关参考

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

  1. Repository(资源库)模式

    Repository(资源库) 协调领域和数据映射层,利用类似于集合的接口来访问领域对象 定义(来自Martin Fowler的<企业应用架构模式>): Mediates between ...

  2. Go 中 ORM 的 Repository(仓储)模式

    ORM 在业务开发中一直扮演着亦正亦邪的角色.很多人赞颂 ORM,认为 ORM 与面向对象的契合度让代码简洁有道.但是不少人厌恶它,因为 ORM 隐藏了太多的细节,埋下了超多的隐患.在 Go 中,我们 ...

  3. 仓储(Repository)和工作单元模式(UnitOfWork)

    仓储和工作单元模式 仓储模式 为什么要用仓储模式 通常不建议在业务逻辑层直接访问数据库.因为这样可能会导致如下结果: 重复的代码 编程错误的可能性更高 业务数据的弱类型 更难集中处理数据,比如缓存 无 ...

  4. Windows下nodejs 模块配置 全局模式与本地模式的区别

    第1步:下载.安装文件 (nodejs的官网http://www.nodejs.org/download/ ) 第2步:安装相关模块环境 打开C:\Program Files\nodejs 目录你会发 ...

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

    “依赖注入”是spring的核心特征,在Web服务器(如Tomcat)加载时,它会根据Spring的配置文件中配置的bean或者是通过注解模式而扫描并装载的bean实例自动注入到Application ...

  6. 工厂方法模式——创建型模式02

    1. 简单工厂模式     在介绍工厂方法模式之前,先介绍一下简单工厂模式.虽然简单工厂模式不属于GoF 23种设计模式,但通常将它作为学习其他工厂模式的入门,并且在实际开发中使用的也较为频繁. (1 ...

  7. WCF学习之旅—请求与答复模式和单向模式(十九)

    一.概述 WCF在通信过程中有三种模式:请求与答复.单向.双工通信.以下我们一一介绍. 二.请求与答复模式 客户端发送请求,然后一直等待服务端的响应(异步调用除外),期间处于假死状态,直到服务端有了答 ...

  8. Java 策略模式和状态模式

    本文是转载的,转载地址:大白话解释Strategy模式和State模式的区别 先上图: 本质上讲,策略模式和状态模式做得是同一件事:去耦合.怎么去耦合?就是把干什么(语境类)和怎么干(策略接口)分开, ...

  9. Spark运行模式与Standalone模式部署

    上节中简单的介绍了Spark的一些概念还有Spark生态圈的一些情况,这里主要是介绍Spark运行模式与Spark Standalone模式的部署: Spark运行模式 在Spark中存在着多种运行模 ...

随机推荐

  1. python os模块 遍历目录

    #os #os ->tree命令 import os #递归 #目录 ->文件,文件夹 -> 文件文件夹 dirpath = input('请输入你要遍历的目录\n') def ge ...

  2. Go语言学习笔记(六)net & net/http

    加 Golang学习 QQ群共同学习进步成家立业工作 ^-^ 群号:96933959 net import "net" net包提供了可移植的网络I/O接口,包括TCP/IP.UD ...

  3. 如何删除sharepoint列表List中的全部数据。

    可以使用excel,但是powershell会比较方便 (admin mode - Sharepoint powershell) [System.reflection.Assembly]::LoadW ...

  4. FZU Monthly-201901 tutorial

    FZU Monthly-201901 tutorial 题目(难度递增) easy easy-medium medium medium-hard hard 思维难度 AHG F B CE D 编码难度 ...

  5. 【2017下集美大学软工1412班_助教博客】团队编程2-需求分析&原型设计团队成绩公示

    作业要求 团队作业2:需求分析&原型设计 团队评分结果 团队名称 作业标题 Total DY SM NABCD FG YX GF SP PHILOSOPHER 团队作业2--需求分析 5.5 ...

  6. 树莓派3B+ wifi 5G连接

    新烧的Raspbian 系统,一开始需要设置wifi的一些配置,其中会选择一个国家(set country),一开始选择的是CN(中国),发现只能连接2.4G的网络,5G的网络连接不上,这很奇怪, 因 ...

  7. HTML常用标签大全

  8. Promise 模式解析:Promise模式与异步及声明式编程

    一.构建流程 1.(异步)数据源(请求)的构建:Promise的构建并执行请求: 2.处理流程的构建:then将处理函数保存: 二.处理: 1.请求的响应返回: 2.调用后继处理流程. 三. 1.构建 ...

  9. HTML标签之marquee

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/zkn_CS_DN_2013/article/details/25229719 <html> ...

  10. Maven 安装源码和文档到本地仓库

    一: 1:   mvn source:jar 生成源码的jar包 2: mvn source:jar install 将源码安装到本地仓库 ,可以直接mvn source:jar install 一部 ...