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 Answer. First, DbContext is a lightweight object; it is designed to be used once per business transaction. Making your DbContext a Singleton and reusing it throughout the application can cause other problems, like concurrency and memory leak issues. And the DbContext class is not thread safe.
The Solution. To be able to use scoped services within a singleton, you must create a scope manually. A new scope can be created by injecting an IServiceScopeFactory into your singleton service (the IServiceScopeFactory is itself a singleton, which is why this works).
This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe. When concurrent access goes undetected, it can result in undefined behavior, application crashes and data corruption.
Client namespace, we first create a new ConnectionFactory , using the localhost hostname. This is where our RabbitMQ server will be running. Next, we create a connection to the server, which abstracts the socket connection. Finally, we create a channel, which is what will allow us to interact with the RabbitMQ APIs.
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With