Как я должен внедрить экземпляр 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.