实现原理
当我们new
一个上下文DbContext
后, 每次执行CURD方式时 ,都会依次调用OnConfiguring()
,OnModelCreating()
两个方法。
OnConfiguring()
我们将用来替换一些服务实现,以支持分表的工作OnModelCreating()
我们将用来重新实现 实体与数据库表 的映射关系
每次调用OnModelCreating()
时,会判断实体与数据库表的映射关系有没有改变,如果改变则采用新的映射关系。
判断是否发生改变,通过替换 IModelCacheKeyFactory
接口的实现来完成。详情可见:在具有相同 DbContext 类型的多个模型之间进行交替
IModelCacheKeyFactory
实现
DbContextBase
是一个DbContext
的实现,,ShardingRule
是DbContextBase
的一个共有属性。
根据分表规则的不同,每次的映射关系也会不同。
public class DynamicModelCacheKeyFactoryDesignTimeSupport : IModelCacheKeyFactory { public object Create(DbContext context, bool designTime) => context is DbContextBase dynamicContext ? (context.GetType(), dynamicContext.ShardingRule, designTime) : (object)context.GetType(); public object Create(DbContext context) => Create(context, false); }
OnConfiguring()
替换接口实现
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); //如果分页规则有 ,代表需要分页, 那么需要替换对应的服务实现 if (!string.IsNullOrEmpty(this.ShardingRule)) { optionsBuilder.ReplaceService<IModelCacheKeyFactory, DynamicModelCacheKeyFactoryDesignTimeSupport>(); } }
ModelCustomizer
实现
在每次调用 OnModelCreating()
时,方法内部会调用实现IModelCustomizer
的 ModelCustomizer.cs
的Customize()
方法,我们可以将映射关系写在此方法内。
通过继承实现:
IShardingTypeFinder
是一个类型查找器,请自行实现。
public class ShardingModelCustomizer : ModelCustomizer { public ShardingModelCustomizer(ModelCustomizerDependencies dependencies) : base(dependencies) { } public override void Customize(ModelBuilder modelBuilder, DbContext context) { base.Customize(modelBuilder, context); var dbContextBase = context as DbContextBase; var shardingTypeFinder = dbContextBase.ServiceProvider.GetService<IShardingTypeFinder>(); //查找需要重新映射表名的类 var shardingTypes = shardingTypeFinder.FindAll(true); if (shardingTypes != null && shardingTypes.Count() > 0) { if (context is DbContextBase contextBase) { if (!string.IsNullOrEmpty(contextBase.ShardingRule)) { foreach (var type in shardingTypes) { switch (contextBase.DbContextOptions.DatabaseType) { case DatabaseType.SqlServer: modelBuilder.Entity(type).ToTable($"{type.Name}_{contextBase.ShardingRule}"); break; case DatabaseType.Sqlite: modelBuilder.Entity(type).ToTable($"{type.Name}_{contextBase.ShardingRule}"); break; case DatabaseType.MySql: modelBuilder.Entity(type).ToTable($"{type.Name}_{contextBase.ShardingRule}".ToMySQLName()); break; case DatabaseType.Oracle: modelBuilder.Entity(type).ToTable($"{type.Name}_{contextBase.ShardingRule}".ToOracleName()); break; default: modelBuilder.Entity(type).ToTable($"{type.Name}_{contextBase.ShardingRule}"); break; } } } } } } }
OnConfiguring()
替换接口实现
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); //如果分页规则有 ,代表需要分页, 那么需要替换对应的服务实现 if (!string.IsNullOrEmpty(this.ShardingRule)) { optionsBuilder.ReplaceService<IModelCacheKeyFactory, DynamicModelCacheKeyFactoryDesignTimeSupport>().ReplaceService<IModelCustomizer, ShardingModelCustomizer>(); } }
DbContextBase
构造函数修改
上文提到了ShardingRule
这个属性的出现 , 如何给这个属性赋值呢?
有两种方式:
- 构造函数传参
- 通过接口获取
构造函数传参
public string ShardingRule { get; set; } public DbContextBase(string shardingRule, DbContextOptions options) : base(options) { ShardingRule = shardingRule; }
通过接口获取
IShardingRule
是实现规则名称的自定义接口,自行实现
protected DbContextBase(DbContextOptions options, IServiceProvider serviceProvider) : base(options) { ShardingRule = (serviceProvider.GetService<IShardingRule>()).GetValue(); }
使用方式
这里只介绍构造函数传参使用方式
DbContextOptionsBuilder<DbContextBase> optionsBuilder = new DbContextOptionsBuilder<DbContextBase>(); optionsBuilder.UseSqlServer("connStr"); var options = optionsBuilder.Options; using (var dbContext = new DbContextBase("202209", options)) { //TODO.... }
跨上下文使用事务
这里需要注意的是,跨上下文使用事务必须使用同一个连接,所以optionsBuilder.UseSqlServer(connection);
这里的写法改变一下,使用同一连接
DbContextOptionsBuilder<DbContextBase> optionsBuilder = new DbContextOptionsBuilder<DbContextBase>(); IDbConnection connection = new SqlConnection("connStr"); optionsBuilder.UseSqlServer(connection); var options = optionsBuilder.Options; using (var dbContext = new DbContextBase("202209", options)) { using (var transaction =await dbContext.Database.BeginTransactionAsync()) { using (var dbContext2 = new DbContextBase("202210", options)) { await dbContext2.Database.UseTransactionAsync(transaction); //TODO.... transaction.Commit(); } } }
总结
EFCore分表的实现大致全是这样,没有什么区别。可以参考一些开源的框架,对现有的系统进行适当的调整,毕竟别人写的并不一定适合你。希望这篇文章可以帮到你。