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

24,927

Solution 1

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.

Solution 2

Thanks all for the ideas. I made this overridden function on my context working without Exception, which is imho a slightly better approach. Also, the primary key name is retrieved using the defined model.

Also, it is EntityFramework 3.0 .NET Core

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System.Linq;



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

        var type = entity.GetType();
        var et = this.Model.FindEntityType(type);
        var key = et.FindPrimaryKey();

        var keys = new object[key.Properties.Count];
        var x = 0;
        foreach(var keyName in key.Properties)
        {
            var keyProperty = type.GetProperty(keyName.Name, BindingFlags.Public | BindingFlags.Instance);
            keys[x++] = keyProperty.GetValue(entity);
        }

        var originalEntity = Find(type, keys);
        if (Entry(originalEntity).State == EntityState.Modified)
        {
            return base.Update(entity);
        }

        Entry(originalEntity).CurrentValues.SetValues(entity);
        return Entry((TEntity)originalEntity);
    }
Share:
24,927
Mark Szabo
Author by

Mark Szabo

Updated on July 05, 2022

Comments

  • Mark Szabo
    Mark Szabo almost 2 years

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