.NET 分表分库动态化处理

介绍

本期主角:ShardingCore 一款ef-core下高性能、轻量级针对分表分库读写分离的解决方案,具有零依赖、零学习成本、零业务代码入侵

我不是efcore怎么办

这边肯定有小伙伴要问有没有不是efcore的,我这边很确信的和你讲有并且适应所有的ADO.NET包括sqlhelper
ShardingConnector 一款基于ado.net下的高性能分表分库解决方案目前已有demo案例,这个框架你可以认为是.Net版本的ShardingSphere但是目前仅实现了ShardingSphere-JDBC,后续我将会实现ShardingSphere-Proxy希望各位.Neter多多关注

背景

最近有个小伙伴来问我,分表下他有一批数据,这个数据是白天可能会相对比较频繁数据录入,但是到了晚上可能基本上就没有对应的数据了,因为看到了我的框架,本来想以按小时来实现分表但是这么以来可能会导致一天有24张表,表多的情况下还导致了数据分布不均匀,这是一个很严重的问题因为可能以24小时制会让8-17这几张白天的表数据很多,但是晚上和凌晨的表基本没有数据,没有数据其实意味着这些表其实不需要去查询,基于这个情况想来问我应该如何实现这个自定义的路由。

听了他的需求,其实我这边又进行了一次确认,针对这个场景更多的其实是这个小伙伴需要的是按需分片,实时建表,来保证需要的数据进行合理的插入,那么我们应该如何在ShardingCore下实现这么一个需求呢,废话不多说直接开始吧~~~

创建项目

本次需求我们以mysql作为测试数据库,然后使用efcore6作为数据库驱动orm来实现怎么处理才能达到这个效果的分表分库(本次只涉及分表)。

新建一个项目

添加依赖

//请安装最新版本第一个版本号6代表efcore的版本号
Install-Package ShardingCore -Version 6.4.3.4Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 6.0.1

新建一个对象表,配置对应的数据库映射关系并且关联到dbcontext

//创建数据库对象public class OrderByHour{public string Id { get; set; }public DateTime CreateTime { get; set; }public string Name { get; set; }}
//映射对象结构到数据库public class OrderByHourMap:IEntityTypeConfiguration<OrderByHour>{public void Configure(EntityTypeBuilder<OrderByHour> builder){builder.HasKey(o => o.Id);builder.Property(o => o.Id).IsRequired().HasMaxLength(50);builder.Property(o => o.Name).IsRequired().HasMaxLength(128);builder.ToTable(nameof(OrderByHour));}}
//创建dbcontext为efcore所用上下文public class DefaultDbContext:AbstractShardingDbContext,IShardingTableDbContext{public DefaultDbContext(DbContextOptions<DefaultDbContext> options) : base(options){}protected override void OnModelCreating(ModelBuilder modelBuilder){base.OnModelCreating(modelBuilder);modelBuilder.ApplyConfiguration(new OrderByHourMap());}public IRouteTail RouteTail { get; set; }}

到这边其实只需要启动时候依赖注入

services.AddDbContext<DefaultDbContext>(o=>o.UseMySql(xxxx));

那么efcore就可以运行了,这么一看其实并没有很复杂而且IEntityTypeConfiguration也不是必须的,efcore允许使用attribute来实现
当然DefaultDbContext:AbstractShardingDbContext,IShardingTableDbContext这一部分在原生efcore中应该是DefaultDbContext:DbContext

创建分片路由

首先我们来看一下ShardingCore针对分片路由的自定义情况的分析,通过文档我们可以了解到,如果想要使用自定义路由那么你只需要自己新建一个路由并且继承实现AbstractShardingOperatorVirtualTableRoute,当然这是分表的,分库是AbstractShardingOperatorVirtualDataSourceRoute.

接下来我们新建一个路由并且实现分表操作。

public class orderByHourRoute : AbstractShardingOperatorVirtualTableRoute<OrderByHour, DateTime>{public override string ShardingKeyToTail(object shardingKey){throw new NotImplementedException();}public override List<string> GetAllTails(){throw new NotImplementedException();}public override void Configure(EntityMetadataTableBuilder<OrderByHour> builder){throw new NotImplementedException();}public override Expression<Func<string, bool>> GetRouteToFilter(DateTime shardingKey, ShardingOperatorEnum shardingOperator){throw new NotImplementedException();}}

