我们知道,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(); } }