.NET 7构建单例DI类时引用ApplicationDbContext报错求助
解决单例服务依赖Scoped DbContext的错误
问题根源
默认情况下,AddDbContext将ApplicationDbContext注册为Scoped生命周期服务,而单例服务的创建依赖根服务提供者,根容器不允许直接解析Scoped服务——这是DI框架的生命周期约束,避免Scoped服务被单例长期持有后,出现资源泄漏、多线程数据不一致等问题。
可行解决方案
方式一:仅在单例初始化时使用DbContext(临时作用域)
如果ApplicationSettings只在创建时需要读取DbContext的数据,之后不再使用DbContext,可以手动创建临时作用域来获取DbContext,使用后自动释放:
builder.Services.AddSingleton<IApplicationSettings, ApplicationSettings>(sp => { using var scope = sp.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); // 此处可读取DbContext数据用于初始化ApplicationSettings return new ApplicationSettings(new List<string> { "workers.maintenance", "shared" }, dbContext); });
⚠️ 注意:如果ApplicationSettings后续还要调用DbContext的方法,这种方式不可行,因为作用域释放后DbContext会被销毁。
方式二:将DbContext注册为Singleton(不推荐)
可以强制指定DbContext的生命周期为Singleton,但这种做法严重违背DbContext的设计初衷,会引发多线程冲突、缓存污染、数据库连接异常等问题,仅在极端场景下考虑:
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"), o => o.EnableRetryOnFailure()), ServiceLifetime.Singleton); // 强制设置为单例
方式三:重构为延迟获取DbContext(推荐)
如果ApplicationSettings需要在运行时多次使用DbContext,不要直接持有DbContext实例,而是注入工厂或服务提供者,在需要时创建/获取DbContext。
子方案3.1:使用IDbContextFactory
首先注册DbContext工厂:
builder.Services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"), o => o.EnableRetryOnFailure()));
修改ApplicationSettings的构造函数,注入工厂并在使用时创建DbContext:
public class ApplicationSettings : IApplicationSettings { private readonly IEnumerable<string> _names; private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory; public ApplicationSettings(IEnumerable<string> names, IDbContextFactory<ApplicationDbContext> dbContextFactory) { _names = names; _dbContextFactory = dbContextFactory; } // 示例:需要操作数据库时创建DbContext public async Task<List<SomeEntity>> GetSomeDataAsync() { using var dbContext = _dbContextFactory.CreateDbContext(); return await dbContext.SomeEntities.ToListAsync(); } }
注册单例时注入工厂:
builder.Services.AddSingleton<IApplicationSettings, ApplicationSettings>(sp => { var dbContextFactory = sp.GetRequiredService<IDbContextFactory<ApplicationDbContext>>(); return new ApplicationSettings(new List<string> { "workers.maintenance", "shared" }, dbContextFactory); });
子方案3.2:注入IServiceProvider
修改ApplicationSettings,注入服务提供者并在需要时创建作用域获取DbContext:
public class ApplicationSettings : IApplicationSettings { private readonly IEnumerable<string> _names; private readonly IServiceProvider _serviceProvider; public ApplicationSettings(IEnumerable<string> names, IServiceProvider serviceProvider) { _names = names; _serviceProvider = serviceProvider; } public async Task<List<SomeEntity>> GetSomeDataAsync() { using var scope = _serviceProvider.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); return await dbContext.SomeEntities.ToListAsync(); } }
注册单例时直接注入服务提供者:
builder.Services.AddSingleton<IApplicationSettings, ApplicationSettings>(sp => { return new ApplicationSettings(new List<string> { "workers.maintenance", "shared" }, sp); });
内容的提问来源于stack exchange,提问作者Yohancef Chin
相关产品推荐
相关产品推荐

