.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.mzph.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 …

【ArcGIS微课1000例】0008:ArcGIS中如何设置相对路径?(解决图层前红色的感叹号)

文章目录 问题举例问题分析解决办法问题举例 我们在使用ArcGIS时经常会碰到这样的问题:将地图文档(.mxd)保存到本地并拷贝到别的电脑上或改变一个路径时,出现数据丢失的现象,具体表现为图层前面出现一个红色的感叹号,如下图所示。 出现以上问题的根本原因是数据GSS.tif的…

[转]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…

【ArcGIS微课1000例】0009:ArcGIS影像拼接(镶嵌、镶嵌至新栅格)

本课程以 DEM数据为例,讲述ArcGIS中影像的拼接方法及注意事项。 文章目录 方法一:Mosaic工具方法二:Mosaic To New Raster工具实验数据下载观察两个数据,接边处切合效果很好。 方法一:Mosaic工具 使用Mosaic工具要千万注意,Mosaic的结果是将输入栅格追加到目标栅格上,…

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.…

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、点—点查询…

[转]收集android上开源的酷炫的交互动画和视觉效果:Interactive-animation

原文链接&#xff1a;http://www.open-open.com/lib/view/open1411443332703.html 描述&#xff1a;收集android上开源的酷炫的交互动画和视觉效果。 1.交互篇 2.视觉篇 交互篇 1.SlidingUpPanelLayout 项目介绍&#xff1a;他的库提供了一种简单的方式来添加一个可拖动滑动面板…

EntityFramework用法探索(三)CodeFirst流畅API

Code First Fluent API&#xff0c;使用流畅API来定义模型映射。 同样使用与上文 Database First 模式相同的例子&#xff0c;假设需要设计一个零售系统&#xff0c;我们先构建一个 Customer 类。 1 public class Customer 2 { 3 public long Id { get; set; } 4 p…

js浏览器对象模型(BOM)

浏览器对象模型(Browser Object Model,BOM)&#xff1a;浏览器为js提供的对象集合。 1 windows对象 windows对象&#xff1a;表示浏览器的框架以及与其相关的内容&#xff0c;比如滚动条和导航栏图标等等。或者说windows对象就代表浏览器本身。windows对象是个全局对象&#xf…

Hello Playwright:(8)等待页面加载

在我们前面的文章中&#xff0c;我们始终使用await page.GotoAsync(url);加载页面&#xff0c;我们的目的是等待足够长的时间让页面上的元素出现。但是&#xff0c;我们更希望永远不要因为等待浪费任何时间。WaitUntilGotoAsync方法的定义如下&#xff1a;Task<IResponse?&…

【ArcGIS微课1000例】0012:ArcGIS创建及连接ArcSDE企业级地理数据库实例

文章目录 实验材料实验过程创建企业级数据库连接企业级数据库创建要素类要素入库实验材料 ArcGIS:版本为10.2Server.ecp:版本为10.2SQL Server:版本为2008实验过程 创建企业级数据库 企业级地理数据库的创建需要通过工具箱来实现。 数据库平台:SQL Server 实例:localhos…

C语言试题172之实现冒泡排序算法

📃个人主页:个人主页 🔥系列专栏:C语言试题200例 💬推荐一款刷算法、笔试、面经、拿大公司offer神器👉 点击跳转进入网站 ✅作者简介:大家好,我是码莎拉蒂,CSDN博客专家(全站排名Top 50),阿里云博客专家、51CTO博客专家、华为云享专家 1、题目 题目:实现冒泡…