Using DbContext in RabbitMQ Consumer (Singleton Service)

自闭症网瘾萝莉.ら 提交于 2020-01-03 11:28:08

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!