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原生查询

efcore如何优雅的实现按年分库按月分表的更多相关文章

  1. MySQL分库备份与分表备份

    MySQL分库备份与分表备份 1.分库备份 要求:将mysql数据库中的用户数据库备份,备份的数据库文件以时间命名 脚本内容如下: [root@db01 scripts]# vim backup_da ...

  2. Mysql数据库分库备份,分表备份

    分库备份 #!/bin/sh DBPATH=/server/backup MYUSER=root MYPASS=oldboy123 SOCKET=/data/3306/mysql.sock MYCMD ...

  3. EasySharding.EFCore 如何设计使用一套代码完成的EFCore Migration 构建Saas系统多租户不同业务需求且满足租户自定义分库分表、数据迁移能力?

    下面用一篇文章来完成这些事情 多租户系统的设计单纯的来说业务,一套Saas多租户的系统,面临很多业务复杂性,不同的租户存在不同的业务需求,大部分相同的表结构,那么如何使用EFCore来完成这样的设计呢 ...

  4. .Net下极限生产力之efcore分表分库全自动化迁移CodeFirst

    .Net下极限生产力之分表分库全自动化Migrations Code-First ## 介绍 本文ShardinfCore版本x.6.x.x+ 本期主角: - [`ShardingCore`](htt ...

  5. efcore在Saas系统下多租户零脚本分表分库读写分离解决方案

    efcore在Saas系统下多租户零脚本分表分库读写分离解决方案 ## 介绍 本文ShardinfCore版本x.6.0.20+ 本期主角: - [`ShardingCore`](https://gi ...

  6. .Net/C#分库分表高性能O(1)瀑布流分页

    .Net/C#分库分表高性能O(1)瀑布流分页 框架介绍 依照惯例首先介绍本期主角:ShardingCore 一款ef-core下高性能.轻量级针对分表分库读写分离的解决方案,具有零依赖.零学习成本. ...

  7. EFCore.Sharding(EFCore开源分表框架)

    EFCore.Sharding(EFCore开源分表框架) 简介 引言 开始 准备 配置 使用 按时间自动分表 性能测试 其它简单操作(非Sharing) 总结 简介 本框架旨在为EF Core提供S ...

  8. efcore分表下"完美"实现

    ShardingCore 如何呈现"完美"分表 这篇文章是我针对efcore的分表的简单介绍,如果您有以下需求那么可以自己选择是否使用本框架,本框架将一直持续更新下去,并且免费开源 ...

  9. .Net下你不得不看的分表分库解决方案-多字段分片

    .Net下你不得不看的分表分库解决方案-多字段分片 介绍 本期主角:ShardingCore 一款ef-core下高性能.轻量级针对分表分库读写分离的解决方案,具有零依赖.零学习成本.零业务代码入侵 ...

  10. mysql 分库分表的方法

    分表后怎么做全文搜索 1.merge方式分表(不好) 2. 使用 sql union 3 使用Sphinx全文检索引擎 一,先说一下为什么要分表 当一张的数据达到几百万时,你查询一次所花的时间会变多, ...

随机推荐

  1. 用HarmonyOS做一个可以手势控制的电子相册应用(ArkTS)

    介绍 本篇 Codelab 介绍了如何实现一个简单的电子相册应用,主要功能包括: 1.  实现首页顶部的轮播效果. 2.  实现页面多种布局方式. 3.  实现通过手势控制图片的放大.缩小.左右滑动查 ...

  2. POJ2251 基础bfs

    题目: 你进入了一个3D的宝藏地宫中探寻宝藏到了宝藏,你可以找到走出地宫的路带出宝藏,或者使用炉石空手回家. 地宫由立方体单位构成,立方体中不定会充满岩石.向上下前后左右移动一个单位需要一分钟.你不能 ...

  3. Kryo反序列化链分析

    前言 Kryo是一个快速序列化/反序列化工具,依赖于字节码生成机制(底层使用了ASM库),因此在序列化速度上有一定的优势,但正因如此,其使用也只能限制在基于JVM的语言上. Kryo序列化出的结果,是 ...

  4. python mmsql连接支持

    前言 因为我使用的是mmsql数据库,因为遇到一点坑,所以发布出来. 正文 准备工作: https://www.lfd.uci.edu/~gohlke/pythonlibs/#pymssql 下载对应 ...

  5. redis 一百二十篇(历史发展)之第二篇

    正文 简介: Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库. Redis 与其他 key - value 缓存产品有以下三个特点: Redis支持数据的持久化, ...

  6. lowdb 在electron 使用中注意的问题

    前言 可能很多人都没有听说过这个lowdb,但是它的确存在,而且在electron 中用到还是挺多的. 如何在electron 的render 进程中是引用electron 模块. 我们知道一个问题, ...

  7. EVA: Visual Representation Fantasies from BAAI

    ​本文做个简单总结,博主不是做自监督领域的,如果错误,欢迎指正. 链接 Code:​ Official:baaivision/EVA MMpretrain:open-mmlab/mmpretrain/ ...

  8. Hadoop HDFS 3.2的部署

    之前写过HDFS 2.6的部署,最近项目中尝试使用最新的HDFS 3.2.1做离线存储,部署方式略有不同,所以这里再简单写一下,这里只涉及到存储因此不再配置yarn,只配置HDFS最基本的服务Name ...

  9. 力扣29(java)-两数相除(中等)

    题目: 给定两个整数,被除数 dividend 和除数 divisor.将两数相除,要求不使用乘法.除法和 mod 运算符. 返回被除数 dividend 除以除数 divisor 得到的商. 整数除 ...

  10. 3 种发布策略,解决 K8s 中快速交付应用的难题

    作者 | 郝树伟(流生)阿里云高级研发工程师 前言 软件技术更新换代很快,但我们追求的目标是一直不变的,那就是在安全稳定的前提下,增加应用的部署频率,缩短产品功能的迭代周期,这样的好处就是企业可以在更 ...