在创建实体类、实体配置类和 DbContext 类之后,接下来就需要生成数据库了。在 EFCore 中,数据库都是根据代码生成的,而不是程序员创建的,即程序员先创建实体,然后通过工具生成数据库,在生成过程中各个实体被映射到数据库的各个表,各个实体的属性被映射到各个表的数据列。
生成数据库之前,需要安装 Microsoft.EntityFrameworkCore.Tools 包,然后在控制台当前项目下执行以下两行命令即可:
Add-Migration Init
Update-Database
执行后,工具会根据 DbContext 中的配置连接对应的数据库,然后生成对应的数据库和表。上述的 Book 实体生成的 SQLServer 数据表设计如下
Migration
上面生成数据库所使用的工具就是 Migration,在 EFCore 中数据库设计的改变是通过 Migration 来实现的。Migration 的中文意思是”迁移“,但在 EFCore 里它具有更广泛的意思:程序员在开发过程中,可能对实体类进行修改,比如增加实体类或增加实体类的属性,这相当于在数据库中增加一张表或者在某张表中增加一列,对数据库的一次修改,称为一次 Migration。
例如上面通过 Add-Migration 命令就提交了一次名为 Init 的 Migration,它将会告诉 EFCore 去创建数据库并增加 Book 实体对应的数据表,虽然这是从无到有,但 EFCore 也认为它属于 Migration 的一种。
假设此时对 Book 实体进行修改,在 BookConfig 中添加如下代码:
class BookConfig : IEntityTypeConfiguration<Book> { public void Configure(EntityTypeBuilder<Book> builder) { builder.ToTable("T_Books"); builder.Property(e => e.Title).HasMaxLength(50).IsRequired(); builder.Property(e => e.Author).HasMaxLength(20).IsRequired(); } }
这将修改实体的 Title 属性和 Author 属性,使它们分别具有 50 和 20 的最大长度,并且都不可为空。此时实体配置类修改了,需要提交一次 Migration,通过 Add-Migration 命令,后面接此 Migration 的名字:
Add-Migration AddMaxLen
我们如何浏览已经提交的 Migration 呢?其实,在每次提交 Migration 时,EFCore 都会在当前项目下的 Migrations 目录创建对应的数据库设计代码文件,这些代码就是用来实现数据库设计的更新的。例如这次 AddMaxLen 的 代码:
using Microsoft.EntityFrameworkCore.Migrations; namespace EFCore.Migrations { public partial class AddMaxLen : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AlterColumn<string>( name: "Title", table: "T_Books", type: "nvarchar(50)", maxLength: 50, nullable: false, defaultValue: "", oldClrType: typeof(string), oldType: "nvarchar(max)", oldNullable: true); migrationBuilder.AlterColumn<string>( name: "Author", table: "T_Books", type: "nvarchar(20)", maxLength: 20, nullable: false, defaultValue: "", oldClrType: typeof(string), oldType: "nvarchar(max)", oldNullable: true); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.AlterColumn<string>( name: "Title", table: "T_Books", type: "nvarchar(max)", nullable: true, oldClrType: typeof(string), oldType: "nvarchar(50)", oldMaxLength: 50); migrationBuilder.AlterColumn<string>( name: "Author", table: "T_Books", type: "nvarchar(max)", nullable: true, oldClrType: typeof(string), oldType: "nvarchar(20)", oldMaxLength: 20); } } }
当然开发人员并不需要编写这些代码,它们都是通过工具生成的,只需要知道每一个 Migration 都有一个 Up 和 Down 的方法,它们分别是这次数据库修改的代码和撤销修改的代码。注意这个时候数据库并没有发送变化,因为提交 Migration 只是让 EFCore 生成了代码,并没有执行。接下来要更新数据库的话需要再执行:
Update-Database
这行命令会让 EFCore 将数据库更新到最新的 Migration。
实际上,Migration 机制有点类似于 Git 的版本控制,每一次数据库设计的修改,都通过一次 Migration 来表示,而 Migration 其实就是一个个 CS 代码文件,每个代码文件中都将这次数据库设计的修改翻译为了 C# 代码,之后通过 Update-Database 命令来执行这些代码,并更新数据库。EFCore 也会同步创建一个名为 __EFMigrationsHistory 的数据库,这个数据库储存了当前程序数据库处于哪个 Migration 状态。
Migration 不仅支持更新,也支持回退,通过
Update-Database XXX
可以让数据库回滚或者更新(这取决于当前数据库处于哪个 Migration)到名为 XXX 的 Migration。
在使用 Update-Database 命令时,EFCore 首先会到 __EFMigrationsHistory 数据库查看当前程序数据库处于哪个 Migration,然后判断这个 Migration 在 XXX 之前还是在 XXX 之后,如果在 XXX 之前,那么 EFCore 就会执行当前 Migration 到 XXX 的所有 Migration 的 Up 方法,这样就实现了数据库更新。如果在 XXX 之后,那么 EFCore 就会执行当前 Migration 到 XXX 的所有 Migration 的 Down 方法,这样就实现了数据库回滚。
通过 Remove-Migration 命令也可以撤销最近提交的一次 Migration,但这只是删除了 Migration,并不会导致数据库发生变化,如果需要撤销数据库中已经发生的更改,应该使用 Update-DataBase 进行回滚。
使用 EFCore,开发人员便只通过 Migration 进行数据库设计的更改,如果此时手动修改数据库设计,可能会导致 Migration 机制无法正常执行。