.NET Core开发实战(第29课:定义仓储:使用EF Core实现仓储层)--学习笔记
29 | 定义仓储:使用EF Core实现仓储层
首先定义仓储层的接口,以及仓储层实现的基类,抽象类
仓储层的接口
namespace GeekTime.Infrastructure.Core
{
/// <summary>
/// 包含普通实体的仓储
/// 约束 TEntity 必须是继承 Entity 的基类,必须实现聚合根 IAggregateRoot
/// 也就是说仓储里面存储的对象必须是一个聚合根对象
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public interface IRepository<TEntity> where TEntity : Entity, IAggregateRoot
{
IUnitOfWork UnitOfWork { get; }
TEntity Add(TEntity entity);
Task<TEntity> AddAsync(TEntity entity, CancellationToken cancellationToken = default);
TEntity Update(TEntity entity);
Task<TEntity> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default);
bool Remove(Entity entity);// 由于没有指定主键,只能根据当前实体进行删除操作
Task<bool> RemoveAsync(Entity entity);
}
/// <summary>
/// 包含指定主键的类型的实体的仓储
/// 继承了上面的接口 IRepository<TEntity>,也就是说拥有了上面定义的所有方法
/// 另外一个,它实现了几个跟 Id 相关的操作的方法
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TKey"></typeparam>
public interface IRepository<TEntity, TKey> : IRepository<TEntity> where TEntity : Entity<TKey>, IAggregateRoot
{
bool Delete(TKey id);
Task<bool> DeleteAsync(TKey id, CancellationToken cancellationToken = default);
TEntity Get(TKey id);
Task<TEntity> GetAsync(TKey id, CancellationToken cancellationToken = default);
}
}
具体抽象类的实现
namespace GeekTime.Infrastructure.Core
{
/// <summary>
/// 定义普通实体的仓储
/// 定义约束 TDbContext 必须是 EFContext,也就是仓储必须依赖于 EFContext 及其子类
/// 将来就可以把自己定义的比如 DomainContext 作为泛型参数传入 Repository,就可以很快捷地定义出来自己的仓储
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TDbContext"></typeparam>
public abstract class Repository<TEntity, TDbContext> : IRepository<TEntity> where TEntity : Entity, IAggregateRoot where TDbContext : EFContext
{
// 具体实现需要依赖 DbContext
protected virtual TDbContext DbContext { get; set; }
public Repository(TDbContext context)
{
this.DbContext = context;
}
public virtual IUnitOfWork UnitOfWork => DbContext;// 因为 DbContext, EFContext 实际上实现了 IUnitOfWork,所以直接返回
// 下面这些方法都是 EntityFramework 提供的能力,所以就能通过简单的几行代码来实现基本的仓储操作
public virtual TEntity Add(TEntity entity)
{
return DbContext.Add(entity).Entity;
}
public virtual Task<TEntity> AddAsync(TEntity entity, CancellationToken cancellationToken = default)
{
return Task.FromResult(Add(entity));
}
public virtual TEntity Update(TEntity entity)
{
return DbContext.Update(entity).Entity;
}
public virtual Task<TEntity> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
{
return Task.FromResult(Update(entity));
}
public virtual bool Remove(Entity entity)
{
DbContext.Remove(entity);
return true;
}
public virtual Task<bool> RemoveAsync(Entity entity)
{
return Task.FromResult(Remove(entity));
}
}
/// <summary>
/// 定义主键的实体的仓储
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TDbContext"></typeparam>
public abstract class Repository<TEntity, TKey, TDbContext> : Repository<TEntity, TDbContext>, IRepository<TEntity, TKey> where TEntity : Entity<TKey>, IAggregateRoot where TDbContext : EFContext
{
public Repository(TDbContext context) : base(context)
{
}
/// <summary>
/// 根据 Id 从 DbContext 获取 Entity,然后再 Remove
/// 这样的好处是可以跟踪对象的状态
/// 坏处是任意的删除都需要先去数据库里面做查询
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public virtual bool Delete(TKey id)
{
var entity = DbContext.Find<TEntity>(id);
if (entity == null)
{
return false;
}
DbContext.Remove(entity);
return true;
}
public virtual async Task<bool> DeleteAsync(TKey id, CancellationToken cancellationToken = default)
{
var entity = await DbContext.FindAsync<TEntity>(id, cancellationToken);
if (entity == null)
{
return false;
}
DbContext.Remove(entity);
return true;
}
public virtual TEntity Get(TKey id)
{
return DbContext.Find<TEntity>(id);
}
public virtual async Task<TEntity> GetAsync(TKey id, CancellationToken cancellationToken = default)
{
return await DbContext.FindAsync<TEntity>(id, cancellationToken);
}
}
}
实现自己的 DbContext
DomainContext
namespace GeekTime.Infrastructure
{
public class DomainContext : EFContext
{
public DomainContext(DbContextOptions options, IMediator mediator, ICapPublisher capBus) : base(options, mediator, capBus)
{
}
public DbSet<Order> Orders { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
#region 注册领域模型与数据库的映射关系
modelBuilder.ApplyConfiguration(new OrderEntityTypeConfiguration());
modelBuilder.ApplyConfiguration(new UserEntityTypeConfiguration());
#endregion
base.OnModelCreating(modelBuilder);
}
}
}
映射关系,针对每一个领域模型创建一个 EntityTypeConfiguration
OrderEntityTypeConfiguration
namespace GeekTime.Infrastructure.EntityConfigurations
{
class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
// 定义主键
builder.HasKey(p => p.Id);
//builder.ToTable("order");
//builder.Property(p => p.UserId).HasMaxLength(20);
//builder.Property(p => p.UserName).HasMaxLength(30);
// 定义导航属性
builder.OwnsOne(o => o.Address, a =>
{
a.WithOwner();
//a.Property(p => p.City).HasMaxLength(20);
//a.Property(p => p.Street).HasMaxLength(50);
//a.Property(p => p.ZipCode).HasMaxLength(10);
});
}
}
}
UserEntityTypeConfiguration
namespace GeekTime.Infrastructure.EntityConfigurations
{
class UserEntityTypeConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.HasKey(p => p.Id);
}
}
}
事务处理
要实现对 DomainContext 的事务处理的话,仅仅需要创建一个类 DomainContextTransactionBehavior
namespace GeekTime.Infrastructure
{
public class DomainContextTransactionBehavior<TRequest, TResponse> : TransactionBehavior<DomainContext, TRequest, TResponse>
{
public DomainContextTransactionBehavior(DomainContext dbContext, ICapPublisher capBus, ILogger<DomainContextTransactionBehavior<TRequest, TResponse>> logger) : base(dbContext, capBus, logger)
{
}
}
}
为了演示效果,在应用程序启动时,添加一行代码
Startup
// 这一行代码的作用是创建一个 Scope,在这个范围内创建 DomainContext
using (var scope = app.ApplicationServices.CreateScope())
{
var dc = scope.ServiceProvider.GetService<DomainContext>();
// 确定数据库已经创建,如果数据库没有创建,这个时候会执行数据库的自动创建过程,根据模型创建数据库
dc.Database.EnsureCreated();
}
数据库的注册部分
ServiceCollectionExtensions
/// <summary>
/// 这个定义就是将连接字符串配置到 dDomainContext
/// </summary>
/// <param name="services"></param>
/// <param name="connectionString"></param>
/// <returns></returns>
public static IServiceCollection AddMySqlDomainContext(this IServiceCollection services, string connectionString)
{
return services.AddDomainContext(builder =>
{
builder.UseMySql(connectionString);
});
}
这一行代码的调用位置是在 ConfigureServices 里面
// 从配置中获取字符串
services.AddMySqlDomainContext(Configuration.GetValue<string>("Mysql"));
启动程序,运行过程中 EF 框架会根据定义的实体映射关系生成数据库,可在 Mysql 数据库中查看生成结果
接着丰富一下 Order 的映射关系
namespace GeekTime.Infrastructure.EntityConfigurations
{
class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
// 定义主键
builder.HasKey(p => p.Id);
builder.ToTable("order");// 修改表名为 order,不带 s
builder.Property(p => p.UserId).HasMaxLength(20);// 修改字段长度
builder.Property(p => p.UserName).HasMaxLength(30);
// 定义导航属性
// OwnsOne 的方式可以将 Address 这个值类型作为同一个表的字段来设置
builder.OwnsOne(o => o.Address, a =>
{
a.WithOwner();
a.Property(p => p.City).HasMaxLength(20);
a.Property(p => p.Street).HasMaxLength(50);
a.Property(p => p.ZipCode).HasMaxLength(10);
});
}
}
}
启动程序,可以看到数据库修改结果
这说明可以在仓储层定义领域模型与数据库的映射关系,这个映射关系可以组织为一个目录,为每一个领域模型设置一个类型来定义,并且这个过程是强类型的,这样的结构,便于后期维护
另外仓储层的话,定义了一个 IOrderRepository,仅仅实现了 IRepository 泛型接口,引进 Order,由于 Order 实际上有一个主键是 long,所以这里把主键类型也传给 IRepository
namespace GeekTime.Infrastructure.Repositories
{
public interface IOrderRepository : IRepository<Order, long>
{
}
}
Order
public class Order : Entity<long>, IAggregateRoot
这样子,Order 的仓储就定义完毕
那么 Order 仓储的实现也非常简单,仅仅需要继承 Repository,把 Order,long,DomainContext 传入泛型 Repository 即可,这里还实现了 IOrderRepository
namespace GeekTime.Infrastructure.Repositories
{
public class OrderRepository : Repository<Order, long, DomainContext>, IOrderRepository
{
public OrderRepository(DomainContext context) : base(context)
{
}
}
}
通过这样简单的继承,可以复用之前定义的代码,快速实现仓储层的定义
可以通过代码提升看到仓储层是有 Add,Update,Remove,Delete 方法,还有 UnitOfWork 的属性


