Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using DbContext in RabbitMQ Consumer (Singleton Service)

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?

like image 749
Dandy Avatar asked Apr 09 '18 08:04

Dandy


People also ask

Should DbContext be a singleton?

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.

How do you consume scoped service from Singleton?

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).

Why DbContext is not thread safe?

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.

How to implement RabbitMQ in .NET core?

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.


1 Answers

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
}
like image 166
Evk Avatar answered Sep 29 '22 03:09

Evk