efcore如何优雅的实现按年分库按月分表
efcore如何优雅的实现按年分库按月分表
介绍
本文ShardinfCore版本
本期主角:
ShardingCore 一款ef-core下高性能、轻量级针对分表分库读写分离的解决方案,具有零依赖、零学习成本、零业务代码入侵适配
距离上次发文.net相关的已经有很久了,期间一直在从事java相关的工作,一不小心就卷了一个java的orm。easy-query 如果有.net相关小伙伴转java可以关注一下也算是打一波小广告。
这次发文主要是在期间有多名用户咨询分库分表相关的事宜,因为我之前并没有针对按年分库按月分表的demo实现,所以本次我打算借着这个机会对该框架进行一次讲解
说明
很多小伙伴我发现不会写GetRouteFilter这个方法不知道是什么意思
那么我们这边做一个很简单的案例
var tails = new List<string>();
tails.Add("202401");
tails.Add("202402");
tails.Add("202403");
tails.Add("202404");
DateTime shardingKey=new DateTime(2024,2,1);
var t = $"{shardingKey:yyyy.MM}";
Func<string, bool> filter = tail => tail.CompareTo(t) > 0;
var list = tails.Where(filter).ToList();
//如果上面的你会写那么下面的你会写吗,无非是上面全部是大于号而实际我们需要根据用户判断来确定应该返回什么
public override Func<string, bool> GetRouteToFilter(DateTime shardingKey, ShardingOperatorEnum shardingOperator)
{
var t = $"{shardingKey:yyyy.MM}";
switch (shardingOperator)
{
case ShardingOperatorEnum.GreaterThan:
case ShardingOperatorEnum.GreaterThanOrEqual:
return tail => String.Compare(tail, t, StringComparison.Ordinal) >= 0;
case ShardingOperatorEnum.LessThan:
{
var currentMonth = ShardingCoreHelper.GetCurrentMonthFirstDay(shardingKey);
//处于临界值 o=>o.time < [2021-01-01 00:00:00] 尾巴20210101不应该被返回
if (currentMonth == 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;
}
}
}
步骤1
安装nuget

efcore架构
新建用户订单根据订单的创建时间年份进行分库月份进行分表
public class OrderItem
{
/// <summary>
/// 用户Id
/// </summary>
public string Id { get; set; }
/// <summary>
/// 购买用户
/// </summary>
public string User { get; set; }
/// <summary>
/// 付款金额
/// </summary>
public decimal PayAmount { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; }
}
//数据库访问上下文
public class TestDbContext:AbstractShardingDbContext,IShardingTableDbContext
{
public DbSet<OrderItem> OrderItems { get; set; }
public TestDbContext(DbContextOptions<TestDbContext> options) : base(options)
{
}
public IRouteTail RouteTail { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<OrderItem>()
.HasKey(o => o.Id);
modelBuilder.Entity<OrderItem>()
.ToTable(nameof(OrderItem));
}
}
//分库路由
public class OrderItemDataSourceRoute:AbstractShardingOperatorVirtualDataSourceRoute<OrderItem,DateTime>
{
private readonly ConcurrentBag<string> dataSources = new ConcurrentBag<string>();
private readonly object _lock = new object();
public override string ShardingKeyToDataSourceName(object shardingKey)
{
return $"{shardingKey:yyyy}";//年份作为分库数据源名称
}
public override List<string> GetAllDataSourceNames()
{
return dataSources.ToList();
}
public override bool AddDataSourceName(string dataSourceName)
{
var acquire = Monitor.TryEnter(_lock, TimeSpan.FromSeconds(3));
if (!acquire)
{
return false;
}
try
{
var contains = dataSources.Contains(dataSourceName);
if (!contains)
{
dataSources.Add(dataSourceName);
return true;
}
}
finally
{
Monitor.Exit(_lock);
}
return false;
}
public override void Configure(EntityMetadataDataSourceBuilder<OrderItem> builder)
{
builder.ShardingProperty(o => o.CreateTime);
}
/// <summary>
/// tail就是2020,2021,2022,2023 所以分片只需要格式化年就可以直接比较了
/// </summary>
/// <param name="shardingKey"></param>
/// <param name="shardingOperator"></param>
/// <returns></returns>
public override Func<string, bool> GetRouteToFilter(DateTime shardingKey, ShardingOperatorEnum shardingOperator)
{
var t = $"{shardingKey:yyyyy}";
switch (shardingOperator)
{
case ShardingOperatorEnum.GreaterThan:
case ShardingOperatorEnum.GreaterThanOrEqual:
return tail => String.Compare(tail, t, StringComparison.Ordinal) >= 0;
case ShardingOperatorEnum.LessThan:
{
var currentYear =new DateTime(shardingKey.Year,1,1);
//处于临界值 o=>o.time < [2021-01-01 00:00:00] 尾巴20210101不应该被返回
if (currentYear == 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;
}
}
}
}
//分表路由
public class OrderItemTableRoute:AbstractShardingOperatorVirtualTableRoute<OrderItem,DateTime>
{
private readonly List<string> allTails = Enumerable.Range(1, 12).Select(o => o.ToString().PadLeft(2, '0')).ToList();
public override string ShardingKeyToTail(object shardingKey)
{
var time = Convert.ToDateTime(shardingKey);
return $"{time:MM}";//01,02.....11,12
}
public override List<string> GetTails()
{
return allTails;
}
public override void Configure(EntityMetadataTableBuilder<OrderItem> builder)
{
builder.ShardingProperty(o => o.CreateTime);
}
//注意这边必须将忽略数据源改成false
//注意这边必须将忽略数据源改成false
//注意这边必须将忽略数据源改成false
protected override bool RouteIgnoreDataSource => false;
//RouteIgnoreDataSource为false的时候那么tail就不是01,02......11,12了而是2021.01,2021.02.....会在tail里面带上数据源,就可以对齐进行筛选了
//如果你的数据源带了其他特殊标识请自行处理
public override Func<string, bool> GetRouteToFilter(DateTime shardingKey, ShardingOperatorEnum shardingOperator)
{
var t = $"{shardingKey:yyyyy.MM}";
switch (shardingOperator)
{
case ShardingOperatorEnum.GreaterThan:
case ShardingOperatorEnum.GreaterThanOrEqual:
return tail => String.Compare(tail, t, StringComparison.Ordinal) >= 0;
case ShardingOperatorEnum.LessThan:
{
var currentMonth = ShardingCoreHelper.GetCurrentMonthFirstDay(shardingKey);
//处于临界值 o=>o.time < [2021-01-01 00:00:00] 尾巴20210101不应该被返回
if (currentMonth == 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;
}
}
}
}
startUp配置
ILoggerFactory efLogger = LoggerFactory.Create(builder =>
{
builder.AddFilter((category, level) => category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Debug).AddConsole();
});
builder.Services.AddShardingDbContext<TestDbContext>()
.UseRouteConfig(o =>
{
o.AddShardingDataSourceRoute<OrderItemDataSourceRoute>();
o.AddShardingTableRoute<OrderItemTableRoute>();
})
.UseConfig((sp, o) =>
{
o.ThrowIfQueryRouteNotMatch = false;
// var redisConfig = sp.GetService<RedisConfig>();
// o.AddDefaultDataSource(redisConfig.Default, redisConfig.DefaultConn);
// //redisConfig.ExtraConfigs
// o.AddExtraDataSource();
o.AddDefaultDataSource("2024", "server=127.0.0.1;port=3306;database=sd2024;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);
});
}).AddShardingCore();
startUp初始化
//初始化额外表
var shardingRuntimeContext = app.Services.GetService<IShardingRuntimeContext<TestDbContext>>();
var dataSourceRouteManager = shardingRuntimeContext.GetDataSourceRouteManager();
var virtualDataSourceRoute = dataSourceRouteManager.GetRoute(typeof(OrderItem));
virtualDataSourceRoute.AddDataSourceName("2024");
virtualDataSourceRoute.AddDataSourceName("2023");
virtualDataSourceRoute.AddDataSourceName("2022");
DynamicShardingHelper.DynamicAppendDataSource(shardingRuntimeContext,"2023","server=127.0.0.1;port=3306;database=sd2023;userid=root;password=root;",false,false);
DynamicShardingHelper.DynamicAppendDataSource(shardingRuntimeContext,"2022","server=127.0.0.1;port=3306;database=sd2022;userid=root;password=root;",false,false);
using (var scope = app.Services.CreateScope())
{
var testDbContext = scope.ServiceProvider.GetService<TestDbContext>();
testDbContext.Database.EnsureCreated();
}
app.Services.UseAutoTryCompensateTable();
编写控制器
public async Task<IActionResult> Init()
{
var orderItems = new List<OrderItem>();
var dateTime = new DateTime(2022,1,1);
var end = new DateTime(2025,1,1);
int i = 0;
while (dateTime < end)
{
orderItems.Add(new OrderItem()
{
Id = i.ToString(),
User = "用户"+i.ToString(),
PayAmount=i,
CreateTime = dateTime,
});
i++;
dateTime = dateTime.AddDays(15);
}
await _testDbContext.OrderItems.AddRangeAsync(orderItems);
await _testDbContext.SaveChangesAsync();
return Ok("hello world");
}
public async Task<IActionResult> Query([FromQuery]int current)
{
var dateTime = new DateTime(2023,1,1);
var shardingPagedResult = await _testDbContext.OrderItems
.Where(o => o.CreateTime > dateTime)
.OrderBy(o=>o.CreateTime)
.ToShardingPageAsync(current, 20);
return Ok(shardingPagedResult);
}
初始化接口


查询

通过断点我们可以清晰地看到路由里面的2022年数据已经被彻底排除仅有2023和2024年的数据


后续

通过观察控制台我们看到了它打印了非常多的sql因为这边并没有对排序进行一个优化具体可以观看我的前几期文章内容做一个CreateEntityQueryConfiguration
分库路由和分表路由都需要进行编写CreateEntityQueryConfiguration
最后的最后
附上demo:ShardingYearDataBaseMonthTable https://github.com/xuejmnet/ShardingYearDataBaseMonthTable
您都看到这边了确定不点个star或者赞吗,一款.Net不得不学的分库分表解决方案,简单理解为分库分表技术在.net中的实现并且支持更多特性和更优秀的数据聚合,拥有原生性能的97%,并且无业务侵入性,支持未分片的所有efcore原生查询
- github地址 https://github.com/xuejmnet/sharding-core
- gitee地址 https://gitee.com/dotnetchina/sharding-core
efcore如何优雅的实现按年分库按月分表的更多相关文章
- MySQL分库备份与分表备份
MySQL分库备份与分表备份 1.分库备份 要求:将mysql数据库中的用户数据库备份,备份的数据库文件以时间命名 脚本内容如下: [root@db01 scripts]# vim backup_da ...
- Mysql数据库分库备份,分表备份
分库备份 #!/bin/sh DBPATH=/server/backup MYUSER=root MYPASS=oldboy123 SOCKET=/data/3306/mysql.sock MYCMD ...
- EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?
下面用一篇文章来完成这些事情 多租户系统的设计单纯的来说业务,一套Saas多租户的系统,面临很多业务复杂性,不同的租户存在不同的业务需求,大部分相同的表结构,那么如何使用EFCore来完成这样的设计呢 ...
- .Net下极限生产力之efcore分表分库全自动化迁移CodeFirst
.Net下极限生产力之分表分库全自动化Migrations Code-First ## 介绍 本文ShardinfCore版本x.6.x.x+ 本期主角: - [`ShardingCore`](htt ...
- efcore在Saas系统下多租户零脚本分表分库读写分离解决方案
efcore在Saas系统下多租户零脚本分表分库读写分离解决方案 ## 介绍 本文ShardinfCore版本x.6.0.20+ 本期主角: - [`ShardingCore`](https://gi ...
- .Net/C#分库分表高性能O(1)瀑布流分页
.Net/C#分库分表高性能O(1)瀑布流分页 框架介绍 依照惯例首先介绍本期主角:ShardingCore 一款ef-core下高性能.轻量级针对分表分库读写分离的解决方案,具有零依赖.零学习成本. ...
- EFCore.Sharding(EFCore开源分表框架)
EFCore.Sharding(EFCore开源分表框架) 简介 引言 开始 准备 配置 使用 按时间自动分表 性能测试 其它简单操作(非Sharing) 总结 简介 本框架旨在为EF Core提供S ...
- efcore分表下"完美"实现
ShardingCore 如何呈现"完美"分表 这篇文章是我针对efcore的分表的简单介绍,如果您有以下需求那么可以自己选择是否使用本框架,本框架将一直持续更新下去,并且免费开源 ...
- .Net下你不得不看的分表分库解决方案-多字段分片
.Net下你不得不看的分表分库解决方案-多字段分片 介绍 本期主角:ShardingCore 一款ef-core下高性能.轻量级针对分表分库读写分离的解决方案,具有零依赖.零学习成本.零业务代码入侵 ...
- mysql 分库分表的方法
分表后怎么做全文搜索 1.merge方式分表(不好) 2. 使用 sql union 3 使用Sphinx全文检索引擎 一,先说一下为什么要分表 当一张的数据达到几百万时,你查询一次所花的时间会变多, ...
随机推荐
- OpenHarmony创新赛丨报名倒计时,超强秘籍带你直通大奖!
OpenHarmony创新赛报名倒计时开始啦! 设于开放原子全球开源大赛下的OpenHarmony创新赛,目前正在如火如荼地进行赛事招募中!这次大赛围绕创新应用.商显行业.金融行业三大赛题,邀请来 ...
- Avalonia的模板控件(Templated Controls)
在Avalonia的UI框架中,TemplatedControl是一个核心组件,它提供了一种强大的方式来创建可重用且高度可定制的控件. 本文将深入探讨TemplatedControl的概念.其带来的优 ...
- RabbitMQ 09 主题模式
主题模式 主题模式结构图: 主题模式实际上就是一种模糊匹配的模式,可以将routingKey以模糊匹配的方式去进行转发. 可以使用*或#来表示: *:任意的一个单词. #:0个或多个单词. 定义配置类 ...
- VS2019 开发 MFC ACtivex (OCX)控件
需求: js调用ocx方法,传递字符串到ocx控件中显示 操作步骤: 一.新建 ocx 项目 二.填写项目信息 三.完成项目创建 四.修改项目属性 打开 项目属性 -> 链接器 -> ...
- Unity 检测FPS工具
检测FPS工具 public class FPS : MonoBehaviour { public float f_UpdateInterval = 0.5F; private float f_Las ...
- 树模型--ID3算法
基于信息增益(Information Gain)的ID3算法 ID3算法的核心是在数据集上应用信息增益准则来进行特征选择,以此递归的构建决策树,以信息熵和信息增益为衡量标准,从而实现对数据的归纳分类. ...
- 重新点亮linux 命令树————守护进程[二十三]
前言 简单整理一下守护进程. 正文 守护进程一般是开机启动的. 使用nohup 与 & 符号配合运行一个命令 nohup命令使进程忽略hangup(挂起)信号 使用tail 查看log文件. ...
- jenkins 持续集成和交付——pipeline(五)
前言 整理一下pipeline. 正文 介绍 什么是pipeline呢? 根据前面的所得,我们知道,以前都是模板形式,但是如果有些复杂的项目,需要用更加自定义的写法,那么就有了pipeline,也就是 ...
- 如何用一行 CSS 实现 10 种现代布局
现代 CSS 布局使开发人员只需按几下键就可以编写十分有意义且强大的样式规则.上面的讨论和接下来的帖文研究了 10 种强大的 CSS 布局,它们实现了一些非凡的工作. 01. 超级居中:place-i ...
- Python爬取网页遇到:selenium.common.exceptions.WebDriverException解决方法
在PyCharm中写好下列程序: 一运行遇到下列报错: selenium.common.exceptions.WebDriverException: Message: 'chromedriver' e ...