Невозможно получить доступ к удаленному объекту в Entity Framework

Я создаю промежуточное программное обеспечение для хранения всех входящих запросов и ответов API, а также некоторых других данных. Я использую Entity Framework в качестве ORM.

Он работает хорошо, но в некоторых запросах и ответах тело ответа не сохраняется.

Пока я регистрирую ошибку, она показывает:

Имя объекта: «TransactionDBContext».строка № 248
Невозможно получить доступ к удаленному объекту. Распространенной причиной этой ошибки является удаление контекста, который был разрешен в результате внедрения зависимостей, а затем попытка использовать тот же экземпляр контекста в другом месте вашего приложения. Это может произойти, если вы вызываете Dispose() в контексте или помещаете контекст в оператор using. Если вы используете внедрение зависимостей, вы должны позволить контейнеру внедрения зависимостей позаботиться об удалении экземпляров контекста.

Я просто каждый метод, использующий async, возвращает задачу и ключевое слово 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())
                    {
                        //context.Response.Body = responseBody;
                        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)
                        {
                            var txnReferenceNo = responseData.Data?.TxnReferenceNo;
                                                        }
                            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);
                    }
                   
                    // end of reading response body
                    //await _next(context);


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

ДбКонтекст

public class TransactionDBContext:DbContext{
public TransactionDBContext(DbContextOptions<TransactionDBContext> options):base(options)
{

}
public DbSet<TransactionLogItem> tblt_TransactionLogs { get; set; }}

Серив.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();
    //TransactionId = transactionLogItem.TransactionLogID;
}
public async Task UpdateTransactionLog(TransactionLogItem LogItem)
{
    var existingLog = await _dbContext.tblt_TransactionLogs.FirstOrDefaultAsync(t => t.TransactionGuid == LogItem.TransactionGuid);
    if (existingLog != null)
    {
        existingLog.EngineCode = LogItem.EngineCode = string.IsNullOrEmpty(LogItem.EngineCode) ? existingLog.EngineCode : LogItem.EngineCode; ;
        existingLog.APIResponse = LogItem.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 = LogItem.XPIN=string.IsNullOrEmpty(LogItem.XPIN)?existingLog.XPIN:LogItem.XPIN;
        existingLog.MTORequest=LogItem.MTORequest=string.IsNullOrEmpty(LogItem.MTORequest)?existingLog.MTORequest:LogItem.MTORequest;
        existingLog.MTOResponse=LogItem.MTOResponse=string.IsNullOrEmpty(LogItem.MTOResponse)?existingLog.MTOResponse:LogItem.MTOResponse;
        existingLog.ExceptionMsg = LogItem.ExceptionMsg = string.IsNullOrEmpty(LogItem.ExceptionMsg) ? existingLog.ExceptionMsg : LogItem.ExceptionMsg;
        existingLog.TransactionStatus = LogItem.TransactionStatus;



    }
    await _dbContext.SaveChangesAsync();

}
}

Почти в 80% случаев он работает нормально, но в некоторых случаях не удается сохранить только ответ.

Как это исправить? Проблема в производстве

В этом коде нет ничего связанного с DbContext, но дубликатов так много, что можно догадаться, что TransactionLogService является одноэлементным, поэтому он сохраняет экземпляр DbContext, созданный с самого первого запроса, и пытается использовать его повторно. Проверьте Использование служб с ограниченной областью действия в BackgroundService. Проблема и решение одни и те же

Panagiotis Kanavos 08.04.2024 09:48

@PanagiotisKanavos извини, но я до сих пор не понимаю, в чем дело

Shakeeb Kp 08.04.2024 09:56

Вы не опубликовали какой-либо соответствующий код или исключение. Ничто во всех этих строках не использует DbContext. Вы заставляете людей гадать. Я подозреваю, что _transactionLogService неправильно использует DbContext.

Panagiotis Kanavos 08.04.2024 09:56

@PanagiotisKanavos Исключением является невозможность доступа к удаленному объекту. Распространенной причиной этой ошибки является удаление контекста, который был разрешен в результате внедрения зависимостей, а затем попытка использовать тот же экземпляр контекста в другом месте вашего приложения. Это может произойти, если вы вызываете Dispose() в контексте или помещаете контекст в оператор using. Если вы используете внедрение зависимостей, вы должны позволить контейнеру внедрения зависимостей позаботиться об удалении экземпляров контекста.

Shakeeb Kp 08.04.2024 09:59

@PanagiotisKanavos Не могли бы вы сейчас забрать добычу?

Shakeeb Kp 08.04.2024 10:20

Во-первых, это не исключение. Фактическое исключение — это то, что возвращает Exception.ToString(), содержащее местоположение, которое выдало, а также стек вызовов, показывающий все вызовы, которые привели к исключению. Однако в данном случае я подозреваю, что это указывает на AddTransaction или UpdateTransaction. Во-вторых, является ли TransactionLogService синглтоном? В этом случае вы не сможете использовать внедрение конструктора для использования службы с ограниченной областью действия, такой как DbContext. В статье, на которую я дал ссылку, объясняется, что нужно сделать. Является ли TransactionLogService синглтоном?

Panagiotis Kanavos 08.04.2024 12:09

@PanagiotisKanavos нет, TransactionLogService ограничен

Shakeeb Kp 08.04.2024 13:37

@ShakeebKp, его область действия должна быть определена и создана IServiceScopeFactory явно, но в вашем коде мы видим то, что пытается объяснить Панайотис Канавос - сервис создается один раз, а затем удаляется DbContext другой областью действия. Читайте внимательно первый комментарий.

Svyatoslav Danyliv 09.04.2024 09:17
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
8
83
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема, которую вы описываете, по-видимому, связана со сроком службы службы (вероятно, TransactionLogService). Обратите внимание, что область действия DbContext всегда ограничена (для каждого запроса создается новая область), поэтому, если у вас есть одноэлементная служба (имеет более высокий срок службы и остается неизменной между запросами), которая зависит от нее. Может случиться так, что в некоторых случаях он пытается получить доступ к контексту базы данных, когда он был утилизирован уже. Несколько возможных решений:

  1. проверьте, как он был добавлен в контейнер внедрения зависимостей в Program.cs, и изучите, каков соответствующий срок службы вашего сервиса, учитывая, что областью действия будет dbcontext.

  2. более механический/верный способ убедиться, что этого не произойдет: вместо прямого внедрения класса DbContext введите IServiceScopeFactory и в каждом методе, где используется контекст, используйте фабрику для создания новой области и получения свежей копии DbContext от него. это дает вам полный контроль над временем существования экземпляра dbcontext, и этих ошибок не произойдет (с единственным недостатком: данные dbcontext могут быть смещены между вызовами SaveChanges).

Надеюсь, вы найдете это полезным.

Дайте мне знать, если вам нужен пример кода для решения 2, я постараюсь добавить их позже.

Другие вопросы по теме