Asp.Net Core HealthChecks并发Entity Framework DbContext错误求解决方案
问题根因
ASP.NET Core 默认会并行执行所有注册的健康检查,你项目中注入的业务服务、DbContext 均为 Scoped 生命周期,同一服务 Scope 内的 DbContext 实例唯一。两个并行执行的健康检查同时调用业务服务操作同一个 DbContext 实例,触发了 EF Core 禁止同一上下文并发操作的限制,所以抛出对应异常。
可行解决方案
以下方案均不需要复制业务逻辑,完全符合 DRY 原则:
方案1:配置健康检查串行执行(改造成本最低)
仅需修改健康检查注册逻辑,设置最大并发执行数为1,所有健康检查按顺序逐个执行,完全不需要修改现有业务代码:
services.AddHealthChecks(options => { // 限制健康检查串行执行,避免并发操作同一个 Scoped DbContext options.MaximumConcurrentExecutions = 1; }) .AddCheck<DuplicateTaskNamesHealthCheck>("Duplicate Task Names", HealthStatus.Unhealthy, tags: new[] { "org_project" }) .AddCheck<DuplicateProjectNamesHealthCheck>("Duplicate Project Names", HealthStatus.Unhealthy, tags: new[] { "org_project" });
方案2:保留并行执行,每个健康检查单独创建服务 Scope
如果需要保留健康检查并行执行的能力,可以让每个健康检查单独创建独立的服务 Scope,获取独立的 DbContext 实例,避免实例冲突,仅需修改健康检查代码即可:
public class DuplicateProjectNamesHealthCheck : IHealthCheck { private readonly IServiceScopeFactory _scopeFactory; private readonly Settings _settings; public DuplicateProjectNamesHealthCheck(IServiceScopeFactory scopeFactory, IOptions<Settings> settings) { _scopeFactory = scopeFactory; _settings = settings.Value; } public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { // 创建独立服务Scope,获取专属的服务和DbContext实例 using var scope = _scopeFactory.CreateScope(); var projectService = scope.ServiceProvider.GetRequiredService<IACME6_PROJECT>(); List<string> duplicateProjectNames = await projectService.GetDuplicateProjectNamesAsync(); if (duplicateProjectNames.Count > 0) { if (_settings.AutoFixDuplicateProjectNames) { await projectService.FixDuplicateProjectNamesAsync(); } else { string errorMessage = "存在重复的项目名称:"; foreach (string projectName in duplicateProjectNames) { errorMessage += projectName + ") |"; } errorMessage += "如需自动修复,请在配置中将 AutoFixDuplicateProjectNames 设置为 true。"; return new HealthCheckResult(status: context.Registration.FailureStatus, errorMessage); } } return HealthCheckResult.Healthy("正常"); } }
两个健康检查均按上述逻辑修改即可,各自的 Scope 完全独立,并行执行不会出现 DbContext 冲突。
方案3:辅助优化业务查询逻辑
你现有业务方法中执行了两次数据库查询(AnyAsync 和 ToListAsync),可以合并为一次查询,减少 DbContext 操作次数,也能降低冲突概率:
public async Task<List<string>> GetDuplicateProjectNamesAsync() { return await _context .PROJECT .AsNoTracking() .GroupBy(x => x.PROJECTNAME) .Where(x => x.Count() > 1) .Select(x => x.Key) .ToListAsync(); }
查询结果为空时会直接返回空列表,不需要额外判断是否存在数据,可减少一次数据库请求。
内容的提问来源于stack exchange,提问作者edelwater
相关产品推荐
相关产品推荐

