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

  • Ef Core花里胡哨系列(10) 动态起来的 DbContext已关闭评论
  • 73 次浏览
  • A+
所属分类:.NET技术
摘要

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


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

我们知道,DbContext有两种托管方式,一种是AddDbContextAddDbContextFactory,但是呢他们各有优劣,例如工厂模式下性能更好呀等等。那么,我们能否自己托管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取出我们对应AppIdDbContext进行操作就可以了:

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();     } }