问题描述
我有一个工作正常的 RabbitMQ 单例,但每当消息到达时都依赖于范围服务:
I have a RabbitMQ Singleton that is working fine, but has a dependency on a scoped service whenever a message arrives:
consumer.Received += _resourcesHandler.ProcessResourceObject; //Scoped Service
我的服务是这样注册的:
My services are registered like so:
services.AddScoped<IHandler, Handler>();
services.AddSingleton<RabbitMqListener>();
作用域服务构造函数将 DI 用于 Db 上下文:
The scoped services constructors uses DI for the Db Context:
private readonly ApplicationDbContext _appDbContext;
public ResourcesHandler(ApplicationDbContext appDbContext)
{
_appDbContext = appDbContext;
}
此范围服务调用 Db 上下文,以便在收到消息时将属性插入数据库.
This scoped service calls the Db Context in order to insert properties to the database on receipt of a message.
但是,由于作用域服务的生命周期不同,因此启动失败.
However, because the scoped service has a different lifetime, startup is failing.
有没有更好的方法来做到这一点?我可以使作用域服务成为单例,但是我会遇到使用 DbContext 作为依赖项的问题.
Is there a better way to do this? I could make the scoped service a singleton, but then I'd have the problem of using DbContext as a dependancy.
DI 中用于在单例服务中调用 dbContext 的协议"是什么?
What's the "protocol" in DI for calling the dbContext in singleton services?
我可以使用 using
语句来确保它被释放,但是我必须使用 DI 来传递 DbContextOptions.这是实现这一目标的唯一方法吗?
I could use a using
statement to make sure its disposed, but then I'd have to pass the DbContextOptions using DI instead. Is this the only way to achieve this?
推荐答案
一种方法是自己创建范围.通常 asp.net 核心在请求开始时为您创建范围,并在请求结束时关闭范围.但在你的情况下 - rabbitmq 消息消费与 http 请求完全无关.不过,您可以说,每个消息处理都代表自己的范围.
One way is to create scope yourself. Usually asp.net core creates scope for you when request starts and closes scope when request ends. But in your case - rabbitmq message consumption is not related to http requests at all. You can say though, that every message processing represents its own scope.
在这种情况下,将 IServiceProvider
注入到 RabbitMqListener
(表示为下面的 _provider
私有字段),然后:
In such case, inject IServiceProvider
to RabbitMqListener
(represented as _provider
private field below) and then:
private void OnMessageReceived(Message message) {
using (var scope = _provider.CreateScope()) {
var handler = scope.ServiceProvider.GetRequiredService<IHandler>();
handler.ProcessResourceObject(message);
}
}
替代方法是在容器中注册 ApplicationDbContext
factory(除了常规范围注册).Factory 将返回 ApplicationDbContext
的新实例,调用者将负责处理它.例如:
Alternative could be to register ApplicationDbContext
factory in container (in addition to regular scoped registration). Factory will return new instance of ApplicationDbContext
and that will be callers responsibility to dispose it. For example:
services.AddSingleton<Func<ApplicationDbContext>>(() =>
{
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
return new ApplicationDbContext(optionsBuilder.Options);
});
然后您可以将 IHandler
注册为单例(而不是像现在这样的范围)并在其构造函数中注入 Func
:
Then you can register IHandler
as singleton (and not scoped like now) and inject Func<ApplicationDbContext>
in its constructor:
private readonly Func<ApplicationDbContext> _appDbContextFactory;
public ResourcesHandler(Func<ApplicationDbContext> appDbContextFactory)
{
_appDbContextFactory = appDbContextFactory;
}
然后,当您需要在处理程序中处理消息时 - 您自己管理上下文:
Then whenever you need to process message in handler - you manage context yourself:
using (var context = _appDbContextFactory()) {
// do stuff
}
这篇关于在 RabbitMQ 消费者(单例服务)中使用 DbContext的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!