Ef Core花里胡哨系列(10) 动态起来的 DbContext

Ef Core花里胡哨系列(10) 动态起来的 DbContext

我们知道,DbContext有两种托管方式,一种是AddDbContextAddDbContextFactory,但是呢他们各有优劣,例如工厂模式下性能更好呀等等。那么,我们能否自己托管DbContext呢?

Github Demo:动态起来的 DbContext

场景:

结合我们之前的文章 [Ef Core花里胡哨系列(5) 动态修改追踪的实体、动态查询] 假设一个应用内有很多的子应用,且都需要更新追踪的动态实体,那么很多表在重置OnModelCreating的时候将会非常的慢。主要体现在modelBuilder.Model.AddEntityType(type),每个实体都需要花费一小段时间,几百个实体就会按分钟计算了,而且还会数据库操作产生一定的影响。

我们先实现一个基础的DbContext用来添加一些通用的实体以及处理动态实体的逻辑,每次需要重置DbContext的时候,都会获取最新的动态实体进行更新:

csharp 复制代码
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

csharp 复制代码
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,以及负责初始化:

csharp 复制代码
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取出我们对应AppIdDbContext进行操作就可以了:

csharp 复制代码
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();
    }
}
相关推荐
Zzz 小生40 分钟前
Claude Code学习笔记(四)-助你快速搭建首个Python项目
大数据·数据库·elasticsearch
nongcunqq4 小时前
abap 操作 excel
java·数据库·excel
rain bye bye5 小时前
calibre LVS 跑不起来 就将setup 的LVS Option connect下的 connect all nets by name 打开。
服务器·数据库·lvs
R-G-B5 小时前
【02】C#入门到精通——C# 变量、输入/输出、类型转换
开发语言·c#·c# 变量·c#输入/输出·c#类型转换
星河队长5 小时前
C# 软件加密方法,有使用时间限制,同时要防止拷贝
开发语言·c#
阿里云大数据AI技术6 小时前
云栖实录|MaxCompute全新升级:AI时代的原生数据仓库
大数据·数据库·云原生
Aevget6 小时前
DevExpress WinForms v25.1亮点 - PDF Viewer(查看器)等全新升级
pdf·c#·界面控件·winform·devexpress·ui开发
不剪发的Tony老师6 小时前
Valentina Studio:一款跨平台的数据库管理工具
数据库·sql
weixin_307779137 小时前
在 Microsoft Azure 上部署 ClickHouse 数据仓库:托管服务与自行部署的全面指南
开发语言·数据库·数据仓库·云计算·azure
InCerry7 小时前
为 .NET 10 GC(DATAS)做准备
性能优化·c#·.net·gc