Abp VNext分表分庫,拒絕手動,我們要happy coding

薛家明發表於2021-10-26

Abp VNext 分表分庫

ShardingCore

ShardingCore 易用、簡單、高效能、普適性,是一款擴充套件針對efcore生態下的分表分庫的擴充套件解決方案,支援efcore2+的所有版本,支援efcore2+的所有資料庫、支援自定義路由、動態路由、高效能分頁、讀寫分離的一款元件,如果你喜歡這元件或者這個元件對你有幫助請點選下發star讓更多的.neter可以看到使用


Gitee Star 助力dotnet 生態 Github Star


背景

你是否在使用efcore,你是否在使用abp,你是否對目前的分表十分厭惡,手動指定表讓你的程式碼無辜多出很多膠水程式碼,那麼這次的文章可以很好的幫你解決掉當前的困難點,sharding-core 針對efcore的分庫分表讀寫分離的痛點進行擴充套件,可以完美整合到efcore生態的系統裡面,無論你是abp還是其他使用efcore的框架,你確定真的不看一下嗎,如何整合abp當時我釋出這個庫的時候就有很多人問我是否支援,我的回答是支援的,只是個人沒有時間去實現。這次已經實現了我這邊將分享下如何整合sharding-coreabp vnext中。

距離上一篇部落格已經兩週了,在這兩週期間本人還是做了很多事情,針對優化sharding-core的使用和體驗,就在上週五傍晚的時候有個使用abp的同學聯絡我,問我什麼時候支援abp vnext,其實這個計劃我很早之前就在issue裡面備註了,只是獲取用abp的同學沒有怎麼關注這個類庫也沒人提出來,所以就擱置了。因為sharding-core是一款幾乎可以說可以整合到任何efcore生態下的所以原則上abp上整合應該是沒什麼難度的,因為本人使用abp不是很多所以這邊自己按官方教程進行了初步的專案搭建,然後又用了一會功夫瞭解了abp原始碼(之前有了解過)清楚了dbcontext的建立過程所以很快就繼承好了一個todoapp
接下來我將用一篇部落格的篇幅來介紹如何將sharing-core整合到abp vnext中。

如何整合

abp專案建立/下載

我這邊是通過github進行了例子的todoapp 進行下載,下載後是一個集合例子,我們獲取TodoApp專案進行單獨處理,開啟然後編譯。

注意如果你自己會新建那麼也是一樣的

整合abp思路

用過abp的使用者都應該知道abp的dbcontext沒有什麼不一樣,唯一需要注意的就是
  • abp:只要你的dbcontext繼承至 public class TDbContext:AbpDbContext<TDbContext>那麼就可以完美使用.
    但是sharding-core的使用我們通過readme來檢視發現
  • sharidng-core:只要你的dbcontext繼承至public class TDbContext:AbstractShardingDbContext那麼你就可以完美使用.

好傢伙直接給想自己整合的同學搞矇蔽了,c#又不是c艹沒有多繼承啊那怎麼辦,但是這邊其實是有一個誤區的就是abp確實需要繼承abpdbcontext但是sharding-core是已介面作為依賴來開發的,所以我們只需要實現ISharingDbContext 這個介面就可以瞭如果需要事務在實現ISupportShardingTransaction
最終我們是通過實現一個抽象基類來繼承abpdbcntext並且實現sharding-core需要的介面 AbstractShardingAbpDbContext 這樣我們就可以在不破壞abp的同時又兼顧了sharding-core

注意:這邊sharing-core讓你們繼承AbstractShardingDbContext是因為重複寫這些介面的實現會很麻煩所以給你們寫了一個抽象方便你們使用

abp整合注意事項

  • 通過原始碼可以看出abp整合需要賦值lazyserviceprovider 因為abp的dbcontext是交由uow自己處理並且需要支援很多特性所以我們在建立dbcontext的時候需要對此處進行賦值注意點。
  • 注意abp預設提供了IEntity<Guid>,IHasCreationTime屬性較為常用所以我們需要注意如何支援這兩種,因為當你用id取模分表或者建立時間分表的使用場景還是比較常見的所以我們需要支援。
    因為在insert時如果sharding-core發現對應的分表欄位為null就無法繼續執行下去,所以為了相容abp需要支援兩個比較常見的需求

