HostedService: экземпляр типа объекта не может быть отслежен - PullRequest
0 голосов
/ 11 февраля 2019

Я разрабатываю веб-интерфейс с 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);  
}

Итак, мой вопрос.Что здесь не так?Может быть, архитектура проблематична или, возможно, вводит в заблуждение конфигурацию ядра?Заранее спасибо за любую поддержку.

1 Ответ

0 голосов
/ 11 февраля 2019

Если честно, я не мог понять, где ваш DBContext фактически внедряется из вашего кода.

Но из сообщения об ошибке я бы сказал, что ваш контекст используется повторно там, где его не должно быть.Таким образом, он вводится один раз, а затем используется снова и снова и снова.

Вы зарегистрировали свой сервис как «Scoped» (потому что это по умолчанию).

Вы должны зарегистрировать его как «Переходный», чтобы получать новый экземпляр при каждом вызове вашего сервиса.Поставщик:

services.AddDbContext<MyDbContext>(cfg =>
{                
    cfg.UseSqlServer(_config.GetConnectionString("LIMSConnectionString"));
}, 
ServiceLifetime.Transient);

Брэд отметил, что это будет иметь последствия для остальной части вашего заявления, и он прав.

Лучшим вариантом может быть оставить область действия DbContext и внедрить IServiceScopeFactory в вашу размещенную службу.Затем создайте новую область, где она вам нужна:

using(var scope = injectedServiceScopeFactory.CreateScope())
{
    var dbContext = scope.ServiceProvider.GetService<DbContext>();

    // do your processing with context

} // this will end the scope, the scoped dbcontext will be disposed here

Обратите внимание, что это по-прежнему не означает, что вы должны параллельно получать доступ к DbContext.Я не знаю, почему ваши звонки все асинхронные.Если вы на самом деле выполняете параллельную работу, убедитесь, что вы создали один DbContext для каждого потока.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...