.Net下極限生產力之efcore分表分庫全自動化遷移CodeFirst

開始

本次我們的主題就是極限生產力,其他語言望塵莫及的分表分庫全自動化Migrations Code-First 加 efcore 分表分庫無感開發

還記得上次發布博客還是在上次,上次發布了如何兼容WTM框架后也有不少小伙伴來問我如何兼容如何遷移等問題,經過這么多框架的兼容我自己也認識到了一些問題,譬如在ShardingCore初始化前使用(畢竟efcore)的初始化是在依賴注入的時候不需要手動調用初始化,比如efcore.tool的遷移的問題,本項目不能遷移,因為efcore.tool在使用命令的時候不會調用Configure導致無法初始化的bug,導致遷移必須要通過新建控制臺程序,而不能在本項目內遷移,再或者code-firstShardingCore的啟動參數沖突導致需要平凡修改,并且不支持分庫,之前有小伙伴分了300個庫如果自動遷移不能用確實是一件很頭疼的事情,雖然這些問題對于分庫分表而言其實是小事情,但是如果一旦分表分庫到達一定的量級就會難以維護。所以ShardingCore在最近三周內開啟了新的版本,新版本主要是解決上述痛點并且將代碼更加標準的使用

開發軟件一般是先能用,然后好用,最后標準化,ShardingCore也是如此,因為需要擴展efcore所以有時候在不熟悉efcore的擴展方式的時候只能靠靜態類來進行注入訪問,而靜態類其實是一個非常不標準的用法,除非萬不得已。那么新版本x.6.x.x ShardingCore帶來了什么請往下看

移除靜態容器

靜態容器的使用導致ShardingCore在整個應用程序聲明周期只有一份數據,那么數據都是共享的這個對于后續的測試維護擴展是相當的不利的,沒有單例那種隔離性來的好,所以移除了ShardingContainer,通過提供IShardingRuntimeContext來保證和之前的參數結構的訪問,同一個DbContext類型在使用不同的IShardingRuntimeContext后可以表現出不同的分表分庫特性。

原生efcore

首先我們針對原生efcore進行擴展來達到分庫分表+code-first自動遷移開發

添加依賴 ShardingCore 6.6.0.3 MySql

//請安裝最新版本目前x.6.0.3+,第一個版本號6代表efcore的版本號
Install-Package ShardingCore -Version 6.6.0.3Install-Package Pomelo.EntityFrameworkCore.MySql  -Version 6.0.1
Install-Package Microsoft.EntityFrameworkCore.Tools  -Version 6.0.6

創建一個todo實體

public class TodoItem{public string Id { get; set; }public string Text { get; set; }
}

創建dbcontext

簡單的將對象和數據庫做了一下映射當然DbSet+Attribute也是可以的

public class MyDbContext:AbstractShardingDbContext,IShardingTableDbContext
{public MyDbContext(DbContextOptions<MyDbContext> options) : base(options){}public IRouteTail RouteTail { get; set; }protected override void OnModelCreating(ModelBuilder modelBuilder){base.OnModelCreating(modelBuilder);modelBuilder.Entity<TodoItem>(mb =>{mb.HasKey(o => o.Id);mb.Property(o => o.Id).IsRequired().HasMaxLength(50).HasComment("id");mb.Property(o => o.Text).IsRequired().HasMaxLength(256).HasComment("事情");mb.ToTable(nameof(TodoItem));});}
}

新建分庫分表路由

分庫路由

public class TodoItemDataSourceRoute:AbstractShardingOperatorVirtualDataSourceRoute<TodoItem,string>
{    /// <summary>/// id的hashcode取模余3分庫/// </summary>/// <param name="shardingKey"></param>/// <returns></returns>/// <exception cref="InvalidOperationException"></exception>public override string ShardingKeyToDataSourceName(object shardingKey){        if (shardingKey == null) throw new InvalidOperationException("sharding key cant null");var stringHashCode = ShardingCoreHelper.GetStringHashCode(shardingKey.ToString());        return $"ds{(Math.Abs(stringHashCode) % 3)}";//ds0,ds1,ds2}private readonly List<string> _dataSources = new List<string>() { "ds0", "ds1", "ds2" };public override List<string> GetAllDataSourceNames(){        return _dataSources;}public override bool AddDataSourceName(string dataSourceName){throw new NotImplementedException();}    /// <summary>/// id分庫/// </summary>/// <param name="builder"></param>public override void Configure(EntityMetadataDataSourceBuilder<TodoItem> builder){builder.ShardingProperty(o => o.Id);}public override Func<string, bool> GetRouteToFilter(string shardingKey, ShardingOperatorEnum shardingOperator){var t = ShardingKeyToDataSourceName(shardingKey);        switch (shardingOperator){            case ShardingOperatorEnum.Equal: return tail => tail == t;            default:{                return tail => true;}}}
}折疊

分表路由

public class TodoItemTableRoute:AbstractSimpleShardingModKeyStringVirtualTableRoute<TodoItem>
{public TodoItemTableRoute() : base(2, 3){}    /// <summary>/// 正常情況下不會用內容來做分片鍵因為作為分片鍵有個前提就是不會被修改/// </summary>/// <param name="builder"></param>public override void Configure(EntityMetadataTableBuilder<TodoItem> builder){builder.ShardingProperty(o => o.Text);}
}

新建遷移數據庫腳本生成

