Abp vnext EFCore 实现动态上下文DbSet踩坑记
背景
我们在用EFCore框架操作数据库的时候,我们会遇到在
xxDbContext中要写大量的上下文DbSet<>; 那我们表少还可以接受,表多的时候每张表都要写一个DbSet, 大量的DbSet无异于是很蛋疼的一件事;而且看上去也很啰嗦,也不美观;至此我们就开始了下边的踩坑之旅;
EFCore 如何实现动态DbSet
我们网上百度一下千篇一律大概都是一下这种方式来实现动态的
- 我们一般都是先定义实体
public class UserJob: IEntity
{
public Guid UserId { get; set; }
public Guid UserId { get; set; }
public string JobName { get; set; }
public bool IsManager { get; set; }
}
- 在我们的
XXDbContext中添加如下方法,在注释我们之前写的DbSet<>
public class CoreDBContext : AbpDbContext<CoreDBContext>
{
// public DbSet<UserJob> UserJob { get; set; }
public CoreDBContext(DbContextOptions<CoreDBContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
DynamicDbSet(modelBuilder);
base.OnModelCreating(modelBuilder);
}
/// <summary>
/// 动态dbSet
/// </summary>
/// <param name="modelBuilder"></param>
private static void DynamicDbSet(ModelBuilder modelBuilder)
{
foreach (var entityType in EntityType())
{
modelBuilder.Model.AddEntityType(entityType);
}
}
/// <summary>
/// 派生IEntity的实体
/// </summary>
/// <returns></returns>
private static List<Type> EntityType()
{
return Assembly.GetExecutingAssembly().GetTypes()
.Where(s => typeof(IEntity).IsAssignableFrom(s))
.Select(s => s).ToList();
}
}
至此我们发现EFCore中实现动态DbSet,最关键的一句就是
modelBuilder.Model.AddEntityType(entityType);就可以了;我们想要的方式也差不多完成了;但是Abp vnext中真的也是这样子的吗?我们往下看;
Abp vnext 中实现动态DbSet
- 复制粘贴准备收工
按照上边的方式我们代码照搬到Abp vnext中,我们发现代码也可以正常的运行,好像问题不大;此时我们调用下接口报错,啪 快乐没了;

竟然报错!这是怎么回事呢,我们来看看报错的详细信息:

