efcore如何優雅的實現按年分庫按月分表

来源:https://www.cnblogs.com/xuejiaming/p/18198827
-Advertisement-
Play Games

efcore如何優雅的實現按年分庫按月分表 介紹 本文ShardinfCore版本 本期主角: ShardingCore 一款ef-core下高性能、輕量級針對分表分庫讀寫分離的解決方案,具有零依賴、零學習成本、零業務代碼入侵適配 距離上次發文.net相關的已經有很久了,期間一直在從事java相關的 ...


efcore如何優雅的實現按年分庫按月分表

介紹

本文ShardinfCore版本
本期主角:

ShardingCore 一款ef-core下高性能、輕量級針對分表分庫讀寫分離的解決方案,具有零依賴、零學習成本、零業務代碼入侵適配

距離上次發文.net相關的已經有很久了,期間一直在從事java相關的工作,一不小心就捲了一個java的orm。easy-query 如果有.net相關小伙伴轉java可以關註一下也算是打一波小廣告。

這次發文主要是在期間有多名用戶咨詢分庫分表相關的事宜,因為我之前並沒有針對按年分庫按月分表的demo實現,所以本次我打算藉著這個機會對該框架進行一次講解

說明

很多小伙伴我發現不會寫GetRouteFilter這個方法不知道是什麼意思
那麼我們這邊做一個很簡單的案例


var tails = new List<string>();
tails.Add("2024.01");
tails.Add("2024.02");
tails.Add("2024.03");
tails.Add("2024.04");
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原生查詢


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • .NET 中的表達式樹(Expression Trees) 表達式樹是什麼? 表達式樹(Expression Trees)是.NET框架中的一個強大功能,它將代碼表示為一個由表達式節點組成的樹形結構。每個節點代表代碼中的一個操作,例如方法調用、算術運算、邏輯運算等。表達式樹允許開發者在運行時分析、修 ...
  • Polly 是一個在 C# 中用於處理瞬態故障和提供彈性的庫。它允許你以聲明式的方式定義策略,如重試、熔斷、超時、回退等,這些策略可以幫助你的代碼在出現故障時保持穩健和可靠。 以下是如何在 C# 中使用 Polly 實現重試策略的基本步驟: 首先,你需要在你的項目中安裝 Polly 包。這可以通過 ...
  • EasyBlog 說明 本博客系統通過構建工具生成純靜態的博客網站,藉助GitHub Pages,你可以在5分鐘內免費擁有個人博客。 它具有以下特點 生成純靜態網站,訪問速度極快 使用markdown格式來編寫博客內容 基於git代碼管理來存儲你的博客 使用CI工具來自動化部署你的博客站點 效果展示 ...
  • 開始做項目管理了(本人3年java,來到這邊之後真沒想到...),天天開會溝通整理需求,他們講話的時候忙裡偷閑整理一下常用的方法,其實語言還是有共通性的,基本上看到方法名就大概能猜出來用法。出去打水的時候看到外面太陽好好,真想在外面坐著曬太陽,回來的時候好兄弟三年前送給我的鍵盤D鍵不靈了,在打"等待 ...
  • 一:背景 1. 講故事 停了一個月沒有更新文章了,主要是忙於寫 C#內功修煉系列的PPT,現在基本上接近尾聲,可以回頭繼續更新這段時間分析dump的一些事故報告,有朋友微信上找到我,說他們的系統出現了大量的http超時,程式不響應處理了,讓我幫忙看下怎麼回事,dump也抓到了。 二:WinDbg分析 ...
  • 一、ReZero簡介 ReZero是一款.NET中間件 : 全網唯一開源界面操作就能生成API , 可以集成到任何.NET6+ API項目,無破壞性,也可讓非.NET用戶使用exe文件 免費開源:MIT最寬鬆協議 , 一直從事開源事業十年,一直堅持開源 1.1 純ReZero開發 適合.Net Co ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • 前言 Spacesniffer 是一個免費的文件掃描工具,通過使用樹狀圖可視化佈局,可以立即瞭解大文件夾的位置,幫助用戶處理找到這些文件夾 當前系統C盤空間 清理後系統C盤空間 下載 Spacesniffer 下載地址:https://spacesniffer.en.softonic.com/dow ...
