Implementing Time-Based Table Sharding with ShardingCore in .NET Core

ShardingCore documentation: AspNetCore Implementation Guide

Install via NuGet package manager. The ShardingCore package already includes:

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.Relational

Developers only need to inherit from the base class AbstractShardingDbContext and implement IShardingTableDbContext for sharding operations.

Time-Based Virtual Routing Implementation

Use AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute for monthly partitioning.

Entity Model Definition


public class OrderRecord
{
    [Key]
    public string OrderId { get; set; }
    public string CustomerName { get; set; }
    public long Amount { get; set; }
    public string Region { get; set; }
    public PaymentStatus Status { get; set; }
    public string CustomerId { get; set; }
    public DateTime OrderDate { get; set; }
}

public enum PaymentStatus
{
    Pending = 1,
    Processing = 2,
    Completed = 3,
    Failed = 4
}

DbContext Configuration


public class OrderDbContext : AbstractShardingDbContext, IShardingTableDbContext
{
    public OrderDbContext(DbContextOptions options) : base(options) {}

    public DbSet<OrderRecord> OrderRecords { get; set; }
    public IRouteTail RouteTail { get; set; }
}

Time-Based Sharding Route


public class MonthlyOrderShardingRoute : AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute<OrderRecord>
{
    public override DateTime GetStartDate()
    {
        return new DateTime(2023, 1, 1);
    }

    public override void Configure(EntityMetadataTableBuilder<OrderRecord> builder)
    {
        builder.ShardingProperty(o => o.OrderDate);
    }

    public override bool AutoCreatePartitions()
    {
        return true;
    }
}

Migration Customization


public class CustomMySqlMigrator : MySqlMigrationsSqlGenerator
{
    private readonly IShardingRuntimeContext _shardingContext;

    public CustomMySqlMigrator(
        IShardingRuntimeContext shardingContext,
        MigrationsSqlGeneratorDependencies dependencies,
        ICommandBatchPreparer commandBatcher,
        IMySqlOptions options)
        : base(dependencies, commandBatcher, options)
    {
        _shardingContext = shardingContext;
    }

    protected override void Generate(
        MigrationOperation operation,
        IModel model,
        MigrationCommandListBuilder builder)
    {
        var originalCommands = builder.GetCommandList().ToList();
        base.Generate(operation, model, builder);
        var newCommands = builder.GetCommandList().ToList();
        var addedCommands = newCommands.Except(originalCommands).ToList();
        
        MigrationHelper.Generate(
            _shardingContext, 
            operation, 
            builder, 
            Dependencies.SqlGenerationHelper, 
            addedCommands);
    }
}

Database Initialization


public static class DatabaseSeeder
{
    public static void SeedDatabase(this IApplicationBuilder app)
    {
        using var scope = app.ApplicationServices.CreateScope();
        var context = scope.ServiceProvider.GetRequiredService<OrderDbContext>();
        
        if (!context.OrderRecords.Any())
        {
            var orders = new List<OrderRecord>(20);
            var startDate = new DateTime(2023, 1, 15);

            for (int i = 0; i < 20; i++)
            {
                orders.Add(new OrderRecord
                {
                    OrderId = $"ORD{i:D5}",
                    CustomerName = $"User{(i % 5)}",
                    Region = $"Zone{(char)('A' + i % 3)}",
                    CustomerId = $"CUST{i % 10}",
                    Amount = 500 + new Random().Next(100, 5000),
                    Status = (PaymentStatus)(i % 4 + 1),
                    OrderDate = startDate.AddDays(i)
                });
            }

            context.AddRange(orders);
            context.SaveChanges();
        }
    }
}

Service Configuration


builder.Services.AddShardingDbContext<OrderDbContext>().UseRouteConfig(config =>
{
    config.AddShardingTableRoute<MonthlyOrderShardingRoute>();
}).UseConfig(options =>
{
    var loggerFactory = LoggerFactory.Create(logging =>
    {
        logging.AddConsole();
    });

    options.UseShardingMigrationConfigure(migrationConfig =>
    {
        migrationConfig.ReplaceService();
    });

    options.ThrowIfQueryRouteNotMatch = false;
    
    options.UseShardingQuery((connectionString, builder) =>
    {
        builder.UseMySql(connectionString, MySqlServerVersion.AutoDetect(connectionString))
               .UseLoggerFactory(loggerFactory);
    });

    options.UseShardingTransaction((connection, builder) =>
    {
        builder.UseMySql(connection, MySqlServerVersion.AutoDetect(connection.ConnectionString))
               .UseLoggerFactory(loggerFactory);
    });

    options.AddDefaultDataSource("main", 
        "Data Source=127.0.0.1;Initial Catalog=sharding_db;uid=root;pwd=SecurePass123!");

    options.AddReadWriteSeparation(sp =>
    {
        return new Dictionary{
        {
            "main", new List<string>
            {
                "Data Source=127.0.0.1;Initial Catalog=sharding_db;uid=root;pwd=SecurePass123!"
            }
        }
        };
    }, ReadStrategyEnum.Loop, defaultEnable: true);
}).AddShardingCore();

Middleware Setup


using (var scope = app.Services.CreateScope())
{
    var dbContext = scope.ServiceProvider.GetService<OrderDbContext>();
    dbContext.Database.Migrate();
}

app.Services.UseAutoTryCompensateTable();
app.SeedDatabase();

Thẻ: ShardingCore .NET Core 6 Entity Framework Core mysql Table Sharding

Đăng vào ngày 28 tháng 7 lúc 19:52