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:
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);
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With