一周排行
    -Advertisement-
    Play Games
  • PasteSpider是什麼? 一款使用.net編寫的開源的Linux容器部署助手,支持一鍵發佈,平滑升級,自動伸縮, Key-Value配置,項目網關,環境隔離,運行報表,差量升級,私有倉庫,集群部署,版本管理等! 30分鐘上手,讓開發也可以很容易的學會在linux上部署你得項目! [從需求角度介 ...
  • SQLSugar是什麼 **1. 輕量級ORM框架,專為.NET CORE開發人員設計,它提供了簡單、高效的方式來處理資料庫操作,使開發人員能夠更輕鬆地與資料庫進行交互 2. 簡化資料庫操作和數據訪問,允許開發人員在C#代碼中直接操作資料庫,而不需要編寫複雜的SQL語句 3. 支持多種資料庫,包括但 ...
  • 在C#中,經常會有一些耗時較長的CPU密集型運算,因為如果直接在UI線程執行這樣的運算就會出現UI不響應的問題。解決這類問題的主要途徑是使用多線程,啟動一個後臺線程,把運算操作放在這個後臺線程中完成。但是原生介面的線程操作有一些難度,如果要更進一步的去完成線程間的通訊就會難上加難。 因此,.NET類 ...
  • 一:背景 1. 講故事 前些天有位朋友在微信上丟了一個崩潰的dump給我,讓我幫忙看下為什麼出現了崩潰,在 Windows 的事件查看器上顯示的是經典的 訪問違例 ,即 c0000005 錯誤碼,不管怎麼說有dump就可以上windbg開幹了。 二:WinDbg 分析 1. 程式為誰崩潰了 在 Wi ...
  • CSharpe中的IO+NPOI+序列化 文件文件夾操作 學習一下常見的文件、文件夾的操作。 什麼是IO流? I:就是input O:就是output,故稱:輸入輸出流 將數據讀入記憶體或者記憶體輸出的過程。 常見的IO流操作,一般說的是[記憶體]與[磁碟]之間的輸入輸出。 作用 持久化數據,保證數據不再 ...
  • C#.NET與JAVA互通之MD5哈希V2024 配套視頻: 要點: 1.計算MD5時,SDK自帶的計算哈希(ComputeHash)方法,輸入輸出參數都是byte數組。就涉及到字元串轉byte數組轉換時,編碼選擇的問題。 2.輸入參數,字元串轉byte數組時,編碼雙方要統一,一般為:UTF-8。 ...
  • CodeWF.EventBus,一款靈活的事件匯流排庫,實現模塊間解耦通信。支持多種.NET項目類型,如WPF、WinForms、ASP.NET Core等。採用簡潔設計,輕鬆實現事件的發佈與訂閱。通過有序的消息處理,確保事件得到妥善處理。簡化您的代碼,提升系統可維護性。 ...
  • 一、基本的.NET框架概念 .NET框架是一個由微軟開發的軟體開發平臺,它提供了一個運行時環境(CLR - Common Language Runtime)和一套豐富的類庫(FCL - Framework Class Library)。CLR負責管理代碼的執行,而FCL則提供了大量預先編寫好的代碼, ...
  • 本章將和大家分享在ASP.NET Core中如何使用高級客戶端NEST來操作我們的Elasticsearch。 NEST是一個高級別的Elasticsearch .NET客戶端,它仍然非常接近原始Elasticsearch API的映射。所有的請求和響應都是通過類型來暴露的,這使得它非常適合快速上手 ...
  • 參考delphi的代碼更改為C# Delphi 檢測密碼強度 規則(仿 google) 仿 google 評分規則 一、密碼長度: 5 分: 小於等於 4 個字元 10 分: 5 到 7 字元 25 分: 大於等於 8 個字元 二、字母: 0 分: 沒有字母 10 分: 全都是小(大)寫字母 20 ...