Service lifetime in ASP.NET Core

.net asp.net-core asp.net-core-3.1 c# entity-framework-core

Question

I have a simple question but didn't find an answer anywhere.

Solution contain two Web API. One is .NET Core 2.1 with EF Core 2.1.1 and the second is 3.1 with EF Core 3.1.1 and my code is the same for both. There are one custom repository and one controller.

Person repository:

public PersonRepository(AppContext appContext)
{
   this.appContext = appContext;
}

public async Task<IEnumerable<Person>> GetAll()
{
    return await appContext.People.ToListAsync();
}

Controller:

public MyController(PersonRepository personRepository)
{
    this.personRepository = personRepository;
}

[HttpGet]
public async Task<ActionResult> Get()
{
    var data = personRepository.GetAll();
    var data1 = personRepository.GetAll();

    var result = await Task.WhenAll(data, data1);

    return Ok(data.Result);
}
services.AddDbContext<AppContext>(options => options
.UseSqlServer("")
.EnableSensitiveDataLogging(true));

It might seem nonsense. But this is only for demonstration. My question is, why this code works in 2.1 solution but in 3.1 not and exception appear InvalidOperationException: A second operation started on this context before a previous operation completed. (Same for IIS and Kestrel).

I know how to fix it in 3.1 this is not my question. I just need to know why this happened and what's changed between these versions or whenever.

Thank you very much for any response.

1
0
2/2/2020 4:41:42 PM

Popular Answer

If you really want to run both queries in parallel you'd need two DbContexts because DbContext is not thread safe.

You need to change how you register the DbContext in your service container to do this:

 services.AddDbContext<AppDbContext>(options => options
            .UseSqlServer("")
            .EnableSensitiveDataLogging(true), 
            ServiceLifetime.Transient);

Add the ability for the depedency to create a new instance of the DbContext (a simple factory):

services.AddTransient<Func<AppDbContext>>(provider => provider.GetRequiredService<AppDbContext>);

and change your dependency accordingly:

public PersonRepository(Func<AppContext> appContextFactory)
{
    this.appContextFactory = appContextFactory;
}

public async Task<IEnumerable<Person>> GetAll()
{
   using (var appContext = appContextFactory())
   {
       return await appContext.People.ToListAsync();
   }
}

Remember that changing the lifetime scope to Transient means that if you inject DbContext in multiple classes within the same request you will not get the same DbContext instance. Use with caution.

0
2/3/2020 2:13:47 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow