单元测试中使用EnsureDeleted时如何不影响_EFMigrations表?
保留迁移表的数据库重置方案(.NET原生实现)
问题根源
EnsureDeleted()会直接删除整个数据库,包括EF Core用来追踪迁移记录的_EFMigrations表。后续执行数据库更新时,EF会误以为从未执行过任何迁移,试图从头运行所有迁移脚本,自然会和已存在的表结构冲突报错。
.NET原生替代方法
1. 只清空业务数据,保留库结构与迁移表
如果单元测试仅需要干净的测试数据,无需重建数据库结构,可以跳过EnsureDeleted(),用EnsureCreated()确保数据库存在后,手动清空所有业务表:
using var context = new YourDbContext(); // 确保数据库已创建(包含迁移表和完整结构) context.Database.EnsureCreated(); // 获取所有业务表(排除迁移表) var businessTables = context.Model.GetEntityTypes() .Where(entity => entity.GetTableName() != "_EFMigrations") .Select(entity => entity.GetTableName()); // 逐个清空表(PostgreSQL需处理外键和自增序列) foreach (var table in businessTables) { context.Database.ExecuteSqlRaw($"TRUNCATE TABLE \"{table}\" RESTART IDENTITY CASCADE;"); } context.SaveChanges();
RESTART IDENTITY会重置自增字段的序列,CASCADE处理外键关联的级联清空,可根据业务需求调整
2. 备份迁移表后删库重建
如果必须重建数据库结构,但要保留迁移记录,可以先备份迁移表数据,删库重建后再恢复:
using var context = new YourDbContext(); // 备份迁移表数据 var migrationRecords = context.Database.SqlQueryRaw<MigrationHistory>( "SELECT MigrationId, ProductVersion FROM \"_EFMigrations\"" ).ToList(); // 删除并重建数据库 context.Database.EnsureDeleted(); context.Database.EnsureCreated(); // 恢复迁移表记录 foreach (var record in migrationRecords) { context.Database.ExecuteSqlRaw( "INSERT INTO \"_EFMigrations\" (MigrationId, ProductVersion) VALUES ({0}, {1})", record.MigrationId, record.ProductVersion ); } context.SaveChanges();
注意:需确保MigrationHistory类的结构与_EFMigrations表字段匹配,也可直接用匿名类型接收数据
3. 用Migrate()替代EnsureCreated()
单元测试中直接调用Migrate(),它会自动应用所有未执行的迁移,不会删除任何表,之后再清空业务数据:
using var context = new YourDbContext(); // 应用所有未完成的迁移(保留迁移表状态) context.Database.Migrate(); // 清空业务表(同方法1) var businessTables = context.Model.GetEntityTypes() .Where(entity => entity.GetTableName() != "_EFMigrations") .Select(entity => entity.GetTableName()); foreach (var table in businessTables) { context.Database.ExecuteSqlRaw($"TRUNCATE TABLE \"{table}\" RESTART IDENTITY CASCADE;"); } context.SaveChanges();
这种方式和生产环境的迁移流程完全一致,避免EnsureCreated()可能导致的结构不一致问题,更适合长期维护
注意点
- 尽量避免在单元测试中频繁调用
EnsureDeleted(),除非完全不需要保留迁移状态 - PostgreSQL表名区分大小写,SQL语句中要给表名加上双引号
- 使用
TRUNCATE前确认外键关系,避免因级联清空导致意外数据丢失
内容的提问来源于stack exchange,提问作者Роман Пермяков
相关产品推荐
相关产品推荐

