如何将Ef Core动态 DbContext为?

摘要:Ef Core花里胡哨系列(10) 动态起来的 DbContext 我们知道,DbContext有两种托管方式,一种是AddDbContext和AddDbContextFactory,但是呢他们各有优劣,例如工厂模式下性能更好呀等等。那么,
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(); } }