同一单元测试中两个不同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

