惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Application and Cybersecurity Blog
Application and Cybersecurity Blog
A
About on SuperTechFans
S
SegmentFault 最新的问题
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Help Net Security
Help Net Security
有赞技术团队
有赞技术团队
博客园 - 【当耐特】
O
OpenAI News
美团技术团队
月光博客
月光博客
Apple Machine Learning Research
Apple Machine Learning Research
Schneier on Security
Schneier on Security
Webroot Blog
Webroot Blog
Cyberwarzone
Cyberwarzone
Hacker News - Newest:
Hacker News - Newest: "LLM"
Google Online Security Blog
Google Online Security Blog
T
Tenable Blog
S
Security Affairs
博客园_首页
S
Schneier on Security
Security Latest
Security Latest
T
Threat Research - Cisco Blogs
T
Tailwind CSS Blog
大猫的无限游戏
大猫的无限游戏
Spread Privacy
Spread Privacy
量子位
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
K
Kaspersky official blog
Hugging Face - Blog
Hugging Face - Blog
TaoSecurity Blog
TaoSecurity Blog
博客园 - 聂微东
Vercel News
Vercel News
M
MIT News - Artificial intelligence
T
Troy Hunt's Blog
B
Blog
MongoDB | Blog
MongoDB | Blog
Martin Fowler
Martin Fowler
Attack and Defense Labs
Attack and Defense Labs
L
LINUX DO - 最新话题
D
DataBreaches.Net
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Stack Overflow Blog
Stack Overflow Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
博客园 - Franky
W
WeLiveSecurity
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
F
Fortinet All Blogs
www.infosecurity-magazine.com
www.infosecurity-magazine.com
C
Check Point Blog
H
Hacker News: Front Page

博客园 - 老码识途呀

基于.NET的Windows窗体编程之WinForms图像控件 基于.NET的Windows窗体编程之WinForms提示控件 基于.NET的Windows窗体编程之WinForms时间控件 基于.NET的Windows窗体编程之WinForms图表控件 基于.NET的Windows窗体编程之WinForms输入与显示 基于.NET的Windows窗体编程之WinForms菜单工具栏 基于.NET的Windows窗体编程之WinForms打印 基于.NET的Windows窗体编程之WinForms对话框 基于.NET的Windows窗体编程之WinForms列表控件 基于.NET的Windows窗体编程之WinForms数据表格 基于.NET的Windows窗体编程之WinForms事件简介 基于.NET的Windows窗体编程之WinForms布局简介 基于.NET的Windows窗体编程之WinForms控件简介 基于.NET的Windows窗体编程之WinForms入门简介 推荐一款基于.NET和百度飞桨的OCR识别组件 新书速递,手把手教你WPF入门与开发 推荐一种并发线程中资源同步常用方法 推荐一种手动设置异步线程等待机制的解决方案 推荐一种异步线程执行过程中更新进度的方法 推荐一款基于.NET的进程间数据交互经典解决方案 推荐一款线程or进程间数据同步解决方案 推荐一款代码规范检查(CodeReview)神器 推荐一款高性能状态机管理解决方案 推荐一款进程间高速交换数据的解决方案 推荐一款基于.NET的进程间通信框架 推荐一种程序崩溃闪退或卡死的分析方法 推荐一款Excel读写神器之MiniExcel入门详解 推荐一款Ioc容器之Ninject入门详解 ASP.NET Core WebApi+React UI开发入门详解
推荐一款基于EF-Core的分库分表利器
老码识途呀 · 2025-06-24 · via 博客园 - 老码识途呀

在实际应用开发中,有些项目可能数据量特别大,在系统应用一段时间后,性能随着数据量的增加会逐步下降,从而造成系统不定时卡顿等现象,在客户使用过程中也会产生不好的印象。在这种情况下,常规操作是增加索引,优化SQL语句等方案,这种常规操作可能会短暂的解决卡顿问题,但是随着数据量持续增多,效果反而越来越不明显。当常规操作逐渐不起作用的时候,我们就需要往更深层次的去考虑,比如分库,分表等缩减数据体量的方案。今天我们以一个简单的小例子,简述如何在ASP.NET Core WebApi程序中,通过引入ShardingCore组件进行分库分表等操作。

什么是ShardingCore?

ShardingCore是一款基于EntityFrameworkCore的高性能、轻量级针对分表分库读写分离的解决方案。它支持efcore2+的所有版本,支持efcore2+的所有数据库、支持自定义路由、动态路由、高性能分页、读写分离的一款组件,一款零依赖第三方组件的扩展,如果您熟悉efcore的使用那么对于这个组件您只需要简单配置即可零成本开始使用。

ShardingCore整体架构图,如下所示:

上图可知整体架构表现为以entity作为核心驱动来牵引整个框架。通过ShardingCore,可以实现同时兼容分库分表操作,还可以兼容部分表不分库分表等场景。

开发环境

在本示例中,开发环境如下:

  1. 开发工具:Visual Studio 2022。
  2. 项目框架:基于.NET8.0的ASP.NET WebApi项目。
  3. ORM框架:Entity Framework Core (EF Core)  v9.0.6
  4. 分库分表组件:ShardingCore 7.9.1.24

