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