ShardingCore官方文档:ShardingCore文档 (xuejmnet.github.io)
调整EFCore-ShardingCore 以下操作步骤均以FoundationTemplate
模板项目做演示,实际操作请根据项目调整
Step1:调整DbContext 将DbContext集成的基类由GCTSharedDbContext
调整为GCTSharedShardingCoreDbContext
GCTSharedShardingCoreDbContext :实现ShardingCore分库分表操作
1 public class FoundationTemplateDbContext : GCTSharedShardingCoreDbContext <FoundationTemplateDbContext >{}
Step2:调整模块类 调整EntityFrameworkCoreModule
模块类注册,取消 默认的DbContext
注册,如下
取消默认的DbContext注册,将以下代码注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public override void PreInitialize ( ){ }
Step3:文件调整 在EntityFrameworkCore
项目 –> 创建ShardingCore
文件夹
1 2 --- ShardingCore --- Routes # 用于存放路由配置类 (或者存放到Core层,也就是各自模块目录)
ShardingCoreExtensions 创建注册ShardingCore服务扩展方法
更多配置请参考:
https://xuejm.gitee.io/sharding-core-doc/guide-upgrade-5-6/#%E7%8E%B0%E5%9C%A8%E7%9A%84startup
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 using GCT.Foundation.ShardingCore;using GCT.Foundation.ShardingCore.Routes;using Microsoft.EntityFrameworkCore.Migrations;using Microsoft.Extensions.DependencyInjection;using ShardingCore;namespace GCT.Foundation.ShardingCore { public static class FoundationTemplateShardingCoreExtensions { public static IServiceCollection ConfigureShardingCore (this IServiceCollection services, string dataSourceName, string connectionString ) { services.AddShardingDbContext<FoundationTemplateDbContext>() .UseRouteConfig((sp, op) => { op.AddShardingTableRoute<OrderVirtualTableRoute>(); op.AddShardingTableRoute<SysUserVirtualTableRoute>(); }).UseConfig(op => { op.UseShardingQuery((connStr, build) => { FoundationTemplateDbContextConfigurer.Configure(build, connStr); }); op.UseShardingTransaction((conn, build) => { FoundationTemplateDbContextConfigurer.Configure(build, conn); }); op.AddDefaultDataSource(dataSourceName, connectionString); op.UseShardingMigrationConfigure(conf => { conf.ReplaceService<IMigrationsSqlGenerator, ShardingSqlServerMigrationsSqlGenerator<FoundationTemplateDbContext>>(); }); }) .AddShardingCore(); return services; } } }
MigrationsSqlGenerator 在ShardingCore 目录下创建ShardingSqlServerMigrationsSqlGenerator.cs
ShardingCore - SqlServer的迁移Sql生成工具类,默认使用SqlServer
支持MySQL、Oracle等其他数据库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 using System.Linq;using Microsoft.EntityFrameworkCore.Metadata;using Microsoft.EntityFrameworkCore.Migrations.Operations;using Microsoft.EntityFrameworkCore.Migrations;using Microsoft.EntityFrameworkCore;using ShardingCore.Core.RuntimeContexts;using ShardingCore.Helpers;using ShardingCore.Sharding.Abstractions;namespace GCT.Foundation.EntityFrameworkCore { public class ShardingSqlServerMigrationsSqlGenerator <TShardingDbContext > : SqlServerMigrationsSqlGenerator where TShardingDbContext : GCTSharedShardingCoreDbContext <TShardingDbContext > { private readonly IShardingRuntimeContext _shardingRuntimeContext; public ShardingSqlServerMigrationsSqlGenerator (MigrationsSqlGeneratorDependencies dependencies, IRelationalAnnotationProvider migrationsAnnotations, IShardingRuntimeContext shardingRuntimeContext ) : 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); } } }
DbContextConfigurer 调整默认的DbContext注册类,移除DbContext限制
1 2 -- public static void Configure(DbContextOptionsBuilder builder, string connectionString) ++ public static void Configure(DbContextOptionsBuilder<FoundationTemplateDbContext> builder, string connectionString)
完整如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 using System.Data.Common;using Microsoft.EntityFrameworkCore;using Microsoft.Extensions.Logging;namespace GCT.Foundation.EntityFrameworkCore { public static class FoundationTemplateDbContextConfigurer { public static readonly ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); }); public static void Configure (DbContextOptionsBuilder builder, string connectionString ) { builder.UseSqlServer(connectionString); ConfigureLog(builder); } public static void Configure (DbContextOptionsBuilder builder, DbConnection connection ) { builder.UseSqlServer(connection); ConfigureLog(builder); } private static void ConfigureLog (DbContextOptionsBuilder builder ) { #if DEBUG builder.UseLoggerFactory(MyLoggerFactory); #endif } } }
DbContextFactory 调整DbContextFactory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 using System;using GCT.Foundation.Configuration;using GCT.Foundation.Helpers;using GCT.Foundation.ShardingCore;using Microsoft.EntityFrameworkCore.Design;using Microsoft.Extensions.DependencyInjection;namespace GCT.Foundation.EntityFrameworkCore { public class FoundationTemplateDbContextFactory : IDesignTimeDbContextFactory <FoundationTemplateDbContext > { private static IServiceProvider _serviceProvider; [Obsolete("Obsolete" ) ] static FoundationTemplateDbContextFactory ( ) { var basePath = FoundationTemplateWebContentDirectoryFinder.CalculateContentRootFolder(); Console.WriteLine("工作目录:" ); Console.WriteLine(basePath); var configuration = AppConfigurations.Get(basePath, "Development" ); var connectionString = configuration.ConnectionStringsDefault(); Console.WriteLine("迁移使用数据库连接字符串:" ); Console.WriteLine(connectionString); var services = new ServiceCollection(); services.ConfigureShardingCore(AppConsts.DefaultDataSourceName, connectionString); _serviceProvider = services.BuildServiceProvider(); } public FoundationTemplateDbContext CreateDbContext (string [] args ) { return _serviceProvider.GetService<FoundationTemplateDbContext>(); } } }
目录结构如下:
注册ShardingCore Step1:Template.Web.Host 注册ShardingCore服务 Startup.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 using GCT.Foundation.ShardingCore;public IServiceProvider ConfigureServices (IServiceCollection services ){ ConfigureHealthCheckService(services); services.ConfigureShardingCore(AppConsts.DefaultDataSourceName, Configuration.GetConnectionString("Default" )); } public void Configure (IApplicationBuilder app ) { UseBasic(app); UseHangfire(app); UseShardingCore(app); } protected virtual void UseShardingCore (IApplicationBuilder app ){ app.ApplicationServices.UseAutoShardingCreate(); }
Step2:Template.Migrator 注册ShardingCore服务 Startup.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using GCT.Foundation.ShardingCore;public void ConfigurationServices (IServiceCollection services ){ var basePath = AppContext.BaseDirectory; var configuration = AppConfigurations.Get(basePath, "Development" ); var connectionString = configuration.ConnectionStringsDefault(); services.AddAppIdentity(); services.ConfigureShardingCore(AppConsts.DefaultDataSourceName, connectionString); services.Add52AbpTypeFinder((options) => { options.AddSkip<FoundationDbContext>(); }); }
分表使用案例
官方文档案例:初始化 | ShardingCore文档 (gitee.io)
创建一个ShardingCore的Demo演示功能,首先在Template.Core
层创建ShardingCoreManagement
目录
先拟定一个场景目前有用户表SysUser
和订单表Order
,再添加一个Setting
配置表
SysUser用户表按用户Age进行取模分表
Order订单表按时间月进行分表
Setting配置表不进行分表
创建实体 Order.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 using System;using Abp.Domain.Entities;namespace GCT.Foundation.ShardingCoreManagement { public class Order : Entity <int > { public int Payer { get ; set ; } public long Money { get ; set ; } public string Area { get ; set ; } public OrderStatusEnum OrderStatus { get ; set ; } public DateTime CreationTime { get ; set ; } } }
OrderStatusEnum.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 namespace GCT.Foundation.ShardingCoreManagement { public enum OrderStatusEnum { NoPay = 1 , Paying = 2 , Payed = 3 , PayFail = 4 } }
Setting.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 using Abp.Domain.Entities;namespace GCT.Foundation.ShardingCoreManagement { public class Setting : Entity <int > { public string Code { get ; set ; } public string Name { get ; set ; } } }
SysUser.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 using Abp.Domain.Entities;namespace GCT.Foundation.ShardingCoreManagement { public class SysUser : Entity <int > { public int Age { get ; set ; } public string Name { get ; set ; } public string SettingCode { get ; set ; } public string Area { get ; set ; } } }
创建领域服务 在ShardingCoreManagement
目录下创建DomainService
目录用于存放领域服务类
OrderManager 1 2 3 4 5 6 7 8 namespace GCT.Foundation.ShardingCoreManagement.DomainService { public interface IOrderManager : I52AbpDomainService <Order , int > { } }
1 2 3 4 5 6 7 8 9 10 11 12 using System;namespace GCT.Foundation.ShardingCoreManagement.DomainService { public class OrderManager : AbpDomainService <Order , int >, IOrderManager { public OrderManager (IServiceProvider serviceProvider ) : base (serviceProvider ) { } } }
SysUserManager 1 2 3 4 5 6 7 namespace GCT.Foundation.ShardingCoreManagement.DomainService { public interface ISysUserManager : I52AbpDomainService <SysUser , int > { } }
1 2 3 4 5 6 7 8 9 10 11 using System;namespace GCT.Foundation.ShardingCoreManagement.DomainService { public class SysUserManager : AbpDomainService <SysUser , int >, ISysUserManager { public SysUserManager (IServiceProvider serviceProvider ) : base (serviceProvider ) { } } }
SettingManager 1 2 3 4 5 6 7 8 namespace GCT.Foundation.ShardingCoreManagement.DomainService { public interface ISettingManager : I52AbpDomainService <Setting , int > { } }
1 2 3 4 5 6 7 8 9 10 11 12 using System;namespace GCT.Foundation.ShardingCoreManagement.DomainService { public class SettingManager : AbpDomainService <Setting , int >, ISettingManager { public SettingManager (IServiceProvider serviceProvider ) : base (serviceProvider ) { } } }
注册实体 和以往一样,在DbContext类 中加入实体注册
在Template.EntityFrameworkCore
的FoundationTemplateDbContext
中注册实体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 using GCT.Foundation.Examples.Entity;using GCT.Foundation.ShardingCoreManagement;using Microsoft.EntityFrameworkCore;namespace GCT.Foundation.EntityFrameworkCore { public class FoundationTemplateDbContext : GCTSharedDbContextShardingCore <FoundationTemplateDbContext > { public FoundationTemplateDbContext (DbContextOptions<FoundationTemplateDbContext> options ) : base (options ) { } public override void ModelCreating (ModelBuilder modelBuilder ) { base .ModelCreating(modelBuilder); modelBuilder.ConfigureGCTShared(); modelBuilder.ConfigureFoundationTemplate(); modelBuilder.UseCollation("Chinese_PRC_CI_AS" ); } public DbSet<BigData> BigData { get ; set ; } public DbSet<Commodity> Commodity { get ; set ; } public DbSet<Order> Order { get ; set ; } public DbSet<Setting> Setting { get ; set ; } public DbSet<SysUser> SysUser { get ; set ; } } }
创建虚拟路由
路由官方文档:默认路由 | ShardingCore文档 (gitee.io)
!!!注意:路由配置类按照约定 统一放在Template.EntityFrameworkCore
层ShardingCore
-> Routes
目录下,
Order订单表路由:OrderVirtualTableRoute.cs
这里实现的基类AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute
按时间分表
该路由为简单的按月分表路由 ,支持分表字段是DateTime
,分表后的后缀为yyyyMM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 using System;using GCT.Foundation.ShardingCoreManagement;using ShardingCore.Core.EntityMetadatas;using ShardingCore.VirtualRoutes.Months;namespace GCT.Foundation.ShardingCore.Routes ;public class OrderVirtualTableRoute : AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute <Order >{ public override void Configure (EntityMetadataTableBuilder<Order> builder ) { builder.ShardingProperty(o => o.CreationTime); } public override bool AutoCreateTableByTime ( ) { return true ; } public override DateTime GetBeginTime ( ) { return new DateTime(2021 , 1 , 1 ); } }
SysUser表路由:SysUserVirtualTableRoute.cs
继承的是Int 类型取模分表路由:AbstractSimpleShardingModKeyIntVirtualTableRoute
该路由为简单的取模hash路由,分表字段是int
类型,接受3个参数,第一个参数表示后缀的位数,第二位表示取模的基数,第三位是取模后缀不足的左补字符。
对应的,如果取模分片字段为String类型,AbstractSimpleShardingModKeyStringVirtualTableRoute
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 using GCT.Foundation.ShardingCoreManagement;using ShardingCore.Core.EntityMetadatas;using ShardingCore.VirtualRoutes.Mods;namespace GCT.Foundation.ShardingCore.Routes { public class SysUserVirtualTableRoute : AbstractSimpleShardingModKeyIntVirtualTableRoute <SysUser > { public SysUserVirtualTableRoute ( ) : base (2 , 3 ) { } public override void Configure (EntityMetadataTableBuilder<SysUser> builder ) { builder.ShardingProperty(o => o.Age); } } }
注册分片路由 在FoundationTemplateShardingCoreExtensions.ConfigureShardingCore
中加入表分片路由配置 ,如下:
FoundationTemplateShardingCoreExtensions.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 using GCT.Foundation.EntityFrameworkCore;using GCT.Foundation.ShardingCore.Routes;using Microsoft.EntityFrameworkCore.Migrations;using Microsoft.Extensions.DependencyInjection;using ShardingCore;namespace GCT.Foundation.ShardingCore { public static class FoundationTemplateShardingCoreExtensions { public static IServiceCollection ConfigureShardingCore (this IServiceCollection services, string dataSourceName, string connectionString ) { services.AddShardingDbContext<FoundationTemplateDbContext>() .UseRouteConfig((sp, op) => { op.AddShardingTableRoute<OrderVirtualTableRoute>(); op.AddShardingTableRoute<SysUserVirtualTableRoute>(); }) return services; } } }
添加种子数据 在Seed/Host目录下创建DefaultOrderBuilder.cs
,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 using System.Collections.Generic;using System;using GCT.Foundation.EntityFrameworkCore;using GCT.Foundation.ShardingCoreManagement;namespace GCT.Foundation.Seed.Host { public class DefaultOrderBuilder { private readonly FoundationTemplateDbContext _context; public DefaultOrderBuilder (FoundationTemplateDbContext context ) { _context = context; } public void Create ( ) { var settings = new List<Setting>(3 ) { new () { Code = "Admin" , Name = "AdminName" }, new () { Code = "User" , Name = "UserName" }, new () { Code = "SuperAdmin" , Name = "SuperAdminName" } }; var users = new List<SysUser>(10 ); for (var i = 0 ; i < 10 ; i++) { var uer = new SysUser() { Age = i, Name = $"MyName{i} " , SettingCode = settings[i % 3 ].Code }; users.Add(uer); } var orders = new List<Order>(300 ); var begin = new DateTime(2021 , 1 , 1 , 3 , 3 , 3 ); for (var i = 0 ; i < 300 ; i++) { var order = new Order() { Payer = i % 10 , Money = 100 + new Random().Next(100 , 3000 ), OrderStatus = (OrderStatusEnum)(i % 4 + 1 ), CreationTime = begin.AddDays(i) }; orders.Add(order); } _context.AddRange(settings); _context.AddRange(users); _context.AddRange(orders); _context.SaveChanges(); } } }
在InitialHostDbBuilder.cs
添加调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 using GCT.Foundation.EntityFrameworkCore;namespace GCT.Foundation.Seed.Host { public class InitialHostDbBuilder { private readonly FoundationTemplateDbContext _context; public InitialHostDbBuilder (FoundationTemplateDbContext context ) { _context = context; } public void Create ( ) { new DefaultOrderBuilder(_context).Create(); _context.SaveChanges(); } } }
创建迁移 按正常步骤执行迁移
1 2 Add-Migration Use-Sharding Update-Database
分表后如下:
CRUD
增删改查除了查询稍微在分表+排序的情况下需要注意其实其他操作和efcore基本上一致
查询示例 在Template.Application
层创建ShardingCoreTestAppService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using Abp.Domain.Repositories;using GCT.Foundation.ShardingCoreManagement;using GCT.Foundation.ShardingCoreManagement.DomainService;using Microsoft.AspNetCore.Mvc;using Microsoft.EntityFrameworkCore;using Newtonsoft.Json;namespace GCT.Foundation.ShardingCoreTest { public class ShardingCoreTestAppService : FoundationTemplateAppServiceBase { private readonly OrderManager _orderManager; private readonly SysUserManager _sysUserManager; private readonly SettingManager _settingManager; public ShardingCoreTestAppService (OrderManager orderManager, SysUserManager sysUserManager, SettingManager settingManager ) { _orderManager = orderManager; _sysUserManager = sysUserManager; _settingManager = settingManager; } } }
单对象查询 官方文档单对象查询 | ShardingCore文档 (gitee.io)
和efcore原生查询没有任何区别,支持未分表下的所有操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public async Task<Order> GetOrder ( ){ var order = await _orderManager.QueryAsNoTracking.FirstOrDefaultAsync(o => o.Id == 3 ); return order; } public async Task<List<Order>> GetOrderList(){ var dateTime = new DateTime(2021 , 3 , 5 ); var orders = await _orderManager.QueryAsNoTracking.Where(x => x.CreationTime >= dateTime).ToListAsync(); return orders; }
连表查询
默认不推荐include
,分表对象+分表、分表对象+分表对象都是在ShardingCore内被支持的,支持的形式为join, 默认推荐join组合而不是include
分表join不分表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [HttpGet ] public async Task<string > QueryJoin ( ){ var query = from user in _sysUserManager.QueryAsNoTracking.Where(o => o.Id == 1 || o.Id == 6 ) join setting in _settingManager.QueryAsNoTracking on user.SettingCode equals setting.Code select new { user.Id, user.Name, user.Area, user.SettingCode, SettingName = setting.Name, }; await query.ToListAsync(); return JsonConvert.SerializeObject(query); }
分表join分表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [HttpGet ] public async Task<string > QueryJoin2 ( ){ var begin = new DateTime(2021 , 3 , 2 ); var end = new DateTime(2021 , 4 , 3 ); var sql1 = from user in _sysUserManager.QueryAsNoTracking.Where(o => o.Id == 1 || o.Id == 6 ) join order in _orderManager.QueryAsNoTracking.Where(o => o.CreationTime >= begin && o.CreationTime <= end) on user.Id equals order.Payer select new { user.Id, user.Name, user.Area, user.SettingCode, OrderId = order.Id, order.Payer, order.CreationTime }; return JsonConvert.SerializeObject(await sql1.ToListAsync()); }
修改示例 自动追踪修改 追踪情况下sharding-core依然可以对结果进行修改,修改的字段是被修改过后的字段
1 2 3 4 5 6 public async Task UpdateUser ( ){ var sysUser = await _sysUserManager.Query.FirstOrDefaultAsync(x => x.Id == 1 ); sysUser.Name = "new Name" ; await _sysUserManager.Update(sysUser); }
非自动追踪修改 非追踪情况下sharding-croe也支持查询修改,但是修改的字段是全字段
1 2 3 4 5 6 public async Task UpdateUserAsNoTracking ( ){ var sysUser = await _sysUserManager.QueryAsNoTracking.FirstOrDefaultAsync(x => x.Id == 1 ); sysUser.Name = "new Name" ; await _sysUserManager.Update(sysUser); }
删除示例 下面操作会将所有分表中id==7
的数据删除
1 2 3 4 public async Task DeleteUser ( ){ await _sysUserManager.Delete(x => x.Id == 7 ); }
注意事项
sharding table key:Id is not ValueGenerated.Never
分片是获取插入数据的分片键,然后通过路由插入到具体的数据库,分片键不可以为自增id,必须是没有生成策略的
使用外键报错
建议在使用外键的时候如果是主表也是分表的情况下最好的情况就是取消外键
ShardingCore 多租户 当前分库分表情况:
非多租户模式,全部通顺,功能正常
多租户模式,不支持,需要修改实现完成功能
ShardingCore的设计时如何支持多租户模式
IVirtualDataSourceManager ,虚拟数据源管理,存储数据库连接字符串配置
添加数据源配置
DynamicShardingHelper.DynamicAppendVirtualDataSourceConfig(new SqlShardingConfiguration(shardingTenantOptions));
切换数据源配置
virtualDataSourceManager.CreateScope(数据源标识)
如何结合Abp工作单元
加载查询当前数据库中的租户数据配置
创建工作单元时,指定租户id(数据源标识)
当前工作单元切换租户时,指定租户id(数据源标识)