Entity Framework core failing on async methods

async-await c# entity-framework entity-framework-core

Question

I'm using async methods with EF Core - getting the context through the built in DI

 public async Task<bool> Upvote(int userId, int articleId)
{
    var article = await context.Articles
                               .FirstOrDefaultAsync(x => x.Id == articleId);
    if (article == null)
    {
        return false;
    }

    var existing = await context.Votes
                                .FirstOrDefaultAsync(x => x.UserId == userId
                                                       && x.ArticleId == articleId);
    if (existing != null)
    ...

which is ran when someone upvotes an article.

Everything runs fine if this function gets ran one at a time (one after another).

When I hit this function several times at the same time, I get this exception:

fail: Microsoft.EntityFrameworkCore.Query.Internal.MySqlQueryCompilationContextFactory[1]
      An exception occurred in the database while iterating the results of a query.
      System.NullReferenceException: Object reference not set to an instance of an object.
         at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable.AsyncEnumerator.<BufferAllAsync>d__12.MoveNext()
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)

The breakpoint hits: var existing = await context.Votes.FirstOrDefaultAsync(x => x.UserId == userId && x.ArticleId == articleId);

I'm also getting this error: Message [string]:"A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe."

What are some possible solutions?

Edit 1: This is how I'm setting up the context: In Startup.cs, I configure the context:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ArticlesContext>(options => 
        options.UseMySql(Configuration.GetConnectionString("ArticlesDB")));
...

And then I inject it in the constructor of the containing class:

private ArticlesContext context;
private ILoggingApi loggingApi;

public VoteRepository(ArticlesContext context, ILoggingApi loggingApi)
{
    this.context = context;
    this.loggingApi = loggingApi;
}

Edit 2: I'm awaiting all the way down to the controller via:

public async Task<bool> Upvote(int articleId)
{
    return await this.votesRepository.Upvote(userId, articleId);
}

And then in the controller...

[HttpPost]
[Route("upvote")]
public async Task<IActionResult> Upvote([FromBody]int articleId)
{
    var success = await votesService.Upvote(articleId);
    return new ObjectResult(success);
}

Edit 3:

I've changed my services/repos to be transient instead of singletons, but now I'm running into another issue with :

public int getCurrentUserId()
{
    if (!httpContextAccessor.HttpContext.User.HasClaim(c => c.Type == "UserId"))
    {
        return -1;
    }

It's the same async issue - but this time, HttpContext is null. I'm injecting the context accessor via

public UserService(IUserRepository userRepository, IHttpContextAccessor httpContextAccessor)
{
    this.userRepository = userRepository;
    this.httpContextAccessor = httpContextAccessor;
}

Answer: IHttpContextAccessor needs to be registered as a singleton an not transient services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

1
0
12/21/2016 6:05:45 PM

Accepted Answer

Entity framework should be added to the services container using the Scoped lifetime, repo and services should be configured as transient so that a new instance is created and injected as needed and guarantees that instances are not reused.

EF should be scoped so that it is created on every request and disposed once the request ends.

Following those guidelines I don't see a problem in storing the injected instances on the controller constructor. The controller should be instantiated on every request and disposed, along with all scoped injected instances, at the end of the request.

This Microsoft docs page explains all this.

3
12/21/2016 5:37:46 PM

Popular Answer

Some other code use same context at same time. Check this question and answer.

If class that contains Upvote method is singleton - check that you do not store context in constructor, instead you should obtain it from IServiceProvider (HttpContext.RequestServices) for each request (scope), or pass it as parameter.



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