You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

同一单元测试中两个不同EF DbContext无法正常工作的问题排查

问题根源分析

这个问题的核心是EF 6的DbConfiguration是全局单例设计——一旦某个DbContext初始化时加载了对应的配置,整个AppDomain里的EF都会复用这个配置,后续的配置变更会被直接忽略。而nUnit 2.x默认会在同一个AppDomain中运行同一个测试类的所有测试方法,这就导致先执行的测试会把EF的全局配置锁定为对应数据库的提供程序,后执行的测试试图使用另一个数据库时,就会因为配置不匹配抛出异常。

单独运行测试时没问题,是因为每个测试都会启动新的AppDomain,EF的配置会重新初始化;但同一个测试类下的多个测试共享同一个AppDomain,就会出现配置冲突。

解决方案

要解决这个问题,我们需要打破EF的全局配置依赖,让每个DbContext独立使用自己的数据库提供程序配置,具体可以通过以下两种方式实现:

1. 直接传入DbConnection初始化Context(推荐)

绕过EF的全局连接工厂和提供程序配置,直接为每个DbContext创建对应的数据库连接对象,这样每个Context会明确使用自己的连接和提供程序,不会受全局配置影响。

修改DbContext构造函数

为两个Context添加接受DbConnection的重载,并禁用默认数据库初始化器(避免额外的配置干扰):

SQL Server Context

// 移除[DbConfigurationType(typeof(MsSqlConfiguration))]特性
public class SqlDbContext: DbContext 
{
    // 新增接受DbConnection的构造函数
    public SqlDbContext(DbConnection connection) 
        : base(connection, contextOwnsConnection: true) 
    {
        Database.SetInitializer<SqlDbContext>(null);
    }

    // 保留原有的连接字符串构造函数(如果需要)
    public SqlDbContext(string connectionString) : base(connectionString) {}

    public DbSet<SomeClass> SomeField { get; set; }
}

SQLite Context

// 移除[DbConfigurationType(typeof(SQLiteConfiguration))]特性
public class SqliteDbContext : DbContext 
{
    public SqliteDbContext(DbConnection connection) 
        : base(connection, contextOwnsConnection: true) 
    {
        Database.SetInitializer<SqliteDbContext>(null);
    }

    public SqliteDbContext(string connectionString) : base(connectionString) {}

    public DbSet<SomeClass> SomeField{ get; set; }
}

修改单元测试代码

在测试方法中手动创建对应数据库的连接对象,再传入Context:

[TestFixture]
class DbContextIntegrationTests 
{
    [Test]
    public void CanReadFromMsSqlDatabase() 
    {
        using (var sqlConn = new System.Data.SqlClient.SqlConnection(someConnString))
        using (var context = new SqlDbContext(sqlConn)) 
        {
            Assert.DoesNotThrow(() => context.SomeField.FirstOrDefault());
        }
    }

    [Test]
    public void CanReadFromSqliteDatabase() 
    {
        using (var sqliteConn = new System.Data.SQLite.SQLiteConnection(someConnString2))
        using (var context2 = new SqliteDbContext(sqliteConn)) 
        {
            Assert.DoesNotThrow(() => context2.SomeField.FirstOrDefault());
        }
    }
}

2. 动态切换DbConfiguration(不推荐,需注意线程安全)

如果必须保留全局配置的方式,可以在每个测试方法执行前,通过反射重置EF的配置状态,再设置当前测试需要的DbConfiguration。这种方式比较hack,因为EF6没有公开重置配置的API,且线程安全难以保证:

[SetUp]
public void TestSetup()
{
    // 通过反射重置EF的全局配置
    var configField = typeof(DbConfiguration).GetField("_currentConfiguration", BindingFlags.Static | BindingFlags.NonPublic);
    configField.SetValue(null, null);
}

[Test]
public void CanReadFromMsSqlDatabase() 
{
    DbConfiguration.SetConfiguration(new MsSqlConfiguration());
    using (var context = IocContainer.Instance.Resolve<SqlDbContext>(someConnString)) 
    {
        Assert.DoesNotThrow(() => context.SomeField.FirstOrDefault());
    }
}

[Test]
public void CanReadFromSqliteDatabase() 
{
    DbConfiguration.SetConfiguration(new SQLiteConfiguration());
    using (var context2 = IocContainer.Instance.Resolve<SqliteDbContext>(someConnString2)) 
    {
        Assert.DoesNotThrow(() => context2.SomeField.FirstOrDefault());
    }
}

这种方式虽然能解决问题,但依赖EF的内部实现,后续版本可能失效,因此更推荐第一种方法。

额外注意事项
  • 确保你的SQLite EF6提供程序版本与EF 6.2兼容,避免出现提供程序清单不匹配的问题。
  • 如果使用Unity IoC,可以在注册Context时,直接注册带DbConnection参数的构造函数,或者为每个数据库单独注册对应的连接和Context实例,避免IoC容器缓存导致的配置冲突。

内容的提问来源于stack exchange,提问作者Vadym K

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:38:02