这是「秦巴牧云踩坑录」第 3 篇,接着第 2 篇的多租户隔离,复盘那场 EF Core 迁移事故。
说真的,做秦巴牧云那阵子,我差点把 EF Core 的迁移当银弹供起来。本地 EnsureCreated 一跑,库和表全冒出来了,那叫一个爽。
等发到生产,三张新表死活没建出来,我才反应过来,迁移这玩意儿有多会骗人。
秦巴牧云是 .NET 8 加 EF Core Code First,多租户,靠 IDbContextFactory 给每个租户开上下文。
一、EnsureCreated 的甜区和雷区
开发期 EnsureCreated 是真省心。应用一启动,DbContext 看没库就建库,没表就建表。
我最早就靠它。写个实体,跑起来,库自己长出来,效率高得离谱。
// 开发期图省事,启动即建库建表
using var scope = app.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Database.EnsureCreated();
可甜区背面就是雷区。EnsureCreated 只认当下模型对不对得上,压根不往 __EFMigrationsHistory 里写半条记录。
更要命的是,模型一变它就 DROP 再重建。开发库被我这么搞丢过几回测试数据,当时压根没往心里去。
生产环境这是碰都不能碰的。你真敢让框架在真实库上说删就删?
二、切 Migrate 的当头一棒
后来我想走正路,把 EnsureCreated 换成 Migrate。本地测试库顺顺当当,迁移记录清清楚楚。
发布到生产那一秒,日志里蹦出一句:There is already an object named 'Feeds' in the database。
根因简单得气人。生产库是早年被 EnsureCreated 建起来的,表都齐,唯独 __EFMigrationsHistory 空着。
// 以为切到 Migrate 就万事大吉
await db.Database.MigrateAsync();
// 生产库:表已存在,但历史表为空 -> 框架以为要从头建 -> 撞已存在的表
Migrate 一看历史表空,以为一张迁移都没跑过,老老实实从头 CreateTable。表早杵在那儿了,直接撞上去。
这坑闷得很。本地好好的,生产一炸,你才发现自己对迁移历史两眼一抹黑。
三、工厂模式又加一层
秦巴牧云用的是工厂模式。每个租户一个 DbContext 实例,靠 IDbContextFactory 现建现用。
工厂模式默认不替你触发迁移。你要手痒在工厂里顺手塞一句 Migrate,麻烦就大了。
// 千万别在工厂里这么写
public AppDbContext CreateDbContext()
{
var ctx = new AppDbContext(_options);
ctx.Database.Migrate(); // 并发请求同时建上下文 -> 同时触发迁移 -> 锁竞争
return ctx;
}
高峰期一堆请求同时建上下文,每个都去跑迁移。迁移这动作本身不带幂等锁,两个线程抢着建同一张表,要么报错要么卡死。
迁移的时机,得从工厂里拎出来,单独在某个启动点跑一次就够。
四、我们的四道防线
第一道,直接拍板:秦巴牧云不启用 EF 迁移。新表一律手写 SQL 脚本,丢进 Sql 目录,受 Git 管。
-- Sql/001_create_feeds.sql 受版本管理,谁改谁留记录
CREATE TABLE Feeds (
Id BIGINT IDENTITY PRIMARY KEY,
TenantId UNIQUEIDENTIFIER NOT NULL,
Name NVARCHAR(200) NOT NULL
);
第二道,上线前必须在测试库把脚本跑一遍。跑完对着实体一对,缺哪个字段立马现原形。
第三道,生产环境把 EnsureCreated 彻底关掉。那段代码拿环境变量 gate 住,只在开发环境才放它出来。
if (env.IsDevelopment())
{
db.Database.EnsureCreated(); // 生产绝不执行这行
}
第四道,建表脚本和实体对齐写进 Code Review 清单。谁加了实体忘了补脚本,评审直接打回去。
四道防线落下去,秦巴牧云再没在发布夜为缺表救过火。迁移这事,开发期偷的懒,生产期都加倍还了。
结尾
EF Core 迁移不是不能用,是要看场景。多租户加工厂模式,手动脚本反而最稳当。
你在 EF Core 迁移上栽过最惨的坑是啥?评论区唠唠。
转发给正被迁移坑过的同事,少走我们趟过的弯路。
关注 CSharp精选营,每周二四 get 能直接抄的 C# 实战。
暂无评论,来抢沙发吧~