Я разрабатываю веб-интерфейс с asp.net core 2.2 и ef core 2.2.1.API, помимо обработки остальных запросов, выполняемых угловым приложением, отвечает за обработку некоторых XML-файлов, которые используются в качестве интерфейса с другим программным обеспечением.Файлы являются локальными для сервера приложений и обнаруживаются с помощью FileWatcher
.
. Во время моих тестов я заметил, что при многократной повторной обработке тестового файла xml, начиная со второй раз, когда файл обрабатывается повторно,Я получаю исключение:
System.InvalidOperationException: экземпляр типа сущности «QualityLot» не может быть отслежен, поскольку другой экземпляр со значением ключа «{QualityLotID: ...}» уже отслеживается.При подключении существующих объектов убедитесь, что подключен только один экземпляр объекта с данным значением ключа.
, когда я вызываю метод DbContext.QualityLot.Update(qualityLot);
Служба "файл обработки" ииспользуемые им сервисы настраиваются в файл Startup.cs
следующим образом:
services.AddHostedService<InterfaceDownloadService>();
services.AddTransient<IQLDwnldService, QLDwnldService>();
контекст БД настраивается следующим образом:
services.AddDbContext<MyDbContext>(cfg =>
{
cfg.UseSqlServer(_config.GetConnectionString("LIMSConnectionString"));
});
и класс выглядит следующим образом:
public class InterfaceDownloadService : BackgroundServiceBase
{
[...]
public InterfaceDownloadService(IHostingEnvironment env,
ILogger<InterfaceDownloadService> logger,
IServiceProvider serviceProvider)
{
_ServiceProvider = serviceProvider;
}
[...]
private void processFiles()
{
[...]
_ServiceProvider.GetService<IQLDwnldService>().QLDownloadAsync(ev);
}
}
public abstract class BackgroundServiceBase : IHostedService, IDisposable
{
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts =
new CancellationTokenSource();
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it,
// this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
try
{
// Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,
cancellationToken));
}
}
public virtual void Dispose()
{
_stoppingCts.Cancel();
}
}
Здесь критическая точка, где у меня есть исключение:
public async Task QLDownloadAsync(FileReceivedEvent fileReceivedEvent)
{
Logger.LogInformation($"QLDwnld file {fileReceivedEvent.Event.FullPath} received for Processing");
try
{
QualityLotDownload qualityRoutingDwnld = deserializeObject<QualityLotDownload>(fileReceivedEvent.XsltPath, fileReceivedEvent.Event.FullPath);
Logger.LogDebug($"QLDwnld file {fileReceivedEvent.Event.FullPath} deserialized correctly. Need to determinate whether Insert or Update QualityLot {qualityRoutingDwnld.QualityLots.QualityLot.QualityLotID}");
for (int remainingRetries = fileReceivedEvent.MaxRetries; remainingRetries > 0; remainingRetries--)
{
using (var transaction = await DbContext.Database.BeginTransactionAsync())
{
try
{
var qualityLotDeserialized = qualityRoutingDwnld.QualityLots.QualityLot;
// insert the object into the database
var qualityLot = await DbContext.QualityLot.Where(x => x.QualityLotID == qualityLotDeserialized.QualityLotID).FirstOrDefaultAsync();
if (qualityLot == null) // INSERT QL
{
await InsertQualityLot(qualityLotDeserialized);
}
else // UPDATE QL
{
await UpdateQualityLot(qualityLot, qualityLotDeserialized);
}
[...]
transaction.Commit();
}
catch (Exception ex)
{
Logger.LogError(ex, $"Retry {fileReceivedEvent.MaxRetries - remainingRetries +1}: Exception processing QLDwnld file {fileReceivedEvent.Event.FullPath}.");
transaction.Rollback();
if (remainingRetries == 1)
{
return;
}
}
Метод UpdateQualityLot(qualityLot, qualityLotDeserialized);
вызывается, потому что сущность уже существует в БД
private async Task UpdateQualityLot(QualityLot qualityLot, QualityLotDownloadQualityLotsQualityLot qualityLotDeserialized)
{
[fields update]
DbContext.QualityLot.Update(qualityLot);
await DbContext.SaveChangesAsync();
}
.вызов DbContext.QualityLot.Update(qualityLot);
завершается неудачей.
Из того, что я вижу, экземпляр QLDwnldService
является новым для каждого обрабатываемого файла, другими словами, следующий метод возвращает каждый раз, когда новый объект (как настроено в Startup.cs)
_ServiceProvider.GetService<IQLDwnldService>().QLDownloadAsync(ev);
, в то время как DbContext используется повторно, и это, вероятно, причина, по которой результаты сущности уже отслеживаются.
Я также попытался настроить опцию отсутствия отслеживания в DbContext OnConfiguring()
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
Итак, мой вопрос.Что здесь не так?Может быть, архитектура проблематична или, возможно, вводит в заблуждение конфигурацию ядра?Заранее спасибо за любую поддержку.