Как я должен внедрить экземпляр DbContext в IHostedService?

Вопрос

Как я должен вводить (используя стандартное внедрение зависимостей) DbContext экземпляр в IHostedService?

Что я пробовал

У меня сейчас есть мой IHostedService класс взять MainContext (исходя из DbContext) экземпляр в конструкторе.

Когда я запускаю приложение, я получаю:

Невозможно использовать службу с ограниченным доступом "Microsoft.EntityFrameworkCore.DbContextOptions" из одноэлементного "Microsoft.Extensions.Hosting.IHostedService".

Поэтому я попытался сделать DbContextOptions переходный процесс, указав:

services.AddDbContext<MainContext>(options => 
                options.UseSqlite("Data Source=development.db"), ServiceLifetime.Transient);

в моем Startup учебный класс.

Но ошибка остается той же самой, хотя, согласно этой решенной проблеме Github DbContextOptions пройденный должен иметь то же время жизни, указанное в AddDbContext вызов.

Я не могу сделать контекст базы данных одноэлементным, иначе одновременные вызовы к нему приведут к исключениям параллелизма (из-за того факта, что контекст базы данных не гарантированно безопасен для потоков).

4 ответа

Решение

Хороший способ использовать сервисы внутри размещенных сервисов - создать область, когда это необходимо. Это позволяет использовать контексты служб / БД и т. Д. С конфигурацией времени жизни, с которой они настроены. Отсутствие области видимости теоретически может привести к созданию одноэлементных DbContexts и неправильному повторному использованию контекста (EF Core 2.0 с пулами DbContext).

Для этого введите IServiceScopeFactory и использовать его для создания области при необходимости. Затем разрешите все необходимые зависимости из этой области. Это также позволяет вам регистрировать пользовательские службы в качестве зависимостей областей действия, если вы хотите переместить логику из размещенной службы и использовать размещенную службу только для запуска какой-либо работы (например, регулярно запускать задачу - это будет регулярно создавать области, создавать службу задачи в эта область, в которую также вводится контекст БД).

public class MyHostedService : IHostedService
{
    private readonly IServiceScopeFactory scopeFactory;

    public MyHostedService(IServiceScopeFactory scopeFactory)
    {
        this.scopeFactory = scopeFactory;
    }

    public void DoWork()
    {
        using (var scope = scopeFactory.CreateScope())
        {
            var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
            …
        }
    }
    …
}

Для конкретного случая использования a из одноэлементной службы

Начиная с .NET 5, вы можете зарегистрироватьдля вас и внедрите его в синглтон-сервисы.

ИспользоватьAddDbContextFactory<TContext>зарегистрировать завод:

      public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContextFactory<MyDbContext>(
        options.UseSqlite("Data Source=development.db"));
}

Примечание. Начиная с .NET 6, вы можете удалить AddDbContext()когда используешь AddDbContextFactory()поскольку последний также регистрирует сам тип контекста как службу с ограниченной областью действия.

В сервисе Singleton внедрите и используйтеCreateDbContext()чтобы создать экземпляр вашегоDbContextгде необходимо:

      public class MySingletonService : BackgroundService, IHostedService
{
    private readonly IDbContextFactory<MyDbContext> _contextFactory;

    public MySingletonService(IDbContextFactory<MyDbContext> contextFactory)
    {
        _contextFactory = contextFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using (MyDbContext dbContext = _contextFactory.CreateDbContext())
        {
            await dbContext.MyData.ToListAsync(stoppingToken);
        }
    }
}

См. также
Документы MS: Использование фабрики DbContext (например, для Blazor).

Примечания
Не путать с:
-из Entity Framework (4.3.1, 5.0.0, 6.2.0).
-IDbContextFactory<TContext>из Entity Framework Core (1.0, 1.1, 2.0, 2.1, 2.2).

Вы можете добавить создание Scope в конструктор, как показано ниже:

       public ServiceBusQueueListner(ILogger<ServiceBusQueueListner> logger, IServiceProvider serviceProvider, IConfiguration configuration)
        {
            _logger = logger;
            _reportProcessor = serviceProvider.CreateScope().ServiceProvider.GetRequiredService<IReportProcessor>();
            _configuration = configuration;
        }

Добавьте

      using Microsoft.Extensions.DependencyInjection;

Вот решение, которое работает для меня, когда мне нужно было создать одноэлементный кеш только для чтения, который использовал контекст БД платформы сущностей с ограниченной областью действия:

      // Allows singleton lifetime classes to obtain DB contexts which has the same
// lifetime as the MyDbContextFactory
MyDbContextFactory : IMyDbContextFactory
{
    private readonly IServiceScope? _scope;

    public MyDbContextFactory(IServiceScopeFactory serviceScopeFactory)
    {
        _scope = serviceScopeFactory.CreateScope();
    }

    public IMyDbContext Create()
    {
        if (_scope == null)
        {
            throw new NullReferenceException("Failed creating scope");

        }

        return _scope.ServiceProvider.GetRequiredService<IMyDbContext>();
    }
}

IServiceScopeFactory взят из Microsoft.Extensions.DependencyInjection.Abstractions.

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