一种更安全的 EF Core 自动迁移方案:生产环境中的可控数据库更新实践
一种更安全、可控的 EF Core 自动迁移方案:模块化架构下的工程化实践
在现代应用开发中,数据库迁移已经成为不可或缺的工程环节。
特别是在 模块化架构、分布式系统、多团队协作 的背景下,传统 EF Core 迁移虽然足够灵活,但在工程化落地方面仍存在一些现实挑战,例如:
- 各模块拥有独立的 DbContext
- 多人并行开发导致迁移脚本冲突
- 生产环境通常禁止破坏性更改
- 某些实体(例如 DTO)不应出现在数据库中
- 不同数据库对大小写的敏感性差异会引发迁移问题
为了在保持 EF Core 使用体验的基础上增强其工程化能力,我设计并开源了一个迁移增强库 —— Daibitx.EFCore.AutoMigrate。
它的目标并不是替代 EF Core,而是在其基础之上提供一种更稳健、安全且更适合模块化工程的迁移方式。
本文将从工程需求出发,系统介绍这一方案背后的设计理念、解决的核心问题,以及它在模块化项目中的实际表现。
📘 GitHub(开源地址)
https://github.com/daibitx/Daibitx.EFCore.AutoMigrate
欢迎 Star,这是对开源作者最大的支持。
📦 NuGet 包
一、为什么传统 EF Core 迁移在工程化场景不够用?
虽然 EF Core 的 Migrations 机制功能丰富,但它默认假设的场景是:
单应用、单团队、单模型、单数据库。
而当架构逐渐模块化或开始多人协作时,一些问题会不可避免地出现。
1. 多 DbContext / 多模块带来的结构管理难度
每个模块维护自己的实体、自己的上下文,一旦参与同一个数据库结构,就会变成“多方共建”模式,而 EF Core 并没有提供模块级的安全隔离。
2. 多团队并行开发容易产生迁移冲突
例如:
- A 模块添加字段
- B 模块删除字段
如果缺少安全边界,生产库可能遭遇不可逆的破坏。
3. 生产环境通常不允许高风险操作
包括但不限于:
- Drop Table
- Drop Column
- Rename Column
- 修改列类型
这些操作在生产环境中极其危险,哪怕在开发环境看似正常。
4. 某些类是 DTO/ReadModel,不应进入迁移系统
真实项目里有大量 DTO 类,例如:
UserDtoOrderQueryDtoProductItemDTO
这些类只是:
- 视图模型
- 读模型
- API 输入输出结构
- 查询模型(CQRS)
如果误被 EF Core 管理,将导致数据库生成无意义的表。
5. 多数据库的大小写敏感差异会导致失败
PostgreSQL、MySQL 对大小写敏感,而 SQL Server 不敏感。
这会导致迁移过程出现:
- 表名冲突
- 索引重复
- 列名冲突
这些通常在生产才暴露,非常危险。
二、Daibitx.EFCore.AutoMigrate 的设计目标
针对以上问题,本方案的目标十分明确:
在 EF Core 原生能力之上,构建一套更安全、更可控、更适合模块化的“增强迁移层”。
核心原则包括:
- 默认安全,不允许破坏性操作
- 迁移过程具备事务性
- 支持 DTO/只读模型过滤
- 兼容多数据库环境
- 并发迁移安全
- 设计时服务可模块化加载
换句话说:
它不是为了“更智能”,而是为了“更可控”。
这也是生产系统真正需要的。
三、关键能力解析
1. 安全模式(SafeMode)
SafeMode 是生产环境最核心的能力。
它会禁止:
- Drop Table
- Drop Column
- Modify Column
- Rename Table / Rename Column
只允许:
- Add Table
- Add Column
- Add Index
- Add ForeignKey
避免迁移脚本对其他团队造成破坏,是模块化架构的基础。
2. 事务式迁移执行
迁移步骤全部在数据库事务中执行:
- 任一步骤失败则自动回滚
- 保证迁移结果一致性
- 避免出现“迁移中断导致数据库半结构”的问题
这在生产环境尤其关键。
3. DTO 自动过滤(工程化特性,强烈推荐)
这是基于真实工程场景加入的最实用功能之一:
任何以
Dto(大小写不敏感)结尾的实体,将自动排除在迁移之外。
源代码过滤逻辑如下:
.Where(e => !e.ClrType.Name.EndsWith("Dto", StringComparison.OrdinalIgnoreCase))
4. 并发安全
服务端集群启动时,多个实例同时执行迁移极容易导致:
- 死锁
- 部分实例失败
AutoMigrate 内置并发安全保护,确保迁移只执行一次。
5. 多数据库 Provider 支持
包括:
- SQL Server
- MySQL
- PostgreSQL
- SQLite
- 以及其他支持 EF Core 的 Provider
四、使用示例
方式 1:在 ASP.NET Core 中自动迁移(推荐)
app.Services.AutoMigrate<MyDbContext>(services =>
{
// 配置设计时服务
}, options =>
{
options.AsSafeMode();
});
方式 2:手动迁移
context.AutoMigrate(...);
方式 3:使用 Builder 模式
var runner = new MigrateBuilder<MyDbContext>(dbContext)
.WithOptions(new AutoMigrationOptions().AsSafeMode())
.Build();
await runner.ExecuteAsync();
五、生产环境最佳实践
1. 强烈建议使用安全模式
options.AsSafeMode();
2. 不要直接修改列结构
请采用:
- 新增列
- 数据迁移
- 删除旧列(开发或测试环境执行)
3. 注意数据库大小写敏感设置
问题描述: 直接在不存在的数据库上使用 EF Core 迁移可能会生成字段不敏感的数据库表,导致后续迁移时出现字段索引名称重复等问题。
解决方案:
方案一:通过 Fluent API 配置(推荐)
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 设置所有字符串列区分大小写
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (property.ClrType == typeof(string))
{
property.SetCollation("SQL_Latin1_General_CP1_CS_AS"); // SQL Server
// 或者对于 MySQL: property.SetCollation("utf8mb4_bin");
// 或者对于 PostgreSQL: property.SetCollation("C");
}
}
}
}
方案二:通过数据注解
public class MyEntity
{
public int Id { get; set; }
[Column(TypeName = "varchar(255) COLLATE SQL_Latin1_General_CP1_CS_AS")]
public string Name { get; set; }
}
方案三:数据库层面设置
在创建数据库时指定区分大小写的排序规则:
-- SQL Server
CREATE DATABASE MyDatabase COLLATE SQL_Latin1_General_CP1_CS_AS;
-- MySQL
CREATE DATABASE MyDatabase CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
-- PostgreSQL
CREATE DATABASE MyDatabase ENCODING 'UTF8' LC_COLLATE 'C' LC_CTYPE 'C';
六、关于 DTO 过滤的设计思考
这个机制是根据大量实际项目经验总结出来的。
在模块化或 DDD 项目中,一个复杂模块通常包含:
- 聚合根实体
- ValueObject
- 多个 DTO(读模型、投影模型)
- 后台报表模型
但其中真正对应数据库表的只有一部分。
如果所有类都被 EF Core 扫描并参与迁移——
- 迁移文件会变得巨大且无意义
- 数据库会出现大量无用表
- 模块之间可能因为 DTO 冲突导致迁移失败
因此,AutoMigrate 内置过滤规则,可以说是模块化数据库建模中一个非常实用、工程化的功能点。
七、总结
Daibitx.EFCore.AutoMigrate 的核心价值在于:
- 让 EF Core 的迁移机制更适合模块化、多团队协作的体系
- 提供清晰、可控、安全的迁移策略
- 解决 DTO/查询模型误导致数据库结构污染的问题
- 对生产环境更友好
- 提供工程级的事务、安全保护与数据库兼容性支持
如果你的架构包含:
- 多模块
- 多 DbContext
- 多团队协作
- 多环境(开发、测试、生产)
- 多数据库适配
那么 AutoMigrate 可能正是你需要的那一层“工程化补丁”。

浙公网安备 33010602011771号