接上一篇 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. oracle 以SYSDBA远程连接数据库

    在服务器用sysdba登陆 grant sysdba to system 然后在远程就可以sysdba登陆数据库了

  2. MySQL(2):数据管理

    一. 外键概念: 如果公共关键字在一个关系中是主关键字,那么这个公共关键字被称为另一个关系的外键.由此可见,外键表示了两个关系之间的相关联系.以另一个关系的外键作主关键字的表被称为主表,具有此外键的表 ...

  3. 用运oracel中的伪列rownum分页

    在实际应用中我们经常碰到这样的问题,比如一张表比较大,我们只要其中的查看其中的前几条数据,或者对分页处理数据.在这些情况下我们都需要用到rownum.因此我们要理解rownum的原理和使用方法. Or ...

  4. 【Java 基础】java 创建对象时重写方法

    TransactionLock mockLock = new TransactionLock() { public boolean lock(String id) { return true; } p ...

  5. Linux提取命令grep 有这一篇就够了

    grep作为linux中使用频率非常高的一个命令,和cut命令一样都是管道命令中的一员.并且其功能也是对一行数据进行分析,从分析的数据中取出我们想要的数据.也就是相当于一个检索的功能.当然了,grep ...

  6. shell脚本 阿里云基线检查一键配置

    一.简介 源码地址 日期:2017/9/1 介绍:安全加固脚本,会符合阿里云基线检查.有幂等性,可重复执行 效果图: 二.使用 适用:centos6/7 语言:中文 注意:脚本是符合阿里云基线检查的配 ...

  7. Python用xlrd读取Excel数据到list中再用xlwt把数据写入到新的Excel中

    一.先用xlrd读取Excel数据到list列表中(存入列表中的数据如下图所示) import xlrd as xd #导入需要的包 import xlwt data =xd.open_workboo ...

  8. tableau绘制饼图

    一.将类别拖拽至列,将销售额拖拽至行 二.点击右上角智能显示选择饼图 三.拖拽销售额至标记卡,右键快速表计算-合计百分比-细节处理最终结果如下图所示

  9. Three.js 实现3D全景侦探小游戏🕵️

    背景 你是嘿嘿嘿侦探社实习侦探️,接到上级指派任务,到甄开心小镇调查市民甄不戳宝石失窃案,根据线人流浪汉老石‍提供的线索,小偷就躲在小镇,快把他找出来,帮甄不戳寻回失窃的宝石吧! 本文使用 Three ...

  10. ciscn_2019_s_3

    拿到题目例行检查 64位程序开启了nx保护,将程序放入ida 看到没有system函数第一时间想到的就是泄露libc来做,后来才知道是我学识尚浅,需要用execve函数来做 进入main发现跳转到vu ...