问题
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>();
The scoped services constructors uses DI for the Db Context:
private readonly ApplicationDbContext _appDbContext;
public ResourcesHandler(ApplicationDbContext appDbContext)
{
_appDbContext = appDbContext;
}
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.
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.
What's the "protocol" in DI for calling the dbContext in singleton services?
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?
回答1:
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.
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);
}
}
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);
});
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
}
回答2:
I think that if you create a ContextFactory and ask it to for the Context would be a good approach.
You can just register your new Factory like
services.AddSingleton<ContextFactory>();
And inject on the constructor of your handler.
And then you can : _yourService.GetContext(); and use it.
Your factory should has the logic about how to create the context and will be isolated of the rest. Any time you need to use the context, you should call the factory.
Remember as long is a Singleton, you should not use states insides.
Any way if you want to use states just register as Transient for example.
**EDIT : remember to return always NEW instance of the context.
来源:https://stackoverflow.com/questions/49728884/using-dbcontext-in-rabbitmq-consumer-singleton-service