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. 【直播回顾】OpenHarmony知识赋能第八期:手把手教你实现涂鸦小游戏

     OpenHarmony第八期知识赋能直播已经在9月29日圆满落幕!从9月15日起,资深OS框架开发工程师巴延兴老师于每周四进行分享,通过实现涂鸦小游戏来帮助大家全面了解ArkUI框架的应用,拓宽知识 ...

  2. Ascend C 自定义算子 Kernel Launch调用入门

    本文分享自华为云社区<Ascend C 自定义算子 Kernel Launch调用入门>,作者: jackwangcumt. 1 Kernel Launch概述 根据官方说明文档的介绍,A ...

  3. Stage模型深入解读

     原文链接:https://mp.weixin.qq.com/s/4Mb5BMw1IgKvqE0Ff9Ts-w,点击链接查看更多技术内容:   HarmonyOS 3.1版本(API 9)推出了全新应 ...

  4. Hypium框架使能ArkTS应用高效测试

     原文链接:https://mp.weixin.qq.com/s/Ncc-x_4zy4wBZmSjknw1lQ,点击链接查看更多技术内容:   HarmonyOS发布了声明式开发框架ArkUI,带来了 ...

  5. 敲重点!HarmonyOS这些更新将会影响原子化服务上架

    原文:https://mp.weixin.qq.com/s/t-MaHqYiJ3z-QxaIsgWNPA,点击链接查看更多技术内容. 一.引言 随着原子化服务生态的发展,我们的业务诉求也在不断地变化, ...

  6. Linux之sudo

    [摘要] 生产环境中为了系统的安全性,Linux主机的root权限是只能管理器使用,普通用户不具有root权限,但是可以通过sudo获取root权限执行一些操作. 一.知识要点 wheel组 在Lin ...

  7. JS解混淆

    JS解混淆 最近在整理之前和一些同伴的分享资料,发现时间已经过了好久,特此整理一些有价值的分享记录. JS混淆 学习js混淆可以逆向分析混淆和加密过程,实战可用于爬虫和渗透信息获取 本文档用于初步介绍 ...

  8. 日志分析qsnctfwp

    使用工具:http Logs Viewer 使用 http Logs Viewer 载入 access.log 按照 Status 排序 其中大量 Status 为404的日志不难推断出,这是在进行目 ...

  9. MMDeploy部署实战系列【第六章】:将编译好的MMdeploy导入到自己的项目中 (C++)

    MMDeploy部署实战系列[第六章]:将编译好的MMdeploy导入到自己的项目中 (C++) 这个系列是一个随笔,是我走过的一些路,有些地方可能不太完善.如果有那个地方没看懂,评论区问就可以,我给 ...

  10. iOS的cer、p12格式证书解析监控

    之前博客写过直接解析ipa包获取mobileprovision文件来监控APP是否过期来,但APP的推送证书还没有做, 大家都知道,iOS的推送证书不会放到ipa包里,只能通过直接解析p12或cer. ...