DDD领域驱动设计:仓储
1 前置阅读
在阅读本文章之前,你可以先阅读:
- 什么是DDD
- DDD的实体、值对象、聚合根的基类和接口:设计与实现
2 什么是仓储?
仓储封装了基础设施来提供查询和持久化聚合操作。 它们集中提供常见的数据访问功能,从而提供更好的可维护性,并将用于访问数据库的基础结构或技术与领域模型层分离。 创建数据访问层和应用程序的业务逻辑层之间的抽象层。 实现仓储可让你的应用程序对数据存储介质的更改不敏感。
3 为什么仓储?
直接访问数据:
- 重复的代码
- 难以集中化与数据相关的策略(例如缓存)
- 编程错误的可能性更高
- 无法独立于外部依赖项轻松测试业务逻辑
使用仓储优点:
- 可以通过将业务逻辑与数据或服务访问逻辑分开来提高代码的可维护性和可读性。
- 可以从许多位置访问数据源,并希望应用集中管理的,一致的访问规则和逻辑。
- 可以通过自动化进行测试的代码量,并隔离数据层以支持单元测试。
- 可以使用强类型的业务实体,以便可以在编译时而不是在运行时识别问题。
- 可以将行为与相关数据相关联。例如,您要计算字段或在实体中的数据元素之间强制执行复杂的关系或业务规则。
- 应用DDD来简化复杂的业务逻辑。
4 实现仓储?
实现基本的增删改查及事务的提交和回滚
首先,定义接口
/// <summary>
/// IRepository提供应用程序仓储模式基本操作的接口
/// </summary>
public interface IRepository
{
#region Methods
void Entry<T>(T t) where T : AggregateRoot;
void Save();
T Get<T>(Guid id, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot;
T Get<T>(Expression<Func<T, bool>> where, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot;
IQueryable<T> Query<T>(Expression<Func<T, bool>> filter=null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy=null, Func<IQueryable<T>, IQueryable<T>> includes=null) where T : AggregateRoot;
IQueryable<T> QueryByPage<T>(int pageIndex, int pageSize, Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot;
void BeginTransaction();
void Commit();
void Rollback();
#endregion
}
最后,实现以上接口
public class Repository : IDisposable, IRepository
{
#region Private Fields
private readonly DbContext context;
private IDbContextTransaction transaction;
#endregion
#region Constructors
public Repository(DbContext context)
{
this.context = context ?? throw new ArgumentNullException(nameof(context));
}
#endregion
#region IRepository<T> Members
public void Entry<T>(T t) where T : AggregateRoot
{
switch (t.AggregateState)
{
case AggregateState.Added:
context.Entry(t).State = EntityState.Added;
break;
case AggregateState.Deleted:
context.Entry(t).State = EntityState.Deleted;
break;
default:
context.Entry(t).State = EntityState.Modified;
break;
}
}
public void Save()
{
context.SaveChanges();
}
public T Get<T>(Guid id, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot
{
return Get(w => w.Id.Equals(id), includes: includes);
}
public T Get<T>(Expression<Func<T, bool>> filter, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot
{
return Query(filter, includes: includes).SingleOrDefault();
}
public IQueryable<T> Query<T>(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot
{
IQueryable<T> query = context.Set<T>();
if (filter != null)
{
query = query.Where(filter);
}
if (includes != null)
{
query = includes(query);
}
if (orderBy != null)
{
query = orderBy(query);
}
return query;
}
public IQueryable<T> QueryByPage<T>(int pageIndex, int pageSize, Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot
{
var query = Query(filter, orderBy, includes)
.Skip(pageSize * (pageIndex - 1))
.Take(pageSize);
return query;
}
public void BeginTransaction()
{
transaction = context.Database.BeginTransaction();
}
public void Rollback()
{
transaction.Rollback();
}
public void Commit()
{
transaction.Commit();
}
public void Dispose()
{
if (transaction != null)
{
transaction.Dispose();
}
context.Dispose();
}
#endregion
}
为数据库上下文和事务上下文声明类变量:
private readonly DbContext context;
private IDbContextTransaction transaction;
构造函数接受数据库上下文实例:
public Repository(DbContext context)
{
this.context = context ?? throw new ArgumentNullException(nameof(context));
}
Get分为通过ID查询或过滤条件进行查询,返回序列中的唯一元素:
public T Get<T>(Guid id, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot
{
return Get(w => w.Id.Equals(id), includes: includes);
}
public T Get<T>(Expression<Func<T, bool>> filter, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot
{
return Query(filter, includes: includes).SingleOrDefault();
}
Query 方法使用 lambda 表达式来允许调用代码指定筛选条件,使用一列来对结果进行排序,允许调用方为预先加载导航属性列表:
// 代码 Expression<Func<T, bool>> filter 意味着调用方将基于 AggregateRoot 类型提供 lambda 表达式,并且此表达式将返回一个布尔值。
// 代码 Func<IQueryable<T>, IOrderedQueryable<T>> orderBy 也意味着调用方将提供 lambda 表达式。 但在这种情况下,表达式的输入是 AggregateRoot 类型的 IQueryable 对象。 表达式将返回 IQueryable 对象的有序版本。
// 代码 Func<IQueryable<T>, IQueryable<T>> includes 也意味着调用方将提供 lambda 表达式。 允许预先加载导航属性列表。
public IQueryable<T> Query<T>(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot
{
IQueryable<T> query = context.Set<T>();
if (filter != null)
{
query = query.Where(filter);
}
if (includes != null)
{
query = includes(query);
}
if (orderBy != null)
{
query = orderBy(query);
}
return query;
}
Entry 方法使用 AggregateRoot.AggregateState 来置 context.Entry(t).State 状态,完成增删改
public void Entry<T>(T t) where T : AggregateRoot
{
switch (t.AggregateState)
{
case AggregateState.Added:
context.Entry(t).State = EntityState.Added;
break;
case AggregateState.Deleted:
context.Entry(t).State = EntityState.Deleted;
break;
default:
context.Entry(t).State = EntityState.Modified;
break;
}
}
Save 等其他方法也类似实现。
DDD领域驱动设计:仓储的更多相关文章
- DDD领域驱动设计仓储Repository
DDD领域驱动设计初探(二):仓储Repository(上) 前言:上篇介绍了DDD设计Demo里面的聚合划分以及实体和聚合根的设计,这章继续来说说DDD里面最具争议的话题之一的仓储Repositor ...
- C#进阶系列——DDD领域驱动设计初探(二):仓储Repository(上)
前言:上篇介绍了DDD设计Demo里面的聚合划分以及实体和聚合根的设计,这章继续来说说DDD里面最具争议的话题之一的仓储Repository,为什么Repository会有这么大的争议,博主认为主要原 ...
- C#进阶系列——DDD领域驱动设计初探(三):仓储Repository(下)
前言:上篇介绍了下仓储的代码架构示例以及简单分析了仓储了使用优势.本章还是继续来完善下仓储的设计.上章说了,仓储的最主要作用的分离领域层和具体的技术架构,使得领域层更加专注领域逻辑.那么涉及到具体的实 ...
- DDD领域驱动设计初探(二):仓储Repository(上)
前言:上篇介绍了DDD设计Demo里面的聚合划分以及实体和聚合根的设计,这章继续来说说DDD里面最具争议的话题之一的仓储Repository,为什么Repository会有这么大的争议,博主认为主要原 ...
- DDD领域驱动设计初探(三):仓储Repository(下)
前言:上篇介绍了下仓储的代码架构示例以及简单分析了仓储了使用优势.本章还是继续来完善下仓储的设计.上章说了,仓储的最主要作用的分离领域层和具体的技术架构,使得领域层更加专注领域逻辑.那么涉及到具体的实 ...
- 浅谈我对DDD领域驱动设计的理解
从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品 ...
- DDD领域驱动设计之领域服务
1.DDD领域驱动设计实践篇之如何提取模型 2.DDD领域驱动设计之聚合.实体.值对象 3.DDD领域驱动设计之领域基础设施层 什么是领域服务,DDD书中是说,有些类或者方法,放实体A也不好,放实体B ...
- DDD 领域驱动设计-三个问题思考实体和值对象(续)
上一篇:DDD 领域驱动设计-三个问题思考实体和值对象 说实话,整理现在这一篇博文的想法,在上一篇发布出来的时候就有了,但到现在才动起笔来,而且写之前又反复读了上一篇博文的内容及评论,然后去收集资料, ...
- DDD 领域驱动设计-看我如何应对业务需求变化,领域模型调整?
写在前面 上一篇:DDD 领域驱动设计-看我如何应对业务需求变化,愚蠢的应对? "愚蠢的应对",这个标题是我后来补充上的,博文中除了描述需求变化.愚蠢应对和一些思考,确实没有实质性 ...
- C#进阶系列——DDD领域驱动设计初探(一):聚合
前言:又有差不多半个月没写点什么了,感觉这样很对不起自己似的.今天看到一篇博文里面写道:越是忙人越有时间写博客.呵呵,似乎有点道理,博主为了证明自己也是忙人,这不就来学习下DDD这么一个听上去高大上的 ...
随机推荐
- 安装简易OpenShift单节点并配置DNS泛域名
1 楔子 公司有个项目部署在 OpenShift 容器云平台上,出现问题较多,于是萌生在公司环境搭建现场仿真环境,由于资源没有生产环境多,就使用单节点简单模拟下 本文主要内容包括以下: 使用 Dnsm ...
- 「每日一题」面试官问你对Promise的理解?可能是需要你能手动实现各个特性
关注「松宝写代码」,精选好文,每日一题 加入我们一起学习,day day up 作者:saucxs | songEagle 来源:原创 一.前言 2020.12.23日刚立的flag,每日一题,题目类 ...
- HBase内存配置及JVM优化
前言 本文从HBase的内存布局说起,先充分了解HBase的内存区的使用与分配,随后给出了不同业务场景下的读写内存分配规划,并指导如何分析业务的内存使用情况,以及在使用当中写内存Memstore及读内 ...
- Spring Data JPA简介 Spring Data JPA特点
Spring Data JPA 是Spring基于ORM框架.JPA规范的基础上封装的一套JPA 应用框架,底层使用了Hibernate 的JPA技术实现,可使开发者用极简的代码即可实现对数据的访问和 ...
- QPushButton
QPushButton 什么是QPushButton? 构造函数 常用属性和方法 QPushButton 什么是QPushButton? QPushButton是一个按键类. class Q_GUI_ ...
- JAVA编程能力提升学习图
阿里大神毕玄整理的关于进阶JAVA的学习体系,知道下...
- .NET 云原生架构师训练营(模块二 基础巩固 MongoDB 问答系统)--学习笔记
2.5.6 MongoDB -- 问答系统 MongoDB 数据库设计 API 实现概述 MongoDB 数据库设计 设计优化 内嵌(mongo)还是引用(mysql) 数据一致性 范式:将数据分散到 ...
- Ubuntu18.04换源
引用:https://blog.csdn.net/u012308586/article/details/102953882 第一步.sudo cp /etc/apt/sources.list ...
- 洛谷P1055 字符串的处理-----ISBN
题目描述 每一本正式出版的图书都有一个ISBN号码与之对应,ISBN码包括99位数字.11位识别码和33位分隔符,其规定格式如x-xxx-xxxxx-x,其中符号-就是分隔符(键盘上的减号),最后一位 ...
- nacos统一配置中心源码解析
配置文件想必大家都很熟悉,无论什么架构 都离不开配置,虽然spring boot已经大大简化了配置,但如果服务很多 环境也好几个,管理配置起来还是很麻烦,并且每次改完配置都需要重启服务,nacos c ...