接上一篇 EntityFrameworkCore数据迁移(一)

  其实上一篇该写的都已经写完了,但是后来又想到两个问题,想了想还是也写出来吧

  

  问题一

  上一篇介绍的迁移过程,都是通过在程序包管理器控制台使用Add-Migration和Update-Database命令执行的,那问题来了,如果是在开发过程中,我可以在VS中这么做,但是在线上环境,我们总不至于在线上服务器也安装个VS吧,那怎么进行迁移呢?

  这个时候就需要我们自己用代码去执行生成的迁移文件了。

  接上篇,我们将实体模型和数据迁移部分分到两个项目中,而EFCoreDemo.EntityFrameworkCore.Host项目是控制台项目,正好可以去用代码去执行数据迁移:

  我们修改Program:  

    class Program
{
static void Main(string[] args)
{
#region 迁移
using (var db = new DemoMigrationsDbContextFactory().CreateDbContext(args))
{
var array = db.Database.GetPendingMigrations();
int count = array.Count();
if (count == 0)
{
Console.WriteLine("nothing to migrate,current migrations is up-to-date...");
}
else
{
Console.WriteLine($"Pending Migrations:{count}");
foreach (var a in array)
{
Console.WriteLine($"Migrations:{a}");
} Console.WriteLine("Do you want to continue?(Y/N)");
if (Console.ReadLine().Trim().ToLower() == "y")
{
Console.WriteLine("Migrating..."); try
{
//执行迁移
db.Database.Migrate();
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
Console.WriteLine("Completed!!!");
}
}
#endregion Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}

  注意,上面的程序仅仅只是执行迁移文件,并不是生成迁移,所以开发过程中我们还是需要使用Add-Migration命令生成迁移。

  另外,还有一个问题需要注意,上一篇中,迁移过程中,我们的数据库连接时写死在DemoMigrationsDbContextFactory类中的,线上库和开发测试库肯定不是同一个库,那怎么去区分?

  一个想法是将数据库连接写到配置文件中去,这里只是去读取!而这个配置文件可以是json文件,也可以是xml文件,比如是json文件:

  我们在EFCoreDemo.EntityFrameworkCore.Host中创建一个json文件,比如就叫appsettings.json,然后修改文件属性的输出配置为【如果较新则复制】

  

  然后在appsettings.json中添加内容:  

  {
"ConnectionString": {
"Default": "Server=192.168.209.128;Port=3306;Database=demodb;Uid=root;Pwd=123456"
}
}

  然后修改DemoMigrationsDbContextFactory:  

    public class DemoMigrationsDbContextFactory : IDesignTimeDbContextFactory<MigrationDbContext>
{
public MigrationDbContext CreateDbContext(string[] args)
{
var configuration = BuildConfiguration(); var builder = new DbContextOptionsBuilder<MigrationDbContext>()
.UseMySql(configuration["ConnectionString:Default"]); return new MigrationDbContext(builder.Options);
} private static IConfigurationRoot BuildConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false); return builder.Build();
}
}

  最后EFCoreDemo.EntityFrameworkCore.Host的项目结构如下:

  

  这样,只需要保证我们上线和开发过程中的这个文件配置不一样就可以了

  测试一下,先清空数据库,因为先前已经生成了迁移文件,所以可以直接运行,运行后提示有两个迁移可以执行,输入y,回车即可:

  

  

  问题二

  上一篇中,我创建了两次迁移,第二迁移增加列,修改了列属性,还添加了外键约束,但是我忽略了索引。

  比如活动记录表中,一般的,一个用户参加活动只会有一条活动记录,因此活动Id和用户Id应该是可以作为一个唯一值索引的,于是乎我修改了ActivityRecordEntityTypeConfiguration:  

    public class ActivityRecordEntityTypeConfiguration : BaseEntityTypeConfiguration<ActivityRecord>
{
/// <summary>
/// 配置实体类型
/// </summary>
/// <param name="builder"></param>
public override void Configure(EntityTypeBuilder<ActivityRecord> builder)
{
base.Configure(builder); builder.HasIndex(p => new { p.ActivityId, p.AccountId }).IsUnique(true);
builder.HasOne(p => p.Activity).WithMany().HasForeignKey(p => p.ActivityId);
builder.HasOne(p => p.Account).WithMany().HasForeignKey(p => p.AccountId);
}
}

  然后执行Add-Migration和Update-Database,出乎我意料的是竟然报错了!

  

  看了一下,这里抛出异常时:Cannot drop index 'IX_demo_activityrecord_ActivityId': needed in a foreign key constraint

  这里是说,删除索引时,因为存在外键约束,所以需要先删除外键才能再删除索引!

  回想上一篇,我在再次迁移的时候,为ActivityRecord增加了外键约束,打开对应的迁移文件alter_20200727,很容易发现

    public partial class alter_20200727 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
//以上省略... migrationBuilder.CreateIndex(
name: "IX_demo_activityrecord_AccountId",
table: "demo_activityrecord",
column: "AccountId"); migrationBuilder.CreateIndex(
name: "IX_demo_activityrecord_ActivityId",
table: "demo_activityrecord",
column: "ActivityId"); migrationBuilder.AddForeignKey(
name: "FK_demo_activityrecord_demo_account_AccountId",
table: "demo_activityrecord",
column: "AccountId",
principalTable: "demo_account",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade); migrationBuilder.AddForeignKey(
name: "FK_demo_activityrecord_demo_activity_ActivityId",
table: "demo_activityrecord",
column: "ActivityId",
principalTable: "demo_activity",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
} protected override void Down(MigrationBuilder migrationBuilder)
{
//省略...
}
}

  原来,mysql创建外键约束的时候,默认会将约束列设置成索引(非唯一值索引),用以提高搜素速度,而这个索引是和外键绑定的!

  打开我们我们新建的迁移文件alter_20200728:  

    public partial class alter_20200728 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_demo_activityrecord_ActivityId",
