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

Entity Framework中无法访问已释放对象问题的修复方案

问题描述

我正在开发一个中间件,用于存储所有传入API的请求、响应体及其他相关数据,使用Entity Framework作为ORM。该中间件大部分情况运行正常,但部分请求的响应体无法存储,日志报错显示:

Object name: 'TransactionDBContext'.line no 248
Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.

我已确保所有异步方法均返回Task并正确使用await关键字。相关代码如下:

Middleware.cs:

public class TransactionLoggingMiddleWare
{
    private readonly RequestDelegate _next;
    private TransactionLogService _transactionLogService;

    public TransactionLoggingMiddleWare(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context, TransactionLogService transactionLogService)
    {
        _transactionLogService = transactionLogService;
        Guid GUID = Guid.NewGuid();

        try
        {
            var request = context.Request;

            if (request.Path.ToString().Contains("Remittance/Post"))
            {
                var requestTime = DateTime.Now;
                var requestBodyContent = await ReadRequestBody(request);
                int bcode = requestBodyContent.IndexOf("content-disposition:");

                if (bcode > 0)
                {
                    string jung1 = requestBodyContent.Substring(0, bcode - 1);
                    requestBodyContent = requestBodyContent.Replace(jung1, "");
                }

                context.Items["Guid"] = GUID;

                requestBodyContent = requestBodyContent.Replace("content-disposition: form-data; name=", "");

                var form = await context.Request.ReadFormAsync();
                var transactionData = new RemittanceRQ();

                var properties = typeof(RemittanceRQ).GetProperties();
                int remIdValue = 0;
                long TxnRefNo = 0;
                string productcode = null;

                foreach (var property in properties)
                {
                    // Get the property name
                    var propertyName = property.Name;

                    // Check if the form data contains the key corresponding to the property name
                    if (form.ContainsKey(propertyName))
                    {
                        // Get the value from the form data
                        var value = form[propertyName];

                        // Convert and assign the value to the property
                        if (value != String.Empty)
                        {
                            // Handle different property types accordingly
                            if (property.PropertyType == typeof(int))
                            {
                                property.SetValue(transactionData, Convert.ToInt32(value));
                            }
                            else if (property.PropertyType == typeof(Int16))
                            {
                                property.SetValue(transactionData , Convert.ToInt16(value));
                            }
                            else if (property.PropertyType == typeof(long))
                            {
                                property.SetValue(transactionData, long.Parse(value));
                            }
                            else if (property.PropertyType == typeof(decimal))
                            {
                                property.SetValue(transactionData, decimal.Parse(value));
                            }
                            else if (property.PropertyType == typeof(short))
                            {
                                property.SetValue(transactionData, Convert.ToInt16(value));
                            }
                            else if (property.PropertyType == typeof(String))
                            {
                                property.SetValue(transactionData, value.ToString());
                            }

                            if (propertyName == "RemID")
                            {
                                remIdValue = Convert.ToInt32(value);
                            }

                            if (propertyName == "TxnRefNumber")
                            {
                                TxnRefNo = long.Parse(value);
                            }
                        }
                    }
                }

                // to store only request body
                await _transactionLogService.AddTransaction(new TransactionLogItem
                {
                    TransactionGuid = GUID.ToString(),
                    CreatedDate = requestTime,
                    APIRequest = JsonConvert.SerializeObject(transactionData),
                    RemId = remIdValue,
                    TxnNumber = TxnRefNo
                });

                // reading the response
                var orginalBodyStream = context.Response.Body;
                var originalBodyCode = context.Response.StatusCode;
                
                try
                {
                    using (var responseBody = new MemoryStream())
                    {
                        var response = context.Response;
                        response.Body = responseBody;

                        await _next(context);

                        responseBody.Seek(0, SeekOrigin.Begin);
                        var responseBodyContent = await new StreamReader(responseBody).ReadToEndAsync();

                        var responseData = JsonConvert.DeserializeObject<ResponseData>(responseBodyContent);
                        
                        if (responseData != null)
                        {
                            await _transactionLogService.UpdateTransactionLog(new TransactionLogItem
                            {
                                TransactionGuid = GUID.ToString(),
                                APIResponse = responseBodyContent,
                                Status_Code = response.StatusCode,
                                TxnNumber = responseData.Data?.TxnReferenceNo,
                                TransactionStatus = responseData.IsSuccess,
                                XPIN = responseData.Data?.TTNUM
                            });
                        }
                        
                        responseBody.Seek(0, SeekOrigin.Begin);
                        await responseBody.CopyToAsync(orginalBodyStream);
                    }
                }
                catch (Exception ex)
                {
                    await _transactionLogService.UpdateTransactionLog(new TransactionLogItem
                    {
                        TransactionGuid = GUID.ToString(),
                        ExceptionMsg = ex.Message
                    });
                    File.AppendAllText("loggerMiddlewareException.txt",ex.Message.ToString()+"line no 231"+$"{GUID}"+"\n");
                }
            }
            else
            {
                await _next(context);
            }
        }
        catch (Exception ex)
        {
            bool translogGuid = context.Items.TryGetValue("Guid", out var requestIdObj);
            var request = context.Request;
            var requestTime = DateTime.Now;
            File.AppendAllText("loggerMiddlewareException.txt", ex.Message.ToString() + "line no 248" + "\n");
            var requestBodyContent = await ReadRequestBody(request);
            await _transactionLogService.UpdateTransactionLog(new TransactionLogItem
            {
                TransactionGuid = GUID.ToString(),
                ExceptionMsg = ex.Message
            });
            await _next(context);
        }
    }

