Проблемы с доступом к DBContext с помощью Scoped IHostedService - PullRequest
0 голосов
/ 23 января 2020

Я пытаюсь создать запланированное задание, которое выполняет команду Sql. Фактический запланированный процесс работает нормально, однако у моего dbcontext есть проблема, из-за которой он не может получить доступ к базе данных.

Он получит контекст со всеми соответствующими таблицами, но любое действие в таблицах выдает InvalidOperationException. При попытке проверить любой из элементов таблицы в режиме отладки все они также говорят InvalidOperationException с красным X в качестве значка.

Я также пытался отправить команду через команду и обработчик MediatR но с такими же ошибками. Каждая попытка изменения кода приводила к одной и той же ошибке без отклонений.

Это моя первая попытка создания фоновых служб, для справки я следовал учебному пособию https://thinkrethink.net/2018/02/21/asp-net-core-background-processing/

Startup.cs

            // truncated
            services.AddDbContext<MyDbContext>();
            services.AddSingleton<IHostedService, Daily_Task>();

Фоновая служба

namespace MyApi.ScheduledTasks
{
    public abstract class BackgroundService : IHostedService
    {
        private Task _executingTask;
        private readonly CancellationTokenSource tokenSource = new CancellationTokenSource();

        public virtual Task StartAsync(CancellationToken token)
        {
            _executingTask = ExecuteAsync(tokenSource.Token);

            return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
        }

        public virtual async Task StopAsync(CancellationToken token)
        {
            if (_executingTask == null)
                return;

            try
            {
                tokenSource.Cancel();
            }
            finally
            {
                await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, token));
            }
        }

        protected virtual async Task ExecuteAsync(CancellationToken token)
        {
            do
                await Process();
            while (!token.IsCancellationRequested);
        }

        protected abstract Task Process();
    }
}

Процессор с областью действия

namespace MyApi.ScheduledTasks
{
    public abstract class ScopedProcessor: BackgroundService
    {
        private readonly IServiceScopeFactory _scopeFactory;

        public ScopedProcessor(IServiceScopeFactory scopeFactory)
        {
            _scopeFactory = scopeFactory;
        }

        protected override async Task Process()
        {
            using (var scope = _scopeFactory.CreateScope())
            {
                await ProcessInScope(scope.ServiceProvider);
            }
        }

        public abstract Task ProcessInScope(IServiceProvider provider);
    }
}

Задача, которую нужно запланировать

namespace MyApi.ScheduledTasks
{
    public class Daily_Task : ScopedProcessor
    {
        private DateTime Start => DateTime.Now;

        private DateTime NextTask { get; private set; }

        public Daily_Task(IServiceScopeFactory scopeFactory) : base(scopeFactory) 
        {
            NextTask = SetNextRun(Start);
        }

        // currently doing minutes for testing
        private DateTime SetNextRun(DateTime date) => date.AddMinutes(1);

        public override async Task ProcessInScope(IServiceProvider provider)
        {
            try
            {
                using (var context = provider.GetRequiredService<MyDbContext>())
                {
                    // even this causes an error
                    var data = context.MyTable.Any();
                }
            }
            catch (Exception e)
            {
                Log.Error(e);
            }
            await Task.CompletedTask;
        }

        protected override async Task ExecuteAsync(CancellationToken token)
        {
            do
            {
                var now = DateTime.Now;
                if (now > NextTask)
                {
                    await Process();
                    NextTask = SetNextRun(now);
                }
            }
            while (!token.IsCancellationRequested);
        }
    }
}

1 Ответ

0 голосов
/ 24 января 2020

Вы можете легко запутаться, изучая, как выполнять фоновые сервисы, потому что есть учебники о том, как это сделать, а затем. NET добавил и изменил лучшие практики при разработке dotnetcore, которые делают недействительными учебники. - это , теперь вы можете реализовать базовый класс BackgroundService, и его следует добавить в ваш контейнер DI с помощью AddHostedService. Это было в Microsoft.Extensions.DependencyInjection в течение некоторого времени. Если вы используете это вместо этого, это значительно упростит то, что вам нужно сделать, чтобы добавить фоновую службу.

Краткое руководство по реализации BackgroundService:

Только переопределить ExecuteAsyn c. Хост будет ожидать его возврата, поэтому, если у вас есть длительная фоновая задача (или постоянная фоновая задача), вы хотите вернуть задачу, а не ждать ее завершения из ExecuteAsyn c (например: use Task.Run) , Когда приложение запускается для отключения, токен отмены, переданный ExecuteAsyn c, будет отменен, и у вас есть несколько секунд, чтобы корректно завершить работу, прежде чем завершится процесс. : P Для размещенной службы по умолчанию область не создается, поэтому вам нужно будет внедрить IServiceProvider, который вы уже знаете, как это сделать.

Ссылка: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio

...