如何在 Blazor 后台服务中创建 DbContextFactory?

问题描述 投票:0回答:1

我正在开发我的第一个 Blazor Server 应用程序,这也是我的第一个 Entity Framework Core 应用程序。我想建立一个后台服务,每天凌晨一次,检查数据库以查看是否已添加昨天的日期的某种记录类型。如果是这样,相关记录将被提取、格式化,然后通过电子邮件发送给利益相关者。

当我通过手动访问页面触发报告时,EF、格式化和电子邮件代码工作得很好。我遇到的问题是如何为后台服务提供

DbContextFactory<OurAppContext>
,以便EF和相关代码可以执行。

到目前为止,我一直使用基于 Razor 的依赖注入通过页面顶部的

IDbContextFactory<OurAppContext>
注入
inject IDbContextFactory<OurAppContext> DbFactory
,然后通过
DbFactory
变量访问 DbFactory。

但是,后台服务(根据此 Microsoft 教程)是通过

Program.cs
设置的,因此我无法在那里访问基于 Razor 的依赖项注入。

我已经设置了我的后台服务(我称之为 PhaseChangeReportService),如上面的链接所示,它会尽职尽责地每 10 秒向控制台输出一次更新的执行计数。我不完全理解各个间接层发生了什么,但它似乎按照 Microsoft 的预期工作。

我注意到后台服务的构造函数接受 ILogger 作为参数,具体来说:

namespace miniDARTS.ScopedService
{
    public sealed class PhaseChangeReportService : IScopedProcessingService
    {
        private int _executionCount;
        private readonly ILogger<PhaseChangeReportService> _logger;
        
        public PhaseChangeReportService(ILogger<PhaseChangeReportService> logger)
        {
            _logger = logger;                        
        }            

        public async Task DoWorkAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                ++_executionCount;

                _logger.LogInformation("{ServiceName} working, execution count: {Count}", nameof(PhaseChangeReportService), _executionCount);

                await Task.Delay(10_000, stoppingToken);
            }
        }
    }    
}

我曾经(现在)很困惑,Visual Studio 中从未引用构造函数,但是当我在其一行代码上放置断点时,它就会被命中。我尝试修改这个构造函数的签名,以便它也接受一个

IDbFactory<OurAppContext>
,这样无论什么黑魔法允许
ILogger<BackgroundServiceType>
进入并分配给
_logger
也可能会引入
DbFactory<OurAppContext>
,就像这样:

private readonly ILogger<PhaseChangeReportService> _logger;
private readonly IDbContextFactory<miniDARTSContext> _dbContextFactory;

public PhaseChangeReportService(ILogger<PhaseChangeReportService> logger, IDbContextFactory<miniDARTSContext> dbContextFactory)
{
    _logger = logger;                        
    _dbContextFactory = dbContextFactory;
}   

但是,这样做只会导致构造函数断点被跳过并且不会中断,不会抛出异常或任何类型的控制台输出(即之前的执行计数控制台输出不再显示)。所以,我放弃了这种方法。

这是 Program.cs 的相关部分:

// Configure the database connection.
string connectionString = builder.Configuration.GetConnectionString("miniDARTSContext");
var serverVersion = new MySqlServerVersion(new Version(8, 0, 28));
builder.Services.AddDbContextFactory<miniDARTSContext>(options => options.UseMySql(connectionString, serverVersion), ServiceLifetime.Scoped);

IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        services.AddHostedService<ScopedBackgroundService>();
        services.AddScoped<IScopedProcessingService, PhaseChangeReportService>();        
    })
    .Build();

host.RunAsync();

这是 IScopedProcessingService.cs:

namespace miniDARTS.ScopedService
{
    public interface IScopedProcessingService
    {
        Task DoWorkAsync(CancellationToken stoppingToken);
    }
}

这是 ScopedBackgroundService.cs:

namespace miniDARTS.ScopedService;

public sealed class ScopedBackgroundService : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ILogger<ScopedBackgroundService> _logger;
    
    public ScopedBackgroundService(IServiceProvider serviceProvider, ILogger<ScopedBackgroundService> logger)
    {
        _serviceProvider = serviceProvider;
        _logger = logger;        
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation($"{nameof(ScopedBackgroundService)} is running.");

        await DoWorkAsync(stoppingToken);
    }

    private async Task DoWorkAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation($"{nameof(ScopedBackgroundService)} is working.");

        using (IServiceScope scope = _serviceProvider.CreateScope())
        {
            IScopedProcessingService scopedProcessingService = scope.ServiceProvider.GetRequiredService<IScopedProcessingService>();

            await scopedProcessingService.DoWorkAsync(stoppingToken);
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation($"{nameof(ScopedBackgroundService)} is stopping.");

        await base.StopAsync(stoppingToken);
    }
}

我确信在服务/依赖注入方面我误解了一些相对基本的东西,但我的谷歌搜索和对过去 StackOverflow 答案的回顾并没有发现任何我可以运行的东西。

entity-framework-core blazor blazor-server-side
1个回答
2
投票

IDbContextFactory 是一个用于创建 DbContext 实例的接口。当您将其添加到 Blazor (

services.AddDbContextFactory(parameters)
) 的 program.cs 上的服务时,它会为您实现 IDbContextFactory。这允许您使用 razor 组件顶部的
@inject IDbContextFactory<YourDbContext> DbFactory
,然后在代码中您可以在需要创建 DbContext 实例时调用 CreateDbContext 方法(例如
using var context = DbFactory.CreateDbContext()
)。

您可以将注入的 DbContextFactory 作为参数从 razor 组件传递到类,然后在方法中使用该 DbContextFactory 来创建 DbContext 的实例(请参阅构造函数注入),但这仍然依赖于 razor 组件首先注入 DbContextFactory。

要创建独立于 razor 组件的 DbContext 实例,您需要使用 DbContext 的构造函数。您的 DbContext 将有一个带有 DbContextOptions 参数的公共构造函数(在 program.cs 中注册工厂服务时需要使用 AddDbContextFactory )。您可以使用此构造函数来实现您自己的工厂。如果您不确定要使用哪些选项,您可以检查您的program.cs以查看您在那里使用了哪些选项。

public class YourDbFactory : IDbContextFactory<YourDbContext>
{
    public YourDbContext CreateDbContext()
    {
        var optionsBuilder = new DbContextOptionsBuilder<YourDbContext>();
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test"));

        return new YourDbContext(optionsBuilder);
    }
}

创建自己的 IDbContextFactory 接口实现后,您就可以在独立于 razor 组件的代码中使用它 - 例如在后台服务类中。

YourDbFactory DbFactory = new YourDbFactory();
using var context = DbFactory.CreateDbContext();
© www.soinside.com 2019 - 2024. All rights reserved.