.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{// 具体实现需要依赖 DbContextprotected 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());#endregionbase.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,不带 sbuilder.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 这个目录下面,为每一个模型定义一个映射类,当领域模型越来越复杂,数据库的结构越来越复杂的时候,这样的组织结构会非常的清晰

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310981.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ueditor单图上传iframe跨域_UEditor单图上传(simpleupload)跨域问题解决方案

代码实现首先我们需要在ueditor.all.js文件中找到原本的单图上传部分的代码搜索关键字 simpleupload&#xff0c;如下图所示&#xff1a;然后找到上传图片的代码片段&#xff0c;如下图所示&#xff1a;然后把 domUtils.on的 input 绑定的事件注释掉或删除掉替换成以下代码:inp…

StringBuilder内存碎片对性能的影响

TL;DR:StringBuilder内部是由多段 char[]组成的半自动链表&#xff0c;因此频繁从中间修改 StringBuilder&#xff0c;会将原本连续的内存分隔为多段&#xff0c;从而影响读取/遍历性能。连续内存与不连续内存的性能差&#xff0c;可能高达 1600倍。背景用 StringBuilder的用户…

java 双击_利用java开发一个双击执行的小程序

之前我们利用java写了很多东西&#xff0c;但是好像都没有什么实际意义。因为有意义桌面小程序怎么都得有个界面&#xff0c;可是界面又不太好搞。或者 了解到这一层的人就少之又少了。呀&#xff0c;是不是还得开辟一些版面来介绍awt和 swing。。。算了 先把这个 双击执行的小…

开发人员如何学习 Kubernetes

虽然“容器编排平台”还没有被整个行业大范围采用&#xff0c;但在这一领域 Kubernetes 已经战胜其他选手&#xff0c;成为了事实标准。近两年的 Web 开发技术社区&#xff0c;随便打开一两个群&#xff0c;你都能看到人们在谈 Kubernetes。很多开发人员&#xff0c;包括曾经的…

安装 java decompiler_Eclipse离线安装Java Decompiler插件(反编译)

Java Decompiler是Java语言的反编译工具&#xff0c;具体介绍见博客Java Decompiler(Java反编译工具)1、下载插件Eclipe的Java Decompiler插件名为JD-Eclipse&#xff0c;2、安装插件Ecipse安装JD-Eclipse(即Java Decompiler)插件步骤如下&#xff1a;打开Help --> Install …

给 ABP vNext 应用安装私信模块

在上一节五分钟完成 ABP vNext 通讯录 App 开发 中&#xff0c;我们用完成了通讯录 App 的基础开发。这本章节&#xff0c;我们会给通讯录 App 安装私信模块&#xff0c;使不同用户能够通过相互发送消息&#xff0c;并接收新私信的通知。在章节的最后&#xff0c;笔者将演示模块…

《C++ Primer》7.5.2节练习

练习7.41: #include <iostream> #include <string> using namespace std;class Sales_data {friend std::istream &read(std::istream &is, Sales_data &item);friend std::ostream &print(std::ostream &os, const Sales_data &item);pu…

零基础玩视频号?创作运营变现,你要的干货都在这了!

点击蓝字“大白技术控”关注我哟加个“星标★”&#xff0c;每日良时&#xff0c;好文必达&#xff01;不少小伙伴应该已经听说过视频号这个新功能了&#xff0c;视频号是微信内测的短视频功能&#xff0c;本人已经在视频号里刷了2个月了。3月中旬正式开通了视频号 「大白技术控…

Asp.Net Core 中IdentityServer4 实战之 Claim详解

一、前言由于疫情原因&#xff0c;让我开始了以博客的方式来学习和分享技术&#xff08;持续分享的过程也是自己学习成长的过程&#xff09;&#xff0c;同时也让更多的初学者学习到相关知识&#xff0c;如果我的文章中有分析不到位的地方&#xff0c;还请大家多多指教&#xf…

程序员还有35岁的坎吗?

昨天晚上和多年未见的前同事聊天&#xff0c;提到了程序员的年龄歧视问题&#xff1a;自己年龄也 30 出头了&#xff0c;在思考 IT 届流传的 35 岁是一个坎的问题&#xff1b;开始注重提升管理能力&#xff0c;担心35岁之后&#xff0c;一线写代码的岗位不能胜任&#xff1b;公…

java 左移 返回值_java左移右移运算符详解

在阅读源码的过程中&#xff0c;经常会看到这些符号<< &#xff0c;>>&#xff0c;>>>&#xff0c;这些符号在Java中叫移位运算符&#xff0c;在写代码的过程中&#xff0c;虽然我们基本上不会去写这些符号&#xff0c;但需要明白这些符号的运算原理&…

人与人的差距在于认知

作者介绍findyi&#xff0c;腾讯、360码农&#xff0c;前哒哒少儿英语技术VP&#xff0c;现任土豆教育CTO。工作和生活中不光要埋头干活&#xff0c;还要抬头看天。思考总结方法论是提升认知的必备途径&#xff0c;是将碎片化知识总结为动态的智慧的过程。认知有多重要&#xf…

.NET5来了你别慌

近日微软.Net大咖Scott在博客中对外宣传.NET5首个预览版&#xff0c;并且我们可以通过微软的官网下载SDK5和运行库。很多朋友感觉.NetCore3.1还没搞明白&#xff0c;.NET5就来了感觉一下子慌了神。在这里我提醒朋友们&#xff0c;瞬息万变的世界中&#xff0c;总有相对不变的真…

java8 stream 最大值_JDK8-Stream流常用方法

Stream流的使用流操作是Java8提供一个重要新特性&#xff0c;它允许开发人员以声明性方式处理集合&#xff0c;其核心类库主要改进了对集合类的 API和新增Stream操作。Stream类中每一个方法都对应集合上的一种操作。将真正的函数式编程引入到Java中&#xff0c;能 让代码更加简…

周三晚6点半!盛派首席架构师“苏老师”在线解密内部系统框架!

工作中有些事&#xff0c;看起来只用一会会儿就能完成&#xff0c;但真正完成起来&#xff0c;总会遇到一些意想不到的困难&#xff01;你一定碰到过这样的情况——开发时间 2 周的项目&#xff0c;搭框架就要用 1 周&#xff0c;刚开发完&#xff0c;各种调试和修 bug又花去 2…

给微软的日志框架写一个基于委托的日志提供者

动手造轮子&#xff1a;给微软的日志框架写一个基于委托的日志提供者Intro微软的日志框架现在已经比较通用&#xff0c;有时候我们不想使用外部的日志提供者&#xff0c;但又希望提供一个比较简单的委托就可以实现日志记录&#xff0c;于是就有了后面的探索和实现。Solution基于…

C++分析使用拷贝控制成员和调用构造函数的时机

我们来分析下面这段代码&#xff1a; #include <iostream> #include <vector>using namespace std;struct X {X() {cout << "构造函数X()" << endl;}X(const X &) {cout << "拷贝构造函数X(const X&)" << en…

《C++ Primer》13.1.4节练习

练习13.14: 这是一个典型的应该定义拷贝控制成员的场合。如果不定义拷贝构造函数和拷贝赋值运算符&#xff0c;依赖合成的版本&#xff0c;则在拷贝构造和赋值时&#xff0c;会简单复制数据成员。对本问题来说&#xff0c;就是将序号简单复制给新对象。 因此&#xff0c;代码中…

十问十答 CDDL 许可证

今天我们来整理一下通用开发和发行许可证 CDDL 的十大问题清单。通用开发与发行许可证&#xff08;Common Development and Distribution License&#xff0c;CDDL&#xff09;由已被甲骨文公司收购的太阳微系统公司&#xff08;Sun Microsystems&#xff09;发布的一种开源许可…

Http Server API路由请求到web程序

引言接上文&#xff0c;容器内web程序一般会绑定到http://0.0.0.0:{某监听端口}或http://:{某监听端口}&#xff0c;以确保使用容器IP可以访问到web应用。正如我们在ASP.NET Core官方镜像显示的&#xff0c;ASP.NET Core程序在容器内80端口监听请求This image sets the ASPNETC…