首页 > 数据库 >ShardingCore兼容Code-First数据库迁移 让update-database能更新分表

ShardingCore兼容Code-First数据库迁移 让update-database能更新分表

时间:2022-12-08 00:34:08浏览次数:81  
标签:Code database get CreationTime update set 分表 public op

参考文章
.Net下极限生产力之分表分库全自动化Migrations Code-First

背景

在我上一篇博客ABP EF CORE 7 集成ShardingCore实现分表写完之后,我发现我还有一个没解决的问题,就是在我执行update-database更新数据库的时候,分表并不会更新,并且程序启动之后也不会更新已经生成好的分表。后来又仔细看了官方的例子才发现,我还要修改 IDesignTimeDbContextFactory<> 的实现类才算真正的Code-First

修改 IDesignTimeDbContextFactory<> 的实现类

在ABP的EntityFrameworkCore项目下的EntityFrameworkCore文件夹里面,有一个DbContextFactory类,对其进行修改后的代码如下:
    /* This class is needed to run "dotnet ef ..." commands from command line on development. Not used anywhere else */
    public class MyABP7NET6DbContextFactory : IDesignTimeDbContextFactory<MyABP7NET6DbContext>
    {
        private static readonly IServiceProvider _serviceProvider;
        static MyABP7NET6DbContextFactory()
        {
            var services = new ServiceCollection();

            services.AddShardingDbContext<MyABP7NET6DbContext>()
                .UseRouteConfig(op =>
                {
                    op.AddShardingTableRoute<TodoTableRoute>();
                })
                .UseConfig((sp, op) =>
                {
                    op.UseShardingQuery((conStr, builder) =>
                    {
                        builder.UseSqlServer(conStr);
                    });
                    op.UseShardingTransaction((connection, builder) =>
                    {
                        builder.UseSqlServer(connection);
                    });
                    op.UseShardingMigrationConfigure(builder =>
                    {
                        builder.ReplaceService<IMigrationsSqlGenerator, ShardingSqlServerMigrationsSqlGenerator>();
                    });
                    op.AddDefaultDataSource(
                        Guid.NewGuid().ToString("n"),
                        "Server=localhost; Database=MyABP7NET6Db; Trusted_Connection=True;TrustServerCertificate=True;"
                    );
                })
                .AddShardingCore();
            _serviceProvider = services.BuildServiceProvider();
        }

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

这段代码就是在执行update-database时,为efcore提供DbContext的,并且正如ABP的注释所说,它不会用在此外任何地方。

对迁移sql生成进行重写

    public class ShardingSqlServerMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator
    {
        private readonly IShardingRuntimeContext _shardingRuntimeContext;

        public ShardingSqlServerMigrationsSqlGenerator(IShardingRuntimeContext shardingRuntimeContext, MigrationsSqlGeneratorDependencies dependencies, ICommandBatchPreparer commandBatchPreparer) : base(dependencies, commandBatchPreparer)
        {
            _shardingRuntimeContext = shardingRuntimeContext;
        }
        protected override void Generate(
            MigrationOperation operation,
            IModel model,
            MigrationCommandListBuilder builder)
        {
            var oldCmds = builder.GetCommandList().ToList();
            base.Generate(operation, model, builder);
            var newCmds = builder.GetCommandList().ToList();
            var addCmds = newCmds.Where(x => !oldCmds.Contains(x)).ToList();

            MigrationHelper.Generate(_shardingRuntimeContext, operation, builder, Dependencies.SqlGenerationHelper, addCmds);
        }
    }
// 添加分片配置
            services.AddShardingDbContext<MyABP7NET6DbContext>()
                .UseRouteConfig(op =>
                {
                    op.AddShardingTableRoute<TodoTableRoute>();
                }).UseConfig((sp, op) =>
                {
                    //...原来的配置不变,添加下面的
                    //如果需要迁移code-first必须要自行处理
                    op.UseShardingMigrationConfigure(b =>
                    {
                        b.ReplaceService<IMigrationsSqlGenerator, ShardingSqlServerMigrationsSqlGenerator>();
                    });
                }).AddShardingCore();

创建主从表实体

接下来就可以修改实体进行测试了
    public class ToDoItem : AggregateRoot<long>, ISoftDelete, IMayHaveTenant, ICreationAudited, IShardingKeyIsCreationTime
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public override long Id { get => base.Id; set => base.Id = value; }
        public bool IsDeleted { get; set; }
        public int? TenantId { get; set; }
        public long? CreatorUserId { get; set; }
        public DateTime CreationTime { get; set; }

        public long? ToDoMasterId { get; set; }
        [ForeignKey(nameof(ToDoMasterId))]
        public ToDoMaster ToDoMaster { get; set; }

        public string Text { get; set; }
    }

    //标识对应的分表对像的分表字段是id时,自动创建guid
    public interface IShardingKeyIsGuId
    {
    }
    //标识对应的分表对象的分表字段是创建时间时,自动创建时间
    public interface IShardingKeyIsCreationTime
    {
    }

    public class ToDoMaster : FullAuditedEntity<long>, IMayHaveTenant
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public override long Id { get => base.Id; set => base.Id = value; }
        public int? TenantId { get; set; }

        public ICollection<ToDoItem> ToDoItems { get; set; }

        public string Name { get; set; }
    }

然后再修改DbContext、分表路由、分表配置,这三部分代码就不贴出来了,因为和第一次添加实体的代码一样。

修改完以上代码以后,就可以执行迁移命令:

PM> Add-Migration Add_ToDoMaster

PM> update-Database

这一次执行完update-Database以后,就能看到数据库的变化了,而不用等到启动程序时才新建表:

