翼度科技»论坛 编程开发 .net 查看内容

ABP VNext 8 + MySQL 数据分表

4

主题

4

帖子

12

积分

新手上路

Rank: 1

积分
12
项目使用ABP框架,最近有需求数据量会持续变大,需要分表存储。
发现ShardinfCore可以快速实现EF分表操作,并且作者@薛家明还特别为ABP集成写了教程,完美的选择。
ShardinfCore作者教程很齐全,这次以ABP 8.*的用户视角进行集成记录,希望帮到需要的人。
开发环境:
ABP VNext 8.1.5 + EF 8.0.4 + ShardinfCore 7.8.1.21 + Mysql 8.2.0
新同学注意区分ABP和ABP VNext,本文用的是这个:ABP.IO - Modern ASP.NET Core Web Application Platform | ABP.IO
参考资料:
Abp VNext分表分库,拒绝手动,我们要happy coding - 薛家明 - 博客园 (cnblogs.com)
.Net下极限生产力之efcore分表分库全自动化迁移CodeFirst - 薛家明 - 博客园 (cnblogs.com)
ABP EF CORE 7 集成ShardingCore实现分表 - cnblogsName - 博客园
 

集成操作 

添加依赖包
  1. // 只需要在YouProjectName.EntityFrameworkCore模块中安装依赖ShardinfCore 7.8.1.21
  2. dotnet add package ShardingCore --version 7.8.1.21
复制代码
 定义分表类型接口

ABP使用Guid作为主键,但ShardinfCore分表键不能接受空值,需要做一些预处理,会在下面AbstractShardingAbpDbContext的CheckAndSetShardingKeyThatSupportAutoCreate方法中编写逻辑,可以根据分表类型反射属性进行预处理。
在YouProjectName.Domain.Shared模块中,添加2个Interface:
IShardingKeyIsCreationTime:根据创建时间分表
IShardingKeyIsGuId:根据ID分表
需要分表的Entity,按需继承类型接口
  1. /// <summary>
  2. /// 分表键是Guid类型,用于分表时继承使用
  3. /// </summary>
  4. public interface IShardingKeyIsGuId
  5. {
  6. }
  7. /// <summary>
  8. /// 分表键是CreationTime类型,用于分表时继承使用
  9. /// </summary>
  10. public interface IShardingKeyIsCreationTime
  11. {
  12. }
复制代码
 
 
封装抽象类集成IShardingDbContext接口实现分表能力