賦值依賴注入支援domain event等事件

自動屬性

瞭解sahrding-core針對dbcontext的分表支援

首先我們需要知道sharding-core是如何對一個普通的dbcontext進行支援的

如果你的dbcontext有用到以下任意一個介面那麼整合起來可能需要自己去實現對應的介面

  • IDbSetSource 用來接管dbset
  • IQueryCompiler 用來接管查詢編譯
  • IDbContextTransactionManager 用來接管事務開啟
  • IRelationalTransactionFactory 用來接管事務的提交、回滾 和加入
  • IModelCacheKeyFactory 用來接管dbcontext的模型快取
  • IModelCustomizer 用來接管dbcontext的模型初始化前後自定義

如果你的efcore想接入sharding-core並且如果你沒有對dbcontext的上述任何介面進行替換那麼可以很容易就接入,如果你的efcore在建立的時候有針對上述的介面進行替換,就需要你自己手動進行兩邊的實現合併。

如何接入sharding-core

這邊我們假設你沒有對上述的dbcontextoptionbuilder的建立進行介面的替換那麼你只需要進行如下操作就可以簡單接入sharding-core

  • 首先就是預設建立dbcontext替換為sharding-core的配置

原先:

public void ConfigureServices(IServiceCollection services)

            services.AddDbContext<DefaultShardingTableDbContext>(o => o.UseSqlServer("Server=.;Database=TodoApp;Trusted_Connection=True"));

現在:


public void ConfigureServices(IServiceCollection services)

//1.先檢查dbcontext建構函式只允許 DbContextOptions<TodoAppDbContext> options
            ShardingCoreHelper.CheckContextConstructors<DefaultShardingTableDbContext>();
//2.新增UseSharding<DefaultShardingTableDbContext>()讓依賴注入建立的dbcontext支援查詢插入事務的管理
            services.AddDbContext<DefaultShardingTableDbContext>(o=>o.UseSqlServer("Server=.;Database=TodoApp;Trusted_Connection=True").UseSharding<DefaultShardingTableDbContext>()); 
//3.新增分表配置
 new ShardingCoreConfigBuilder<DefaultShardingTableDbContext>(context.Services,((s, builder) =>
             {
                 builder.UseSqlServer(s);
             } )).Begin(o =>
                 {
                     o.CreateShardingTableOnStart = false;
                     o.EnsureCreatedWithOutShardingTable = false;
                     o.AutoTrackEntity = true;
                 })
                 .AddShardingTransaction((connection, builder) =>
                     builder.UseSqlServer(connection))
                 .AddDefaultDataSource("ds0", "Server=.;Database=TodoApp;Trusted_Connection=True")
                 .AddShardingTableRoute(o =>
                 {
                     o.AddShardingTableRoute<ToDoItemVirtualTableRoute>();
                 }).End();

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
//4.初始化分表配置
            var shardingBootstrapper = app.ApplicationServices.GetRequiredService<IShardingBootstrapper>();
            shardingBootstrapper.Start();

綜上所述我們接入任何efcore的系統只需要進行4步(第一步都可以去掉只需要3步)就可以完美接入了,可以保證使用

abp正式接入替換

修改將todoitem表作為id取模來進行分表演示

修改TodoItem實體

預設TodoApp有一個TodoItem實體物件我們首先建立兩個空介面

//標識對應分表對像的i分表欄位是id,是自動建立的guid
    public interface IShardingKeyIsGuId
    {
    }
//標識對應的分表對應的分表欄位是建立時間
    public interface IShardingKeyIsCreationTime
    {
    }
//修改TodoItem
    public class TodoItem : BasicAggregateRoot<Guid>,IShardingTable,IShardingKeyIsGuId
    {
        [ShardingTableKey]
        public override Guid Id { get; protected set; }
        public string Text { get; set; }
    }

