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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 11:37:09