翼度科技»论坛 编程开发 .net 查看内容

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

6

主题

6

帖子

18

积分

新手上路

Rank: 1

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

我们知道,DbContext有两种托管方式,一种是AddDbContext和AddDbContextFactory,但是呢他们各有优劣,例如工厂模式下性能更好呀等等。那么,我们能否自己托管DbContext呢?
Github Demo:动态起来的 DbContext
场景:
结合我们之前的文章 [Ef Core花里胡哨系列(5) 动态修改追踪的实体、动态查询] 假设一个应用内有很多的子应用,且都需要更新追踪的动态实体,那么很多表在重置OnModelCreating的时候将会非常的慢。主要体现在modelBuilder.Model.AddEntityType(type),每个实体都需要花费一小段时间,几百个实体就会按分钟计算了,而且还会数据库操作产生一定的影响。
我们先实现一个基础的DbContext用来添加一些通用的实体以及处理动态实体的逻辑,每次需要重置DbContext的时候,都会获取最新的动态实体进行更新:
  1. public class DbContextBase : DbContext
  2. {
  3.     public DbSet<User> Users { get; set; } = null!;
  4.     public DbSet<Department> Departments { get; set; } = null!;
  5.     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  6.     {
  7.         optionsBuilder.UseSqlite("Data Source=sample.db");
  8.         optionsBuilder.ReplaceService<IModelCacheKeyFactory, MyModelCacheFactory>();
  9.         base.OnConfiguring(optionsBuilder);
  10.     }
  11.     protected override void OnModelCreating(ModelBuilder modelBuilder)
  12.     {
  13.         var name = GetType().Name.Split("_");
  14.         if (name.Length > 1)
  15.         {
  16.             foreach (var item in FormTypeBuilder.GetAppTypes(name[0]).Where(item => modelBuilder.Model.FindEntityType(item.Value) is null))
  17.             {
  18.                 modelBuilder.Model.AddEntityType(item.Value);
  19.             }
  20.         }
  21.         base.OnModelCreating(modelBuilder);
  22.     }
  23. }
复制代码
然后实现一个动态DbContext的生成器,用于针对不同的AppId生成不同的DbContext:
  1. public class DbContextGenerator
  2. {
  3.     private readonly ConcurrentDictionary<string, Type> _contextTypes = new()
  4.     {
  5.     };
  6.     public Type GetOrCreate(string appId)
  7.     {
  8.         if (!_contextTypes.TryGetValue(appId, out var value))
  9.         {
  10.             value = GeneratorDbContext(appId);
  11.             _contextTypes.TryAdd(appId, value);
  12.         }
  13.         return value;
  14.     }
  15.     public Type GeneratorDbContext(string appId)
  16.     {
  17.         var assemblyName = new AssemblyName("__RuntimeDynamicDbContexts");
  18.         var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
  19.         var moduleBuilder = assemblyBuilder.DefineDynamicModule("__RuntimeDynamicModule");
  20.         var typeBuilder = moduleBuilder.DefineType($"{appId.ToLower()}_DbContext", TypeAttributes.Public | TypeAttributes.Class, typeof(DbContextBase));
  21.         var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { });
  22.         var ilGenerator = constructorBuilder.GetILGenerator();
  23.         ilGenerator.Emit(OpCodes.Ldarg_0);
  24.         ilGenerator.Emit(OpCodes.Call, typeof(DbContextBase).GetConstructor(Type.EmptyTypes));
  25.         ilGenerator.Emit(OpCodes.Ret);
  26.         typeBuilder.CreateType();
  27.         var dbContextType = assemblyBuilder.GetType($"{appId.ToLower()}_DbContext");
  28.         return dbContextType;
  29.     }
  30. }
复制代码
然后我们需要实现一个DbContext的容器用于管理我们生成的DbContext,以及负责初始化:
  1. public class DbContextContainer : IDisposable
  2. {
  3.     private readonly DbContextGenerator _generator;
  4.     private readonly Dictionary<string, DbContext> _contexts = new();
  5.     public DbContextContainer(DbContextGenerator generator)
  6.     {
  7.         _generator = generator;
  8.     }
  9.     public DbContext Get(string appId)
  10.     {
  11.         if (!_contexts.TryGetValue(appId, out var context))
  12.         {
  13.             context = (DbContext)Activator.CreateInstance(_generator.GetOrCreate(appId))!;
  14.             _contexts[appId] = context;
  15.         }
  16.         return context;
  17.     }
  18.     public void Dispose()
  19.     {
  20.         _contexts.Clear();
  21.     }
  22. }
复制代码
DbContextContainer的生命周期即DbContext的生命周期,因为DbContext的缓存是共享的,所以我们也不用担心一些性能问题。
使用时也非常简单,我们只需要在DbContextContainer取出我们对应AppId的DbContext进行操作就可以了:
  1. public class DynamicController : ApiControllerBase
  2. {
  3.     private readonly DbContextContainer _container;
  4.     public DynamicController(DbContextContainer container)
  5.     {
  6.         _container = container;
  7.     }
  8.     [HttpGet]
  9.     public async Task<IActionResult> GetCompanies()
  10.     {
  11.         var res = await _container.Get("test1").DynamicSet(typeof(Company)).ToDynamicListAsync();
  12.         return Ok(res);
  13.     }
  14.     [HttpGet]
  15.     public async Task<IActionResult> AddCompany()
  16.     {
  17.         var db = _container.Get("test1");
  18.         FormTypeBuilder.AddDynamicEntity("test1", "Companies", typeof(Company));
  19.         db.UpdateVersion();
  20.         return Ok();
  21.     }
  22. }
复制代码
来源:https://www.cnblogs.com/donpangpang/Undeclared/17944918
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具