新增数据

            var msg = "测试数据";
            var currentTime = DateTime.Now;

            var master = await _dbContext.Set<ToDoMaster>().AddAsync(new ToDoMaster()
            {
                Name = msg + "Master",
            });
            var masterId = master.Entity.Id;

            var insertList = new List<ToDoItem>()
            {
                new ToDoItem(){ ToDoMasterId = masterId, Text = msg+"1" },
                new ToDoItem(){ ToDoMasterId = masterId, Text = msg+"2" },
                new ToDoItem(){ ToDoMasterId = masterId, Text = msg+"3" },
                new ToDoItem(){ ToDoMasterId = masterId, Text = msg+"4" },
                new ToDoItem(){ ToDoMasterId = masterId, Text = msg+"5" }
            };
            await _dbContext.Set<ToDoItem>().AddRangeAsync(insertList);

            await _dbContext.SaveChangesAsync();

从表数据截图

主表数据截图

左连接

按时间过滤后,只会查询对应时间的分表;如果查询条件不加时间,会查询所有分表。

var sql = from item in _dbContext.Set<ToDoItem>().Where(x => x.CreationTime >= startTime && x.CreationTime < endTime)
          select new
          {
              item.ToDoMaster.Name,
              item.Text,
              item.CreationTime
          };

对应的sql

SELECT [t0].[Name], [t].[Text], [t].[CreationTime]
FROM [ToDoItems_202212] AS [t]
LEFT JOIN (
    SELECT [t1].[Id], [t1].[Name]
    FROM [ToDoMasters_202212] AS [t1]
    WHERE (0 = CAST(1 AS bit) OR [t1].[IsDeleted] = CAST(0 AS bit)) AND (0 = CAST(1 AS bit) OR ([t1].[TenantId] IS NULL))
) AS [t0] ON [t].[ToDoMasterId] = [t0].[Id]
WHERE (0 = CAST(1 AS bit) OR [t].[IsDeleted] = CAST(0 AS bit)) AND (0 = CAST(1 AS bit) OR ([t].[TenantId] IS NULL)) AND [t].[CreationTime] >= '2022-12-01T00:00:00' AND [t].[CreationTime] < '2023-01-01T00:00:00'

分片对象暂不支持导航属性

            var list = await _dbContext.Set<ToDoItem>()
                .Include(x => x.ToDoMaster)
                .Where(x => x.CreationTime >= startTime && x.CreationTime < endTime)
                .Where(x => x.ToDoMaster.CreationTime >= startTime && x.ToDoMaster.CreationTime < endTime)
                .Select(x => new
                {
                    x.ToDoMaster.Name,
                    x.Text,
                    x.CreationTime,
                })
                .ToListAsync();

对应的sql

可以看到只有ToDoItem的分表成功路由到了,而它的主表虽然写了时间查询条件,但依然用所有的分表做连接操作了。
所以,目前做查询尽量不要用到导航属性。

更详细的查询说明在官方文档也有。

标签:Code,database,get,CreationTime,update,set,分表,public,op
From: https://www.cnblogs.com/cnblogsName/p/16964694.html

相关文章

  • 力扣 leetcode 78. 子集
    问题描述给你一个整数数组nums,数组中的元素互不相同。返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。你可以按任意顺序返回解集。提示:1<=nums.le......
  • Codeforces Round #836 (Div. 2)(持续更新)
    Preface补题,上周末没比赛很难受啊,而且这周要考CET-4,这周的模考听力只错了2pts,感觉自信慢慢flag嘛值得一提的是学校还是沦陷了,让我们自愿返乡了但是我知道以我的自制力现......
  • 力扣 leetcode 1775. 通过最少操作次数使数组的和相等
    问题描述给你两个长度可能不等的整数数组nums1和nums2。两个数组中的所有值都在1到6之间(包含1和6)。每次操作中,你可以选择任意数组中的任意一个整数,将它变成......
  • leetcode_D6_69X的平方根
    1.题目 2.解一   主要思路:个人思路,通过二分查找的方法寻找0-x之间的算数平方根。写的比较冗余。3.解二  主要思路:牛顿迭代法求解快速零点,官方参考方法,最后......
  • vscode配置arduino环境
    vscode配置arduino环境这里再补充下vscodearduino开发环境的设置。前期准备确保本地安装了arduino环境打开vscode,安装arduino扩展:添加配置配置arduino环境......
  • CodeStar2022年秋第9周周赛普及奠基组
    T1:矩阵涂色本题难度简单,考察二维数组的基本使用。矩阵最终状态中,如果某一行全是红色,说明最后一次操作是R操作,如果某一列全是蓝色,说明最后一次操作一定是B操作代......
  • CodeStar2022年春第十一周周赛普及奠基组
    T1:牛奶供应本题难度简单,主要考察贪心算法。第\(i\)天的牛奶成本价为\(\min(c_i,minp+s)\),其中\(minp\)为前\(i-1\)天中牛奶的最低成本价代码实现#include<bit......
  • #yyds干货盘点# LeetCode程序员面试金典:返回倒数第 k 个节点
    题目:实现一种算法,找出单向链表中倒数第k个节点。返回该节点的值。注意:本题相对原题稍作改动示例:输入:1->2->3->4->5和k=2输出:4代码实现:/***Definitionforsingly-......
  • Android 通过KeyEvent.KEYCODE_BACK退出程序,只退出了当前界面,其它界面未退出简单解决
    因为我程序中有很多跳转,使用finish()只能退出当前界面,其它界面无法退出android.os.Process.killProcess(android.os.Process.myPid())   //获取PID System.exit(0);......
  • LeetCode 66.加一
    题目题目链接:https://leetcode.cn/problems/plus-one/给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。最高位数字存放在数组的首位,数组中每个......