接下来我们依次来实现并且说明各个接口。

  • ShardingKeyToTail:将你的对象转成数据库的后缀尾巴,比如你是按月分片,那么你的分片值大概率是datetime,那么只需要datetime.ToString("yyyyMM")就可以获取到分片后缀

  • GetAllTails:返回集合,集合是数据库现有的当前表的所有后缀,仅程序启动时被调用,这个接口就是需要你返回当前数据库中当前表在系统里面有多少张表,然后返回这些表的后缀

  • Configure:配置当前对象按什么字段分片

  • GetRouteToFilter:因为ShardingCore内存有当前所有表的后缀,假设后缀为list集合,返回的Expression<Func<string, bool>>在经过AndOr后的组合进行Compile(),然后对list.Where(expression.Compile()).ToList()就可以返回对应的本次查询的后缀信息

废话不多说针对这个条件我们直接开始操作完成路由的实现

路由的编写

1.ShardingKeyToTail:因为我们是按小时分表所以格式化值后缀我们采用日期格式化

//因为分片建是DateTime类型所以直接强转public override string ShardingKeyToTail(object shardingKey){var dateTime = (DateTime)shardingKey;return ShardingKeyFormat(dateTime);}private string ShardingKeyFormat(DateTime dateTime){var tail = $"{dateTime:yyyyMMddHH}";return tail;}

2.Configure:分表配置

public override void Configure(EntityMetadataTableBuilder<OrderByHour> builder){builder.ShardingProperty(o => o.CreateTime);}

3.GetRouteToFilter:路由比较,因为是时间字符串的后缀具有和按年,按月等相似的属性所以我们直接参考默认路由来实现