建立TodoItem的分表路由

通過繼承預設分表取模路由AbstractSimpleShardingModKeyStringVirtualTableRoute

//id取模雖然是string但是guid也是一樣的最多兩位就是00_99,按5來取模
    public class ToDoItemVirtualTableRoute:AbstractSimpleShardingModKeyStringVirtualTableRoute<TodoItem>
    {
        public ToDoItemVirtualTableRoute() : base(2, 5)
        {
        }
    }

實現抽象類修改TodoItemDbContext

實現 AbstractShardingAbpDbContext


    public class TodoAppDbContext :AbstractShardingAbpDbContext<TodoAppDbContext>,
        IIdentityDbContext,
        ITenantManagementDbContext,
        IShardingTableDbContext

修改實體TodoItem

 public class TodoItem : BasicAggregateRoot<Guid>,IShardingTable,IShardingKeyIsGuId
    {
        [ShardingTableKey]
        public override Guid Id { get; protected set; }
        public string Text { get; set; }
    }

其中別的介面都和sharding-core一致,為了支援abp的部分自動屬性這邊進行了新的介面新增用來標識當前的物件是通過什麼方式來進行分表的,然後可以高效的通過介面來進行賦值,比如IShardingKeyIsGuId告訴系統是id為guid的進行分表的

public abstract class AbstractShardingAbpDbContext<TDbContext>...
{
......
        private void CheckAndSetShardingKeyThatSupportAutoCreate<TEntity>(TEntity entity) where TEntity : class
        {
            if (entity is IShardingKeyIsGuId)
            {

                if (entity is IEntity<Guid> guidEntity)
                {
                    if (guidEntity.Id != default)
                    {
                        return;
                    }
                    var idProperty = entity.GetProperty(nameof(IEntity<Guid>.Id));

                    var dbGeneratedAttr = ReflectionHelper
                        .GetSingleAttributeOrDefault<DatabaseGeneratedAttribute>(
                            idProperty
                        );

                    if (dbGeneratedAttr != null && dbGeneratedAttr.DatabaseGeneratedOption != DatabaseGeneratedOption.None)
                    {
                        return;
                    }

                    EntityHelper.TrySetId(
                        guidEntity,
                        () => GuidGenerator.Create(),
                        true
                    );
                }
            }
              else if (entity is IShardingKeyIsCreationTime)
            {
                AuditPropertySetter?.SetCreationProperties(entity);
            }
        }
}

新增虛擬路由

既然你講TodoItem進行了分表,那麼你這邊需要告訴系統你是按怎麼個規則進行分表的,假設我們預設按id取模那麼可以繼承sharding-core預設提供的取模路由


    public class ToDoItemVirtualTableRoute:AbstractSimpleShardingModKeyStringVirtualTableRoute<TodoItem>
    {
        public ToDoItemVirtualTableRoute() : base(2, 5)
        {
        }
    }

簡單說明就是分表字尾為2位數00-99,5代表模5也就是00,01,02,03,04
注意: IShardingTableDbContext如果dbcontext需要實現分表功能必須實現IShardingTableDbContext
到目前為止我們的準備工作已經完成了,接下來需要進行codefirst的支援和具體專案的配置使用了

選中TodoApp.EntityFrameworkCore專案開啟TodoAppDbContextFactory替換dbcontext的建立方法,主要是替換codefirst的建表語句
這邊是採用了EFCore.Sharding

 static TodoAppDbContextFactory()
        {
            var services = new ServiceCollection();
            var configuration = BuildConfiguration();
            services.AddShardingDbContext<TodoAppDbContext>(
                    (conn, o) =>
                        o.UseSqlServer(conn)
                            .ReplaceService<IMigrationsSqlGenerator, ShardingSqlServerMigrationsSqlGenerator<TodoAppDbContext>>()
                ).Begin(o =>
                {
                    o.AutoTrackEntity = true;
                })
                .AddShardingTransaction((connection, builder) =>
                    builder.UseSqlServer(connection))
                .AddDefaultDataSource("ds0",
                    configuration.GetConnectionString("Default"))
                .AddShardingTableRoute(o =>
                {
                    o.AddShardingTableRoute<ToDoItemVirtualTableRoute>();
                }).End();
            services.AddLogging();
            var buildServiceProvider = services.BuildServiceProvider();
            ShardingContainer.SetServices(buildServiceProvider);
            new ShardingBootstrapper(buildServiceProvider).Start();
        }

        public TodoAppDbContext CreateDbContext(string[] args)
        {
            return ShardingContainer.GetService<TodoAppDbContext>();
        }

