Ef Core花里胡哨系列(10) 动态起来的 DbContext
Ef Core花里胡哨系列(10) 动态起来的 DbContext
我们知道,DbContext有两种托管方式,一种是AddDbContext和AddDbContextFactory,但是呢他们各有优劣,例如工厂模式下性能更好呀等等。那么,我们能否自己托管DbContext呢?
Github Demo:动态起来的 DbContext
场景:
结合我们之前的文章 [Ef Core花里胡哨系列(5) 动态修改追踪的实体、动态查询] 假设一个应用内有很多的子应用,且都需要更新追踪的动态实体,那么很多表在重置OnModelCreating的时候将会非常的慢。主要体现在modelBuilder.Model.AddEntityType(type),每个实体都需要花费一小段时间,几百个实体就会按分钟计算了,而且还会数据库操作产生一定的影响。
我们先实现一个基础的DbContext用来添加一些通用的实体以及处理动态实体的逻辑,每次需要重置DbContext的时候,都会获取最新的动态实体进行更新:
public class DbContextBase : DbContext
{
    public DbSet<User> Users { get; set; } = null!;
    public DbSet<Department> Departments { get; set; } = null!;
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite("Data Source=sample.db");
        optionsBuilder.ReplaceService<IModelCacheKeyFactory, MyModelCacheFactory>();
        base.OnConfiguring(optionsBuilder);
    }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        var name = GetType().Name.Split("_");
        if (name.Length > 1)
        {
            foreach (var item in FormTypeBuilder.GetAppTypes(name[0]).Where(item => modelBuilder.Model.FindEntityType(item.Value) is null))
            {
                modelBuilder.Model.AddEntityType(item.Value);
            }
        }
        base.OnModelCreating(modelBuilder);
    }
}
然后实现一个动态DbContext的生成器,用于针对不同的AppId生成不同的DbContext:
public class DbContextGenerator
{
    private readonly ConcurrentDictionary<string, Type> _contextTypes = new()
    {
    };
    public Type GetOrCreate(string appId)
    {
        if (!_contextTypes.TryGetValue(appId, out var value))
        {
            value = GeneratorDbContext(appId);
            _contextTypes.TryAdd(appId, value);
        }
        return value;
    }
    public Type GeneratorDbContext(string appId)
    {
        var assemblyName = new AssemblyName("__RuntimeDynamicDbContexts");
        var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule("__RuntimeDynamicModule");
        var typeBuilder = moduleBuilder.DefineType($"{appId.ToLower()}_DbContext", TypeAttributes.Public | TypeAttributes.Class, typeof(DbContextBase));
        var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { });
        var ilGenerator = constructorBuilder.GetILGenerator();
        ilGenerator.Emit(OpCodes.Ldarg_0);
        ilGenerator.Emit(OpCodes.Call, typeof(DbContextBase).GetConstructor(Type.EmptyTypes));
        ilGenerator.Emit(OpCodes.Ret);
        typeBuilder.CreateType();
        var dbContextType = assemblyBuilder.GetType($"{appId.ToLower()}_DbContext");
        return dbContextType;
    }
}
然后我们需要实现一个DbContext的容器用于管理我们生成的DbContext,以及负责初始化:
public class DbContextContainer : IDisposable
{
    private readonly DbContextGenerator _generator;
    private readonly Dictionary<string, DbContext> _contexts = new();
    public DbContextContainer(DbContextGenerator generator)
    {
        _generator = generator;
    }
    public DbContext Get(string appId)
    {
        if (!_contexts.TryGetValue(appId, out var context))
        {
            context = (DbContext)Activator.CreateInstance(_generator.GetOrCreate(appId))!;
            _contexts[appId] = context;
        }
        return context;
    }
    public void Dispose()
    {
        _contexts.Clear();
    }
}
DbContextContainer的生命周期即DbContext的生命周期,因为DbContext的缓存是共享的,所以我们也不用担心一些性能问题。
使用时也非常简单,我们只需要在DbContextContainer取出我们对应AppId的DbContext进行操作就可以了:
public class DynamicController : ApiControllerBase
{
    private readonly DbContextContainer _container;
    public DynamicController(DbContextContainer container)
    {
        _container = container;
    }
    [HttpGet]
    public async Task<IActionResult> GetCompanies()
    {
        var res = await _container.Get("test1").DynamicSet(typeof(Company)).ToDynamicListAsync();
        return Ok(res);
    }
    [HttpGet]
    public async Task<IActionResult> AddCompany()
    {
        var db = _container.Get("test1");
        FormTypeBuilder.AddDynamicEntity("test1", "Companies", typeof(Company));
        db.UpdateVersion();
        return Ok();
    }
}
Ef Core花里胡哨系列(10) 动态起来的 DbContext的更多相关文章
- 在EF Core里面如何使用以前EntityFramework的DbContext.Database.SqlQuery<SomeModel>自定义查询
		问: With Entity Framework Core removing dbData.Database.SqlQuery<SomeModel> I can't find a solu ... 