操作步骤

使用用ShardingCore进行分库分表操作,主要步骤如下所示:

创建项目并安装组件

首先创建一个ASP.NET Core WebApi项目,然后安装ShardingCore组件。在Visual Studio 2022中,通过Nuget包管理器进行安装,当前最新版本为7.9.1.24,如下所示:

创建模型

我们以订单表(Order)为例,订单表包含订单Id,购买人,订单金额,所属区域,订单状态,创建时间等内容,如下所示:

namespace Okcoder.ShardingCore.Model
{
    /// <summary>
    /// 订单表
    /// </summary>
    public class Order
    {
        /// <summary>
        /// 订单Id
        /// </summary>
        public string Id { get; set; }

        /// <summary>
        /// 付款人
        /// </summary>
        public string Payer { get; set; }

        /// <summary>
        /// 订单金额
        /// </summary>
        public long Money { get; set; }

        /// <summary>
        /// 所属区域
        /// </summary>
        public string Area { get; set; }

        /// <summary>
        /// 订单状态
        /// </summary>
        public OrderStatusEnum OrderStatus { get; set; }

        /// <summary>
        /// 创建时间
        /// </summary>
        public DateTime CreationTime { get; set; }
    }

    /// <summary>
    /// 订单状态枚举
    /// </summary>
    public enum OrderStatusEnum
    {
        NoPay = 1,
        Paying = 2,
        Payed = 3,
        PayFail = 4
    }
}

创建DbContext

EF-Core框架需要实现DbContext,在ShardingCore项目中,如果只分库,则仅需要继承自AbstractShardingDbContext;如果还需要分表,则需要实现IShardingTableDbContext接口。本示例需要同时实现分库分表,所以既需要继承自AbstractShardingDbContext,又需要实现IShardingTableDbContext。主要实现OnModelCreating方法,将模型Order和数据表进行映射,以及构造函数。如下所示:

using Microsoft.EntityFrameworkCore;
using Okcoder.ShardingCore.Model;
using ShardingCore.Core.VirtualRoutes.TableRoutes.RouteTails.Abstractions;
using ShardingCore.Sharding.Abstractions;
using ShardingCore.Sharding;

namespace Okcoder.ShardingCore.DAL
{
    public class DefaultShardingDbContext : AbstractShardingDbContext, IShardingTableDbContext
    {
        public DefaultShardingDbContext(DbContextOptions<DefaultShardingDbContext> options) : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<Order>(entity =>
            {
                entity.HasKey(o => o.Id);
                entity.Property(o => o.Id).IsRequired().IsUnicode(false).HasMaxLength(50);
                entity.Property(o => o.Payer).IsRequired().IsUnicode(false).HasMaxLength(50);
                entity.Property(o => o.Area).IsRequired().IsUnicode(false).HasMaxLength(50);
                entity.Property(o => o.OrderStatus).HasConversion<int>();
                entity.ToTable(nameof(Order));
            });
        }
        /// <summary>
        /// 如果分表,则空实现即可
        /// </summary>
        public IRouteTail RouteTail { get; set; }
    }
}

创建虚拟分表路由

通过ShardingCore架构图可以看出,每一个表对应一个分表路由,同时ShardingCore默认实现了很多路由,这样可以直接继承,省去了很多麻烦,本示例主要继承自AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute,实现按月分表功能。主要定义分表的规则逻辑,包括分表起始时间,分表列,分表后缀等内容,具体如下所示:

using Okcoder.ShardingCore.Model;
using ShardingCore.Core.EntityMetadatas;
using ShardingCore.VirtualRoutes.Months;

namespace Okcoder.ShardingCore.DAL
{
    /// <summary>
    /// 路由构造函数支持依赖注入,依赖注入的对象生命周期必须是单例
    /// </summary>
    public class OrderVirtualTableRoute : AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute<Order>
    {
        /// <summary>
        /// 获取分表起始时间
        /// </summary>
        /// <returns></returns>
        public override DateTime GetBeginTime()
        {
            return new DateTime(2024, 1, 1);
        }
        /// <summary>
        /// 配置分表属性
        /// </summary>
        /// <param name="builder"></param>

        public override void Configure(EntityMetadataTableBuilder<Order> builder)
        {
            builder.ShardingProperty(o => o.CreationTime);
            //builder.AutoCreateTable(true);
        }
        /// <summary>
        /// 允许创建分表流程
        /// </summary>
        /// <returns></returns>

        public override bool AutoCreateTableByTime()
        {
            return true;
        }

        /// <summary>
        /// 分表后缀格式
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        protected override string TimeFormatToTail(DateTime time)
        {
            return time.ToString("MM");
        }

        protected override bool RouteIgnoreDataSource => false;
    }
}

说明:虚拟路由就是联系虚拟表和物理表的中间介质,虚拟表在整个程序中只有一份,那么程序如何知道要查询系统哪一张表呢,最简单的方式就是通过虚拟表对应的路由IVirtualTableRoute 。

创建虚拟分库路由

分库路由继承自AbstractShardingOperatorVirtualDataSourceRoute,并实现抽象方法,主要包括动态创建数据源,以及路由Filter筛选函数,和ShardingKey和数据源映射方法,如下所示:

