.NET 6中DbContext连接关闭/释放异常问题求助
ASP.NET Core 6 Web API DbContext连接已关闭/释放异常问题
问题场景
在ASP.NET Core 6 Web API中使用DbContext时,遇到连接已关闭或释放的异常。最初因全局复用同一DbContext,首次数据库操作完成后,后续操作触发该异常;尝试通过IServiceProvider在单独线程创建新DbContext后,仍出现首次操作成功、第二次调用_logger.LogToTable时触发异常的情况。
相关代码
业务逻辑代码(ClientServices)
public class ClientServices : IClientServices { private readonly Context _dbContext; private readonly IHttpContextAccessor _httpContextAccessor; private readonly IConfiguration _configuration; private readonly ApiClient _apiClient; private readonly LoggingService _logger; private readonly AzureFileShareService _azurefileshareservice; private readonly IServiceProvider _serviceProvider; public ClientServices(Context dbContext, IHttpContextAccessor httpContextAccessor, IConfiguration configuration, AzureFileShareService azurefileshareservice, ApiClient apiClient, LoggingService logger, IServiceProvider serviceProvider) { _dbContext = dbContext; _httpContextAccessor = httpContextAccessor; _configuration = configuration; _azurefileshareservice = azurefileshareservice; _apiClient = apiClient; _logger = logger; _serviceProvider = serviceProvider; } public async Task<byte[]> GetReport(string tempNo, string tempype, Guid UserId) { byte[] report = null; var requestLogDto = new RptRequestLog(); requestLogDto.UserId = UserId; requestLogDto.RequestTime = DateTime.Now; var obj = new { tempNo = tempNo, tempype = tempype }; var customHeaders = new Dictionary<string, string> { // some values... }; var response = await _apiClient.PostAsync(_configuration["client:URL"], obj, customHeaders); if (response.Data is byte[] byteArray) { report = byteArray; } var responseLogDto = new ResponseLog(); responseLogDto.Doc = report; responseLogDto.ResponseTime = DateTime.Now; Task.Run(async () => { var docUlr = await _uploadAzureFilestorage(params); responseLogDto.DocUrl = docUlr; await loggingInSeparateThread(report, tempNo, requestLogDto, responseLogDto, "RptRequestLog", "RptResponseLog"); }); return report; } public async Task loggingInSeparateThread(byte[] report, string tempNo, dynamic requestLogDto, dynamic responseLogDto, string reqlogTable, string respLogtable) { using (var scope = _serviceProvider.CreateScope()) { var dbContext = scope.ServiceProvider.GetRequiredService<Context>(); JsonSerializerOptions options = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve, WriteIndented = true }; await Log(requestLogDto, responseLogDto, reqlogTable, respLogtable, dbContext); await AuditHistory(System.Text.Json.JsonSerializer.Serialize(requestLogDto, options), System.Text.Json.JsonSerializer.Serialize(responseLogDto, options), requestLogDto.HshUserId, requestLogDto.RequestTime.Value, DateTime.Now, dbContext); } } public async Task Log(dynamic requestBody, dynamic responseBody, string requesTable, string responseTable, Context context) { var requestLogId = await _logger.LogToTable(requestBody, requesTable, context); responseBody.ReptRequestLogId = requestLogId; await _logger.LogToTable(responseBody, responseTable, context); } public async Task AuditHistory(string requestBody, string responseBody, Guid UserId, DateTime requestTime, DateTime responseTime, Context context) { var auditHistory = new AuditHistory(); auditHistory.RequestBody = requestBody; auditHistory.RequestTime = requestTime; auditHistory.ResponseBody = responseBody; auditHistory.ResponseTime = responseTime; await _logger.LogAuditHistory(auditHistory, "AuditHistory", context); } }
日志服务代码(LoggingService)
public class LoggingService { private readonly Context context; public LoggingService(Context dbContext) { context = dbContext; } public async Task<Guid> LogToTable<T>(T model, string tableName,Context context) where T : class { try { var dbSet = context.Set<T>(); dbSet.Add(model); await context.SaveChangesAsync(); var idProperty = typeof(T).GetProperty("Id"); if (idProperty != null) { var newId = (Guid)idProperty.GetValue(model); return newId; } } catch (Exception ex) { // 建议添加异常日志 } return Guid.Empty; } }
异常原因分析
- LoggingService生命周期冲突:
LoggingService构造函数注入了Context,默认DbContext是Scoped生命周期,若ClientServices为Singleton(或更长生命周期),会导致LoggingService持有的DbContext被提前释放。 - 异步操作未正确等待:
Task.Run中调用loggingInSeparateThread时未用await,线程上下文提前结束,Scoped DbContext被释放。 - DbContext混用问题:
LoggingService自身持有的全局DbContext实例,与线程内新创建的DbContext可能发生冲突,第二次调用时误用已释放的旧实例。
解决方案
1. 调整LoggingService,移除全局DbContext依赖
修改LoggingService,去掉构造函数的DbContext注入,仅在方法中使用传入的Scoped DbContext:
public class LoggingService { public LoggingService() { } public async Task<Guid> LogToTable<T>(T model, string tableName, Context context) where T : class { try { var dbSet = context.Set<T>(); dbSet.Add(model); await context.SaveChangesAsync(); var idProperty = typeof(T).GetProperty("Id"); if (idProperty != null) { var newId = (Guid)idProperty.GetValue(model); return newId; } } catch (Exception ex) { // 添加异常日志记录 } return Guid.Empty; } public async Task LogAuditHistory(AuditHistory model, string tableName, Context context) { try { var dbSet = context.Set<AuditHistory>(); dbSet.Add(model); await context.SaveChangesAsync(); } catch (Exception ex) { // 异常日志 } } }
2. 确保异步操作完整等待
在Task.Run中添加await,保证异步方法执行完毕:
Task.Run(async () => { var docUlr = await _uploadAzureFilestorage(params); responseLogDto.DocUrl = docUlr; await loggingInSeparateThread(report, tempNo, requestLogDto, responseLogDto, "RptRequestLog", "RptResponseLog"); });
3. 修正服务生命周期配置
在Program.cs中调整服务生命周期,避免Singleton持有Scoped资源:
// 注册为Transient,每次获取都是新实例 builder.Services.AddTransient<LoggingService>(); // ClientServices建议注册为Scoped,与DbContext生命周期匹配 builder.Services.AddScoped<IClientServices, ClientServices>();
4. 替换dynamic为具体实体类型
避免dynamic带来的类型不确定性,改用明确的实体类型:
public async Task Log(RptRequestLog requestBody, ResponseLog responseBody, string requesTable, string responseTable, Context context) { var requestLogId = await _logger.LogToTable(requestBody, requesTable, context); responseBody.ReptRequestLogId = requestLogId; await _logger.LogToTable(responseBody, responseTable, context); }
验证效果
修改后,每次数据库操作都会使用独立的Scoped DbContext,避免连接提前释放;异步操作完整执行,线程上下文不会提前销毁,可解决首次成功、后续失败的异常问题。
内容的提问来源于stack exchange,提问作者Apire
相关产品推荐
相关产品推荐

