Я создаю промежуточное программное обеспечение для хранения всех входящих запросов и ответов 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% случаев он работает нормально, но в некоторых случаях не удается сохранить только ответ.
Как это исправить? Проблема в производстве
@PanagiotisKanavos извини, но я до сих пор не понимаю, в чем дело
Вы не опубликовали какой-либо соответствующий код или исключение. Ничто во всех этих строках не использует DbContext. Вы заставляете людей гадать. Я подозреваю, что _transactionLogService неправильно использует DbContext.
@PanagiotisKanavos Исключением является невозможность доступа к удаленному объекту. Распространенной причиной этой ошибки является удаление контекста, который был разрешен в результате внедрения зависимостей, а затем попытка использовать тот же экземпляр контекста в другом месте вашего приложения. Это может произойти, если вы вызываете Dispose() в контексте или помещаете контекст в оператор using. Если вы используете внедрение зависимостей, вы должны позволить контейнеру внедрения зависимостей позаботиться об удалении экземпляров контекста.
@PanagiotisKanavos Не могли бы вы сейчас забрать добычу?
Во-первых, это не исключение. Фактическое исключение — это то, что возвращает Exception.ToString(), содержащее местоположение, которое выдало, а также стек вызовов, показывающий все вызовы, которые привели к исключению. Однако в данном случае я подозреваю, что это указывает на AddTransaction или UpdateTransaction. Во-вторых, является ли TransactionLogService синглтоном? В этом случае вы не сможете использовать внедрение конструктора для использования службы с ограниченной областью действия, такой как DbContext. В статье, на которую я дал ссылку, объясняется, что нужно сделать. Является ли TransactionLogService синглтоном?
@PanagiotisKanavos нет, TransactionLogService ограничен
@ShakeebKp, его область действия должна быть определена и создана IServiceScopeFactory явно, но в вашем коде мы видим то, что пытается объяснить Панайотис Канавос - сервис создается один раз, а затем удаляется DbContext другой областью действия. Читайте внимательно первый комментарий.





Проблема, которую вы описываете, по-видимому, связана со сроком службы службы (вероятно, TransactionLogService). Обратите внимание, что область действия DbContext всегда ограничена (для каждого запроса создается новая область), поэтому, если у вас есть одноэлементная служба (имеет более высокий срок службы и остается неизменной между запросами), которая зависит от нее. Может случиться так, что в некоторых случаях он пытается получить доступ к контексту базы данных, когда он был утилизирован уже. Несколько возможных решений:
проверьте, как он был добавлен в контейнер внедрения зависимостей в Program.cs, и изучите, каков соответствующий срок службы вашего сервиса, учитывая, что областью действия будет dbcontext.
более механический/верный способ убедиться, что этого не произойдет: вместо прямого внедрения класса DbContext введите IServiceScopeFactory и в каждом методе, где используется контекст, используйте фабрику для создания новой области и получения свежей копии DbContext от него. это дает вам полный контроль над временем существования экземпляра dbcontext, и этих ошибок не произойдет (с единственным недостатком: данные dbcontext могут быть смещены между вызовами SaveChanges).
Надеюсь, вы найдете это полезным.
Дайте мне знать, если вам нужен пример кода для решения 2, я постараюсь добавить их позже.
В этом коде нет ничего связанного с DbContext, но дубликатов так много, что можно догадаться, что
TransactionLogServiceявляется одноэлементным, поэтому он сохраняет экземпляр DbContext, созданный с самого первого запроса, и пытается использовать его повторно. Проверьте Использование служб с ограниченной областью действия в BackgroundService. Проблема и решение одни и те же