In our application, we have an api which will store some data to a database. We are using Entity Framework Core 3.1.1
. After this entity has been stored, a message is posted to Azure Servicebus
and a consumer will read this message a store the message to another table in the same DbContext
.
As far as I understand, a LifetimeScope
is defined per request to the api. The LifetimeScope
will start when the api request hits the ApiController
and finishes when the endpoint is done processing the request.
The issue we are facing is related to the DbContext
being disposed, so it cannot be used in the Consumer sinced its disposed.
From our code its pretty obvious why it fails. The DbContext
is registered this way:
containerBuilder.RegisterType<TDbContext>().AsSelf().InstancePerLifetimeScope().As<IDbContext>().IfNotRegistered(typeof(TDbContext));
We set the InstancePerLifetimeScope
when registering the DbContext
, resulting the DbContext
to be disposed when the api request is finished and throwing ObjectDisposedException
when we try to use it in the consumer.
Just as an experiment I tried registering the DbContext
as SingleInstance
this way:
containerBuilder.RegisterType<TDbContext>().AsSelf().SingleInstance().As<IDbContext>().IfNotRegistered(typeof(TDbContext));
But it still throws the ObjectDisposedException when we try using it in the Consumer.
Any ideas why this is happening and how we can solve this?
EDIT:
The DbContext is registered via an extension method and passed in as a generic:
public static class ContainerBuilderExtensions
{
public static void RegisterDbContext<TDbContext>(this ContainerBuilder builder)
{
containerBuilder.RegisterType<TDbContext>().AsSelf().InstancePerLifetimeScope().As<IDbContext>().IfNotRegistered(typeof(TDbContext));
}
}
Assuming that you are calling this
containerBuilder.RegisterType<TDbContext>().AsSelf().SingleInstance().As<IDbContext>().IfNotRegistered(typeof(TDbContext));
after this
containerBuilder.RegisterType<TDbContext>().AsSelf().InstancePerLifetimeScope().As<IDbContext>().IfNotRegistered(typeof(TDbContext));
the second registration will not have any effect.
Secondly, registering the DbContext
as a singleton is not a very good idea. It's not thread-safe.
Instead, you can inject IServiceProvider
in the class where the consumer for the servicebus is registered and resolve the context manually when a new message is being consumed.
Option 1: Using IServiceProvider
using (var scope = _serviceProvider.CreateScope())
{
using (var context = scope.ServiceProvider.GetRequiredService<IDbContext>())
{
// do work here
}
}
Option 2: Using IServiceProvider
and casting down to ILifetimeScope
using the extension available since Autofac.Extensions.Microsoft.DependencyInjection
version 5.
using (var lifetimeScope = _serviceProvider.GetAutofacRoot().BeginLifetimeScope())
{
using (var context = lifetimeScope.Resolve<IDbContext>())
{
// do work here
}
}
This will create an instance of IDbContext
everytime you need it and disposes it. While this might take a little longer, it shouldn't be a problem, when messages are consumed in the background.
EDIT: You can also register the DbContext
as Transient
containerBuilder.RegisterType<TDbContext>().AsSelf().InstancePerDependency().As<IDbContext>().IfNotRegistered(typeof(TDbContext));