    private async Task<string> ReadRequestBody(HttpRequest request)
    {
        request.EnableBuffering();
        var buffer = new byte[Convert.ToInt32(request.ContentLength??0)];
        await request.Body.ReadAsync(buffer, 0, buffer.Length);
        var bodyAsText = Encoding.UTF8.GetString(buffer);
        request.Body.Seek(0, SeekOrigin.Begin);
        return bodyAsText;
    }
}

服务注册:

services.AddDbContext<TransactionDBContext>(options => options.UseSqlServer(connectionString));

DbContext:

public class TransactionDBContext:DbContext{
public TransactionDBContext(DbContextOptions<TransactionDBContext> options):base(options)
{
}
public DbSet<TransactionLogItem> tblt_TransactionLogs { get; set; }
}

Service.cs:

public class TransactionLogService{
private readonly TransactionDBContext _dbContext;
public int TransactionId { get; set; }
public TransactionLogService(TransactionDBContext transactionDBContext)
{
    _dbContext = transactionDBContext;
}
public async Task AddTransaction(TransactionLogItem transactionLogItem)
{
    _dbContext.tblt_TransactionLogs.Add(transactionLogItem);
    await _dbContext.SaveChangesAsync();
}
public async Task UpdateTransactionLog(TransactionLogItem LogItem)
{
    var existingLog = await _dbContext.tblt_TransactionLogs.FirstOrDefaultAsync(t => t.TransactionGuid == LogItem.TransactionGuid);
    if (existingLog != null)
    {
        existingLog.EngineCode = string.IsNullOrEmpty(LogItem.EngineCode) ? existingLog.EngineCode : LogItem.EngineCode;
        existingLog.APIResponse = string.IsNullOrEmpty(LogItem.APIResponse)? existingLog.APIResponse: LogItem.APIResponse;
        existingLog.Status_Code = LogItem.Status_Code==0?existingLog.Status_Code:LogItem.Status_Code;
        existingLog.TxnNumber= LogItem.TxnNumber == null || LogItem.TxnNumber == 0 ? existingLog.TxnNumber:LogItem.TxnNumber;
        existingLog.XPIN = string.IsNullOrEmpty(LogItem.XPIN)?existingLog.XPIN:LogItem.XPIN;
        existingLog.MTORequest=string.IsNullOrEmpty(LogItem.MTORequest)?existingLog.MTORequest:LogItem.MTORequest;
        existingLog.MTOResponse=string.IsNullOrEmpty(LogItem.MTOResponse)?existingLog.MTOResponse:LogItem.MTOResponse;
        existingLog.ExceptionMsg = string.IsNullOrEmpty(LogItem.ExceptionMsg) ? existingLog.ExceptionMsg : LogItem.ExceptionMsg;
        existingLog.TransactionStatus = LogItem.TransactionStatus;
    }
    await _dbContext.SaveChangesAsync();
}
}

该功能在约80%的场景下可正常工作,但部分场景下无法存储响应体,请问如何修复生产环境中的这个问题?


解决方案
  • 核心问题:默认AddDbContext注册的是Scoped生命周期,中间件注入的TransactionLogService会持有同生命周期的TransactionDBContext。若后续业务逻辑(await _next(context))触发了Scope提前释放(比如请求提前终止、异常中断流程),后续再调用_transactionLogService就会访问已被释放的DbContext实例。此外,外层catch块中GUID变量可能未初始化,会引发额外错误。

  • 修复步骤:

    1. 避免复用跨Scope的服务实例:移除中间件类中的private TransactionLogService _transactionLogService;字段,每次需要操作数据库时,从当前请求的ServiceProvider创建新的Scope获取服务,确保使用未被释放的DbContext实例。
    2. 确保GUID始终有效:将GUID的声明移到try块外部,避免catch块中使用未初始化的变量。
    3. 统一Scope创建逻辑:所有数据库操作(包括Add和Update)都通过新Scope获取服务,彻底规避已释放实例的问题。

