Entity framework 使用范围托管服务

Entity framework with Scoped Hosted Services

我有如下界面

internal interface IScopedProcessingService
{
    Task DoWork(CancellationToken stoppingToken);
}

和实施

public class ConsumeScopedServiceHostedService : BackgroundService
{
    private readonly ILogger<ConsumeScopedServiceHostedService> _logger;

    public ConsumeScopedServiceHostedService(IServiceProvider services, 
        ILogger<ConsumeScopedServiceHostedService> logger)
    {
        Services = services;
        _logger = logger;
    }

    public IServiceProvider Services { get; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service running.");

        await DoWork(stoppingToken);
    }

    private async Task DoWork(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is working.");

        using (var scope = Services.CreateScope())
        {
            var scopedProcessingService = 
                scope.ServiceProvider
                    .GetRequiredService<IScopedProcessingService>();

            await scopedProcessingService.DoWork(stoppingToken);
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is stopping.");

        await Task.CompletedTask;
    }
}

此代码来自微软官方文档。 Background scoped services

和文档中一样,我有 ScopedProcessingService 但更难一些。这是代码:

internal class ScopedProcessingService : IScopedProcessingService
{
    private int _executionCount;

    private readonly ILogger<ConsumeScopedServiceHostedService> _logger;

    private readonly IPushRepository _pushRepository;
    private readonly IPushTemplateRepository _pushTemplateRepository;
    private readonly ISenderLogRepository _senderLogRepository;
    private readonly IDistributionRepository _distributionRepository;

    // services
    private readonly IPushTemplateService _pushTemplateService;
    private readonly ISendPushService _sendPushService;


    public ScopedProcessingService(
        ILogger<ConsumeScopedServiceHostedService> logger,
        IPushTemplateService pushTemplateService, ISendPushService sendPushService,
        IPushRepository pushRepository,
        ISenderLogRepository senderLogRepository, IDistributionRepository distributionRepository,
        IPushTemplateRepository pushTemplateRepository)
    {
        _logger = logger;
        _pushTemplateService = pushTemplateService;
        _sendPushService = sendPushService;
        _pushRepository = pushRepository;
        _senderLogRepository = senderLogRepository;
        _distributionRepository = distributionRepository;
        _pushTemplateRepository = pushTemplateRepository;
    }

    public async Task DoWork(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _executionCount = _senderLogRepository.SenderLogs.Count();

            var logMessage = new StringBuilder();

            logMessage.AppendLine($"Начинаю рассылку № {_executionCount}.");

            // get all templates. THIS CALL IS A SOURCE OF PROBLEMS
            var templates = _pushTemplateRepository.PushTemplates.Where(x => x.isActive)
                .Include(x => x.Messages)
                .ThenInclude(x => x.PushLang)
                .Include(x => x.Category)
                .Include(x => x.AdvertiserPushTemplates)
                .ThenInclude(x => x.Advertiser)
                .ToList();
    }
}

Startup.cs class中我使用下面的代码来注入它:

services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();

这一行的问题var templates = _pushTemplateRepository.PushTemplates.Where(x => x.isActive)。如果我使用 PushTemplate 进行一些更改,则此更改不会在后台任务中生效。我会处理旧数据。我的意思是,如果我改名 对于 PushTemplate,例如,从 Name_1Name_2 的 id = 15 比我将在后台任务中有 Name_1

如何在Scoped后台服务中正确注入EF?我没有使用清晰的 EF 上下文。我有存储库层。

public interface IPushTemplateRepository
{
    IQueryable<PushTemplate> PushTemplates { get; }

    void Save(PushTemplate pushTemplate);
    void Delete(int templateid);
}

和实施

public class PushTemplateRepository : IPushTemplateRepository
{
    private readonly ApplicationDbContext _applicationContext;

    public PushTemplateRepository(ApplicationDbContext applicationContext)
    {
        _applicationContext = applicationContext;
    }

    public IQueryable<PushTemplate> PushTemplates => _applicationContext.PushTemplates;

    public void Save(PushTemplate pushTemplate)
    {
      // ...
    }

    public void Delete(int templateid)
    {
      // ... 
    }
}

问题是在具有无限循环的单个范围内捕获 DbContext

范围永远不会被释放,因此将保留创建范围时的数据。

重构以将循环移出一个级别并在每次需要所需功能时创建一个新范围。

ConsumeScopedServiceHostedService

protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
    _logger.LogInformation("Consume Scoped Service Hosted Service is working.");

    while (!stoppingToken.IsCancellationRequested) {
        using (var scope = Services.CreateScope()) {
            IServiceProvider serviceProvider = scope.ServiceProvider;
            var service = serviceProvider.GetRequiredService<IScopedProcessingService>();    
            await service.DoWork(stoppingToken);
        }
        //Add a delay between executions.
        await Task.Delay(SomeIntervalBetweenCalls, stoppingToken);
    }
}

ScopedProcessingService

//...

public async Task DoWork(CancellationToken stoppingToken) {
    _executionCount = _senderLogRepository.SenderLogs.Count();

    var logMessage = new StringBuilder();

    logMessage.AppendLine($"Начинаю рассылку № {_executionCount}.");

    // get all templates.
    var templates = _pushTemplateRepository.PushTemplates.Where(x => x.isActive)
        .Include(x => x.Messages)
        .ThenInclude(x => x.PushLang)
        .Include(x => x.Category)
        .Include(x => x.AdvertiserPushTemplates)
        .ThenInclude(x => x.Advertiser)
        .ToList();

    //...
}