0

In my ASP.NET Web API application, I get the following error in one of my actions, at the line below:

var dateExists = await _unitOfWork.GetRepository<Booking>().All()
    .AnyAsync(b => b.User.UserName == booking.User.UserName && b.Date == model.Date);

Here's the error:

A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.

But, as you can see, I am using await. I'm also using await on all the other async operations in the action. Then, what might be the problem?

Edit:

Here's my entire action:

[HttpPut]
[Route("Update")]
public async Task<IHttpActionResult> Put(BookingEditBindingModel model)
{
    if (!ModelState.IsValid)
    {
        // Return a bad request response if the model state is invalid...
        return BadRequest(ModelState);
    }

    try
    {
        var booking = await _unitOfWork.GetRepository<Booking>().FindAsync(model.BookingId);
        if (booking == null)
        { 
            return NotFound();
        }

        // Only Admin is allowed to update other users' data...
        if (!User.IsInRole("Admin") && booking.User.UserName != User.Identity.Name)
        {
            return Unauthorized();
        }

        // There can only be one entry per date/user.
        if (model.Date != booking.Date)
        {
            var dateExists = await _unitOfWork.GetRepository<Booking>().All()
                .AnyAsync(b => b.User.UserName == booking.User.UserName && b.Date == model.Date);
            if (dateExists)
            {
                ModelState.AddModelError("Date", "Data already exists for this date.");
                return BadRequest(ModelState);
            }
        }

        booking.Date = model.Date;
        // Change other properties...

        await _unitOfWork.SaveAsync();

        return Ok();
    }
    catch (Exception ex)
    {
        return InternalServerError(ex);
    }
}

Update:

I disabled lazy loading, but I still had a problem. It looks like, as other people had guessed, the issue was with booking.User, because the FindAsync() in the first query does not include the User. I replaced the first query with the one below, and the issue was resolved.

var booking = await _unitOfWork.GetRepository<Booking>().All()
    .Include(b => b.User)
    .SingleOrDefaultAsync(b => b.BookingId == model.BookingId);
ataravati
  • 8,891
  • 9
  • 57
  • 89
  • Can you explain why you downvoted? – ataravati Feb 28 '18 at 21:06
  • Possible duplicate of [Why Does Await Not Appear to Prevent Second Operation on EF Context](https://stackoverflow.com/questions/34773533/why-does-await-not-appear-to-prevent-second-operation-on-ef-context) – JSteward Feb 28 '18 at 21:09
  • 1
    The code is far from complete. What other operations are part of the Action? This could come from forgetting an await elsewhere. – H H Feb 28 '18 at 21:10
  • @HenkHolterman, I'll add some more code. – ataravati Feb 28 '18 at 21:11
  • 1
    The also include that `.All()` and tell us if lazy loading could be used. Like in the suggested duplicate, `booking.User` could be the culprit. – H H Feb 28 '18 at 21:13
  • I edited the question to add more code. – ataravati Feb 28 '18 at 21:19
  • On a side note I strongly recommend you do not create additional wrappers around EF. The type `DbContext` is an implementation of a UoW pattern and the type `DbSet` is an implementation of a Repository pattern. Why re-wrap these types in your own implementation of the same pattern? You are adding nothing of value, just more code and a poor abstraction which results in code that is harder to read, debug, and use. – Igor Feb 28 '18 at 21:19
  • @Igor, ok, thanks for the recommendation! – ataravati Feb 28 '18 at 21:21
  • By the way, I have a similar code in my Post action, but don't have any issues with it. – ataravati Feb 28 '18 at 21:21

1 Answers1

4

I am going to go out on a limb and guess that you have lazy loading enabled. This would cause a call like booking.User.UserName to go to the database to retrieve the value and putting that inside of a call like AnyAsync could cause the DbContext to try to open a second connection to retrieve that value after it already opened one for the AnyAsync statement.

The quick fix in this case is to retrieve the user name before the call to AnyAsync.

var bookingUserName = booking.User.UserName;
var dateExists = await _unitOfWork.GetRepository<Booking>().All()
                .AnyAsync(b => b.User.UserName == bookingUserName && b.Date == model.Date);

A better fix would be to retrieve this information in advance in the call where you get the booking instance so you do not have to make an additional round trip.

Personally I always disable lazy loading on my DbContext types and use Include statements to explicitly specify what I want to be retrieved with any call. This gives me more control over my execution.

ataravati
  • 8,891
  • 9
  • 57
  • 89
Igor
  • 60,821
  • 10
  • 100
  • 175
  • I disabled lazy loading, and now the error was replaced with this one: ""Non-static method requires a target.". So, it has something to do with the expression. I'll have to move it outside of the query. Thanks for your help! – ataravati Feb 28 '18 at 21:38
  • Apparently, the problem is the FindAsync() on the first query, because it does not include the User. – ataravati Feb 28 '18 at 21:46