【问题标题】:Using DbContext in RabbitMQ Consumer (Singleton Service)在 RabbitMQ 消费者(单例服务)中使用 DbContext
【发布时间】:2018-04-09 08:45:41
【问题描述】:

我有一个工作正常的 RabbitMQ 单例,但每当消息到达时都依赖于范围服务:

consumer.Received += _resourcesHandler.ProcessResourceObject; //Scoped Service

我的服务是这样注册的:

services.AddScoped<IHandler, Handler>();
services.AddSingleton<RabbitMqListener>();

作用域服务构造函数将 DI 用于 Db 上下文:

private readonly ApplicationDbContext _appDbContext;

public ResourcesHandler(ApplicationDbContext appDbContext)
{
    _appDbContext = appDbContext;
}

此范围服务调用 Db 上下文,以便在收到消息时将属性插入数据库。

但是,由于作用域服务的生命周期不同,因此启动失败。

有没有更好的方法来做到这一点?我可以使作用域服务成为单例,但是我会遇到使用 DbContext 作为依赖项的问题。

DI 中用于在单例服务中调用 dbContext 的“协议”是什么?

我可以使用using 语句来确保它被释放,但是我必须使用 DI 来传递 DbContextOptions。这是实现这一目标的唯一方法吗?

【问题讨论】:

  • 如果服务引用数据库上下文,则服务和数据库上下文必须具有相同的生命周期。
  • @StanleyOkpalaNwosa 是的。我明白这一点。我的问题是,在我的情况下,MQ 侦听器必须是单例服务才能收集消息。这是使用 ApplicationDbContext 的单例的依赖关系。有没有办法在单例中安全地调用 DbContext?
  • 您是指作用域还是瞬态?我在您的代码中没有看到 AddScoped 方法。 Scoped 仅对通过 ASP.NET Core 管道传入的请求有意义。我不确定 RabbitMq 消息是否这样做。编辑:我看到您将 AddTransient 更改为 AddScoped...
  • @HansKilian 刷新!我正在使用 Transient 进行测试,但改回了作用域。道歉。扩展 - 我的 RabbitMQ 监听器在启动时获取注册的服务,因此它必须是单例的才能在启动时正确注册。

标签: c# .net asp.net-core rabbitmq


【解决方案1】:

一种方法是自己创建范围。通常 asp.net 核心在请求开始时为您创建范围,并在请求结束时关闭范围。但在你的情况下 - rabbitmq 消息消费与 http 请求完全无关。不过,您可以说,每个消息处理都代表自己的范围。

在这种情况下,将IServiceProvider注入RabbitMqListener(表示为下面的_provider私有字段)然后:

private void OnMessageReceived(Message message) {
    using (var scope = _provider.CreateScope()) {
        var handler = scope.ServiceProvider.GetRequiredService<IHandler>();
        handler.ProcessResourceObject(message);
    }
}

替代方法是在容器中注册ApplicationDbContextfactory(除了常规范围注册)。 Factory 将返回 ApplicationDbContext 的新实例,调用者将负责处理它。例如:

services.AddSingleton<Func<ApplicationDbContext>>(() =>
{
    var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
    optionsBuilder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
    return new ApplicationDbContext(optionsBuilder.Options);
});

然后您可以将IHandler 注册为单例(而不是像现在这样的范围)并在其构造函数中注入Func&lt;ApplicationDbContext&gt;

private readonly Func<ApplicationDbContext> _appDbContextFactory;

public ResourcesHandler(Func<ApplicationDbContext> appDbContextFactory)
{
    _appDbContextFactory = appDbContextFactory;
}

然后,当您需要在处理程序中处理消息时 - 您自己管理上下文:

using (var context = _appDbContextFactory()) {
    // do stuff
}

【讨论】:

  • 这似乎是解决此问题的好方法。我没有考虑到在这种情况下,我可能为这个特定的服务选择了错误的框架。我没有考虑到这一点。谢谢!我会试试这个。
  • @Dandy 如果您不想打扰范围,我添加了替代方法,不确定提问者是否收到答案编辑通知。
  • 由于某种原因,自定义范围没有专门用于 DbContext,总是失败,因为它没有被实例化。我没有弄清楚这一点,而是对using 使用了工厂方法并进行了处理。这就像一种享受!
  • '无法访问已处置的对象。对象名称:'IServiceProvider'。'
【解决方案2】:

我认为,如果您创建一个 ContextFactory 并要求它提供 Context 将是一个好方法。

你可以像这样注册你的新工厂

services.AddSingleton<ContextFactory>();

并注入处理程序的构造函数。

然后你可以: _yourService.GetContext();并使用它。

您的工厂应该有关于如何创建上下文的逻辑,并将与其余部分隔离。任何时候你需要使用上下文,你应该调用工厂。

记住只要是单例,就不要在内部使用状态。

如果您想使用状态,只需注册为 Transient 即可。

**EDIT : 记住总是返回上下文的新实例。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-05-10
    • 1970-01-01
    • 2011-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多