public class ShardingMySqlMigrationsSqlGenerator:MySqlMigrationsSqlGenerator
{private readonly IShardingRuntimeContext _shardingRuntimeContext;public ShardingMySqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, IRelationalAnnotationProvider annotationProvider, IMySqlOptions options,IShardingRuntimeContext shardingRuntimeContext) : base(dependencies, annotationProvider, options){_shardingRuntimeContext = shardingRuntimeContext;}protected override void Generate(MigrationOperation operation,IModel model,MigrationCommandListBuilder builder){var oldCmds = builder.GetCommandList().ToList();base.Generate(operation, model, builder);var newCmds = builder.GetCommandList().ToList();var addCmds = newCmds.Where(x => !oldCmds.Contains(x)).ToList();MigrationHelper.Generate(_shardingRuntimeContext,operation, builder, Dependencies.SqlGenerationHelper, addCmds);}
}

配置依賴注入

ILoggerFactory efLogger = LoggerFactory.Create(builder =>
{builder.AddFilter((category, level) => category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information).AddConsole();
});
var builder = WebApplication.CreateBuilder(args);builder.Services.AddControllers();builder.Services.AddShardingDbContext<MyDbContext>().UseRouteConfig(op =>{op.AddShardingTableRoute<TodoItemTableRoute>();op.AddShardingDataSourceRoute<TodoItemDataSourceRoute>();}).UseConfig((sp,op) =>{op.UseShardingQuery((con, b) =>{b.UseMySql(con, new MySqlServerVersion(new Version())).UseLoggerFactory(efLogger);});op.UseShardingTransaction((con, b) =>{b.UseMySql(con, new MySqlServerVersion(new Version())).UseLoggerFactory(efLogger);});op.AddDefaultDataSource("ds0", "server=127.0.0.1;port=3306;database=mydb0;userid=root;password=root;");op.AddExtraDataSource(sp=>new Dictionary<string, string>(){{"ds1", "server=127.0.0.1;port=3306;database=mydb1;userid=root;password=root;"},{"ds2", "server=127.0.0.1;port=3306;database=mydb2;userid=root;password=root;"}});op.UseShardingMigrationConfigure(b =>{b.ReplaceService<IMigrationsSqlGenerator, ShardingMySqlMigrationsSqlGenerator>();});}).AddShardingCore();var app = builder.Build();// Configure the HTTP request pipeline.//如果有按時間分片的需要加定時任務否則可以不加app.Services.UseAutoShardingCreate();using (var scope = app.Services.CreateScope()){var defaultShardingDbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();     if (defaultShardingDbContext.Database.GetPendingMigrations().Any()){defaultShardingDbContext.Database.Migrate();}} //如果需要在啟動后掃描是否有表卻掃了可以添加這個//app.Services.UseAutoTryCompensateTable();//......app.Run();折疊

添加遷移文件

Add-Migration Init

b6f7ae2edf830a6389181fcaea655bc2.png

啟動程序

分表分庫自動遷移
a14f5e4141cbf6801ddb7d4117ba8bca.png

crud

54aa660adcd50bc2784cecb2bdb1432f.png
07a2641a8f719c047b45fca19509ca72.png
6cac5dd52eee399019c0d5bb99705b10.png
357215871eb8ce9fdb844dcedee6c326.png

添加todo字段并遷移

接下來我們將針對TodoItem添加一個name字段并且新增一張既不分庫也不分表的表然后進行遷移

public class TodoItem{public string Id { get; set; }public string Text { get; set; }public string Name { get; set; }
}
public class TodoTest{public string Id { get; set; }public string Test { get; set; }
}//docontextprotected override void OnModelCreating(ModelBuilder modelBuilder){base.OnModelCreating(modelBuilder);modelBuilder.Entity<TodoItem>(mb =>{mb.HasKey(o => o.Id);mb.Property(o => o.Id).IsRequired().HasMaxLength(50).HasComment("id");mb.Property(o => o.Text).IsRequired().HasMaxLength(256).HasComment("事情");mb.Property(o => o.Name).HasMaxLength(256).HasComment("姓名");mb.ToTable(nameof(TodoItem));});modelBuilder.Entity<TodoTest>(mb =>{mb.HasKey(o => o.Id);mb.Property(o => o.Id).IsRequired().HasMaxLength(50).HasComment("id");mb.Property(o => o.Test).IsRequired().HasMaxLength(256).HasComment("測試");mb.ToTable(nameof(TodoTest));});}

78cc77ef91015a85853df2c14f841877.png
不出意外我們成功了然后再次啟動
6152e427a88cb428187425b168c727d8.png
啟動程序后我們驚奇的發現不單原先的表新增了一個name字段,并且為分片未分開的表也被添加進來了

到此為止efcore的原生分庫分表+全自動化遷移Code-First已經全部完成,這不僅大大的提高了程序的性能并且大大的方便了開發人員的維護。

集成AbpVNext

完成了efcore原生的分表分庫遷移我們將進行abp下的操作
首先我們去github下的abp-samples里面下載對應的demo測試,這邊選擇todo-mvc
接著我們本地打開安裝依賴,只需要安裝·ShardingCore· 6.6.0.3。

新建兩個接口用于賦值創建時間和guid

因為ShardingCore需要add,update,remove的時候shardingkey不可以為空,你可以自己賦值,但是這樣efcore的不分性能就不能用了

//在TodoApp.Domain.Shared新增兩個接口(非必須)public interface IShardingKeyIsCreationTime{}public class IShardingKeyIsGuId{}

AbpDbContext抽象類

因為Abp需要繼承AbpDbContext所以這邊進行一個修改因為ShardingCore只需要接口所以可以滿足任何情況
//為了篇幅移除了大部分代碼剩下的可以在文末demo處查看