public override Expression<Func<string, bool>> GetRouteToFilter(DateTime shardingKey, ShardingOperatorEnum shardingOperator){var t = ShardingKeyFormat(shardingKey);switch (shardingOperator){case ShardingOperatorEnum.GreaterThan:case ShardingOperatorEnum.GreaterThanOrEqual:return tail => String.Compare(tail, t, StringComparison.Ordinal) >= 0;case ShardingOperatorEnum.LessThan:{var currentHourBeginTime = new DateTime(shardingKey.Year,shardingKey.Month,shardingKey.Day,shardingKey.Hour,0,0);//处于临界值 不应该被返回if (currentHourBeginTime == shardingKey)return tail => String.Compare(tail, t, StringComparison.Ordinal) < 0;return tail => String.Compare(tail, t, StringComparison.Ordinal) <= 0;}case ShardingOperatorEnum.LessThanOrEqual:return tail => String.Compare(tail, t, StringComparison.Ordinal) <= 0;case ShardingOperatorEnum.Equal: return tail => tail == t;default:{return tail => true;}}}

4.GetAllTails:比较特殊我们因为并不是连续生成的所以没办法使用起始时间然后一直推到当前时间来实现后缀的返回,只能依靠ado.net的能力读取数据库然后返回对应的表后缀,当然你也可以使用redis等三方工具来存储

//1.构造函数注入 IVirtualDataSourceManager<DefaultDbContext> virtualDataSourceManager//2/mysql的ado.net读取数据库表(sqlserver和mysql有差异自行百度或者查看ShardingCore的SqlServerTableEnsureManager类)private const string CurrentTableName = nameof(OrderByHour);private const string Tables = "Tables";private const string TABLE_SCHEMA = "TABLE_SCHEMA";private const string TABLE_NAME = "TABLE_NAME";private readonly ConcurrentDictionary<string, object?> _tails = new ConcurrentDictionary<string, object?>();/// <summary>/// 如果你是非mysql数据库请自行实现这个方法返回当前类在数据库已经存在的后缀/// 仅启动时调用/// </summary>/// <returns></returns>public override List<string> GetAllTails(){//启动寻找有哪些表后缀using (var connection = new MySqlConnection(_virtualDataSourceManager.GetCurrentVirtualDataSource().DefaultConnectionString)){connection.Open();var database = connection.Database;using (var dataTable = connection.GetSchema(Tables)){for (int i = 0; i < dataTable.Rows.Count; i++){var schema = dataTable.Rows[i][TABLE_SCHEMA];if (database.Equals($"{schema}", StringComparison.OrdinalIgnoreCase)){var tableName = dataTable.Rows[i][TABLE_NAME]?.ToString()??string.Empty;if (tableName.StartsWith(CurrentTableName, StringComparison.OrdinalIgnoreCase)){//如果没有下划线那么需要CurrentTableName.Length有下划线就要CurrentTableName.Length+1_tails.TryAdd(tableName.Substring(CurrentTableName.Length),null);}}}}}return _tails.Keys.ToList();}

动态创建添加表

到目前为止我们已经完成了路由的静态分片的处理,但是还有一点需要处理就是如何在插入值得时候判断当前有没有对应的数据库表是否需要创建等操作

查看AbstractShardingOperatorVirtualTableRoute分表抽象类的父类我们发现当前抽象类有两个地方会调用路由的获取判断方法

  • DoRouteWithPredicate:使用条件路由也就是where后面的表达式

  • RouteWithValue:使用值路由也就是我们的新增和修改整个对象的时候会被调用

所以通过上述流程的梳理我们可以知道只需要在RouteWithValue处进行动手脚即可,又因为我们需要动态建表所以我们可以参考默认路由的自动建表的代码进行参考
AbstractShardingAutoCreateOperatorVirtualTableRoute下的ExecuteAsync

private readonly IVirtualDataSourceManager<DefaultDbContext> _virtualDataSourceManager;private readonly IVirtualTableManager<DefaultDbContext> _virtualTableManager;private readonly IShardingTableCreator<DefaultDbContext> _shardingTableCreator;private readonly ConcurrentDictionary<string, object?> _tails = new ConcurrentDictionary<string, object?>();private readonly object _lock = new object();public OrderByHourRoute(IVirtualDataSourceManager<DefaultDbContext> virtualDataSourceManager,IVirtualTableManager<DefaultDbContext> virtualTableManager, IShardingTableCreator<DefaultDbContext> shardingTableCreator){_virtualDataSourceManager = virtualDataSourceManager;_virtualTableManager = virtualTableManager;_shardingTableCreator = shardingTableCreator;}public override IPhysicTable RouteWithValue(List<IPhysicTable> allPhysicTables, object shardingKey){var shardingKeyToTail = ShardingKeyToTail(shardingKey);if (!_tails.TryGetValue(shardingKeyToTail,out var _)){lock (_lock){if (!_tails.TryGetValue(shardingKeyToTail,out var _)){var virtualTable = _virtualTableManager.GetVirtualTable(typeof(OrderByHour));
//必须先执行AddPhysicTable在进行CreateTable_virtualTableManager.AddPhysicTable(virtualTable, new DefaultPhysicTable(virtualTable, shardingKeyToTail));try{_shardingTableCreator.CreateTable<OrderByHour>(_virtualDataSourceManager.GetCurrentVirtualDataSource().DefaultDataSourceName, shardingKeyToTail);}catch (Exception ex){Console.WriteLine("尝试添加表失败" + ex);}_tails.TryAdd(shardingKeyToTail,null);}}}var needRefresh = allPhysicTables.Count != _tails.Count;if (needRefresh){var virtualTable = _virtualTableManager.GetVirtualTable(typeof(OrderByHour));//修复可能导致迭代器遍历时添加的bugvar keys = _tails.Keys.ToList();foreach (var tail in keys){var hashSet = allPhysicTables.Select(o=>o.Tail).ToHashSet();if (!hashSet.Contains(tail)){var tables = virtualTable.GetAllPhysicTables();var physicTable = tables.FirstOrDefault(o=>o.Tail==tail);if (physicTable!= null){allPhysicTables.Add(physicTable);}}}}var physicTables = allPhysicTables.Where(o => o.Tail== shardingKeyToTail).ToList();if (physicTables.IsEmpty()){throw new ShardingCoreException($"sharding key route not match {EntityMetadata.EntityType} -> [{EntityMetadata.ShardingTableProperty.Name}] ->【{shardingKey}】 all tails ->[{string.Join(",", allPhysicTables.Select(o=>o.FullName))}]");}if (physicTables.Count > 1)throw new ShardingCoreException($"more than one route match table:{string.Join(",", physicTables.Select(o => $"[{o.FullName}]"))}");return physicTables[0];}

通过和父类的比较我们只是在对应的根据值判断当前系统是否存在xx表如果不存在就在ShardingCore上插入AddPhysicTable然后CreateTable最后_tails.TryAdd(shardingKeyToTail,null);

needRefresh处的代码需要针对如果当前需要和传入的全量表进行匹配因为新加的表后缀不在全量表里面所以需要先进行对其的处理然后再进行执行

启动配置必不可少

ILoggerFactory efLogger = LoggerFactory.Create(builder =>
{builder.AddFilter((category, level) => category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information).AddConsole();
});var builder = WebApplication.CreateBuilder(args);// Add services to the container.builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddShardingDbContext<DefaultDbContext>().AddEntityConfig(o =>{o.ThrowIfQueryRouteNotMatch = false;o.CreateShardingTableOnStart = true;o.EnsureCreatedWithOutShardingTable = true;o.AddShardingTableRoute<OrderByHourRoute>();}).AddConfig(o =>{o.ConfigId = "c1";o.AddDefaultDataSource("ds0", "server=127.0.0.1;port=3306;database=shardingTest;userid=root;password=root;");o.UseShardingQuery((conn, b) =>{b.UseMySql(conn, new MySqlServerVersion(new Version())).UseLoggerFactory(efLogger);});o.UseShardingTransaction((conn, b) =>{b.UseMySql(conn, new MySqlServerVersion(new Version())).UseLoggerFactory(efLogger);});o.ReplaceTableEnsureManager(sp=>new MySqlTableEnsureManager<DefaultDbContext>());}).EnsureConfig();
var app = builder.Build();// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{app.UseSwagger();app.UseSwaggerUI();
}
app.Services.GetRequiredService<IShardingBootstrapper>().Start();
app.UseAuthorization();app.MapControllers();app.Run();

最后我们直接启动运行调试代码

fe2d22710dd1aeac725a012be50591c7.png

当我们插入一个没有的时间对应的框架会帮我们对应的创建表并且插入数据
d5b9eaf43c670a63c65c5247d4d9f999.png
这个思路就是可以保证需要的时候就创建表不需要就不创建
598e493964ff1a4f39a650778a48ac59.png

最后的最后

demo地址 https://github.com/dotnetcore/sharding-core/tree/main/samples/Sample.AutoCreateIfPresent

您都看到这边了确定不点个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/286760.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

addEventListener 的事件函数的传递【转载】

addEventListener 参数如下&#xff1a; addEventListener(type, listener[, useCapture]); type&#xff0c;事件名称listener&#xff0c;事件处理器useCapture&#xff0c;是否捕获一直把 listener 记成是响应函数&#xff0c;function 类型。相信很多人也是这么理解的。多数…

Android之elevation实现阴影效果

1 需求 需要控件实现阴影效果 2 实现 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"andr…

十、小程序实战 (IVX 快速开发教程)

十、小程序实战 使用小程序完成一个二手信息站点与 WebApp 实现流程类型&#xff0c;只是部分内容使用了微信小程序特有的组件&#xff0c;例如微信登录与 WebApp 略有差别&#xff0c;其它逻辑实现较为类似。我们先制作页面&#xff0c;之后再实现功能。 由于之前已经完成了…

源代码下载 第六章 注解式控制器详解

2019独角兽企业重金招聘Python工程师标准>>> 源代码请到附件中下载。 其他下载&#xff1a; 跟着开涛学SpringMVC 第一章源代码下载 第二章 Spring MVC入门 源代码下载 Controller接口控制器详解 源代码下载 源码下载——第四章 Controller接口控制器详解——跟着开…

WPF|快速添加新手引导功能(支持MVVM)

阅读导航前言案例一案例二案例三&#xff08;本文介绍的方式&#xff09;如何使用&#xff1f;控件如何开发的&#xff1f;总结1. 前言案例一站长分享过 眾尋 大佬的一篇 WPF 简易新手引导 一文&#xff0c;新手引导的效果挺不错的&#xff0c;如下图&#xff1a;该文给出的代码…

三、界面介绍(IVX快速手册)

三、集成开发环境界面介绍 通过本节你将了解 iVX 在线集成开发环境 界面&#xff0c;快速建立对 在线集成开发环境 的认识。 文章目录三、集成开发环境界面介绍3.1 界面区域3.2 舞台3.3 组件工具栏3.4 对象树/素材面板3.5 属性面板3.6 菜单面板3.7 逻辑工具面板3.8 辅助工具3.…

Android studio之提示Failed to resolve: com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.46

1、错误提示如下 Failed to resolve: com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.46 Show in Project Structure dialog Affected Modules: app2、解决办法 在project的build.gradle里面加入 maven { url https://jitpack.io }

【VB测绘程序设计】第二章 VB测绘程序基础

第一节 数据类型 VB中提供了以下11中基本的数据类型: 一、数值型 二、字符串 三、日期型 1.界面设计 2. 代码 <

React-引领未来的用户界面开发框架-读书笔记(六)

第12章 服务端渲染 想让搜索引擎抓取到你的站点&#xff0c;服务端渲染这一步不可或缺&#xff0c;服务端渲染还可以提升站点的性能&#xff0c;因为在加载JavaScript脚本的同时&#xff0c;浏览器就可以进行页面渲染。 React的虚拟DOM是其可被用于服务端渲染的关键。首先每个R…

TrimPath - Js模板引擎

当页面中引用template.js文件之后&#xff0c;脚本将创建一个TrimPath对象供你使用。 parseDOMTemplate(elementId,optionalDocument)  //获得模板字符串代码 得到页面中Id为elementId的DOM组件的InnerHTML&#xff0c;将其解析成一个模板&#xff0c;这个返回一个templateOb…

一、iVX简介(IVX 快速开发教程)

一、iVX简介 通过本节你将对 iVX 有一个大致的认识&#xff0c;并且了解 iVX 能够做些什么&#xff0c;有哪一些优势&#xff0c;这将帮助你更好的上手 iVX 进行应用的开发&#xff0c;初步了解 iVX 的强大之处。 文章目录一、iVX简介1.1 iVX 是什么&#xff1f;1.2 iVX适合怎…

WPF效果第一百八十六篇之又玩ListBox

大周末的接着上一篇玩耍TreeView,这二天又再次去玩耍ListBox;毕竟是我的最爱,没办法就喜欢玩耍他;闲话也不多扯了,直接看咱们最终效果:2、原来一直ItemTemplate,这次直接ListBoxItem的Template:<Setter Property"Template"><Setter.Value><ControlTem…

【十分钟】学会微信小游戏,攀登不止小游戏制作(IVX 快速开发教程十一)

十一、攀登不止小游戏制作 制作微信小游戏大致流程与微信小程序、Web类似&#xff0c;不同的在于是组件的使用。我们此节需要完成的小游戏需求为&#xff1a; 小球触碰矩形块会跳跃或攀爬小球触碰顶部或底部游戏结束点击屏幕将会使小球朝着该方向移动小球进行跳跃时分数会增加…

十天冲刺---Day8

站立式会议 站立式会议内容总结&#xff1a;燃尽图照片最近思考一个问题。项目是怎么进行到这一步的。算了&#xff0c;这个发在明天的冲刺总结吧。。还需继续努力&#xff0c;队友快回来快回来。。转载于:https://www.cnblogs.com/imguang/p/4965054.html

一篇文学会商用可编辑问卷表单制作【iVX 十二】

公共表单 在 iVX 快速教程中&#xff0c;我们使用一个公共表单项目作为 WebApp 应用的演示说明。公共表单项目可以用于企业内部或一个问卷公共平台做问卷调查&#xff0c;用户可以自由的设置表单元素以及样式&#xff0c;并且可以手动设置表单结束下载填写问卷后的调查数据。 …

【地图学】地图投影的定义和分类

一、地图投影 1、地图投影的定义 地图投影是利用一定数学法则把地球表面的经、纬线转换到平面上的理论和方法。 2、地图投影的分类 (1)按变形性质 • 等角投影: 投影面上两微分线段的夹角与地面上的相应两线段的夹角相等,及没有角度变形的投影叫 ~ 。

React-引领未来的用户界面开发框架-读书笔记(八)

第16章 架构模式 React主要功能在于渲染HTML。可以将其看成是MVC中的V&#xff0c;它不会影响到组件中直接调用AJAX请求之类的操作&#xff1a; var TakeSurveyReact.CreateClass({getInitialData&#xff1a;function(){return{survey:null}&#xff1b;},componentDidMount:…

confluence5.8.10的使用

之前在windows上安装了confluence5.8.10,结果有一天知什么缘故&#xff0c;数据库数据损坏&#xff0c;知识库彻底打不开了&#xff0c;所有的文档都付之东流&#xff0c;真的不是一般心痛。因此考虑将其装到linux机器上&#xff0c;因为tomcat和mysql实际上都为了linux而生的&…

Android之提示Unable to get provider com.google.android.gms.ads.MobileAdsInitProvider

1 问题 接入SDK提示错误如下 java.lang.RuntimeException: Unable to get provider com.google.android.gms.ads.MobileAdsInitProvider: java.lang.IllegalStateException: 2 解决办法 在AndroidManifest.xml文件下面配置如下 在application目录下面配置如下&#xff0c;…

RPA之PAD(Power Automate Desktop)组件开发

本文由网友蓝创精英团队投稿&#xff0c;欢迎转载、分享原文作者&#xff1a;蓝创精英团队原文链接&#xff1a;https://blog.csdn.net/i2blue/article/details/125040323其实&#xff0c;PAD&#xff0c;现在官方文档还没有对外组件式或者插件式开发接口。但是&#xff0c;有一…