前言

年底工作比较忙,年度总结还没写,项目要上线,回老家过年各种准备。尤其是给长辈给侄子侄女准备礼物头都大了。

原来想年前先出一版能用的,我看有点悬了,尽量先把大体功能弄出来,扔掉一些,保证能考试,然后再搞点扩展的东西。

本节主要是做一下EF的封装,在DDD设计中,有两个概念不得不提,工作单元模式和仓储模式。纯属个人理解,不对的地方大家交流。

下面是一张图来自Microsoft 文档站点

仓储模式

定义

Repository模式用于通过抽象接口来管理CRUD操作,该接口公开领域实体并隐藏数据库访问代码的实现细节。

为什么

其实定义已经很明确了,就是封装了一下,让上一层不知道数据访问是怎么实现的,为什么要这么做呢?那就又回到了DDD的概念中了,充血模型下更关注业务功能而不是数据持久层怎么实现,以前三层的时候,DAL实际上关注的就是数据怎么取出来,怎么存进去。为了避免重回三层架构的窠臼,DDD中专门把持久化分出去作为一层,这一层与业务之间的分隔就是通过仓储模式。

一般讲,在领域中提供仓储的接口,即要做什么。具体实现交给持久层,至于持久层是用ADO.NET 还是用ORM框架,数据放在RDBMS、XML、JSON、Cache或者Nosql中,这些都与领域模型无关,领域模型只关心接口,有没有实现,这样就保证了领域模型的与数据持久化之间的独立。

优点

  1. 隔离业务与数据持久化,实现关注点分离。
  2. 便于自动化的单元测试或者测试驱动的开发
  3. 代码更加规整,可读性高

参考资料:Why the Repository Pattern

工作单元模式

定义

Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.   --P of EAA – Martin Fowler

“工作单元”模式用于将一个或多个操作(通常是数据库操作)分组到单个事务或“工作单元”中,以便所有操作都可以通过或失败。 --网友意译。

为什么

工作单元的工作是为了保证业务操作的原子性,也就是一个连贯的业务在提交操作的时候,要么全部成功,要么全部失败,比如用户下单操作,要生成订单,要消减库存,要派送物流,业务上分析的话,这三个操作是同时完成的,不能分离,要么都成功,要么都失败。

Entity Framework本身具有这一特性,在一个操作里面,只有调用 db.SaveChanges()时 ,操作才会进行保存,并且是同时成功或者同时失败,既然EF本身以及有这一功能,为什么还要搞一个工作单元呢?

已经有了存储模式,当单个实体操作进行增删改查是没有问题的,但是当涉及多个实体模型的操作时,就会出现有多个DbContext,大量重复代码,跨实体操作的代码无处安放等。

比如一个创建用户的操作,如下两个表(这个例子不是很恰当,一般理解下就行):

注册用户的时候,有可能分布填写信息,最后要添加两条实体,一条账号记录一条用户信息记录。

在账户仓储类中,我可以添加一条记录,在用户信息仓储中也可以添加一条记录。最后无法保证两条记录同时成功或者失败。

实现

public interface IUnitOfWork
{
/// <summary>
/// 命令
/// </summary>
/// <param name="commandText"></param>
/// <param name="parameters"></param>
/// <returns></returns>
int Command(string commandText, IDictionary<string, object> parameters); /// <summary>
/// 事务的提交状态
/// </summary>
bool IsCommited { get; set; } /// <summary>
/// 提交事务
/// </summary>
/// <returns></returns>
void Commit(); /// <summary>
/// 回滚事务
/// </summary>
void RollBack();
}

本节正文-通用仓储类实现

发现每次整理来整理去都墨迹的不行,前面介绍仓储模式和工作单元花了一大堆精力,其实我项目中根本没用工作单元,原因是项目比较小,如果需要工作单元的地方,在仓储中自定义一个借口就给解决了。还有就是以前用工作单元+Ioc的模式,除了问题不好调试,为了保证项目进度,先实现功能,然后在重构吧。

首先仓储模式是一个接口和接口的实现,我们在Domain项目中添加IRepository接口,定义内容如下:

public interface IRepository<TEntity> where TEntity : class
{
IQueryable<TEntity> All(); TEntity Single(long id); TEntity Single(Expression<Func<TEntity, bool>> predicate); TEntity Single(long id, params Expression<Func<TEntity, object>>[] propertySelectors); IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> predicate); IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] propertySelectors); int Count(); int Count(Expression<Func<TEntity, bool>> criteria); IEnumerable<TEntity> Get<TOrderBy>(Expression<Func<TEntity, bool>> criteria, Expression<Func<TEntity, TOrderBy>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending); TEntity Delete(long id); bool Add(TEntity entity); bool Update(TEntity entity); int Save(TEntity entity);
}