table: "demo_activityrecord"); migrationBuilder.CreateIndex(
name: "IX_demo_activityrecord_ActivityId_AccountId",
table: "demo_activityrecord",
columns: new[] { "ActivityId", "AccountId" },
unique: true);
} protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_demo_activityrecord_ActivityId_AccountId",
table: "demo_activityrecord"); migrationBuilder.CreateIndex(
name: "IX_demo_activityrecord_ActivityId",
table: "demo_activityrecord",
column: "ActivityId");
}
}

  其中,Up方法中是先删除索引在创建新的索引,这就是迁移发生异常的原因!

  假如之前的迁移已经全部更新到线上,但是现在迁移又报错了,那怎办?

  我们的第一反应是,先到数据库自行把相关的外键约束和索引删除,在执行迁移,执行完成之后再去数据库把外键约束添加上!

  千万不到这么做!

  宁可以后开发放弃使用迁移也不要这么做,原因有二:

  第一、如果是要更新到线上,而删除约束,再执行迁移,这之间有时间差,如果有异常数据进来,可能导致约束加不回去了。如果这样,还不如直接全部使用SQL脚本执行!

  第二、本来我们只需要一个空的数据库,然后执行一次迁移就能初始化完成,然后系统就可以在这个库上运作起来,而手动改数据库后,可能导致迁移不能一次执行

  遇到这种问题,个人比较喜欢去修改迁移文件,比如这个问题,既然是删除索引时报错,那就可以把删除索引的语句注释掉:  

    public partial class alter_20200728 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
//migrationBuilder.DropIndex(
// name: "IX_demo_activityrecord_ActivityId",
// table: "demo_activityrecord"); migrationBuilder.CreateIndex(
name: "IX_demo_activityrecord_ActivityId_AccountId",
table: "demo_activityrecord",
columns: new[] { "ActivityId", "AccountId" },
unique: true);
} protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_demo_activityrecord_ActivityId_AccountId",
table: "demo_activityrecord"); //migrationBuilder.CreateIndex(
// name: "IX_demo_activityrecord_ActivityId",
// table: "demo_activityrecord",
// column: "ActivityId");
}
}

  这里需要注意的是,Up和Down两个方法建议同步修改!

  修改后执行再执行Update-Database就通过了:

  

  这里只是一种处理方式,当然还可以根据提示,先删除外键约束,再删除索引,接着加上外键约束:  

    public partial class alter_20200728 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
