.NET 6 BackgroundService注入EF Core仓储空引用异常解决方法
问题根源
- 你在
Program.cs里创建了两个完全独立的宿主:第一个单独用Host.CreateDefaultBuilder建的宿主只注册了APIService,根本没注册DataContext、IAppRepository这些依赖,而且你先调用了await host.RunAsync()这个阻塞方法,后面写的Web应用相关的服务注册、中间件配置代码根本不会执行,完全是无效代码。 APIService写了两个构造函数,依赖注入容器只会选择一个它能满足所有参数的构造函数执行。在第一个独立宿主的容器里只有ILogger<APIService>能被解析,所以容器只会调用第一个只接收Logger参数的构造函数,_appRepository从来没被赋值,当然是null。- 生命周期不匹配:
BackgroundService是单例生命周期,而你注册的DbContext和IAppRepository都是Scoped生命周期,单例服务不能直接注入Scoped服务,就算前面两个问题修复,这里也会抛出 captive dependency 异常,还会导致DbContext被长期持有不释放、并发访问报错等问题。
修复方案
第一步:合并Program.cs里的两个宿主,所有服务注册到同一个容器
删掉你单独创建的Generic Host代码,把后台服务、DbContext、仓储都注册到WebApplication的服务集合里,只保留一个WebApplication实例运行。
正确的Program.cs代码:
var builder = WebApplication.CreateBuilder(args); // 注册MVC控制器服务 builder.Services.AddControllersWithViews(); // 注册EF Core DbContext builder.Services.AddDbContext<DataContext>(x => x.UseSqlServer(builder.Configuration.GetConnectionString("BankSearchConnection"))); // 注册仓储 builder.Services.AddScoped<IAppRepository, AppRepository>(); // 把后台服务注册到当前服务容器 builder.Services.AddHostedService<APIService>(); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.MapControllerRoute( name: "default", pattern: "{controller}/{action=Index}/{id?}"); app.MapFallbackToFile("index.html"); // 只需要运行这一个应用即可 app.Run();
第二步:修改APIService,合并构造函数,通过作用域工厂获取Scoped服务
删掉重复的构造函数,不要直接注入IAppRepository,改为注入IServiceScopeFactory,每次需要操作数据库的时候创建一个作用域,从作用域中获取仓储实例,用完自动释放。
修改后的APIService.cs代码:
public class APIService : BackgroundService { private readonly ILogger<APIService> _logger; private readonly IServiceScopeFactory _scopeFactory; // 只保留一个构造函数,声明所有需要的依赖 public APIService(ILogger<APIService> logger, IServiceScopeFactory scopeFactory) { _logger = logger; _scopeFactory = scopeFactory; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("Banka verileri güncellendi! Saat: {time}", DateTimeOffset.Now); BankAPI(); _logger.LogInformation("Garanti Bankası geçti"); await Task.Delay(180000, stoppingToken); } } private void BankAPI() { string uri = "https://apis.garantibbva.com.tr:443/loans/v1/paymentPlan"; var client = new RestClient(uri); var request = new RestRequest(Method.POST); request.AddHeader("apikey", "l7xx8af86c14ea7e44e0ab3fbfcc6137ae09"); request.AddQueryParameter("loanType", "1"); request.AddQueryParameter("campaignCode", "BankSearch"); request.AddQueryParameter("loanAmount", "10000"); IRestResponse response = client.Execute(request); int dueNum = 3; float monthlyAnnualCostRate = JObject.Parse(response.Content)["data"]["list"] .Where(p => (int)p["dueNum"] == dueNum) .Select(p => (float)p["monthlyAnnualCostRate"]) .FirstOrDefault(); _logger.LogInformation("Garanti Bankası Response: {monthlyAnnualCostRate}", monthlyAnnualCostRate); AddKredi("Garanti Bankası",1,monthlyAnnualCostRate); } private void AddKredi(string bankaAdi, int krediVadeKodu, float krediFaizi) { Kredi kredi = new Kredi(bankaAdi, krediVadeKodu, krediFaizi); // 创建作用域解析Scoped服务,用完自动释放 using var scope = _scopeFactory.CreateScope(); var appRepository = scope.ServiceProvider.GetRequiredService<IAppRepository>(); appRepository.Add(kredi); appRepository.SaveAll(); } }
IAppRepository.cs和AppRepository.cs的代码不需要修改。
改完之后就能正常注入,不会出现空引用,也符合EF Core在后台服务中使用的生命周期规范。
内容的提问来源于stack exchange,提问作者durmazoguzhan
相关产品推荐
相关产品推荐