修改后的关键代码片段:

public class TransactionLoggingMiddleWare
{
    private readonly RequestDelegate _next;

    public TransactionLoggingMiddleWare(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        Guid GUID = Guid.NewGuid(); // 移到try外确保catch能访问
        try
        {
            var request = context.Request;

            if (request.Path.ToString().Contains("Remittance/Post"))
            {
                var requestTime = DateTime.Now;
                var requestBodyContent = await ReadRequestBody(request);
                int bcode = requestBodyContent.IndexOf("content-disposition:");

                if (bcode > 0)
                {
                    string jung1 = requestBodyContent.Substring(0, bcode - 1);
                    requestBodyContent = requestBodyContent.Replace(jung1, "");
                }

                context.Items["Guid"] = GUID;

                requestBodyContent = requestBodyContent.Replace("content-disposition: form-data; name=", "");

                var form = await context.Request.ReadFormAsync();
                var transactionData = new RemittanceRQ();

                var properties = typeof(RemittanceRQ).GetProperties();
                int remIdValue = 0;
                long TxnRefNo = 0;
                string productcode = null;

                foreach (var property in properties)
                {
                    // 省略原有属性赋值逻辑...
                }

                // 创建新Scope存储请求日志
                using (var scope = context.RequestServices.CreateScope())
                {
                    var logService = scope.ServiceProvider.GetRequiredService<TransactionLogService>();
                    await logService.AddTransaction(new TransactionLogItem
                    {
                        TransactionGuid = GUID.ToString(),
                        CreatedDate = requestTime,
                        APIRequest = JsonConvert.SerializeObject(transactionData),
                        RemId = remIdValue,
                        TxnNumber = TxnRefNo
                    });
                }

                // 读取响应部分
                var originalBodyStream = context.Response.Body;
                try
                {
                    using (var responseBody = new MemoryStream())
                    {
                        context.Response.Body = responseBody;

                        await _next(context);

                        responseBody.Seek(0, SeekOrigin.Begin);
                        var responseBodyContent = await new StreamReader(responseBody).ReadToEndAsync();

                        var responseData = JsonConvert.DeserializeObject<ResponseData>(responseBodyContent);
                        
                        if (responseData != null)
                        {
                            // 创建新Scope更新响应日志
                            using (var scope = context.RequestServices.CreateScope())
                            {
                                var logService = scope.ServiceProvider.GetRequiredService<TransactionLogService>();
                                await logService.UpdateTransactionLog(new TransactionLogItem
                                {
                                    TransactionGuid = GUID.ToString(),
                                    APIResponse = responseBodyContent,
                                    Status_Code = context.Response.StatusCode,
                                    TxnNumber = responseData.Data?.TxnReferenceNo,
                                    TransactionStatus = responseData.IsSuccess,
                                    XPIN = responseData.Data?.TTNUM
                                });
                            }
                        }
                        
                        responseBody.Seek(0, SeekOrigin.Begin);
                        await responseBody.CopyToAsync(originalBodyStream);
                    }
                }
                catch (Exception ex)
                {
                    using (var scope = context.RequestServices.CreateScope())
                    {
                        var logService = scope.ServiceProvider.GetRequiredService<TransactionLogService>();
                        await logService.UpdateTransactionLog(new TransactionLogItem
                        {
                            TransactionGuid = GUID.ToString(),
                            ExceptionMsg = ex.Message
                        });
                    }
                    File.AppendAllText("loggerMiddlewareException.txt",$"{ex.Message} line no 231 {GUID}\n");
                }
            }
            else
            {
                await _next(context);
            }
        }
        catch (Exception ex)
        {
            File.AppendAllText("loggerMiddlewareException.txt", $"{ex.Message} line no 248\n");
            
            using (var scope = context.RequestServices.CreateScope())
            {
                var logService = scope.ServiceProvider.GetRequiredService<TransactionLogService>();
                await logService.UpdateTransactionLog(new TransactionLogItem
                {
                    TransactionGuid = GUID.ToString(),
                    ExceptionMsg = ex.Message
                });
            }
            await _next(context);
        }
    }

    // 省略ReadRequestBody方法...
}
  • 额外优化建议:
    • 可以将Scope创建和服务获取逻辑封装成私有方法,减少代码重复。
    • 检查业务逻辑中是否存在提前终止请求的场景(比如直接返回context.Response.WriteAsync后未继续执行中间件流程),确保中间件后续操作能正常触发。
    • 考虑使用批量更新或事务来优化数据库操作性能。

内容的提问来源于stack exchange,提问作者Shakeeb Kp

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 04:04:55