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>();