接口中定义了增删改查各种操作,所有的实现都在持久化层,我们这里封装一下,提供一个通用仓储类如下:

public class GenericRepository<TEntity> : IRepository<TEntity> where TEntity : EneityOfLongPrimarykey
{
private MyDbContext context; public GenericRepository()
{
context = new MyDbContext();
// Load navigation properties explicitly (avoid serialization trouble)
context.Configuration.LazyLoadingEnabled = false; // Do NOT enable proxied entities, else serialization fails.
context.Configuration.ProxyCreationEnabled = false; // Because Web API will perform validation, we don't need/want EF to do so
context.Configuration.ValidateOnSaveEnabled = false;
} public virtual IQueryable<TEntity> All()
{
return context.Set<TEntity>().AsQueryable();
} public TEntity Single(long id)
{
return All().Single(t => t.Id == id);
} public TEntity Single(long id, params Expression<Func<TEntity, object>>[] propertySelectors)
{
return Find(s => s.Id == id, propertySelectors).FirstOrDefault();
} public TEntity Single(Expression<Func<TEntity, bool>> predicate)
{
return All().Single(predicate);
} public IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
{
if (predicate != null)
{
return All().Where(predicate).AsNoTracking();
}
else
{
return All();
}
} /// <summary>
/// 取过滤数据,启用延迟查询
/// </summary>
/// <param name="predicate">过滤条件</param>
/// <param name="propertySelectors">需要Left Join 的表</param>
/// <returns></returns>
public IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] propertySelectors)
{
if (propertySelectors.IsNullOrEmpty())
{
return Find(predicate);
} var query = Find(predicate); foreach (var propertySelector in propertySelectors)
{
query = query.Include(propertySelector);
} return query;
} public int Count()
{
return All().Count();
} public int Count(Expression<Func<TEntity, bool>> criteria)
{
return All().Count(criteria);
} public IEnumerable<TEntity> Get<TOrderBy>(Expression<Func<TEntity, bool>> criteria, Expression<Func<TEntity, TOrderBy>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending)
{
if (sortOrder == SortOrder.Ascending)
{
return All().OrderBy(orderBy).Skip((pageIndex - 1) * pageSize).Take(pageSize).AsEnumerable();
}
return All().OrderByDescending(orderBy).Skip((pageIndex - 1) * pageSize).Take(pageSize).AsEnumerable();
} public TEntity Delete(long id)
{
var entity = Single(id);
context.Set<TEntity>().Remove(entity);
context.SaveChanges();
return entity;
} public bool Add(TEntity entity)
{
if (context.Entry<TEntity>(entity).State != EntityState.Detached)
{
context.Entry<TEntity>(entity).State = EntityState.Added;
}
context.Set<TEntity>().Add(entity);
return context.SaveChanges() > 0;
} public bool Update(TEntity entity)
{
if (context.Entry<TEntity>(entity).State != EntityState.Detached)
{
context.Set<TEntity>().Attach(entity);
}
context.Entry<TEntity>(entity).State = EntityState.Modified;
return context.SaveChanges() > 0;
} public int Save(TEntity entity)
{
return context.SaveChanges();
} }

使用方法:

考试科目的仓储接口

/// <summary>
/// 仓储层接口——ExamSubject
/// </summary>
public partial interface IExamSubjectRepository : IRepository<ExamSubject>
{ }

考试科目的仓储接口实现:

using Trump.Domain.Entities;
using Trump.EF.Common; namespace Trump.EF.Repository
{
/// <summary>
/// 仓储实现——ExamQuestionBizType
/// </summary>
public partial class ExamSubjectRepository : GenericRepository<ExamSubject>, IExamSubjectRepository
{ }
}

如果在这个实体模型中,除了CRUD没有其他操作的话,那么这样两个类就完成了。

最后项目截图:

在不断的整理,有空就整理一点功能,整个功能实现后会提供源码。