using Okcoder.ShardingCore.Model;
using ShardingCore.Core.EntityMetadatas;
using ShardingCore.Core.VirtualRoutes.DataSourceRoutes.Abstractions;
using ShardingCore.Core.VirtualRoutes;
using System.Collections.Concurrent;

namespace Okcoder.ShardingCore.DAL
{
    /// <summary>
    /// 分库路由
    /// </summary>
    public class OrderVirtualDbRoute : AbstractShardingOperatorVirtualDataSourceRoute<Order, DateTime>
    {
        private readonly ConcurrentBag<string> dataSources = new ConcurrentBag<string>();

        private readonly object _lock = new object();

        public OrderVirtualDbRoute():base()
        {
            for (int i = 0; i < 10; i++)
            {
                dataSources.Add(DateTime.Now.AddYears(i).ToString("yyyy"));
            }
        }

        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<Order> builder)
        {
            builder.ShardingProperty(o => o.CreationTime);
        }

        public override List<string> GetAllDataSourceNames()
        {
            return dataSources.ToList();
        }

        /// <summary>
        /// tail就是2020,2021,2022,2023,2024,2025 所以分片只需要格式化年就可以直接比较了
        /// </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 override string ShardingKeyToDataSourceName(object shardingKey)
        {
            return $"{shardingKey:yyyy}";//年份作为分库数据源名称
        }
    }
}

 说明:数据源名称用来将对象路由到具体的数据源。

配置启动项

在Program的Main方法中配置ShardingCore启动项,主要配置默认数据源,扩展数据源,已经使用数据类型等内容。如下所示:

using Microsoft.EntityFrameworkCore;
using Okcoder.ShardingCore.DAL;
using ShardingCore;

namespace Okcoder.ShardingCore
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.
            builder.Services.AddControllers();
            builder.Services.AddShardingDbContext<DefaultShardingDbContext>()
                .UseRouteConfig(op =>
                {
                    op.AddShardingDataSourceRoute<OrderVirtualDbRoute>();
                    op.AddShardingTableRoute<OrderVirtualTableRoute>();
                }).UseConfig(op =>
                {
                    op.UseShardingQuery((connStr, builder) =>
                    {
                        //connStr is delegate input param
                        builder.UseSqlServer(connStr);
                    });
                    op.UseShardingTransaction((connection, builder) =>
                    {
                        //connection is delegate input param
                        builder.UseSqlServer(connection);
                    });
                    op.AddDefaultDataSource("2024", builder.Configuration.GetConnectionString("Default"));
                    op.AddExtraDataSource(sp =>
                    {
                        var dict = new Dictionary<string, string>();
                        for (int i = 0; i < 10; i++)
                        {
                            var key = DateTime.Now.AddYears(i).ToString("yyyy");
                            dict.Add(key, $"Server=localhost;Database=TestDb{key};Trusted_Connection=True;User Id=sa;Password=abc123;Encrypt=True;TrustServerCertificate=True;");
                        }
                        ;
                        return dict;
                    });
                }).AddShardingCore();
            var app = builder.Build();

            // Configure the HTTP request pipeline.
            app.UseHttpsRedirection();

            app.UseAuthorization();
            app.MapControllers();


            using (var scope = app.Services.CreateScope())
            {
                var testDbContext = scope.ServiceProvider.GetService<DefaultShardingDbContext>();
                testDbContext.Database.EnsureCreated();
            }
            app.Services.UseAutoTryCompensateTable();
            app.Run();
        }
    }
}

在上述示例中,最重要的实现自动创建分库,分表的是使用UseAutoTryCompensateTable()方法。

配置数据源

在appsettings.json文件中,配置默认数据源,如下所示:

创建控制器

在本示例中,为了测试,创建了OrderController,主要用于插入测试订单,如下所示:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Okcoder.ShardingCore.DAL;
using Okcoder.ShardingCore.Model;

namespace Okcoder.ShardingCore.Controllers
{
    [ApiController]
    [Route("[controller]/[action]")]
    public class OrderController : ControllerBase
    {
        private readonly DefaultShardingDbContext dbContext;

        public OrderController(DefaultShardingDbContext dbContext)
        {
            this.dbContext = dbContext;
        }

        [HttpGet()]
        public string Add()
        {
            dbContext.Add(new Order()
            {
                Id = Guid.NewGuid().ToString("n"),
                Payer = "111",
                Area = "123",
                OrderStatus = OrderStatusEnum.Payed,
                Money = 100,
                CreationTime = DateTime.Now
            });
            dbContext.SaveChanges();
            return "Ok";
        }
    }
}

运行实例

通过上述步骤,可以实现按年分库,按月分表的功能,运行程序后,发现已经创建成功,如下所示:

调用Order/Add接口后,查看数据库后,发现数据已经成功插入,如下所示:

 

参考文档

本文主要参考官方文档等资料:

官方文档:https://gitee.com/hubo/sharding-core

以上就是《推荐一款基于EF-Core的分库分表利器》的全部内容,旨在抛砖引玉,一起学习,共同进步。