在YouProjectName.EntityFrameworkCore模块中,添加一个抽象类,用于继承AbpDbContext和IShardingDbContext
  1. using Microsoft.EntityFrameworkCore;
  2. using ShardingCore.Core.VirtualRoutes.TableRoutes.RouteTails.Abstractions;
  3. using ShardingCore.Extensions;
  4. using ShardingCore.Sharding.Abstractions;
  5. using System;
  6. using System.ComponentModel.DataAnnotations.Schema;
  7. using System.Threading.Tasks;
  8. using Volo.Abp.Domain.Entities;
  9. using Volo.Abp.EntityFrameworkCore;
  10. using Volo.Abp.Reflection;
  11. namespace YourProjectName.EntityFrameworkCore;
  12. /// <summary>
  13. /// 继承sharding-core接口
  14. /// 封装实现抽象类
  15. /// </summary>
  16. /// <typeparam name="TDbContext"></typeparam>
  17. public abstract class AbstractShardingAbpDbContext<TDbContext> : AbpDbContext<TDbContext>, IShardingDbContext
  18.                                 where TDbContext : DbContext
  19. {
  20.     private bool _createExecutor = false;
  21.     protected AbstractShardingAbpDbContext(DbContextOptions<TDbContext> options) : base(options)
  22.     {
  23.     }
  24.     private IShardingDbContextExecutor _shardingDbContextExecutor;
  25.     public IShardingDbContextExecutor GetShardingExecutor()
  26.     {
  27.         if (!_createExecutor)
  28.         {
  29.             _shardingDbContextExecutor = this.DoCreateShardingDbContextExecutor();
  30.             _createExecutor = true;
  31.         }
  32.         return _shardingDbContextExecutor;
  33.     }
  34.     private IShardingDbContextExecutor DoCreateShardingDbContextExecutor()
  35.     {
  36.         var shardingDbContextExecutor = this.CreateShardingDbContextExecutor();
  37.         if (shardingDbContextExecutor != null)
  38.         {
  39.             shardingDbContextExecutor.EntityCreateDbContextBefore += (sender, args) =>
  40.             {
  41.                 CheckAndSetShardingKeyThatSupportAutoCreate(args.Entity);
  42.             };
  43.             shardingDbContextExecutor.CreateDbContextAfter += (sender, args) =>
  44.             {
  45.                 var dbContext = args.DbContext;
  46.                 if (dbContext is AbpDbContext<TDbContext> abpDbContext && abpDbContext.LazyServiceProvider == null)
  47.                 {
  48.                     abpDbContext.LazyServiceProvider = this.LazyServiceProvider;
  49.                     if (dbContext is IAbpEfCoreDbContext abpEfCoreDbContext && this.UnitOfWorkManager.Current != null)
  50.                     {
  51.                         abpEfCoreDbContext.Initialize(
  52.                             new AbpEfCoreDbContextInitializationContext(
  53.                                 this.UnitOfWorkManager.Current
  54.                             )
  55.                         );
  56.                     }
  57.                 }
  58.             };
  59.         }
  60.         return shardingDbContextExecutor;
  61.     }
  62.     private void CheckAndSetShardingKeyThatSupportAutoCreate<TEntity>(TEntity entity) where TEntity : class
  63.     {
  64.         if (entity is IShardingKeyIsGuId)
  65.         {
  66.             if (entity is IEntity<Guid> guidEntity)
  67.             {
  68.                 if (guidEntity.Id != default)
  69.                 {
  70.                     return;
  71.                 }
  72.                 var idProperty = entity.GetObjectProperty(nameof(IEntity<Guid>.Id));
  73.                 var dbGeneratedAttr = ReflectionHelper
  74.                     .GetSingleAttributeOrDefault<DatabaseGeneratedAttribute>(
  75.                         idProperty
  76.                     );
  77.                 if (dbGeneratedAttr != null && dbGeneratedAttr.DatabaseGeneratedOption != DatabaseGeneratedOption.None)
  78.                 {
  79.                     return;
  80.                 }
  81.                 EntityHelper.TrySetId(
  82.                     guidEntity,
  83.                     () => GuidGenerator.Create(),
  84.                     true
  85.                 );
  86.             }
  87.         }
  88.         else if (entity is IShardingKeyIsCreationTime)
  89.         {
  90.             AuditPropertySetter?.SetCreationProperties(entity);
  91.         }
  92.     }
  93.     /// <summary>
  94.     /// abp 5.x+ 如果存在并发字段那么需要添加这段代码<br>   /// 种子数据需要
  95.     /// </summary>
  96.     protected override void HandlePropertiesBeforeSave()
  97.     {
  98.         if (GetShardingExecutor() == null)
  99.         {
  100.             base.HandlePropertiesBeforeSave();
  101.         }
  102.     }
  103.     public IRouteTail RouteTail { get; set; }
  104.     public override void Dispose()
  105.     {
  106.         _shardingDbContextExecutor?.Dispose();
  107.         base.Dispose();
  108.     }
  109.     public override async ValueTask DisposeAsync()
  110.     {
  111.         if (_shardingDbContextExecutor != null)
  112.         {
  113.             await _shardingDbContextExecutor.DisposeAsync();
  114.         }
  115.         await base.DisposeAsync();
  116.     }
  117. }
复制代码
 

改造原DbContext

打开YouProjectNameDbContext.cs文件,继承刚刚添加的AbstractShardingAbpDbContext和IShardingTableDbContext
  1. using Microsoft.EntityFrameworkCore;
  2. using Volo.Abp.AuditLogging.EntityFrameworkCore;
  3. using Volo.Abp.BackgroundJobs.EntityFrameworkCore;
  4. using Volo.Abp.Data;
  5. using Volo.Abp.DependencyInjection;
  6. using Volo.Abp.EntityFrameworkCore;
  7. using Volo.Abp.EntityFrameworkCore.Modeling;
  8. using Volo.Abp.FeatureManagement.EntityFrameworkCore;
  9. using Volo.Abp.Identity;
  10. using Volo.Abp.Identity.EntityFrameworkCore;
  11. using Volo.Abp.OpenIddict.EntityFrameworkCore;
  12. using Volo.Abp.PermissionManagement.EntityFrameworkCore;
  13. using Volo.Abp.SettingManagement.EntityFrameworkCore;
  14. using Volo.Abp.TenantManagement;
  15. using Volo.Abp.TenantManagement.EntityFrameworkCore;
  16. using ShardingCore.Sharding.Abstractions;
  17. namespace YourProjectName.EntityFrameworkCore;
  18. [ReplaceDbContext(typeof(IIdentityDbContext))]
  19. [ReplaceDbContext(typeof(ITenantManagementDbContext))]
  20. [ConnectionStringName("Default")]
  21. public class YourProjectNameDbContext :
  22.     AbstractShardingAbpDbContext<YourProjectNameDbContext>,
  23.     IIdentityDbContext,
  24.     ITenantManagementDbContext,
  25.     IShardingTableDbContext //如果dbcontext需要实现分表功能必须实现IShardingTableDbContext
  26. {
  27.   // 原工程的代码,内容不用变...   
  28. }