//删除外键约束
migrationBuilder.DropForeignKey(
name: "FK_demo_activityrecord_demo_account_AccountId",
table: "demo_activityrecord"
); //删除外键约束
migrationBuilder.DropForeignKey(
name: "FK_demo_activityrecord_demo_activity_ActivityId",
table: "demo_activityrecord"
); migrationBuilder.DropIndex(
name: "IX_demo_activityrecord_ActivityId",
table: "demo_activityrecord"); migrationBuilder.CreateIndex(
name: "IX_demo_activityrecord_ActivityId_AccountId",
table: "demo_activityrecord",
columns: new[] { "ActivityId", "AccountId" },
unique: true); //添加外键约束
migrationBuilder.AddForeignKey(
name: "FK_demo_activityrecord_demo_account_AccountId",
table: "demo_activityrecord",
column: "AccountId",
principalTable: "demo_account",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade); //添加外键约束
migrationBuilder.AddForeignKey(
name: "FK_demo_activityrecord_demo_activity_ActivityId",
table: "demo_activityrecord",
column: "ActivityId",
principalTable: "demo_activity",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
} protected override void Down(MigrationBuilder migrationBuilder)
{
//删除外键约束
migrationBuilder.DropForeignKey(
name: "FK_demo_activityrecord_demo_account_AccountId",
table: "demo_activityrecord"
); //删除外键约束
migrationBuilder.DropForeignKey(
name: "FK_demo_activityrecord_demo_activity_ActivityId",
table: "demo_activityrecord"
); migrationBuilder.DropIndex(
name: "IX_demo_activityrecord_ActivityId_AccountId",
table: "demo_activityrecord"); migrationBuilder.CreateIndex(
name: "IX_demo_activityrecord_ActivityId",
table: "demo_activityrecord",
column: "ActivityId"); //添加外键约束
migrationBuilder.AddForeignKey(
name: "FK_demo_activityrecord_demo_account_AccountId",
table: "demo_activityrecord",
column: "AccountId",
principalTable: "demo_account",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade); //添加外键约束
migrationBuilder.AddForeignKey(
name: "FK_demo_activityrecord_demo_activity_ActivityId",
table: "demo_activityrecord",
column: "ActivityId",
principalTable: "demo_activity",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}

  

  总之,使用数据迁移时,要注意以下几点:

  1、可以手动去改数据库中数据,但是尽量不要手动去改数据库结构

  2、如果迁移过程抛出异常,应先通过修改迁移文件来解决问题

  3、对于多次生成的迁移,尽量确保对一个空库,只需要指定一个Update-Database将所有迁移更新至数据库,系统就能在这个库中运作起来

  

  最后,补上测试项目的代码: https://pan.baidu.com/s/1D1chtNWDEA2pZQqtvrqAGg 提取码: epwm

EntityFrameworkCore数据迁移(二)的更多相关文章

  1. EntityFrameworkCore数据迁移(一)

    .net core出来已经有很长一段时间了,而EentityFrameworkCore(后面简称EFCore)是.net framework的EntityFramework在.net core中的实现 ...

  2. 从零自学Hadoop(17):Hive数据导入导出,集群数据迁移下

    阅读目录 序 将查询的结果写入文件系统 集群数据迁移一 集群数据迁移二 系列索引 本文版权归mephisto和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作. 文章是哥(mephis ...

  3. mongodb系列~mongodb数据迁移

    一 简介:今天来聊聊mongo的数据迁移二 迁移   1 具体迁移命令   nohup mongodump --port --db dbname --collection tablename --qu ...

  4. [EF Core]数据迁移(二)

    摘要 在实际项目中,大多都需要对业务逻辑以及操作数据库的逻辑进行分成操作,这个时候该如何进行数据的迁移呢? 步骤 上篇文章:EF Core数据迁移操作 比如,我们将数据上下文放在了Data层. 看一下 ...

  5. Asp.net Core 2.0+EntityFrameWorkCore 2.0添加数据迁移

    Asp.net Core 由于依赖注入的广泛使用,配置数据迁移,与Asp.net大不相同,本篇介绍一下Asp.net Core添加数据迁移的过程 添加Nuget包 Install-Package Mi ...

  6. Saiku数据库迁移后的刷新脚本-Shell脚本读取数据库中的数据(二十三)

    Saiku数据库迁移后的刷新脚本 之前有谈过对saiku中的数据进行刷新,因为saiku默认会从缓存中查询数据,但是配置不使用缓存又会效率低下... 所以这里就需要做一个数据刷新,每次ETL之后都需要 ...

  7. infobright系列二:数据迁移

    安装之后把之前infobright的数据迁移到新安装的infobright上. 1:挺掉相关的服务 2:scp 把旧数据拷到新安装的infobright上 3:修改/etc/my-ib.cnf的数据目 ...

  8. ABP学习入门系列(二)(abp的数据迁移)

    本文将介绍在ABP框架中将实体类迁移至数据库表 1.下图是abp的体系结构. 我们要是实现创建实体类并迁移数据到数据库的功能主要就是在下图中domain(领域层)做相应的一些操作. 2,看一下解决方案 ...

  9. EF学习之CodeFirst(二)--数据迁移

    使用CodeFirst时,如果Model发生改变的话,例如我们给User类里面新加个Sex属性,运行时会出现如下错误: 这时我们需要使用数据迁移来将model的改变同步更新到数据库中. 1.启用数据迁 ...

随机推荐

  1. 【Linux】【Services】【VersionControl】Git基础概念及使用

    1. 简介 1.1. 版本控制工具: 本地版本控制系统: 集中化版本控制系统:CVS,SVN 分布式版本控制系统: BitKeeper,Git 1.2. 官方网站: https://git-scm.c ...

  2. maven根据profile,resources,filters来区分部署环境

    项目过程中,在不同的阶段,分别需要部署开发环境,测试环境,线上环境.如果都用一套配置文件,很容易弄乱,所以维持多套配置文件很有必要. maven提供了一组属性以供开发人员灵活搭配,可以根据环境来打包, ...

  3. 1.ElasticSearch相关概念

    1.为ElasticSearch设置跨域访问 http.cors.enabled: truehttp.cors.allow-origin: "*" 2.什么是ElasticSear ...

  4. Django REST framework完全入门

    Django REST framework 一个强大灵活的Django工具包,提供了便捷的 REST API 开发框架 我们用传统的django也可以实现REST风格的api,但是顶不住Django ...

  5. 培训班输出的大量学员,会对IT行业产生哪些影响?

    先说下会有哪些影响呢?   1 可能也就是些大城市的,规模比较大的,口碑比较好的培训学校输出的码农才能入行,而且能做长久.一些线上的所谓培训机构,或者小城市的培训学校,输出的能入行的码农,其实规模很有 ...

  6. Jenkins动态选择分支/tag

    目录 一.简介 二.配置 三.配置tag 四.其它方法 五.List Git Branches插件 一.简介 一般选择分支构建,Git Parameter插件即可.这里是应用pipline的同时,可以 ...

  7. pipeline 结构设计

    目录 一.pipeline步骤 二.案例 pipeline详解 只生成一次制品 不同环境部署 系统集成测试 指定版本部署 一.pipeline步骤 当团队开始设计第一个pipeline时,该如何下手呢 ...

  8. 如何查看Python的版本号

    一.如何查看Python的版本号 win+r输入cmd在输入:python --version回车即可

  9. Tableau如何绘制堆叠柱状图

    一.将类别,子类别拖拽至列上 二.将度量值拖拽至行上 三.将度量名称拖拽至筛选器上,右键度量名称,编辑筛选器,选择销售额 四.将事先准备的目标销售额拖拽至度量值 五.将度量名称拖拽至标记,分别以颜色和 ...

  10. Table.ReorderColumns移动…Reorder…(Power Query 之 M 语言)

    数据源: 至少两列 目标: 列顺序重新排列 操作过程: 选取待移动的列>鼠标拖放列标题 选取待移动的列>[转换]>[移动]>选取 M公式:  = Table.ReorderCo ...