0

We have upgraded our application from NserviceBus v5 to v6 and after that we run into major problem, most of the time receiving the following error.

The operation cannot be completed because the DbContext has been disposed.

It was not obvious until we got load to our system.

We are running with eight concurrent threads and by that we receive the above error.

public class EndpointInitializer
{
    public void Initialize(IKernel container)
    {
        var endpointConfiguration = new EndpointConfiguration("MyEndpoint");
        endpointConfiguration.UseContainer<NinjectBuilder>(
            customizations => { customizations.ExistingKernel(container); });
 //More settings...
    }
}

.

public class MyMessageHandler : IHandleMessages<MyCommand>
{
    private readonly IPersonRepository _personRepository;
    public MyMessageHandler(IPersonRepository personRepository)
    {
        _personRepository = personRepository;
    }
    public async Task Handle(MyCommand message, IMessageHandlerContext context)
    {
        var person = await _personRepository.GetByIdentifierAsync(message.Identifier).ConfigureAwait(false);
        //More code...
        await _personRepository.UpdateAsync(person);
    }
}

.

[Serializable]
public class MyCommand
{
    public string Identifier { get; set; }
}

.

public class DependencyRegistrar
{
    public IKernel Container { get; set; }
    public void Create()
    {
        Container = new StandardKernel();
        RegisterTypes(Container);
    }
    public void RegisterTypes(IKernel kernel)
    {
        kernel.Bind<IPersonRepository>().To<PersonRepository>();
        kernel.Bind<DbContext>().To<MyDbContext>().InThreadScope();
        //More registrations...
    }
}

.

public class MyDbContext : DbContext
{
    public MyDbContext() : base("MyConn")
    {
    }
}

.

public interface IPersonRepository
{
    Task<Person> GetByIdentifierAsync(string identifier);
    Task UpdateAsync(Person entity);
    //More methods...
}

.

public class PersonRepository : IPersonRepository
{
    private readonly DbContext _dbContext;
    public PersonRepository(DbContext dbContext)
    {
        _dbContext = dbContext;
    }
    public async Task<Person> GetByIdentifierAsync(string identifier)
    {
        var personList = await _dbContext.Set<Person>().Where(x => x.Identifier == identifier).ToListAsync().ConfigureAwait(false);
        //More code... 
        return personList.SingleOrDefault();
    }
    public async Task UpdateAsync(Person entity)
    {
        //More code...
        await _dbContext.SaveChangesAsync().ConfigureAwait(false);
    }
}

.

public class Person
{
    public int Id { get; set; }
    public string Identifier { get; set; }
    //More properties...
}

One option that we noticed is working is to pick up DataContext using Particulars example to use UnitOfWorkSetupBehavior. But it does not fit that well in our scenario because we have a complicated setup with services and repositories injecting the DbContext in the constructor throughout our application.

Ie, the (partial) solution for now is to call the method on the repository like;

    var person = await _personRepository.GetByIdentifierAsync(context.DataContext(), message.Identifier).ConfigureAwait(false);

But now, when we run inte more complicated scenarios this won¨t suffice.

So, what are we missing? What is really the issue here?

Per
  • 1,393
  • 16
  • 28
  • Ok. you are only saying what I myself realized, but since this is a common pattern working with ioc, how should I instead think? I don't want to create my repositories with new Repository in my Handle?? Is that the only way? – Per Jun 16 '18 at 21:48
  • Do you want to try https://discuss.particular.net/ or email support@particular.net? – Sean Farmar Jun 17 '18 at 12:23
  • @SeanFarmar, yes, I already been in contact with them earlier. But as it looks like Daniels answer seems to solve my problem. Just need to make sure it is the case before marking it as the solution. – Per Jun 18 '18 at 21:00

1 Answers1

3

Ninject PerThreadScope uses System.Threading.Thread.CurrentThread. With async in place the thread can change potentially for every continuation (the code that follows an await statement). You can either use a custom named scope, an async local scope or use the InUnitOfWorkScope from NServiceBus.Ninject.

Daniel Marbach
  • 2,294
  • 12
  • 15
  • Is this problem specific for ninject? Or are other containers having the same problem? – Per Jun 18 '18 at 05:45
  • Any container can run into that problem depending on when dependencies are resolved in combination with async. In the async world, you have to structure your code around tasks and have to forget threads. Any construct bound to the notion of threads (for example ThreadLocal) can behave unexpected since for every continuation another thread can pick up the task. So IoC scopes that are bound to threads are dangerous with async unless you can guarantee that everything is resolved when the hierarchy is resolved and nothing gets lazy resolved. – Daniel Marbach Jun 18 '18 at 12:24