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

.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;
     }
}

异常原因分析

  1. LoggingService生命周期冲突:LoggingService构造函数注入了Context,默认DbContext是Scoped生命周期,若ClientServices为Singleton(或更长生命周期),会导致LoggingService持有的DbContext被提前释放。
  2. 异步操作未正确等待:Task.Run中调用loggingInSeparateThread时未用await,线程上下文提前结束,Scoped DbContext被释放。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 16:34:52