查缺补漏系统学习 EF Core 6 - 迁移与播种

这是 EF Core 系列的第三篇文章,上一篇文章讲述了 EF Core 中的实体属性配置。

这篇文章讲一讲 EF Core 实体的数据库迁移与种子数据的填充。

迁移

「迁移是 EF Core 通过实体创建和更新数据库的一种标准方式。」

迁移过程有两个步骤:「创建迁移」「应用迁移」

数据库结构必须与 EF Core 实体模型保持一致, EF Core 实体模型中的每一个变化,都需要被迁移到数据库。.

比如,实体类属性的变化、配置的改变、添加或移除上下文类的 DbSet 属性等。

迁移所需的 EF Core 工具,由 Microsoft.EntityFrameworkCore.Tools 库提供支持,需要我们自己安装。

迁移工具会收集有关该应用的实体类型、及其如何映射到数据库的详细信息。

如果是一个 ASP.NET Core 应用,迁移工具会尝试从应用的服务容器中,自动获取数据上下文实例。

但如果只是一个普通的控制台应用,就需要我们自己创建一个「设计时上下文工厂类」,如下所示:

public class ApplicationContextFactory : IDesignTimeDbContextFactory<ApplicationContext>
{
    public ApplicationContext CreateDbContext(string[] args)
    {
        var config = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .Build();

        var configconfig = new DbContextOptionsBuilder<ApplicationContext>();
        optionsBuilder.UseSqlServer(config.GetConnectionString("DefaultConnection"));

        return new ApplicationContext(optionsBuilder.Options);
    }
}

一个读取 「appsettings.json」 配置文件的配置对象 config

一个上下文配置构建对象 configconfig,主要是指定连接字符串;

最后返回一个上下文实例 ApplicationContext,迁移工具会自动获取它。

接着,我们可以在包管理器控制台窗口,通过命令来创建迁移:

Add-Migration InitialMigration

执行这条命令之后,EF Core 在幕后做了几件事来准备我们的迁移:

第一件事是检查相关的实体类,以及我们应用的任何配置。

之后,它在 「Migrations」 文件夹中,创建了三个不同的文件:

查缺补漏系统学习 EF Core 6 - 迁移与播种

「ApplicationContextModelSnapshot.cs」 文件保存了数据库的模型,每次添加新的迁移时都会更新。

另外两个文件,「xxx_InitialMigration.cs」 和 「xxx_InitialMigration.Designer.cs」 是包含了描述新创建的迁移的文件。

我们来看一下 「xxx_InitialMigration.cs」 文件,它有两个方法 Up和 Down:

查缺补漏系统学习 EF Core 6 - 迁移与播种

Up方法包含了当我们应用这个迁移时,将要执行的命令。

Down方法包含了当我们移除这个迁移时,要执行的命令,比如这里就是简单的删除表。

应用迁移

在我们创建了迁移之后,我们就可以应用它了,以使变化在数据库中生效。

应用迁移的方法有多种,这里我们仍然可以使用命令:

Update-Database

关于迁移,还有几个重要的事情,我们必须要知道。

如果我们检查数据库,会发现一个奇怪的表 「_EFMigrationsHistory」。EF Core 使用这个表,来跟踪应用的所有迁移。

这就意味着,如果我们在代码中,创建了另一个迁移并应用它,EF Core 将只应用新创建的迁移。

那么,EF Core 是如何知道哪个迁移需要被应用呢?

如果我们查看这个表的数据,可以发现,这个表里有一个迁移 Id 的字段。

迁移 Id 是迁移文件的唯一名称,EF Core 永远不会次执行相同名称的迁移文件。

每个迁移都是在一个 SQL 事务中应用的,也就说整个迁移要么成功、要么失败。

如果我们有多个迁移需要应用,那么它们会按照创建的顺序被依次应用。

自定义迁移

前面,我们已经知道了 「InitialMigration.cs」 文件中,Up 和 Down 方法的用途。

这些方法中的所有代码,都是由 EF Core 生成的,如果需要,我们也可以添加我们自定义的代码。

比如,我们想要在迁移表的同时,创建一个存储过程,就可以在 Up 方法中这么做:

migrationBuilder.Sql(
  @"CREATE PROCEDURE MyCustomProcedure
    AS
    SELECT * FROM Account"
);

可以使用 MigrationBuilder 参数,来帮助我们完成自定义的迁移。

当然,还应该确保在 Down 方法中,设置移除迁移时执行的相反动作:

migrationBuilder.Sql(
  @"DROP PROCEDURE MyCustomProcedure"
);

我们可以先删除刚才创建的数据库,回到未应用迁移的状态,通过命令行就可以:

Drop-Database

然后,再重新应用迁移,就可以在数据库找到这个存储过程了。

移除迁移

现在,我们已经知道如何创建迁移。

有时候,我们可能会创建出一个有问题迁移,比如我这里创建了一个空白的迁移:

Add-Migration test

我们可以使用移除指令,移除这个迁移:

Remove-Migration

种子数据

在大多数项目中,我们希望在创建的数据库中,拥有一些初始数据。

因此,当我们执行迁移来创建和配置数据库时,希望用一些初始数据来填充它,这个动作被称为数据播种。

数据播种与 Fluent API 一样,都需要在上下文类中的 OnModelCreating 方法里去定义:

modelBuilder.Entity<Account>()
    .HasData(
    new Account
    {
        Id = Guid.NewGuid(),
        Name = "Zilor",
        Age = 18
    },
    new Account
    {
        Id = Guid.NewGuid(),
        Name = "Kevin",
        Age = 18
    }
);

使用 HasData 方法来告诉 EF Core 要播种的数据。

接着,我们可以创建一个新的迁移,并应用到数据库:

Add-Migration SeedInitialData
Update-Database

进阶版

虽然把所有的配置代码放在 OnModelCreating 方法中没有任何问题。

但是,如果我们有一个更大的项目,有更多的类和更多的数据需要播种呢?

这个方法里的代码肯定会变得又多又乱,让我们难以阅读和维护。

EF Core 为我们提供了一个更好的办法,可以把这些配置分离开。

通过这个方法,我们可以将每个实体的配置,划分为自己单独的配置类。

那么该如何做到呢?我们来看这个示例:

查缺补漏系统学习 EF Core 6 - 迁移与播种

这个示例中,有一个 「Configuration」 文件夹,其中有一个 「AccountConfiguration」 类,其内容如下:

查缺补漏系统学习 EF Core 6 - 迁移与播种

该类实现了一个 IEntityTypeConfiguration 泛型接口,配置与之前的 OnModelCreating 方法中的代码一样。不同的是,不必再使用 Entity 方法来指定实体类了、

这是因为构建器对象,已经是 EntityTypeBuilder<Account> 的类型。

我们只需要在 OnModelCreating 方法中,应用这个实体配置就可以了。

这样不管实体类有多少,每一个实体类都有一个独立的配置,代码的可读性和维护性就有了。

小结

这篇文章主要讲了 EF Core 实体的数据库迁移以及数据播种,下篇文章我们将盘点一下 EF Core 数据查询的几种方式。