这是 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」 文件夹中,创建了三个不同的文件:
「ApplicationContextModelSnapshot.cs」 文件保存了数据库的模型,每次添加新的迁移时都会更新。
另外两个文件,「xxx_InitialMigration.cs」 和 「xxx_InitialMigration.Designer.cs」 是包含了描述新创建的迁移的文件。
我们来看一下 「xxx_InitialMigration.cs」 文件,它有两个方法 Up
和 Down
:
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 为我们提供了一个更好的办法,可以把这些配置分离开。
通过这个方法,我们可以将每个实体的配置,划分为自己单独的配置类。
那么该如何做到呢?我们来看这个示例:
这个示例中,有一个 「Configuration」 文件夹,其中有一个 「AccountConfiguration」 类,其内容如下:
该类实现了一个 IEntityTypeConfiguration
泛型接口,配置与之前的 OnModelCreating
方法中的代码一样。不同的是,不必再使用 Entity
方法来指定实体类了、
这是因为构建器对象,已经是 EntityTypeBuilder<Account>
的类型。
我们只需要在 OnModelCreating
方法中,应用这个实体配置就可以了。
这样不管实体类有多少,每一个实体类都有一个独立的配置,代码的可读性和维护性就有了。
小结
这篇文章主要讲了 EF Core 实体的数据库迁移以及数据播种,下篇文章我们将盘点一下 EF Core 数据查询的几种方式。