- ASP.NET Boilerplate 学习    AspNet Core2 浏览器缓存使用    c#基础,单线程,跨线程访问和线程带参数   wpf 禁用启用webbroswer右键菜单   EF Core 2.0使用MsSql/MySql实现DB First和Code First   ASP.NET Core部署到Windows IIS  QRCode.js:使用 JavaScript 生成
		ASP.NET Boilerplate 学习 1.在http://www.aspnetboilerplate.com/Templates 网站下载ABP模版 2.解压后打开解决方案,解决方案目录: ... 
- EF Core 2.0使用MsSql/Mysql实现DB First和Code First
		参考地址 EF官网 ASP.NET Core MVC 和 EF Core - 教程系列 环境 Visual Studio 2017 最新版本的.NET Core 2.0 SDK 最新版本的 Windo ... 
- Asp.net core下利用EF core实现从数据实现多租户(2) : 按表分离
		前言 在上一篇文章中,我们介绍了如何根据不同的租户进行数据分离,分离的办法是一个租户一个数据库. 也提到了这种模式还是相对比较重,所以本文会介绍一种更加普遍使用的办法: 按表分离租户. 这样做的好处是 ... 
- EF Core 2.0中Transaction事务会对DbContext底层创建和关闭数据库连接的行为有所影响
		数据库 我们先在SQL Server数据库中建立一个Book表: CREATE TABLE [dbo].[Book]( ,) NOT NULL, ) NULL, ) NULL, ) NULL, [Cr ... 
- 9.10 翻译系列:EF数据注解特性之StringLength【EF 6 Code-First系列】
		原文链接:https://www.entityframeworktutorial.net/code-first/stringlength-dataannotations-attribute-in-co ... 
- 9.翻译系列:EF 6以及EF Core中的数据注解特性(EF 6 Code-First系列)
		原文地址:http://www.entityframeworktutorial.net/code-first/dataannotation-in-code-first.aspx EF 6 Code-F ... 
- 9.4 翻译系列:EF 6以及 EF Core中的NotMapped特性(EF 6 Code-First系列)
		原文链接:http://www.entityframeworktutorial.net/code-first/notmapped-dataannotations-attribute-in-code-f ... 
- 10.翻译系列:EF 6中的Fluent API配置【EF 6 Code-First系列】
		原文链接:https://www.entityframeworktutorial.net/code-first/fluent-api-in-code-first.aspx EF 6 Code-Firs ... 
- 10.1.翻译系列:EF 6中的实体映射【EF 6 Code-First系列】
		原文链接:https://www.entityframeworktutorial.net/code-first/configure-entity-mappings-using-fluent-api.a ... 
随机推荐
- 使用shuffle sharding增加容错性
			使用shuffle sharding增加容错性 最近在看kubernetes的API Priority and Fairness,它使用shuffle sharding来为请求选择处理队列,以此防止高 ... 
- 「codeforces - 585E」Present for Vitalik the Philatelist
			link. 设 \(\displaystyle f(x) = \# S', s.t. S' \subseteq S, S' \neq \varnothing, \gcd(S') = x\),\(g(x ... 
- package.json指南
			一.属性 name 定义项目的名称,不能以"."和"_"开头,不能包含大写字母 version 定义项目的版本号,格式为:大版本号.次版本号.修订号 descr ... 
- Oracle中的substr()函数和INSTR()函数和mysql中substring_index函数字符截取函数用法:计算BOM系数用量拼接字符串*计算值方法
			最近一直在研究计算产品BOM的成本系数,将拼接的元件用量拼接后拆分计算是个问题,后来受到大佬在mysql中截取字符串的启发在oracle中以substr和instr实现了 1.以下是我在mysql中 ... 
- 一行代码搞定 font-size 响应式
			前言 公司要做大屏,但是大屏还要有个嵌在系统的版本,屏幕(iframe)小了但字体大了怎么办.网上找了很多代码都很长,个人参考了资料后实现了一个一行代码 font-size 响应式. TL;DR ht ... 
- WPF 笔迹算法 从点集转笔迹轮廓
			本文将告诉大家一些笔迹算法,从用户输入的点集,即鼠标轨迹点或触摸轨迹点等,转换为一个可在界面绘制显示笔迹画面的基础数学算法.尽管本文标记的是 WPF 的笔迹算法,然而实际上本文更侧重基础数学计算,理论 ... 
- 虚拟机和Linux操作系统的安装
			虚拟机和Linux操作系统的安装 简述 linux是完全免费的 只要你足够强大,可以对linux系统的源码进行编译 市场上的版本:发行版 Ubantu 红帽 每两年发布一个版本 下面我们开始进行安装 ... 
- 未能添加SSL证书,错误1312
			1.win+r打开运行,输入mmc 2.在控制台1[控制台根节点]->文件->添加/删除....->选择证书->添加-选择计算机账户->完成->确认 3.找到证书文 ... 
- DP 杂题选做
			部分详见: 概率期望 DP 学习笔记 树形 DP 学习笔记 其余题就不具体分类了. P1220 关路灯 题解说这是区间 DP 经典题,但我以前居然没听说过,这下尴尬了. 设 \(f_{i,j,0/1} ... 
- js 加密、解密算法类库
			有些功能需要前端进行加密解密,就会用到这些库 crypto-js 是一个纯 javascript 写的加密算法类库 ,可以非常方便地在 javascript 进行 MD5.SHA1.SHA2.SHA3 ... 
