EFCore分表實現
實現原理
當我們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分表的實現大致全是這樣,沒有什麼區別。可以參考一些開源的框架,對現有的系統進行適當的調整,畢竟別人寫的並不一定適合你。希望這篇文章可以幫到你。