WebApplicationFactory中Singleton单例失效:原因及解决办法
问题:Singleton生命周期的SqliteConnection在DbContext中解析时并非单例
调试代码时发现,注册为Singleton生命周期的SqliteConnection,在执行GetAsync("")后从AddDbContext中解析时并非单例。添加了以下代码验证单例失效:
if (!ReferenceEquals(_sharedConnection, connection)) throw new ApplicationException("SqliteConnection should be singleton.");
完整代码
被测Web API
public class AppDbContext(DbContextOptions<AppDbContext> o) : DbContext(o); public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet("/", (AppDbContext db) => "Hello"); app.Run(); } }
测试代码
public class CustomWebAppFactory : WebApplicationFactory<Program> { private static SqliteConnection _sharedConnection = null!; protected override void ConfigureWebHost(IWebHostBuilder builder) { base.ConfigureWebHost(builder); builder.ConfigureTestServices(services => { services.RemoveAll(typeof(SqliteConnection)); services.RemoveAll(typeof(DbContextOptions<AppDbContext>)); services.AddSingleton(provider => { var connection = new SqliteConnection("DataSource=:memory:"); return connection; }); services.AddDbContext<AppDbContext>((provider, options) => { var connection = provider.GetRequiredService<SqliteConnection>(); if (!ReferenceEquals(_sharedConnection, connection)) throw new ApplicationException("SqliteConnection should be singleton."); options.UseSqlite(connection); }); var provider = services.BuildServiceProvider(); if (_sharedConnection != null) throw new ApplicationException("_sharedConnection should be null before reaching the following line."); _sharedConnection = provider.GetRequiredService<SqliteConnection>(); }); } }
public class TrivialTest { [Fact] public async Task Should_return_OK() { using var factory = new CustomWebAppFactory(); using var client = factory.CreateClient(); var response = await client.GetAsync(""); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } }
问题原因
核心问题在于你在ConfigureTestServices内部调用services.BuildServiceProvider()创建了一个临时的服务提供者实例,而后续应用处理请求时使用的是另一个完全独立的服务提供者:
_sharedConnection是从这个临时服务提供者中获取的SqliteConnection单例- 请求触发时,AppDbContext从应用运行时的正式服务提供者中解析,其中的SqliteConnection是该提供者内的单例,但和临时提供者中的实例并非同一对象
- 两个服务提供者互相独立,各自维护自己的Singleton实例,因此
ReferenceEquals返回false,触发异常
解决办法
不要在ConfigureTestServices内部通过BuildServiceProvider获取_sharedConnection,而是直接在注册SqliteConnection的委托中赋值,确保_sharedConnection和后续应用使用的是同一个实例:
修改测试代码中的SqliteConnection注册逻辑:
services.AddSingleton(provider => { var connection = new SqliteConnection("DataSource=:memory:"); _sharedConnection = connection; // 直接在此处赋值共享实例 return connection; });
同时删除ConfigureTestServices中以下冗余代码:
var provider = services.BuildServiceProvider(); if (_sharedConnection != null) throw new ApplicationException("_sharedConnection should be null before reaching the following line."); _sharedConnection = provider.GetRequiredService<SqliteConnection>();
修改后,_sharedConnection会直接指向注册的Singleton实例,后续应用服务提供者解析的SqliteConnection和它是同一个对象,ReferenceEquals验证将通过。
补充:AddDbContext默认生命周期为Scoped,但这不会影响SqliteConnection的Singleton特性,每次Scoped的DbContext都会解析同一个Singleton连接实例。
内容的提问来源于stack exchange,提问作者D G
相关产品推荐
相关产品推荐