主要程式碼就是告訴efcore.tools如何建立對應的dbcontext
然後開啟nuget控制檯

選中需要生成遷移的專案

啟動項設定為

執行命令

PM> Add-Migration InitTodoApp

PM> update-database

到此為止我們的code first已經完成了,系統會自動根據分表的配置來進行建立對應的sql語句

abp啟動

因為sharding-core是基於介面和dbcontext所以只要你的efcore那麼基本上你的生態就可以接入sharding-core,主要就是注意1點

  • 自定義替換DbContextOptions的部分服務
  • dbcontext的建構函式是DbContextOptions或者是他的泛型類

修改TodoAppEntityFrameworkCoreModule

public override void ConfigureServices(ServiceConfigurationContext context)
       {
          ......
           Configure<AbpDbContextOptions>(options =>
           {
               /* The main point to change your DBMS.
                * See also TodoAppDbContextFactory for EF Core tooling. */
               options.UseSqlServer();
               options.Configure<TodoAppDbContext>(context1 =>
               {
                   context1.DbContextOptions.UseSqlServer("Server=.;Database=TodoApp;Trusted_Connection=True").UseSharding<TodoAppDbContext>();
               });
           });
           ShardingCoreHelper.CheckContextConstructors<TodoAppDbContext>();
            new ShardingCoreConfigBuilder<TodoAppDbContext>(context.Services,((s, builder) =>
            {
                builder.UseSqlServer(s);
            } )).Begin(o =>
                {
                    o.CreateShardingTableOnStart = false;
                    o.EnsureCreatedWithOutShardingTable = false;
                    o.AutoTrackEntity = true;
                })
                .AddShardingTransaction((connection, builder) =>
                    builder.UseSqlServer(connection))
                .AddDefaultDataSource("ds0", "Server=.;Database=TodoApp;Trusted_Connection=True")
                .AddShardingTableRoute(o =>
                {
                    o.AddShardingTableRoute<ToDoItemVirtualTableRoute>();
                }).End();
       }



       public override void OnPostApplicationInitialization(ApplicationInitializationContext context)
       {
           context.ServiceProvider.GetRequiredService<IShardingBootstrapper>().Start();
       }

稍微解釋下

   options.Configure<TodoAppDbContext>(context1 =>
                {
                    context1.DbContextOptions.UseSqlServer("Server=.;Database=TodoApp;Trusted_Connection=True").UseSharding<TodoAppDbContext>();
                });

用來告訴abp,TodoAppDbContext的建立需要使用useSharding,
之後就是sharding-core預設提供的builder,當然你們可以自行封裝一下,別忘了在啟動的時候

            context.ServiceProvider.GetRequiredService<IShardingBootstrapper>().Start();

這個千萬不能忘

執行TodoApp.Web


通過新增efcore的日誌我們可以清晰地看到abp能夠正確的將對應的資料插入進去,並且完全不需要修改現有程式碼,基本上的零基礎使用,簡單的配置,
如果您喜歡本庫就點點star點點贊,來都來了點個推薦不過分吧。為.net生態做一份貢獻,希望各位個多多提issue,和pr十分感激

專案demo

AbpVNextShardingTodoApp

分表分庫元件求贊求star


部落格

QQ群:771630778

個人QQ:326308290(歡迎技術支援提供您寶貴的意見)

個人郵箱:326308290@qq.com

特別感謝

相關文章