将数据库上下文注入 Hangfire 重复作业的正确方法是什么?

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

我使用 HangFire 定期在后台向用户发送电子邮件。

我正在从数据库获取电子邮件地址,但我不确定是否将数据库上下文“注入”到负责正确发送电子邮件的服务

这个工作正常,有更好的方法吗?

public void Configure(IApplicationBuilder app, IHostingEnvironment env, Context context)
{
    (...)

    app.UseHangfireDashboard();
    app.UseHangfireServer(new BackgroundJobServerOptions
    {
        HeartbeatInterval = new System.TimeSpan(0, 0, 5),
        ServerCheckInterval = new System.TimeSpan(0, 0, 5),
        SchedulePollingInterval = new System.TimeSpan(0, 0, 5)
    });

    RecurringJob.AddOrUpdate(() => new MessageService(context).Send(), Cron.Daily);

    (...)
    app.UseMvc();
}

public class MessageService
{
    private Context ctx;

    public MessageService(Context c)
    {
        ctx = c;
    }

    public void Send()
    {
        var emails = ctx.Users.Select(x => x.Email).ToList();

        foreach (var email in emails)
        {
            sendEmail(email, "sample body");
        }
    }
}
c# asp.net-core .net-core hangfire
4个回答
18
投票

我只是查看了类似的问题,但没有在一处找到信息,所以在这里发布我的解决方案。

假设您已将

Context
配置为服务,即

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    ....
    services.AddDbContext<Context>(options => { ... });
    ....
}

这使得

IServiceProvider
能够解决
Context
依赖性。

接下来,我们需要更新

MessageService
类,以便不永远保留
Context
,而只是实例化它来执行任务。

public class MessageService
{
    IServiceProvider _serviceProvider;
    public MessageService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void Send()
    {
        using (IServiceScope scope = _serviceProvider.CreateScope())
        using (Context ctx = scope.ServiceProvider.GetRequiredService<Context>())
        {
            var emails = ctx.Users.Select(x => x.Email).ToList();

            foreach (var email in emails)
            {
                sendEmail(email, "sample body");
            }
        }
    }
}

最后我们要求 Hangfire 为我们实例化

MessageService
,它也会为我们解决
IServiceProvider
依赖关系:

RecurringJob.AddOrUpdate<MessageService>(x => x.Send(), Cron.Daily);

1
投票

绝对需要使用 DI(StructureMap 等)来解决您的问题。请重构您的配置文件并将“Context”类依赖项与配置类解耦。还要引入一个容器类来映射 DI 类(自动或手动)。

创建Container类

将容器添加到 Hangfire:

GlobalConfiguration.Configuration.UseStructureMapActivator(Bootstrapper.Bootstrap());

还要更改配置类中的作业注册:

RecurringJob.AddOrUpdate<MessageService>(x => x.Send(), Cron.Daily);

1
投票

当您直接访问 IServiceProvider 时,您绕过了 ASP.NET 依赖注入容器提供的自然作用域机制,从而导致服务实例的生命周期和管理出现潜在问题。

直接在控制器中使用 IServiceProvider 可能会导致意外行为和问题,例如:

与范围相关的错误:在瞬态控制器中解析范围或单例服务可能会导致请求之间出现意外的共享状态,从而导致数据损坏或并发问题。

资源泄漏:手动管理通过IServiceProvider获得的服务的生命周期可能会导致处置不当,从而导致资源泄漏和应用程序性能下降。

范围不一致:应用程序的不同部分可能期望不同的服务生命周期。直接在多个地方使用 IServiceProvider 可能会使在整个应用程序中保持服务范围一致变得困难。

虽然按照最受好评的答案中的建议创建自己的范围,在技术上可能可行,但它被认为是代码味道并且是不必要的。


0
投票

我在 github 中关注了这个存储库,并且让一切都像魅力一样工作!我因为这个问题安静地呆了几天。

https://github.com/gonzigonz/HangfireCore-Example/tree/master

© www.soinside.com 2019 - 2024. All rights reserved.