复制代码
 
添加ShardingMigrationsSqlGenerator类

作用是在执行Migrator程序自动迁移时,可以创建分表结构。
在YouProjectName.EntityFrameworkCore模块中新建一个文件ShardingMigrationsSqlGenerator.cs
  1. using System;
  2. using System.Linq;
  3. using JetBrains.Annotations;
  4. using Microsoft.EntityFrameworkCore.Metadata;
  5. using Microsoft.EntityFrameworkCore.Migrations;
  6. using Microsoft.EntityFrameworkCore.Migrations.Operations;
  7. using Microsoft.EntityFrameworkCore.Update;
  8. using Pomelo.EntityFrameworkCore.MySql.Infrastructure.Internal;
  9. using Pomelo.EntityFrameworkCore.MySql.Migrations;
  10. using ShardingCore.Core.RuntimeContexts;
  11. using ShardingCore.Helpers;
  12. namespace YouProjectName.EntityFrameworkCore;
  13. public class ShardingMigrationsSqlGenerator : MySqlMigrationsSqlGenerator
  14. {
  15.     private readonly IShardingRuntimeContext _shardingRuntimeContext;
  16.     public ShardingMigrationsSqlGenerator(IShardingRuntimeContext shardingRuntimeContext, MigrationsSqlGeneratorDependencies dependencies, ICommandBatchPreparer commandBatchPreparer, IMySqlOptions options) : base(dependencies, commandBatchPreparer, options)
  17.     {
  18.         _shardingRuntimeContext = shardingRuntimeContext;
  19.     }
  20.     protected override void Generate(
  21.         MigrationOperation operation,
  22.         IModel model,
  23.         MigrationCommandListBuilder builder)
  24.     {
  25.         var oldCmds = builder.GetCommandList().ToList();
  26.         base.Generate(operation, model, builder);
  27.         var newCmds = builder.GetCommandList().ToList();
  28.         var addCmds = newCmds.Where(x => !oldCmds.Contains(x)).ToList();
  29.         MigrationHelper.Generate(_shardingRuntimeContext, operation, builder, Dependencies.SqlGenerationHelper, addCmds);
  30.     }
  31. }
复制代码
 
编写分表路由规则Routes

在YouProjectName.EntityFrameworkCore模块中,新建Routes文件夹
新建路由规则类,告诉分表组件按照规则进行分表 
按ID分表,需要继承AbstractSimpleShardingModKeyStringVirtualTableRoute
  1. using YouProjectName.MsgAudits;
  2. using ShardingCore.Core.EntityMetadatas;
  3. using ShardingCore.VirtualRoutes.Mods;
  4. namespace YouProjectName.Routes;
  5. /// <summary>
  6. /// 根据ID分表
  7. /// </summary>
  8. public class KeywordTableRoute : AbstractSimpleShardingModKeyStringVirtualTableRoute<Keyword>
  9. {
  10.     /// <summary>
  11.     /// 简单说明就是表后缀是2位,分3个表,例00,01,02
  12.     /// </summary>
  13.     public KeywordTableRoute() : base(2, 3)
  14.     {
  15.     }
  16.     public override void Configure(EntityMetadataTableBuilder<Keyword> builder)
  17.     {
  18.         //告诉框架通过Id字段分表
  19.         builder.ShardingProperty(o => o.Id);
  20.     }
  21. }
复制代码
 
按时间月份分表,需要继承AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute
  1. using System;
  2. using YouProjectName.MsgAudits;
  3. using ShardingCore.Core.EntityMetadatas;
  4. using ShardingCore.VirtualRoutes.Months;
  5. namespace YouProjectName.Routes;
  6. /// <summary>
  7. /// 设置ChatRecord表的分表路由规则
  8. /// 根据时间月份分表
  9. /// </summary>
  10. public class ChatRecordTableRoute : AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute<ChatRecord>
  11. {
  12.     public override bool AutoCreateTableByTime() => true;
  13.     public override void Configure(EntityMetadataTableBuilder<ChatRecord> builder)
  14.     {
  15.         //告诉框架通过哪个字段分表,消息最好是按消息时间分表
  16.         builder.ShardingProperty(o => o.MsgTime);
  17.         //builder.ShardingProperty(o=>o.CreationTime); // 可以选择按创建时间分表
  18.     }
  19.     public override DateTime GetBeginTime()
  20.     {
  21.         //如果按消息时间分表,那这里应该返回最早消息的时间
  22.         return new DateTime(2023, 01, 01);
  23.     }
  24. }