这样一来就完成了仓储层的定义,可以看到仓储层的代码非常的薄,仅仅包含了一些接口的定义和类的继承,需要自定义一些方法的时候,可以在仓储层定义一些特殊方法,比如 AddABC 等特殊的逻辑都可以在这里去实现
namespace GeekTime.Infrastructure.Repositories
{
public class OrderRepository : Repository<Order, long, DomainContext>, IOrderRepository
{
public OrderRepository(DomainContext context) : base(context)
{
}
}
public void AddABC()
{
}
}
另外一个在组织领域模型和数据库的关系的时候,可以很清晰的看到,是在 EntityConfiguration 这个目录下面,为每一个模型定义一个映射类,当领域模型越来越复杂,数据库的结构越来越复杂的时候,这样的组织结构会非常的清晰

GitHub源码链接:
https://github.com/witskeeper/geektime



本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。
如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。
.NET Core开发实战(第29课:定义仓储:使用EF Core实现仓储层)--学习笔记的更多相关文章
- 2月送书福利:ASP.NET Core开发实战
大家都知道我有一个公众号“恰童鞋骚年”,在公众号2020年第一天发布的推文<2020年,请让我重新介绍我自己>中,我曾说到我会在2020年中每个月为所有关注“恰童鞋骚年”公众号的童鞋们送一 ...
- Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- [ASP.NET Core开发实战]开篇词
前言 本系列课程文章主要是学习官方文档,再输出自己学习心得,希望对你有所帮助. 课程大纲 本系列课程主要分为三个部分:基础篇.实战篇和部署篇. 希望通过本系列课程,能让大家初步掌握使用ASP.NET ...
- 《ASP.NET Core项目开发实战入门》带你走进ASP.NET Core开发
<ASP.NET Core项目开发实战入门>从基础到实际项目开发部署带你走进ASP.NET Core开发. ASP.NET Core项目开发实战入门是基于ASP.NET Core 3.1 ...
- 【无私分享:ASP.NET CORE 项目实战(第二章)】添加EF上下文对象,添加接口、实现类以及无处不在的依赖注入(DI)
目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 上一章,我们介绍了安装和新建控制器.视图,这一章我们来创建个数据模型,并且添加接口和实现类. 添加EF上下文对象 按照我们以前 ...
- [翻译 EF Core in Action 1.11] 何时不应该使用EF Core
Entity Framework Core in Action Entityframework Core in action是 Jon P smith 所著的关于Entityframework Cor ...
- [翻译 EF Core in Action 1.6]你的第一个EF Core应用程序
Entity Framework Core in Action Entityframework Core in action是 Jon P smith 所著的关于Entityframework Cor ...
- Oracle .NET Core Beta驱动已出,自己动手写EF Core Oracle
使用.net core也有一段时间了,一直都没有Oracle官方的正式版驱动程序,更别说EF版本了.之前基于Oracle官方的.net core预览版本写了个Dapper的数据库操作实现,但是总感觉不 ...
- 体验 ASP.NET Core 集成测试三剑客:xUnit.net、TestServer、EF Core InMemory
这是昨天解决的一个问题,针对一个 web api 的客户端代理类写集成测试,既要测试 web api,又要测试 web api 客户端. 测试 web api,就要在运行测试时自动启动 web api ...
- .NET Core开发实战(第11课:文件配置提供程序)--学习笔记
11 | 文件配置提供程序:自由选择配置的格式 文件配置提供程序 Microsoft.Extensions.Configuration.Ini Microsoft.Extensions.Configu ...
随机推荐
- C#设计模式11——享元模式的写法
1. 什么是享元模式? 享元模式是一种结构型设计模式,目的是通过共享对象来尽量减少内存使用和对象数量.它通过将对象分为可共享的和不可共享的来实现这一目的. 2. 为什么要使用享元模式? 使用享元模式可 ...
- 每天学五分钟 Liunx 101 | 存储篇:LVM
LVM LVM(Logical Volume Manager),逻辑卷管理器.一种高级文件系统管理方式,它可以动态扩展文件系统. LVM 的示意图如下所示:
- Go-方法-method
- 浪潮CE3000F飞腾PC安装UOS/银河麒麟双系统的过程
浪潮CE3000F飞腾PC安装UOS/银河麒麟双系统的过程 背景 为了进行兼容性验证, 部门采购过一批浪费CE3000F的PC机器. 前期系统安装的是UOS, 但是有同事借走机器后重装了银河麒麟V10 ...
- Nginx日志规则以及根据日志进行性能问题判断的思路
Nginx日志规则以及根据日志进行性能问题判断的思路 背景 Nginx是开源方案里面能实现反向代理 负载均衡的首选. 但是有时候性能出问题比较难以分析和定位, 不知道是不是nginx的瓶颈 性能问题的 ...
- [转帖]Prometheus系列之Grafana 版本9.0.0 设置Email邮件报警实战
目录 1. 配置文件conf/defaults.ini修改 2. Grafana Web页面配置报警邮箱接收者 3. 创建Dashboard 4. 创建Alert的文件夹 5. 设置Notificat ...
- [转帖]MinIO系列7 - Minio性能压测
https://www.zhihu.com/people/keen-wang 前言 声明:此文为本人历史笔记的整理,文章实际撰写时间为2021年2月份,所以本中所使用的相关组件版本较老.此文是通过压力 ...
- [转帖]rsar - Extract data from plain-text sar files
sar -A -t -f /tmp/sa11 >/tmp/sar11 https://github.com/ryran/rsar When dealing with sysstat sar da ...
- [转帖]Linux系统top命令中的io使用率,很多人都误解了它的具体含义
https://baijiahao.baidu.com/s?id=1641356547223820839&wfr=spider&for=pc 最近在做连续数据流的缓冲系统,C语言代 ...
- Redis6.x 在Windows上面编译安装的过程
背景说明 在github上面仅能够找到 redis3.2.100的Windows安装文件 比较新的版本比较难以找到, 同事经常出现这个版本的redis卡死的情况, 所以想尝试进行一下升级. 第一部分下 ...