用MVC5+EF6+WebApi 做一个考试功能(六) 仓储模式 打造EF通用仓储类的更多相关文章

  1. 用MVC5+EF6+WebApi 做一个考试功能(五) 前端主题

    内容概述 前面絮絮叨叨没正事,到现在为止也没有开始写代码,不过在考虑下貌似这一节还是开始不了. B/S架构开发有一个特点,就是用浏览器打开,不同的用户群体可能有不同的风格,不论是管理平台还是普通的网站 ...

  2. 用MVC5+EF6+WebApi 做一个小功能(三) 项目搭建

    一般一个项目开始之前都会有启动会,需求交底等等,其中会有一个环节,大讲特讲项目的意义,然后取一个高大上的项目名字,咱这是一个小功能谈不上项目,但是名字不能太小气了.好吧,就叫Trump吧.没有任何含义 ...

  3. 用MVC5+EF6+WebApi 做一个小功能(一)开场挖坑,在线答题系统

    从哪开始说呢,这几年微软的技术一直在变,像是牟足了劲要累死所有的NET程序员,从WebForm到MVC到现在MPA.SPA .Razor单页,从net2.0一直走到现在.net4.6.2,后面还有一个 ...

  4. 用MVC5+EF6+WebApi 做一个小功能(二) 项目需求整理

    在一个项目开始前,需求整理大概要占到整个项目周期15%甚至30%的比重,可以说需求理得越清楚,后续开发中返工几率越小.在一个项目中,开发新功能的花费的精力要远远小于修改功能的精力,这基本是一个共识.老 ...

  5. 用MVC5+EF6+WebApi 做一个小功能(四) 项目分层功能以及文件夹命名

    在上一节,我们完成了一个项目搭建,我们看到的是一个项目的分层架子,那接下来每一层做什么以及需要引用哪些内容呢?在本节内容我们还逐步拆分每一层的功能,顺带添加package包 Trump.Domain ...

  6. TTS-零基础入门-10分钟教你做一个语音功能

    在本片博客正式開始之前,大家先跟我做一个简单的好玩的 小语音. 新建一个文本文档,然后再文档里输入这样 一句话  CreateObject("SAPI.SpVoice").Spea ...

  7. 利用random模块做一个抢红包功能

    我们都知道random模块是一个生成随机数的模块,用它来做抢红包的功能很合适. 抢红包,抢到的金额是随机的,但怎么让每个人抢到的随机金额公平合理呢 比如:我想发一个100元的红包让10个人抢,我可以把 ...

  8. 使用reactjs做一个CRUD功能

    第一步:引入reactjs所依赖的js文件,本案例使用的是bootstrap前端框架,所以引入了相应的js和css文件 第二步:body里面添加两个div 第三步:开始编写reactjs脚本 < ...

  9. MVC5+EF6简单实例---以原有SQLServer数据库两表联合查询为例

    有二三年没写代码了,**内的工作就是这样,容易废人!看到园子里这么多大侠朝气蓬勃的,我想也要学点东西并和大家分享,共同进步!快乐每一天,进步每一天!言归正传! 通过最近一段时间对MVC5.EF6的学习 ...

随机推荐

  1. spring配置数据库连接池druid

    连接池原理 连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象.使用完毕后,用户也并非将连 ...

  2. Spring框架整合Struts2框架的传统方法

    1. 导入CRM项目的UI页面,找到添加客户的页面,修改form表单,访问Action * 将menu.jsp中133行的新增客户的跳转地址改为:href="${pageContext.re ...

  3. PAT 1082 射击比赛(20)(代码+思路)

    1082 射击比赛(20 分) 本题目给出的射击比赛的规则非常简单,谁打的弹洞距离靶心最近,谁就是冠军:谁差得最远,谁就是菜鸟.本题给出一系列弹洞的平面坐标(x,y),请你编写程序找出冠军和菜鸟.我们 ...

  4. 20172325 2017-2018-2 《Java程序设计》第九周学习总结

    20172325 2017-2018-2 <Java程序设计>第九周学习总结 教材学习内容总结 异常 1.学习了异常的基本概念: 2.区分异常与错误: 一个异常是指一个定义非正常情况或错误 ...

  5. OSGi 系列(三)之 bundle 事件监听

    OSGi 系列(三)之 bundle 事件监听 bundle 的事件监听是在 bundle 生命周期的不同状态相互转换时,OSGi 框架会发出各种不同的事件供事先注册好的事件监听器处理. 1. 事件监 ...

  6. stl学习记录(1)

    Effective STL 中文版学习记录 条款4 判断容器是否为空 使用empty而不是size().size()操作在实现上不是一个时间常数操作条款5 尽量使用区间成员函数代替它们的单元素兄弟.S ...

  7. 2018.09.23 bzoj1076: [SCOI2008]奖励关(期望+状压dp)

    传送门 一道神奇的期望状压dp. 用f[i][j]f[i][j]f[i][j]表示目前在第i轮已选取物品状态为j,从现在到第k轮能得到的最大贡献. 如果我们从前向后推有可能会遇到不合法的情况. 所以我 ...

  8. CentOS yum 源的配置与使用(引用)

    http://www.cnblogs.com/mchina/archive/2013/01/04/2842275.html

  9. java.sql.SQLException: null, message from server: "Host 'xxx' is not allowed to connect to this MySQL server"

    java.sql.SQLException: null,  message from server: "Host 'xxx' is not allowed to connect to thi ...

  10. 零停重启程序工具Huptime研究

    目录 目录 1 1. 官网 1 2. 功能 1 3. 环境要求 2 4. 实现原理 2 5. SIGHUP信号处理 3 6. 重启线程 4 7. 重启目标程序 5 8. 系统调用钩子辅助 6 9. 被 ...