复制代码
 
 添加ShardinfCore 配置

 在YouProjectName.EntityFrameworkCore模块中,编辑YouProjectNameEntityFrameworkCoreModule.cs,修改ConfigureServices方法添加配置
  1. using Microsoft.Extensions.DependencyInjection;
  2. using Volo.Abp.AuditLogging.EntityFrameworkCore;
  3. using Volo.Abp.BackgroundJobs.EntityFrameworkCore;
  4. using Volo.Abp.EntityFrameworkCore;
  5. using Volo.Abp.EntityFrameworkCore.MySQL;
  6. using Volo.Abp.FeatureManagement.EntityFrameworkCore;
  7. using Volo.Abp.Identity.EntityFrameworkCore;
  8. using Volo.Abp.Modularity;
  9. using Volo.Abp.OpenIddict.EntityFrameworkCore;
  10. using Volo.Abp.PermissionManagement.EntityFrameworkCore;
  11. using Volo.Abp.SettingManagement.EntityFrameworkCore;
  12. using Volo.Abp.TenantManagement.EntityFrameworkCore;
  13. using Volo.Abp.EntityFrameworkCore.DependencyInjection;
  14. using Microsoft.EntityFrameworkCore;using ShardingCore;
  15. using YouProjectName.Routes;
  16. using Microsoft.EntityFrameworkCore.Migrations;
  17. using Microsoft.Extensions.Configuration;<br><br>-----------------引用参考⬆️--------------------------<br><br>
  18. public override void ConfigureServices(ServiceConfigurationContext context)
  19.     {
  20.         var configuration = context.Services.GetConfiguration();
  21.         context.Services.AddAbpDbContext<YouProjectNameDbContext>(options =>
  22.         {
  23.             /* Remove "includeAllEntities: true" to create
  24.              * default repositories only for aggregate roots */
  25.             options.AddDefaultRepositories(includeAllEntities: true);
  26.         });
  27.         Configure<AbpDbContextOptions>(options =>
  28.         {
  29.             /* The main point to change your DBMS.
  30.              * See also YouProjectNameMigrationsDbContextFactory for EF Core tooling. */
  31.             options.UseMySQL();
  32.             //分表组件增加配置
  33.             options.Configure<YouProjectNameDbContext>(innerContext =>
  34.             {
  35.                 ShardingCoreExtension.UseDefaultSharding<YouProjectNameDbContext>(innerContext.ServiceProvider, innerContext.DbContextOptions);
  36.             });
  37.         });
  38.         //分表组件单独配置内容
  39.         context.Services.AddShardingConfigure<YouProjectNameDbContext>()
  40.             .UseRouteConfig(op =>
  41.             {
  42.                 // op.AddShardingDataSourceRoute<TodoDataSourceRoute>(); //分库规则,这次不包含
  43.                 op.AddShardingTableRoute<ChatRecordTableRoute>(); //分表规则,单表添加
  44.                 op.AddShardingTableRoute<ExternalContactTableRoute>();
  45.                 op.AddShardingTableRoute<KeywordTableRoute>();
  46.             })
  47.             .UseConfig((sp, op) =>
  48.             {
  49.                 //var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
  50.                 op.UseShardingQuery((conStr, builder) =>
  51.                 {
  52.                     builder.UseMySql(conStr, MySqlServerVersion.LatestSupportedServerVersion);
  53.                 });
  54.                 op.UseShardingTransaction((connection, builder) =>
  55.                 {
  56.                     builder.UseMySql(connection, MySqlServerVersion.LatestSupportedServerVersion);
  57.                 });
  58.                 op.UseShardingMigrationConfigure(builder =>
  59.                 {
  60.                     builder.ReplaceService<IMigrationsSqlGenerator, ShardingMigrationsSqlGenerator>();
  61.                 });
  62.                 op.AddDefaultDataSource("ds0", configuration.GetConnectionString("Default"));
  63.             })
  64.             .AddShardingCore();
  65.         // 你的其它代码...
  66. }
复制代码
 
 
使用DbMigrator创建数据库结构

到此所有的配置都完成了,尝试分表是否生效。
清空Migrations文件夹在YouProjectName.EntityFrameworkCore模块中。
执行你的YouProjectName.DbMigrator,查看表结构是否符合预期。(DbMigrator会自动生成初始化迁移)
注意:不要使用“dotnet ef database update”这种方式更新数据库,分表不会正确创建。
注意:分表不能作为其它表的外键表。
注意:不能使用Include方式操作数据,如果使用改造为Join方式查询。
注意:如果DbMigrator执行报错,查看日志排查。提醒检查是否有Entity包含Entity的属性,EF会自动创建外键映射。
 
最后添加数据验证是否符合预期
来源:https://www.cnblogs.com/kaili/p/18346556
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具