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();
    }
}
相关推荐
南城花随雪。2 分钟前
硬盘(HDD)与固态硬盘(SSD)详细解读
数据库
儿时可乖了3 分钟前
使用 Java 操作 SQLite 数据库
java·数据库·sqlite
懒是一种态度5 分钟前
Golang 调用 mongodb 的函数
数据库·mongodb·golang
天海华兮7 分钟前
mysql 去重 补全 取出重复 变量 函数 和存储过程
数据库·mysql
神仙别闹33 分钟前
基于C#和Sql Server 2008实现的(WinForm)订单生成系统
开发语言·c#
gma9991 小时前
Etcd 框架
数据库·etcd
爱吃青椒不爱吃西红柿‍️1 小时前
华为ASP与CSP是什么?
服务器·前端·数据库
Yz98762 小时前
hive的存储格式
大数据·数据库·数据仓库·hive·hadoop·数据库开发
苏-言2 小时前
Spring IOC实战指南:从零到一的构建过程
java·数据库·spring
Ljw...2 小时前
索引(MySQL)
数据库·mysql·索引