在 .net6 中实现 Dbcontext 时面临问题

问题描述 投票:0回答:1

我在 ASP.NET Core 6 Web API 中遇到

DbContext
的问题,获取异常连接处置或关闭。因为我在整个应用程序中使用相同的
DbContext
,所以当数据库相关操作第一次完成时,我会遇到这个问题,并且我尝试使用
DbContext
执行其他操作,关闭或处置异常连接。

我知道这是因为

DbContext
的生命周期,但是我尝试使用
DbContext
在单独的线程中使用新的
serviceProvider
执行第二个操作,但仍然存在相同的问题,第一个操作执行,而第二个操作总是失败。

这是我的经理代码:

public class ClientServices : IClientServices
{
    private readonly Context _dbContext;
    private readonly IHttpContextAccessor _httpContextAccessor; // Injected IHttpContextAccessor
    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;// new ApiClient(_configuration);
        _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;

            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 // Optional: for better readability
            };

            Log(requestLogDto, responseLogDto, reqlogTable, respLogtable, dbContext);

            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;
        _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);
    }
}

这是日志服务:

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

任何人都可以解释如何实施通用解决方案来做到这一点吗?

供参考,当日志方法调用在数据库中插入记录时,第一个操作成功执行(在数据库中插入记录),但当它第二次插入时,请考虑这一点:

_logger.LogToTable(responseBody, responseTable, context);

然后我得到异常连接关闭/处置。

c# asp.net-core entity-framework-core .net-6.0 dbcontext
1个回答
1
投票

这里的代码不起作用:

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>();
         
             // ... 

在后台任务中执行操作时,不能依赖注入的 DbContext,它需要限定在任务本身的范围内。解决这个问题的最简单方法是创建一个 DbContextFactory 并将其注入到具有后台任务类实例的类中,然后后台任务可以使用它来实例化

DbContext
的作用域实例:

public class LoggingService
{
     private readonly IAppDbContextContextFactory contextFactory;

     public LoggingService(IAppDbContextFactory contextFactory)
     {
         this.contextFactory = contextFactory;
     }

     public async Task<Guid> LogToTable<T>(T model, string tableName,Context context) where T : class
     {
         try
         {
             using context = contextFactory.Create();

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

这应该可以解决问题。例如,具有

async
操作的控制器可以使用范围仅限于 Web 请求的
DbContext
,因为这些操作将等待并在 Web 请求范围内执行,其中
DbContext
将保持活动状态。如果您将其交给后台工作人员并使用相同的
DbContext
实例对其进行初始化,您可能会遇到各种问题,包括后台工作人员和 Web 请求同时尝试访问 DbContext 的可能性(跨线程问题,因为 DbContext 是非线程安全)或 Web 请求结束的情况,触发 DbContext 的处置并且后台线程尝试访问对其的引用。

© www.soinside.com 2019 - 2024. All rights reserved.