Tobias Moe Thorstensen
Tobias Moe Thorstensen

Reputation: 8981

Start a new LifetimeScope when a Masstransit consumer is triggered

We have the following code to configure the recieveEndpoint:

private Action<IServiceBusReceiveEndpointConfigurator> GetReceiveEndpointConfigurator(IEnumerable<Type> consumerTypes)
{
    return c =>
        {
            c.EnableDeadLetteringOnMessageExpiration = true;
            c.SubscribeMessageTopics = false;
            c.MaxDeliveryCount = 3;
            c.EnableDeadLetteringOnMessageExpiration = true;
            c.UseRetry(Retry.Exponential(3, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(1)));
            foreach (var consumerType in consumerTypes)
            {
                c.Consumer(consumerType, p => _componentContext.Resolve(p));
            }
       };
}

All of our consumers are autodiscovered through reflection once our application starts up. We have a DbContext that we want to use in many of our consumers. The problem we face is that the DbContext is disposed due to it being registered as InstancePerLifetimeScope. More details here:

AspNet Core Autofac disposing my DbContext even if its registered as SingleInstance

Two suggestions came from this post:

  1. Register the DbContext as InstancePerDependency
  2. Create a new Scope within the consumer to start a new LifetimeScope

The first suggestion wont work in our application as we have a UnitOfWork which triggers the SaveChangesAsync on the DbContext. The result would be that the Repository and the UnitOfWork will get two different instances of the DbContext and SaveChangesAsync will not persist our changes as the ChangeTracker has no changes in the UnitOfWork implementation, but these changes belongs to the instance in the Repository.

The second suggestion works perfectly. Within my Consumer I create a new LifetimeScope and resolves the components that I need:

public async Task Consume(ConsumeContext<RetailerCreatedEvent> context)
 {
     using (var scope = _serviceProvider.CreateScope())
     {
         var unitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork<MyDbContext>>();
     }
 }

It works, but it doesn't look that good.

Is there a way to start a new LifetimeScope before the Consumer triggers? Or should I rewrite my UnitOfWork-pattern to ensure that the same DbContext is being reused in the Repositories and the UnitOfWork?

Suggestions are much appreciated

Upvotes: 2

Views: 1478

Answers (1)

Chris Patterson
Chris Patterson

Reputation: 33278

You need to use the MassTransit.Autofac package to resolve your consumers, which will use the AutofacScopeProvider (part of the package) to create a lifetime scope and resolve your consumer.

The documentation shows the configuration, including how to automatically discover your consumers via scanning and add them to MassTransit.

Your consumers shouldn't have any container code in them using this package, they should just add DbContext as a constructor dependency and let Autofac do the work.

Upvotes: 3

Related Questions