public abstract class AbstractShardingAbpDbContext<TDbContext> : AbpDbContext<TDbContext>, IShardingDbContext, ISupportShardingReadWritewhere TDbContext : DbContext{private readonly IShardingDbContextExecutor _shardingDbContextExecutor;protected AbstractShardingAbpDbContext(DbContextOptions<TDbContext> options) : base(options){var wrapOptionsExtension = options.FindExtension<ShardingWrapOptionsExtension>();            if (wrapOptionsExtension != null){_shardingDbContextExecutor = new ShardingDbContextExecutor(this);}}public DbContext GetDbContext(string dataSourceName, CreateDbContextStrategyEnum strategy, IRouteTail routeTail){var dbContext = _shardingDbContextExecutor.CreateDbContext(strategy, dataSourceName, routeTail);            if (dbContext is AbpDbContext<TDbContext> abpDbContext && abpDbContext.LazyServiceProvider == null){abpDbContext.LazyServiceProvider = this.LazyServiceProvider;}            return dbContext;}}

新增分庫分表路由

todoitem id取模分庫

public class TodoDataSourceRoute:AbstractShardingOperatorVirtualDataSourceRoute<TodoItem,string>{public override string ShardingKeyToDataSourceName(object shardingKey){            if (shardingKey == null) throw new InvalidOperationException("sharding key cant null");var stringHashCode = ShardingCoreHelper.GetStringHashCode(shardingKey.ToString());            return $"ds{(Math.Abs(stringHashCode) % 3)}";//ds0,ds1,ds2}public override List<string> GetAllDataSourceNames(){            return new List<string>(){                "ds0", "ds1", "ds2"};}public override bool AddDataSourceName(string dataSourceName){throw new NotImplementedException();}public override void Configure(EntityMetadataDataSourceBuilder<TodoItem> builder){builder.ShardingProperty(o => o.Id);}public override Func<string, bool> GetRouteToFilter(string shardingKey, ShardingOperatorEnum shardingOperator){var t = ShardingKeyToDataSourceName(shardingKey);            switch (shardingOperator){                case ShardingOperatorEnum.Equal: return tail => tail == t;                default:{                    return tail => true;}}}}

todoitem text 取模分表

public class TodoTableRoute:AbstractSimpleShardingModKeyStringVirtualTableRoute<TodoItem>{public TodoTableRoute() : base(2, 5){}public override void Configure(EntityMetadataTableBuilder<TodoItem> builder){builder.ShardingProperty(o => o.Text);}}

編寫sqlserver分片遷移腳本生成

public class ShardingSqlServerMigrationsSqlGenerator: SqlServerMigrationsSqlGenerator{private readonly IShardingRuntimeContext _shardingRuntimeContext;public ShardingSqlServerMigrationsSqlGenerator(IShardingRuntimeContext shardingRuntimeContext,[NotNull] MigrationsSqlGeneratorDependencies dependencies, [NotNull] IRelationalAnnotationProvider migrationsAnnotations) : base(dependencies, migrationsAnnotations){_shardingRuntimeContext = shardingRuntimeContext;}protected override void Generate(MigrationOperation operation,IModel model,MigrationCommandListBuilder builder){var oldCmds = builder.GetCommandList().ToList();base.Generate(operation, model, builder);var newCmds = builder.GetCommandList().ToList();var addCmds = newCmds.Where(x => !oldCmds.Contains(x)).ToList();MigrationHelper.Generate(_shardingRuntimeContext,operation, builder, Dependencies.SqlGenerationHelper, addCmds);}}

abp的efcore模塊注入

TodoAppEntityFrameworkCoreModule編寫注入

public class TodoAppEntityFrameworkCoreModule : AbpModule{public static readonly ILoggerFactory efLogger = LoggerFactory.Create(builder =>{builder.AddFilter((category, level) => category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information).AddConsole();});public override void PreConfigureServices(ServiceConfigurationContext context){TodoAppEfCoreEntityExtensionMappings.Configure();}public override void ConfigureServices(ServiceConfigurationContext context){context.Services.AddAbpDbContext<TodoAppDbContext>(options =>{                /* Remove "includeAllEntities: true" to create* default repositories only for aggregate roots */options.AddDefaultRepositories(includeAllEntities: true);});Configure<AbpDbContextOptions>(options =>{                /* The main point to change your DBMS.* See also TodoAppDbContextFactory for EF Core tooling. */options.UseSqlServer();options.Configure<TodoAppDbContext>(innerContext =>{ShardingCoreExtension.UseDefaultSharding<TodoAppDbContext>(innerContext.ServiceProvider, innerContext.DbContextOptions);});});context.Services.AddShardingConfigure<TodoAppDbContext>().UseRouteConfig(op =>{op.AddShardingDataSourceRoute<TodoDataSourceRoute>();op.AddShardingTableRoute<TodoTableRoute>();}).UseConfig((sp, op) =>{                  //var loggerFactory = sp.GetRequiredService<ILoggerFactory>();op.UseShardingQuery((conStr, builder) =>{builder.UseSqlServer(conStr).UseLoggerFactory(efLogger);});op.UseShardingTransaction((connection, builder) =>{builder.UseSqlServer(connection).UseLoggerFactory(efLogger);});op.UseShardingMigrationConfigure(builder =>{builder.ReplaceService<IMigrationsSqlGenerator, ShardingSqlServerMigrationsSqlGenerator>();});op.AddDefaultDataSource("ds0", "Server=.;Database=TodoApp;Trusted_Connection=True");op.AddExtraDataSource(sp =>{                        return new Dictionary<string, string>(){{ "ds1", "Server=.;Database=TodoApp1;Trusted_Connection=True" },{ "ds2", "Server=.;Database=TodoApp2;Trusted_Connection=True" }};});}).AddShardingCore();}public override void OnPostApplicationInitialization(ApplicationInitializationContext context){base.OnPostApplicationInitialization(context);            //創建表的定時任務如果有按年月日系統默認路由的需要系統創建的記得開起來context.ServiceProvider.UseAutoShardingCreate();            //補償表 //自動遷移的話不需要//context.ServiceProvider.UseAutoTryCompensateTable();}}折疊

啟動abp遷移項目

啟動
6aec7ededfefe311d500da4fb1e78b19.png
等待輸出

22fb3f99c4ab73d71a172b6d11362d89.png
72665bfb4882ef06552de1a9debb2527.png

插入todoitem

93e0dda9b0976f8c9d24198b1eaff28b.png

查詢

448956204c3bc7ba84f55dd2d3607398.png

驗證
03ae6a9938ad5292c04fa03d4ab05e5d.png

到此為止我們這邊完成了針對abpvnext的分表分庫+自動化遷移的操作

集成Furion

接下來我們開始集成Furion的操作
首先依舊安裝依賴

添加依賴 ShardingCore 6.6.0.3 MySql

Install-Package Furion -Version 3.7.5
//請安裝最新版本目前x.6.0.5+,第一個版本號6代表efcore的版本號
Install-Package ShardingCore -Version 6.6.0.5Install-Package Pomelo.EntityFrameworkCore.MySql  -Version 6.0.1
Install-Package Microsoft.EntityFrameworkCore.Tools  -Version 6.0.6

新增todoitem

public class TodoItem:IEntity, IEntityTypeBuilder<TodoItem>
{public string Id { get; set; }public string Text { get; set; }public void Configure(EntityTypeBuilder<TodoItem> entityBuilder, DbContext dbContext, Type dbContextLocator){entityBuilder.HasKey(o => o.Id);entityBuilder.Property(o => o.Id).IsRequired().HasMaxLength(50).HasComment("id");entityBuilder.Property(o => o.Text).IsRequired().HasMaxLength(256).HasComment("事情");entityBuilder.ToTable(nameof(TodoItem));}
}

新增帶分片的DbContext和Abp一樣

抽象對象直接看遠嗎,這邊直接新增一個dbcontext

public class MyDbContext : AppShardingDbContext<MyDbContext>,IShardingTableDbContext
{public MyDbContext(DbContextOptions<MyDbContext> options) : base(options){}public IRouteTail RouteTail { get; set; }
}

新增分表分庫路由

新增分庫路由

public class TodoItemDataSourceRoute:AbstractShardingOperatorVirtualDataSourceRoute<TodoItem,string>
{    /// <summary>/// id的hashcode取模余3分庫/// </summary>/// <param name="shardingKey"></param>/// <returns></returns>/// <exception cref="InvalidOperationException"></exception>public override string ShardingKeyToDataSourceName(object shardingKey){        if (shardingKey == null) throw new InvalidOperationException("sharding key cant null");var stringHashCode = ShardingCoreHelper.GetStringHashCode(shardingKey.ToString());        return $"ds{(Math.Abs(stringHashCode) % 3)}";//ds0,ds1,ds2}private readonly List<string> _dataSources = new List<string>() { "ds0", "ds1", "ds2" };public override List<string> GetAllDataSourceNames(){        return _dataSources;}public override bool AddDataSourceName(string dataSourceName){throw new NotImplementedException();}    /// <summary>/// id分庫/// </summary>/// <param name="builder"></param>public override void Configure(EntityMetadataDataSourceBuilder<TodoItem> builder){builder.ShardingProperty(o => o.Id);}public override Func<string, bool> GetRouteToFilter(string shardingKey, ShardingOperatorEnum shardingOperator){var t = ShardingKeyToDataSourceName(shardingKey);        switch (shardingOperator){            case ShardingOperatorEnum.Equal: return tail => tail == t;            default:{                return tail => true;}}}
}折疊

新增分表路由

public class TodoItemTableRoute:AbstractSimpleShardingModKeyStringVirtualTableRoute<TodoItem>
{public TodoItemTableRoute() : base(2, 3){}    /// <summary>/// 正常情況下不會用內容來做分片鍵因為作為分片鍵有個前提就是不會被修改/// </summary>/// <param name="builder"></param>public override void Configure(EntityMetadataTableBuilder<TodoItem> builder){builder.ShardingProperty(o => o.Text);}
}

編寫遷移文件

using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure.Internal;
using Pomelo.EntityFrameworkCore.MySql.Migrations;
using ShardingCore.Core.RuntimeContexts;
using ShardingCore.Helpers;namespace TodoApp;public class ShardingMySqlMigrationsSqlGenerator:MySqlMigrationsSqlGenerator
{private readonly IShardingRuntimeContext _shardingRuntimeContext;public ShardingMySqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, IRelationalAnnotationProvider annotationProvider, IMySqlOptions options,IShardingRuntimeContext shardingRuntimeContext) : base(dependencies, annotationProvider, options){_shardingRuntimeContext = shardingRuntimeContext;}protected override void Generate(MigrationOperation operation,IModel model,MigrationCommandListBuilder builder){var oldCmds = builder.GetCommandList().ToList();base.Generate(operation, model, builder);var newCmds = builder.GetCommandList().ToList();var addCmds = newCmds.Where(x => !oldCmds.Contains(x)).ToList();MigrationHelper.Generate(_shardingRuntimeContext,operation, builder, Dependencies.SqlGenerationHelper, addCmds);}
}

啟動注入

這邊簡單看了一下furion貌似沒有提供Func<IServiceProvider,DbContextOptionBuilder>efcore注入方式所以這邊不得已采用靜態方式,
如果采用靜態的方式需要實現一個接口IDbContextCreator

//靜態創建IShardingRuntimeContextpublic class ShardingCoreProvider{private static ILoggerFactory efLogger = LoggerFactory.Create(builder =>{builder.AddFilter((category, level) => category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information).AddConsole();});private static readonly IShardingRuntimeContext instance;public static IShardingRuntimeContext ShardingRuntimeContext => instance;    static ShardingCoreProvider(){instance=new ShardingRuntimeBuilder<MyDbContext>().UseRouteConfig(op =>{op.AddShardingTableRoute<TodoItemTableRoute>();op.AddShardingDataSourceRoute<TodoItemDataSourceRoute>();}).UseConfig((sp,op) =>{op.UseShardingQuery((con, b) =>{b.UseMySql(con, new MySqlServerVersion(new Version())).UseLoggerFactory(efLogger);});op.UseShardingTransaction((con, b) =>{b.UseMySql(con, new MySqlServerVersion(new Version())).UseLoggerFactory(efLogger);});op.AddDefaultDataSource("ds0", "server=127.0.0.1;port=3306;database=furion0;userid=root;password=root;");op.AddExtraDataSource(sp=>new Dictionary<string, string>(){{"ds1", "server=127.0.0.1;port=3306;database=furion1;userid=root;password=root;"},{"ds2", "server=127.0.0.1;port=3306;database=furion2;userid=root;password=root;"}});op.UseShardingMigrationConfigure(b =>{b.ReplaceService<IMigrationsSqlGenerator, ShardingMySqlMigrationsSqlGenerator>();});}).ReplaceService<IDbContextCreator, CustomerDbContextCreator>(ServiceLifetime.Singleton).Build();}
}//啟動服務public class ShardingCoreComponent:IServiceComponent
{public void Load(IServiceCollection services, ComponentContext componentContext){services.AddControllers();services.AddEndpointsApiExplorer();services.AddSwaggerGen();services.AddDatabaseAccessor(options =>{            // 配置默認數據庫options.AddDb<MyDbContext>(o =>{o.UseDefaultSharding<MyDbContext>(ShardingCoreProvider.ShardingRuntimeContext);});});        //依賴注入services.AddSingleton<IShardingRuntimeContext>(sp => ShardingCoreProvider.ShardingRuntimeContext);}
}
public class CustomerDbContextCreator:ActivatorDbContextCreator<MyDbContext>
{public override DbContext GetShellDbContext(IShardingProvider shardingProvider){var dbContextOptionsBuilder = new DbContextOptionsBuilder<MyDbContext>();dbContextOptionsBuilder.UseDefaultSharding<MyDbContext>(ShardingCoreProvider.ShardingRuntimeContext);        return new MyDbContext(dbContextOptionsBuilder.Options);}
}
public class UseShardingCoreComponent:IApplicationComponent
{public void Load(IApplicationBuilder app, IWebHostEnvironment env, ComponentContext componentContext){        //......app.ApplicationServices.UseAutoShardingCreate();var serviceProvider = app.ApplicationServices;using (var scope = app.ApplicationServices.CreateScope()){var defaultShardingDbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();            if (defaultShardingDbContext.Database.GetPendingMigrations().Any()){defaultShardingDbContext.Database.Migrate();}}        // app.Services.UseAutoTryCompensateTable();}
}//Programusing TodoApp;Serve.Run(RunOptions.Default.AddComponent<ShardingCoreComponent>().UseComponent<UseShardingCoreComponent>());折疊

添加遷移文件

e01c945f9bacb24eaeb6a38209581b91.png

啟動

2e16709673ef761ebbf4e17f99df572d.png

增刪改查
f6daf9e7915fc20072874740cebc71ba.png

fe3dcf5e5e47679b777fe460e45056aa.png

集成WTM

之前也有一次繼承過之后也有因為遷移過于麻煩所以這邊ShardingCore出了更加完善遷移方案并且使用起來code-first更加無感

添加依賴

添加依賴 ShardingCore 6.6.0.3 MySql

//請安裝最新版本目前x.6.0.5+,第一個版本號6代表efcore的版本號
Install-Package ShardingCore -Version 6.6.0.5
Install-Package Microsoft.EntityFrameworkCore.Tools -Version 6.0.6

新增分表分庫路由

//分庫路由public class TodoDataSourceRoute:AbstractShardingOperatorVirtualDataSourceRoute<Todo,string>
{    /// <summary>/// id的hashcode取模余3分庫/// </summary>/// <param name="shardingKey"></param>/// <returns></returns>/// <exception cref="InvalidOperationException"></exception>public override string ShardingKeyToDataSourceName(object shardingKey){        if (shardingKey == null) throw new InvalidOperationException("sharding key cant null");var stringHashCode = ShardingCoreHelper.GetStringHashCode(shardingKey.ToString());        return $"ds{(Math.Abs(stringHashCode) % 3)}";//ds0,ds1,ds2}private readonly List<string> _dataSources = new List<string>() { "ds0", "ds1", "ds2" };public override List<string> GetAllDataSourceNames(){        return _dataSources;}public override bool AddDataSourceName(string dataSourceName){throw new NotImplementedException();}    /// <summary>/// id分庫/// </summary>/// <param name="builder"></param>public override void Configure(EntityMetadataDataSourceBuilder<Todo> builder){builder.ShardingProperty(o => o.Id);}public override Func<string, bool> GetRouteToFilter(string shardingKey, ShardingOperatorEnum shardingOperator){var t = ShardingKeyToDataSourceName(shardingKey);        switch (shardingOperator){            case ShardingOperatorEnum.Equal: return tail => tail == t;            default:{                return tail => true;}}}
}//分表路由public class TodoTableRoute:AbstractSimpleShardingModKeyStringVirtualTableRoute<Todo>
{public TodoTableRoute() : base(2, 3){}    /// <summary>/// 正常情況下不會用內容來做分片鍵因為作為分片鍵有個前提就是不會被修改/// </summary>/// <param name="builder"></param>public override void Configure(EntityMetadataTableBuilder<Todo> builder){builder.ShardingProperty(o => o.Name);}
}折疊

創建DbContextCreator

public class WTMDbContextCreator:IDbContextCreator
{public DbContext CreateDbContext(DbContext shellDbContext, ShardingDbContextOptions shardingDbContextOptions){var context = new DataContext((DbContextOptions<DataContext>)shardingDbContextOptions.DbContextOptions);context.RouteTail = shardingDbContextOptions.RouteTail;        return context;}public DbContext GetShellDbContext(IShardingProvider shardingProvider){var dbContextOptionsBuilder = new DbContextOptionsBuilder<DataContext>();dbContextOptionsBuilder.UseDefaultSharding<DataContext>(ShardingCoreProvider.ShardingRuntimeContext);        return new DataContext(dbContextOptionsBuilder.Options);}
}

遷移腳本

public class ShardingMySqlMigrationsSqlGenerator:MySqlMigrationsSqlGenerator
{private readonly IShardingRuntimeContext _shardingRuntimeContext;public ShardingMySqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, IRelationalAnnotationProvider annotationProvider, IMySqlOptions options,IShardingRuntimeContext shardingRuntimeContext) : base(dependencies, annotationProvider, options){_shardingRuntimeContext = shardingRuntimeContext;}protected override void Generate(MigrationOperation operation,IModel model,MigrationCommandListBuilder builder){var oldCmds = builder.GetCommandList().ToList();base.Generate(operation, model, builder);var newCmds = builder.GetCommandList().ToList();var addCmds = newCmds.Where(x => !oldCmds.Contains(x)).ToList();MigrationHelper.Generate(_shardingRuntimeContext,operation, builder, Dependencies.SqlGenerationHelper, addCmds);}
}

靜態構造IShardingRuntimeContext

因為WTM在創建dbcontext并不是通過依賴注入創建的而是由其余的內部實現所以為了兼容我們這邊只能通過靜態IShardingRuntimeContext注入

public class ShardingCoreProvider{private static ILoggerFactory efLogger = LoggerFactory.Create(builder =>{builder.AddFilter((category, level) => category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information).AddConsole();});private static readonly IShardingRuntimeContext instance;public static IShardingRuntimeContext ShardingRuntimeContext => instance;    static ShardingCoreProvider(){instance=new ShardingRuntimeBuilder<DataContext>().UseRouteConfig(op =>{op.AddShardingTableRoute<TodoRoute>();op.AddShardingDataSourceRoute<TodoDataSourceRoute>();}).UseConfig((sp,op) =>{op.UseShardingQuery((con, b) =>{b.UseMySql(con, new MySqlServerVersion(new Version())).UseLoggerFactory(efLogger);});op.UseShardingTransaction((con, b) =>{b.UseMySql(con, new MySqlServerVersion(new Version())).UseLoggerFactory(efLogger);});op.AddDefaultDataSource("ds0", "server=127.0.0.1;port=3306;database=wtm0;userid=root;password=root;");op.AddExtraDataSource(sp=>new Dictionary<string, string>(){{"ds1", "server=127.0.0.1;port=3306;database=wtm1;userid=root;password=root;"},{"ds2", "server=127.0.0.1;port=3306;database=wtm2;userid=root;password=root;"}});op.UseShardingMigrationConfigure(b =>{b.ReplaceService<IMigrationsSqlGenerator, ShardingMySqlMigrationsSqlGenerator>();});}).ReplaceService<IDbContextCreator, WTMDbContextCreator>(ServiceLifetime.Singleton).Build();}
}折疊

創建抽象分片DbContext

因為過于長所以這邊只顯示主要部分其余通過demo查看

public abstract class AbstractShardingFrameworkContext:FrameworkContext, IShardingDbContext, ISupportShardingReadWrite{protected IShardingDbContextExecutor ShardingDbContextExecutor{get;}public AbstractShardingFrameworkContext(CS cs): base(cs){ShardingDbContextExecutor =new ShardingDbContextExecutor(this);IsExecutor = false;}public AbstractShardingFrameworkContext(string cs, DBTypeEnum dbtype): base(cs, dbtype){ShardingDbContextExecutor =new ShardingDbContextExecutor(this);IsExecutor = false;}public AbstractShardingFrameworkContext(string cs, DBTypeEnum dbtype, string version = null): base(cs, dbtype, version){ShardingDbContextExecutor =new ShardingDbContextExecutor(this);IsExecutor = false;}public AbstractShardingFrameworkContext(DbContextOptions options) : base(options){var wrapOptionsExtension = options.FindExtension<ShardingWrapOptionsExtension>();            if (wrapOptionsExtension != null){ShardingDbContextExecutor =new ShardingDbContextExecutor(this);;}IsExecutor = wrapOptionsExtension == null;}protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){            if (this.CSName!=null){base.OnConfiguring(optionsBuilder);optionsBuilder.UseDefaultSharding<DataContext>(ShardingCoreProvider.ShardingRuntimeContext);}}public DbContext GetDbContext(string dataSourceName, CreateDbContextStrategyEnum strategy, IRouteTail routeTail){            return ShardingDbContextExecutor.CreateDbContext(strategy, dataSourceName, routeTail);}
}折疊

修改dbcontext

public class DataContextFactory : IDesignTimeDbContextFactory<DataContext>{public DataContext CreateDbContext(string[] args){var virtualDataSource = ShardingCoreProvider.ShardingRuntimeContext.GetVirtualDataSource();var defaultConnectionString = virtualDataSource.DefaultConnectionString;            return new DataContext(defaultConnectionString, DBTypeEnum.MySql);}}

注入ShardingCore

移除掉了之前的多余代碼

public void ConfigureServices(IServiceCollection services){            //....services.AddSingleton<IShardingRuntimeContext>(sp => ShardingCoreProvider.ShardingRuntimeContext);}public void Configure(IApplicationBuilder app, IOptionsMonitor<Configs> configs){IconFontsHelper.GenerateIconFont();            // using (var scope = app.ApplicationServices.CreateScope())// {//     var requiredService = scope.ServiceProvider.GetRequiredService<WTMContext>();//     var requiredServiceDc = requiredService.DC;// }//定時任務app.ApplicationServices.UseAutoShardingCreate();using (var dbconContext=new DataContextFactory().CreateDbContext(new string[0])){dbconContext.Database.Migrate();}            //補齊表防止iis之類的休眠導致按天按月的表沒有新建//app.ApplicationServices.UseAutoTryCompensateTable();//....}

遷移

45b4dacb89fea4e0edcb7371ae5cd41b.png

啟動程序

1a601f1e9ddc21d4d90b3068617f123d.png

crud

45b1ef7a235dd3d8929a5eb375eb9234.png

最后的最后

(ShardingWithFrameWork)[https://github.com/xuejmnet/ShardingWithFramework]?https://github.com/xuejmnet/ShardingWithFramework

您都看到這邊了確定不點個star或者贊嗎,一款.Net不得不學的分庫分表解決方案,簡單理解為sharding-jdbc在.net中的實現并且支持更多特性和更優秀的數據聚合,擁有原生性能的97%,并且無業務侵入性,支持未分片的所有efcore原生查詢

  • github地址?https://github.com/xuejmnet/sharding-core

  • gitee地址?https://gitee.com/dotnetchina/sharding-core

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/285092.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/285092.shtml
英文地址,請注明出處:http://en.pswp.cn/news/285092.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Hadoop日常管理與維護

本文描述了hadoop、hbase的啟動關閉、表操作以及權限管理。一、Hadoop服務的啟動與關閉1、啟動使用hadoop以及hbase自帶的腳本進行啟動&#xff0c;先啟動hadoop個服務&#xff0c;再啟動hbase服務。 hadoopbdi:~$ start-dfs.sh hadoopbdi:~$ start-yarn.sh hadoopbdi:~$ start…

Mathematica修改默認字體

1. 打開Option Inspector 2. 第一個下拉框選擇Global Preference, 搜索stylehints 3. 修改字體為想要換的字體FamilyName, 比如換成蘋果黑體 SimHei, 字體FamilyName自行研究 4. 效果 轉載于:https://www.cnblogs.com/dabaopku/p/6221960.html

基于JavaScript 數組的經典程序應用源碼(強烈建議收藏)

文章目錄設計一個數組輸入并顯示的程序。數組輸入和顯示選擇排序選擇排序排序程序包排序網頁楊輝三角形楊輝三角形網頁C語言畫一個sin(x)的曲線螺旋線訪問二維數組JavaScript數組的定義、使用都是非常簡單的&#xff0c;僅僅定義的話&#xff0c;就使用&#xff1a; var anew …

C語言試題169之誰家孩子跑得最慢

??個人主頁:個人主頁 ??系列專欄:C語言試題200例 ??推薦一款刷算法、筆試、面經、拿大公司offer神器?? 點擊跳轉進入網站 ?作者簡介:大家好,我是碼莎拉蒂,CSDN博客專家(全站排名Top 50),阿里云博客專家、51CTO博客專家、華為云享專家 1、題目 題目:張、王、…

【ArcGIS微課1000例】0008:ArcGIS中如何設置相對路徑?(解決圖層前紅色的感嘆號)

文章目錄 問題舉例問題分析解決辦法問題舉例 我們在使用ArcGIS時經常會碰到這樣的問題:將地圖文檔(.mxd)保存到本地并拷貝到別的電腦上或改變一個路徑時,出現數據丟失的現象,具體表現為圖層前面出現一個紅色的感嘆號,如下圖所示。 出現以上問題的根本原因是數據GSS.tif的…

IPC-----消息隊列

消息隊列&#xff08;報文隊列&#xff09;:兩個進程間通過發送數據塊的形式進行通信。一個進程把需要發送的消息通過一個函數發送到消息隊列中&#xff0c;另一個進程再從消息隊列中讀取該消息。函數&#xff1a;# include <sys/types.h># include <sys/ipc.h>key…

[轉]OKR結合CFR的管理模式

讀前預 無論任何管理書籍&#xff0c;都是圍繞著人性&#xff0c;如果激發員工的人性中的自尊和自我價值觀、自我成就感。 作為一名領導者&#xff0c;在管理前&#xff0c;必須要是冷靜&#xff0c;安靜的對待他人 約翰杜爾為谷歌送上大禮 “好主意”再加上”卓越的執行”&…

NetCore 國際化最佳實踐

NetCore 國際化最佳實踐ASP.NET Core中提供了一些本地化服務和中間件&#xff0c;可將網站本地化為不同的語言文化。ASP.NET Core中我們可以使用Microsoft.AspNetCore.Localization庫來實現本地化。但是默認只支持使用資源文件方式做多語言存儲&#xff0c;很難在實際場景中使用…

復分析——第1章——復分析準備知識(E.M. Stein R. Shakarchi)

第一章 復分析準備知識 (Preliminaries to Complex Analysis) The sweeping development of mathematics during the last two centuries is due in large part to the introduction of complex numbers; paradoxically, this is based on the seemingly absurd no…

網構軟件-Internetware

網構軟件&#xff0c;973項目提的一個概念。概念。作為一種新型軟件形態&#xff0c;它應該涵蓋哪些研究范疇&#xff1f;評價它成功的基本標準是什么&#xff1f;我們看待一件事物&#xff0c;首先都會從已知的認知空間里尋找與之對應者。那么&#xff0c;能與網構相對應的大概…

List集合的remove一個對象的方法

import java.util.ArrayList;import java.util.List;class A{   public boolean equals(Object obj){     return true;   }} public class ListTest2 { public static void main(String[] args) {   // TODO Auto-generated method stub     System.out.println…

【ArcGIS微課1000例】0009:ArcGIS影像拼接(鑲嵌、鑲嵌至新柵格)

本課程以 DEM數據為例,講述ArcGIS中影像的拼接方法及注意事項。 文章目錄 方法一:Mosaic工具方法二:Mosaic To New Raster工具實驗數據下載觀察兩個數據,接邊處切合效果很好。 方法一:Mosaic工具 使用Mosaic工具要千萬注意,Mosaic的結果是將輸入柵格追加到目標柵格上,…

Python:file (read,readline,readline )使用方法

Python讀取文件時&#xff0c;在使用readlin、readlines時會有疑惑&#xff0c;下面給大家詳解&#xff1a;一、例&#xff1a;a.txt的內容為 aaa 123 bbb 456二、首先我先設置個變量&#xff1a; a"a.txt" cfile(a)三、此時我們分別看下使用read、readl…

C語言試題170之矩陣轉置

??個人主頁:個人主頁 ??系列專欄:C語言試題200例 ??推薦一款刷算法、筆試、面經、拿大公司offer神器?? 點擊跳轉進入網站 ?作者簡介:大家好,我是碼莎拉蒂,CSDN博客專家(全站排名Top 50),阿里云博客專家、51CTO博客專家、華為云享專家 1、題目 題目:設有一矩…

【ArcGIS微課1000例】0010:ArcGIS影像裁剪(裁剪、掩膜提取)

文章目錄 裁剪方法方法一:Extract By Mask(按掩膜提取)方法二:Clip(裁剪)數據下載裁剪方法 方法一:Extract By Mask(按掩膜提取) 加載配套的實驗數據,運行Extract By Mask(按掩膜提取)工具,參數設置如下: 掩膜提取結果: 方法二:Clip(裁剪) 加載配套的實驗…

阿里創新自動化測試工具平臺--Doom

背景 信息系統上線后通常會需要迭代升級甚至重構&#xff0c;如何確保系統原有業務的正確性非常重要。曾經有一家叫瑞穗證券的證券公司因為一個系統bug導致了數億美金的損失&#xff0c;賠掉了公司一年的利潤。這樣的極端例子雖然少見&#xff0c;但是卻像達摩克利斯之劍警示著…

《微信讀書》自定義樣式

一直用微信讀書看書&#xff0c;但是微信讀書的Web版的布局不太喜歡。 重寫下它的樣式再加上單擊關閉工具欄&#xff0c;這樣看著舒服多了^_^&#xff1a; /*浮動工具欄*/ document.getElementsByClassName("readerControls")[0].style.left"0"; document.…

內核態和用戶態的區別

當一個任務&#xff08;進程&#xff09;執行系統調用而陷入內核代碼中執行時&#xff0c;我們就稱進程處于內核運行態&#xff08;或簡稱為內核態&#xff09;。此時處理器處于特權級最高的&#xff08;0級&#xff09;內核代碼中執行。當進程處于內核態時&#xff0c;執行的內…

C# NanoFramework WIFI掃描、連接和HttpWebRequest 之 ESP32

可喜可賀新板子終于到了&#xff0c;啥也不說&#xff0c;趕緊搞起來。ESP32的主要功能就是WIFI&#xff0c;有了WIFI &#xff0c;就可以直接連接家里的WIFI路由器啥的&#xff0c;直接連接到外網了&#xff0c;這個時候&#xff0c;它就相當于是一個小型的電腦或手機&#xf…

【ArcGIS微課1000例】0011:ArcGIS空間查詢(按位置選擇Select by Location)完全案例詳解

利用按位置選擇工具,您可以根據要素相對于另一圖層要素的位置來進行選擇。例如,如果您想了解最近的洪水影響了多少家庭,那么可以選擇該洪水邊界內的所有家庭。 您可使用多種選擇方法,選擇與同一圖層或其他圖層中的要素接近或重疊的點、線或面要素。 文章目錄 1、點—點查詢…