Search code examples
.nethttpentity-framework-corecontroller

DBConcurrencyException when trying to return NotFound() in Update


I try to implement Microsoft's case for an API Update which is shown below. First I get the record from the database to determine if it is null. If not, an update will be triggered. In by code efCore throws an DBConcurrencyException because the element has already been tracked.

What's the problem?

// PUT api/contactsconvention/{guid}
[HttpPut("{id}")]
[ApiConventionMethod(typeof(DefaultApiConventions), 
                     nameof(DefaultApiConventions.Put))]
public IActionResult Update(string id, Contact contact)
{
    // First tracking in my code
    var contactToUpdate = _contacts.Get(id);

    if (contactToUpdate == null)
    {
        return NotFound();
    }

    // Second tracking in my code -> Exception at dbContext.SaveChanges()
    _contacts.Update(contact);

    return NoContent();
}

Solution

  • Behind the .Get Method must be a non-chagen-tracking method. So I added it and now it works.

    // PUT api/contactsconvention/{guid}
    [HttpPut("{id}")]
    [ApiConventionMethod(typeof(DefaultApiConventions), 
                         nameof(DefaultApiConventions.Put))]
    public IActionResult Update(string id, Contact contact)
    {
        // First tracking in my code
        var contactToUpdate = _contacts.GetAsNoTracking(id);
    
        if (contactToUpdate == null)
        {
            return NotFound();
        }
    
        // Second tracking in my code -> Exception at dbContext.SaveChanges()
        _contacts.Update(contact);
    
        return NoContent();
    }
    
    // Non-tracking repository method:
    return context.Contacts.AsNoTracking().Single(s => s.Id == id);
    

    Also I had a problem with my UnitTests which use a mocked dbSet. The AddRange method also adds ChangeTracking values. These can be removed with the following code as described in this post.

        foreach (var entity in InMemoryScheduleDbContext.ChangeTracker.Entries())
        {
            entity.State = EntityState.Detached;
        }