我正在开发我的第一个 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 答案的回顾并没有发现任何我可以运行的东西。
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();