上图我们不难看出它说构造函数无法获取到参数
IRepository1[DotNet.EFCore.Entity.UserJob]`,那我们发现这个参数是构造函数从容器中获取的,我们不难猜测到是不是没有注入到容器中去?我们一想这玩样儿也不是我们注册进去的啊,这东西我哪儿会啊,此时Abp又背锅了(心里已经骂了起来....);但是又一想,Abp vnext集成EFCore时好像有个默认仓储配置这东西;发现这东西好像在哪儿见过,果不其然我们发现有如下代码
services.AddAbpDbContext<CoreDBContext>(options =>
{
options.AddDefaultRepositories(includeAllEntities: true);
});
一顿F12,这也没辙啊,怎么办这不完犊子了吗! 奔着不服输的心那我们继续看看源码是怎么操作的:
- 源码解析看究竟哪一步出了问题
上边我们也可以看到在
AddAbpDbContext<>中有添加默认仓储,那我们就从这个AddAbpDbContext<>入手,看代码我们发现了关键性的一句代码new EfCoreRepositoryRegistrar(options).AddRepositories();:
public static IServiceCollection AddAbpDbContext<TDbContext>(this IServiceCollection services,
Action<IAbpDbContextRegistrationOptionsBuilder> optionsBuilder = null)
where TDbContext : AbpDbContext<TDbContext>
{
var options = new AbpDbContextRegistrationOptions(typeof(TDbContext), services);
optionsBuilder?.Invoke(options); // 初始化AbpCommonDbContextRegistrationOptions选项配置
// 其他代码...
new EfCoreRepositoryRegistrar(options).AddRepositories(); // 添加仓储
return services;
}
跟着脚步我们继续往里看,看
.AddRepositories()中到底做了什么, 发现了如下三个方法来注册仓储:
public virtual void AddRepositories()
{
RegisterCustomRepositories(); // 注册自定义仓储
RegisterDefaultRepositories(); // 注册默认仓储
RegisterSpecifiedDefaultRepositories(); // 注册指定的Entity仓储
}
这里我们只看
RegisterDefaultRepositories()这个方法,其他两个我们先不看;
protected virtual void RegisterDefaultRepositories()
{
if (!Options.RegisterDefaultRepositories) // 就是边配置项的 options.AddDefaultRepositories(includeAllEntities: true);
{
return;
}
foreach (var entityType in GetEntityTypes(Options.OriginalDbContextType)) // 获取所有的EntityType
{
if (!ShouldRegisterDefaultRepositoryFor(entityType))
{
continue;
}
RegisterDefaultRepository(entityType); // 注册默认仓储服务
}
}
往循环这里看返现在遍历EntityType,这里我似乎好像懂了写什么,我们继续看
GetEntityTypes(Options.OriginalDbContextType)这个方法是一个抽象方法;
protected abstract IEnumerable<Type> GetEntityTypes(Type dbContextType);
上边的这个抽象方法的EFCore代码实现
EfCoreRepositoryCustomerRegistrar如下;
protected override IEnumerable<Type> GetEntityTypes(Type dbContextType)
{
return DbContextHelper.GetEntityTypes(dbContextType);
}
internal static class DbContextHelper
{
public static IEnumerable<Type> GetEntityTypes(Type dbContextType)
{
return
from property in dbContextType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance)
where
ReflectionHelper.IsAssignableToGenericType(property.PropertyType, typeof(DbSet<>)) &&
typeof(IEntity).IsAssignableFrom(property.PropertyType.GenericTypeArguments[0])
select property.PropertyType.GenericTypeArguments[0];
}
}
此时我们发现了这里读取EntityType是从
xxDbContext里面反射读取DbSet<>属性来拿所有的EntityType的;至此我们在返回来看那个循环,也就是说如果我们把xxDbContext中的DbSet<>属性都删除了GetEntityTypes必定是空的,Abp是不会往循环里走帮我们注入默认仓储服务的;至此我们已经发现了大概的问题出在哪儿,删除了
xxDbContext中的DbSet<>属性,Abp无法获取到EntityType, 不能实现默认仓储注入;
- 发现问题解决问题
接合上边的问题,我们解决的重点在于获取到EntityType, 注册默认仓储实现;
话不多说直接开干,我们继承一下EfCoreRepositoryCustomerRegistrar, 重写下AddRepositories实现;
using System.Reflection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore.DependencyInjection;
namespace DotNet.EFCore.EfCore;
public class EfCoreRepositoryCustomerRegistrar : EfCoreRepositoryRegistrar
{
public EfCoreRepositoryCustomerRegistrar(AbpDbContextRegistrationOptions options) : base(options)
{
}
public override void AddRepositories()
{
foreach (var entityType in GetEntityType())
{
RegisterDefaultRepository(entityType);
}
}
private IEnumerable<Type> GetEntityType()
{
return Assembly.GetExecutingAssembly().GetTypes()
.Where(s => typeof(IEntity).IsAssignableFrom(s)).ToList();
}
}
添加一个IServiceCollection的扩展
using Volo.Abp.EntityFrameworkCore.DependencyInjection;
namespace DotNet.EFCore.EfCore;
public static class ServiceDynamicDbSet
{
public static void AddDefaultRepositories(this IServiceCollection services)
{
// 传递一个AbpCommonDbContextRegistrationOptions类型,便于RepositoryRegistrarBase基类属性注入
var options = new AbpDbContextRegistrationOptions(typeof(CoreDBContext), services);
// 我们上边自定义获取EntityType实现注入默认仓储
new EfCoreRepositoryCustomerRegistrar(options).AddRepositories();
}
}
在
EntityFrameWorkCoreModule中添加如下代码:
context.Services.AddDefaultRepositories();
至此我们运行代码,发现好像貌似差不多可以了,到此大功告成;
小结
上述我们也不难发现,其实EFCore本身实现动态DbSet就是一行代码的事儿,Abp vnext中不行是因为框架在注入默认仓储的时候,通过获取DbCotext中我们写的DbSet来获取实体类型,通过实体类型来注入仓储默认实现的;
上述也是自己的踩坑经验,也百度过也想白嫖(毕竟CV程序员嘛),但是都没有对应的答案,所以才有此文,希望帮下其他伙伴给个参照;
可能也还有其他更优的解决方案,或者其中也存在bug,欢迎各位大佬指正;
作者:代码驿站
本文地址:https://www.cnblogs.com/Jinfeng1213/p/15813900.html
声明:原创博客请在转载时保留原文链接或者在文章开头加上本人博客地址,如发现错误,欢迎批评指正。凡是转载于本人的文章,不能设置打赏功能,如有特殊需求请与本人联系!
Abp vnext EFCore 实现动态上下文DbSet踩坑记的更多相关文章
- Vue + TypeScript + Element 搭建简洁时尚的博客网站及踩坑记
前言 本文讲解如何在 Vue 项目中使用 TypeScript 来搭建并开发项目,并在此过程中踩过的坑 . TypeScript 具有类型系统,且是 JavaScript 的超集,TypeScript ...
- Spark踩坑记——Spark Streaming+Kafka
[TOC] 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark strea ...
- Spark踩坑记——数据库(Hbase+Mysql)
[TOC] 前言 在使用Spark Streaming的过程中对于计算产生结果的进行持久化时,我们往往需要操作数据库,去统计或者改变一些值.最近一个实时消费者处理任务,在使用spark streami ...
- 【踩坑记】从HybridApp到ReactNative
前言 随着移动互联网的兴起,Webapp开始大行其道.大概在15年下半年的时候我接触到了HybridApp.因为当时还没毕业嘛,所以并不清楚自己未来的方向,所以就投入了HybridApp的怀抱. Hy ...
- Spark踩坑记——共享变量
[TOC] 前言 Spark踩坑记--初试 Spark踩坑记--数据库(Hbase+Mysql) Spark踩坑记--Spark Streaming+kafka应用及调优 在前面总结的几篇spark踩 ...
- Spark踩坑记——从RDD看集群调度
[TOC] 前言 在Spark的使用中,性能的调优配置过程中,查阅了很多资料,之前自己总结过两篇小博文Spark踩坑记--初试和Spark踩坑记--数据库(Hbase+Mysql),第一篇概况的归纳了 ...
- djangorestframework+vue-cli+axios,为axios添加token作为headers踩坑记
情况是这样的,项目用的restful规范,后端用的django+djangorestframework,前端用的vue-cli框架+webpack,前端与后端交互用的axios,然后再用户登录之后,a ...
- HttpWebRequest 改为 HttpClient 踩坑记-请求头设置
HttpWebRequest 改为 HttpClient 踩坑记-请求头设置 Intro 这两天改了一个项目,原来的项目是.net framework 项目,里面处理 HTTP 请求使用的是 WebR ...
- vue踩坑记
vue踩坑记 易错点 语法好难啊qwq 不要把'data'写成'date' 在v-html/v-bind中使用vue变量时不需要加变量名 在非vue事件中使用vue中变量时需要加变量名 正确 < ...
随机推荐
- 录入任务信息(Project)
<Project2016 企业项目管理实践>张会斌 董方好 编著 日历设置好了,就该录入任务了.当然在录入任务之前还要对任务进行一下面分解,就是一个项目,要分几个大步完成,每个大步又分几个 ...
- WPF之交互触发器(CallMethodAction)学习
需求背景: 当我们需要制作画板时,我们的VM需要记录我们的坐标并保存到Path的Data中,用我们普通的Command是无法办到的,这时我们就衍生出来了一个交互触发器CallMethodAction ...
- 用 shell 脚本做自动化测试
前言 项目中有一个功能,需要监控本地文件系统的变更,例如文件的增.删.改名.文件数据变动等等.之前只在 windows 上有实现,采用的是 iocp + ReadDirectoryChanges 方案 ...
- Java 数据类型:集合接口Map:HashTable;HashMap;IdentityHashMap;LinkedHashMap;Properties类读取配置文件;SortedMap接口和TreeMap实现类:【线程安全的ConcurrentHashMap】
Map集合java.util.Map Map用于保存具有映射关系的数据,因此Map集合里保存着两个值,一个是用于保存Map里的key,另外一组值用于保存Map里的value.key和value都可以是 ...
- 当是class com.cosl.po.Pc$$EnhancerByCGLIB$$38c58f03时,反射属性都他妈不好用了
当是class com.cosl.po.Pc$$EnhancerByCGLIB$$38c58f03时,反射属性都他妈不好用了 搞不懂为什么?
- 最强最全面的大数据SQL经典面试题(由31位大佬共同协作完成)
本套SQL题的答案是由许多小伙伴共同贡献的,1+1的力量是远远大于2的,有不少题目都采用了非常巧妙的解法,也有不少题目有多种解法.本套大数据SQL题不仅题目丰富多样,答案更是精彩绝伦! 注:以下参考答 ...
- UDP&串口调试助手用法(1)
一览 UDP 串口 常用 功能概述 概览 支持UDP通信协议: 广播.单播.组播 支持串口通信 配置了常用的配置,常用的进制转化: 2进制,8进制,10进制,和16进制之间的转换 配置了 计算器,加减 ...
- 串口之完整封装包含发送和接收(windows+ubuntu已通过初步测试)(持续更新)
这里下载源码 更新日志 16-08-2021 V1.0.3 1.修复接收数据没有将数据传递给应用层的bug 2.windows版本:设置接收数据相邻字节间间隔为5ms 24-09-2020 V1.0. ...
- 【LeetCode】762. Prime Number of Set Bits in Binary Representation 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 遍历数字+质数判断 日期 题目地址:https:// ...
- 【LeetCode】605. Can Place Flowers 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 解题方法 贪婪算法 日期 题目地址:https://leetcode.c ...