Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The instance of entity type ... cannot be tracked because another instance of this type with the same key is already being tracked

Given an ASP.NET Core webapp using Entity Framework Core and an SQL database.

An absolute simple action is throwing this exception when trying to update an entity in the database. First noticed by a bug report in production.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(string id, [Bind("Group")] EditViewModel model)
{
    if (id != model.Group.Id) return NotFound();

    if (!ModelState.IsValid) return View(model);

    _context.Update(model.Group);
    await _context.SaveChangesAsync();

    return RedirectToAction("Index");
}

Exception is thrown at the line: _context.Update(model.Group);

InvalidOperationException: The instance of entity type 'Group' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.

Clearly there is no other instance. I was able to reproduce the exception in my development environment when I stopped the code with a breakpoint on that line and expanded the Results property of the _context.Group object: screenshot of expanded Results property of the _context.Group object It's understandable, that when expanding the Results, it loads the instance needed to be updated and that's why the exception is thrown. But what's about the deployed production environment?

Thanks for the help!

UPDATE1 Group model:

public class Group
{
    [Display(Name = "ID")]
    public string Id { get; set; }

    public virtual Country Country { get; set; }

    [Required]
    [Display(Name = "Country")]
    [ForeignKey("Country")]
    public string CountryCode { get; set; }

    [Required]
    [Display(Name = "Name")]
    public string Name { get; set; }
}

UPDATE2 Based on @Mithgroth's answer, I was able to override the function _context.Update() to not need try-catch every time I use it:

public interface IEntity 
{
    string Id { get; }
}

public override EntityEntry<TEntity> Update<TEntity>(TEntity entity)
{
    if (entity == null)
    {
        throw new System.ArgumentNullException(nameof(entity));
    }

    try
    {
        return base.Update(entity);
    }
    catch (System.InvalidOperationException)
    {
        var originalEntity = Find(entity.GetType(), ((IEntity)entity).Id);
        Entry(originalEntity).CurrentValues.SetValues(entity);
        return Entry((TEntity)originalEntity);
    }
}
like image 229
Mark Szabo Avatar asked Jan 05 '18 16:01

Mark Szabo


1 Answers

Use the following instead:

var group = _context.Group.First(g => g.Id == model.Group.Id);
_context.Entry(group).CurrentValues.SetValues(model.Group); 
await _context.SaveChangesAsync();

The exception can be caused by many different scenarios but the thing is, you are trying to change the state of an object which is already marked differently.

For instance, this would produce the same exception:

var group = new Group() { Id = model.Id, ... };
db.Update(group);

Or you might have detached N-tier children, that's all possible.

This ensures that you are just overwriting an existing entity's values.

like image 177
Mithgroth Avatar answered Nov 14 '22 21:11

Mithgroth