如何在xUnit中替换EF Core的默认Schema?
在EF Core(Database-First)集成测试中覆盖Oracle默认Schema
问题背景
我需要对基于EF Core(Database-First)连接Oracle数据库的API做集成测试。一开始用Testcontainers.Oracle,但本地运行速度太慢,所以打算用独立Schema做测试,且不想用内存数据库。现有两个Schema:
- Schema1:生成DbContext时用的原始Schema
- Schema2:打算在此创建Schema1的表并运行测试
现在想在Web应用工厂中覆盖默认Schema,之前试过在源项目里判断是否运行xUnit测试,但不想这么做——因为重新生成DbContext时会被覆盖,希望所有修改都在测试项目中完成。
现有代码如下:
public class MyApiFactory : WebApplicationFactory<IApiMarker> { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureTestServices(services => { services.RemoveAll(typeof(ApplicationDbContext)); services.AddDbContext<ApplicationDbContext>(optionsBuilder => optionsBuilder.UseOracle("")); // 是否可以在这里修改model builder的默认Schema? }); } } public partial class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public virtual DbSet<MyTable> MyTable{ get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasDefaultSchema("Schema1"); #if DEBUG if (UnitTestDetector.IsRunningFromXUnit) { modelBuilder.HasDefaultSchema("Schema2"); } #endif modelBuilder.Entity<MyTable1>(entity => { // 表配置逻辑 }); } }
解决方案
方案一:派生测试专用DbContext(推荐)
在测试项目中创建ApplicationDbContext的派生类,重写OnModelCreating方法覆盖默认Schema,所有测试相关逻辑都放在这里,完全隔离源项目代码,不会被DbContext重生成操作覆盖。
步骤1:创建测试用DbContext
public class TestApplicationDbContext : ApplicationDbContext { public TestApplicationDbContext(DbContextOptions<TestApplicationDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { // 先执行基类的模型配置,确保原始表结构、约束等被完整加载 base.OnModelCreating(modelBuilder); // 覆盖默认Schema为Schema2 modelBuilder.HasDefaultSchema("Schema2"); // 如果基类中实体配置硬编码了Schema(比如ToTable("MyTable1", "Schema1")),需要修改实体的Schema // 批量修改所有实体Schema的方式: foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { entityType.SetSchema("Schema2"); } } }
步骤2:在Web应用工厂中注册测试用DbContext
public class MyApiFactory : WebApplicationFactory<IApiMarker> { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureTestServices(services => { // 移除源项目中注册的DbContext选项 var dbContextOptionsDescriptor = services.SingleOrDefault( d => d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>)); if (dbContextOptionsDescriptor != null) { services.Remove(dbContextOptionsDescriptor); } // 注册测试用的DbContext,替换原始的ApplicationDbContext services.AddDbContext<ApplicationDbContext, TestApplicationDbContext>(optionsBuilder => { optionsBuilder.UseOracle("你的测试Oracle连接字符串"); // 可选:添加测试相关配置,比如禁用查询跟踪提升性能 optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); }); }); } }
方案二:直接在Web应用工厂中修改模型配置
如果不想派生DbContext,可以在注册DbContext时,手动修改模型的Schema配置:
public class MyApiFactory : WebApplicationFactory<IApiMarker> { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureTestServices(services => { services.RemoveAll(typeof(DbContextOptions<ApplicationDbContext>)); services.AddDbContext<ApplicationDbContext>(optionsBuilder => { optionsBuilder.UseOracle("你的测试Oracle连接字符串"); // 自定义模型配置,覆盖默认Schema optionsBuilder.UseModel(modelBuilder => { // 先应用原始DbContext的模型配置 var originalContext = new ApplicationDbContext(new DbContextOptions<ApplicationDbContext>()); originalContext.OnModelCreating(modelBuilder); // 修改默认Schema modelBuilder.HasDefaultSchema("Schema2"); // 批量修改所有实体的Schema foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { entityType.SetSchema("Schema2"); } }); }); }); } }
注意事项
- 确保测试环境中的Oracle数据库已创建Schema2,且包含与Schema1一致的表结构(可在测试初始化时执行DDL脚本或EF迁移)
- 若源项目的DbContext是通过Scaffold命令重新生成的,方案一的派生类不会被影响,完全隔离测试逻辑
- 若实体配置中硬编码了Schema名称,必须修改实体的Schema配置,仅修改默认Schema无法生效
内容的提问来源于stack exchange,提问作者darrenleeyx
相关产品推荐
相关产品推荐

