在EF Core的Code First开发模式中,实体类和数据库上下文是数据结构的主要来源。要让关系型数据库与这些C#类保持一致,手动编写SQL脚本不仅工作量大,而且当模型频繁调整时很容易遗漏字段或索引。EF Core的迁移功能正是为解决这一问题而设计:它把模型变化转化为一组可重复执行的迁移文件,并能自动判断哪些迁移尚未应用到目标数据库。本文以控制台项目为例,完整演示如何安装迁移工具、生成初始迁移、更新数据库,以及后续修改实体后如何追加迁移和回滚。

一、EF Core迁移机制解决了什么问题
EF Core迁移并不是在每次启动时清空数据库再重新建表,而是基于模型快照进行增量对比。项目第一次执行迁移命令时,工具会为整个DbContext生成一个初始快照文件,同时创建第一个迁移类。快照记录的是当前模型完整的结构信息,例如每个实体包含哪些属性、最大长度、是否可空、主外键关系等。后续添加迁移时,EF Core会把当前模型与快照进行差异比较,只把新增、修改或删除的部分写入新的迁移文件,并同步更新快照。
这样做的好处有两个:一是数据库结构变更可以纳入版本控制,多人协作时只要提交迁移文件,其他成员拉取代码后执行更新命令就能得到相同结构;二是迁移文件本身可读可改,遇到特殊需求时可以在自动生成的Up和Down方法中手动补充SQL逻辑。相比完全依赖数据库优先或手动维护SQL脚本,Code First迁移更适合以领域模型为中心、需求持续变化的项目。
理解这套机制后再看命令,就不容易混淆。添加迁移只负责生成文件,不会连接数据库执行任何变更;更新数据库才真正读取迁移记录并应用尚未执行的迁移。因此日常开发通常是先改实体,再添加迁移,最后更新本地开发库。
二、创建初始迁移并生成数据库
第一步是准备一个包含EF Core的C#项目。可以在空控制台项目中加入Microsoft.EntityFrameworkCore.SqlServer和Microsoft.EntityFrameworkCore.Tools两个包,前者提供SQL Server数据库提供程序,后者提供迁移命令。以.NET 8控制台项目为例,项目文件中的包引用如下。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
</ItemGroup>
</Project>
接下来定义实体和数据库上下文。这里创建一个简单的Blog实体,包含主键、标题、正文和创建时间,并让AppDbContext继承DbContext,在OnConfiguring方法中配置SQL Server连接字符串。为了演示简洁,连接字符串直接写在代码中;实际项目建议读取配置文件。
using Microsoft.EntityFrameworkCore;
public class Blog
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
}
public class AppDbContext : DbContext
{
public DbSet<Blog> Blogs { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=DemoDb;Trusted_Connection=True;");
}
}
完成模型定义后,在项目根目录打开终端,先执行添加初始迁移命令。命令执行成功后,项目中会出现Migrations文件夹,里面包含一个以时间戳加InitialCreate命名的迁移类、一个设计时工厂类和一个模型快照文件。随后执行更新数据库命令,EF Core会连接数据库,创建Blogs表和__EFMigrationsHistory记录表。
dotnet ef migrations add InitialCreate dotnet ef database update
此时可以打开数据库查看,Blogs表的字段与Blog实体一一对应,__EFMigrationsHistory表中则记录了InitialCreate这条迁移的Id和生成时间。如果数据库不存在,EF Core会先创建数据库再执行迁移;如果数据库已经存在但没有迁移记录表,首次更新时会自动创建该表并执行所有迁移。
三、修改模型后的增量迁移与回滚
初始数据库生成后,需求发生变化,例如Blog需要增加一个Author字段。正确流程是先在Blog类中添加属性,然后再次执行添加迁移命令,并给这次迁移起一个能描述变更的名称。
public class Blog
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
public string Author { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
}
然后执行命令:dotnet ef migrations add AddBlogAuthor。EF Core会对比当前模型与快照,识别出Blogs表多了一个Author列,并生成新的迁移文件。打开该迁移类可以看到自动生成的Up和Down方法,分别对应升级和降级操作。
public partial class AddBlogAuthor : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Author",
table: "Blogs",
type: "nvarchar(max)",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Author",
table: "Blogs");
}
}
执行更新数据库命令后,数据库中的Blogs表会新增Author列,__EFMigrationsHistory表也会追加AddBlogAuthor记录。如果发现这次变更有问题,可以回滚到上一个迁移版本。例如执行dotnet ef database update InitialCreate,EF Core会运行AddBlogAuthor迁移类的Down方法,删除Author列,并把迁移记录从历史表中移除。回滚操作依赖Down方法,因此自动生成的迁移都提供默认降级逻辑,但如果手动修改了Up方法,也要同步修改Down方法,保证来回切换不会产生不一致。
在开发阶段如果只生成了迁移文件但尚未应用到数据库,可以使用dotnet ef migrations remove删除最后一个迁移。这个命令要求该迁移尚未被数据库执行,否则会报错。频繁调整模型时,先用remove撤销未应用的迁移,再修改实体后重新添加,可以保持迁移历史简洁。
四、迁移文件的内部结构与注意事项
每个迁移通常对应三个文件。以AddBlogAuthor为例,AddBlogAuthor.cs是迁移主文件,包含Up和Down方法;AddBlogAuthor.Designer.cs用于设计时元数据,帮助工具在后续变更中准确对比;AppDbContextModelSnapshot.cs是模型快照,每次添加迁移后都会更新为当前完整模型。迁移主文件中的migrationBuilder参数提供了大量API,例如AddColumn、DropColumn、CreateIndex、AlterColumn,这些API最终由数据库提供程序翻译成具体的DDL语句。
数据库中的__EFMigrationsHistory表是迁移执行状态的关键。它只有两列:MigrationId和ProductVersion。更新数据库时,EF Core先读取该表已应用的迁移Id,再按依赖顺序执行尚未应用的迁移;如果某个迁移已经执行过,就会被跳过。因此不要手动删除该表中的记录,否则EF Core可能认为某些变更尚未应用,导致重复建表或列名冲突。
实际使用中还有几个容易忽略的点。第一,修改实体属性类型或长度时,如果数据库列不允许隐式转换,自动生成的AlterColumn语句可能失败,此时需要在Up方法中手动先执行SQL转换数据,再修改列定义。第二,迁移文件一旦提交到版本库并被其他环境应用,就不要私自改动历史迁移类的代码,应该新增一个迁移来修正。第三,生产环境部署时如果没有安装dotnet-ef工具,可以在应用启动时调用context.Database.Migrate(),让程序自动执行待处理的迁移。
using (var context = new AppDbContext())
{
context.Database.Migrate();
}
掌握这些基础操作后,可以进一步学习种子数据迁移、复杂索引、表拆分等高级主题。EF Core迁移的价值在于把数据库结构纳入C#代码的版本管理流程中,让团队协作和部署更加可靠。
EF Core迁移Code First数据库更新修改时间:2026-08-28 22:40:02