Un objeto con la misma clave ya existe en el ObjectStateManager. El ObjectStateManager no puede rastrear varios objetos con la misma clave


Usando EF5 con un Patrón de Repositorio genérico y ninject para injenction de dependencias y encontrándose con un problema al intentar actualizar una entidad a la base de datos utilizando procs almacenados con mi edmx.

Mi actualización en DbContextRepository.cs es:

public override void Update(T entity)
{
    if (entity == null)
        throw new ArgumentException("Cannot add a null entity.");

    var entry = _context.Entry<T>(entity);

    if (entry.State == EntityState.Detached)
    {
        _context.Set<T>().Attach(entity);
        entry.State = EntityState.Modified;
    }
}

Desde mi AddressService.cs que vuelve a mi repositorio tengo:

 public int Save(vw_address address)
{
    if (address.address_pk == 0)
    {
        _repo.Insert(address);
    }
    else
    {
        _repo.Update(address);
    }

    _repo.SaveChanges();

    return address.address_pk;
}

Cuando golpea el Attach y EntityState.Modificado vomita con el error:

Un objeto con la misma clave ya existe en el ObjectStateManager. El ObjectStateManager no puede rastrear varios objetos con la misma clave.

He revisado muchas de las sugerencias en stack y en Internet y no he encontrado nada que lo resuelva. Cualquier solución sería apreciada.

Gracias!

Author: Chris Schiffhauer, 2012-09-25

10 answers

Editar : Respuesta original usada Find en lugar de Local.SingleOrDefault. Funcionaba en combinación con el método Save de @Juan, pero podía causar consultas innecesarias a la base de datos y else la parte probablemente nunca se ejecutó (ejecutar la parte else causaría una excepción porque Find ya consultó la base de datos y no encontró la entidad por lo que no se pudo actualizar). Gracias a @BenSwayne por encontrar el problema.

Debe verificar si una entidad con la misma clave ya está rastreada por el contexto y modificar esa entidad en lugar de adjuntar la actual:

public override void Update(T entity) where T : IEntity {
    if (entity == null) {
        throw new ArgumentException("Cannot add a null entity.");
    }

    var entry = _context.Entry<T>(entity);

    if (entry.State == EntityState.Detached) {
        var set = _context.Set<T>();
        T attachedEntity = set.Local.SingleOrDefault(e => e.Id == entity.Id);  // You need to have access to key

        if (attachedEntity != null) {
            var attachedEntry = _context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
        } else {
            entry.State = EntityState.Modified; // This should attach entity
        }
    }
}  

Como puede ver, el problema principal es que el método SingleOrDefault necesita conocer la clave para encontrar la entidad. Puede crear una interfaz sencilla exponiendo la clave (IEntity en mi ejemplo) e implementarla en todas las entidades que desee procesar de esta manera.

 125
Author: Ladislav Mrnka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-07 02:10:43

No quería contaminar mis clases EF generadas automáticamente agregando interfaces o atributos. así que esto es realmente un poco de algunas de las respuestas anteriores (por lo que el crédito va a Ladislav Mrnka). Esto me proporcionó una solución simple.

Agregué un func al método update que encontró la clave entera de la entidad.

public void Update(TEntity entity, Func<TEntity, int> getKey)
{
    if (entity == null) {
        throw new ArgumentException("Cannot add a null entity.");
    }

    var entry = _context.Entry<T>(entity);

    if (entry.State == EntityState.Detached) {
        var set = _context.Set<T>();
        T attachedEntity = set.Find.(getKey(entity)); 

        if (attachedEntity != null) {
            var attachedEntry = _context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
        } else {
            entry.State = EntityState.Modified; // This should attach entity
        }
    }
}  

Luego, cuando llame a su código, puede usarlo..

repository.Update(entity, key => key.myId);
 8
Author: Geoff Wells,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-11 21:01:17

En realidad se puede recuperar el Id a través de la reflexión, ver ejemplo a continuación:

        var entry = _dbContext.Entry<T>(entity);

        // Retreive the Id through reflection
        var pkey = _dbset.Create().GetType().GetProperty("Id").GetValue(entity);

        if (entry.State == EntityState.Detached)
        {
            var set = _dbContext.Set<T>();
            T attachedEntity = set.Find(pkey);  // access the key
            if (attachedEntity != null)
            {
                var attachedEntry = _dbContext.Entry(attachedEntity);
                attachedEntry.CurrentValues.SetValues(entity);
            }
            else
            {
                entry.State = EntityState.Modified; // attach the entity
            }
        }
 7
Author: Toffee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-31 14:49:47

@serj-sagan debes hacerlo de esta manera:

**Tenga en cuenta que YourDb debe ser una clase derivada de DbContext.

public abstract class YourRepoBase<T> where T : class
{
    private YourDb _dbContext;
    private readonly DbSet<T> _dbset;

    public virtual void Update(T entity)
    {
        var entry = _dbContext.Entry<T>(entity);

        // Retreive the Id through reflection
        var pkey = _dbset.Create().GetType().GetProperty("Id").GetValue(entity);

        if (entry.State == EntityState.Detached)
        {
           var set = _dbContext.Set<T>();
           T attachedEntity = set.Find(pkey);  // access the key
           if (attachedEntity != null)
           {
               var attachedEntry = _dbContext.Entry(attachedEntity);
               attachedEntry.CurrentValues.SetValues(entity);
           }
           else
           {
              entry.State = EntityState.Modified; // attach the entity
           }
       }
    }

}

 2
Author: Toffee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:15

Otra solución (basada en la respuesta de @Sergey) podría ser:

private void Update<T>(T entity, Func<T, bool> predicate) where T : class
{
    var entry = Context.Entry(entity);
    if (entry.State == EntityState.Detached)
    {
        var set = Context.Set<T>();
        T attachedEntity = set.Local.SingleOrDefault(predicate); 
        if (attachedEntity != null)
        {
            var attachedEntry = Context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
        }
        else
        {
            entry.State = EntityState.Modified; // This should attach entity
        }
    }
}

Y entonces lo llamarías así:

Update(EntitytoUpdate, key => key.Id == id)
 1
Author: Andrés S.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-24 19:04:52

Sin reflexión y si no desea utilizar interfaces, puede utilizar delegados funcionales para encontrar una entidad en la base de datos. Aquí está la muestra actualizada de arriba.

private void Update<T>(T entity, Func<ObservableCollection<T>, T> locatorMap) where T : class
{
    var entry = Context.Entry(entity);
    if (entry.State == EntityState.Detached)
    {
        var set = Context.Set<T>();
        T attachedEntity = locatorMap(set.Local); 

        if (attachedEntity != null)
        {
            var attachedEntry = Context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
        }
        else
        {
            entry.State = EntityState.Modified; // This should attach entity
        }
    }
}

Lo llamarías así:

Update(EntitytoUpdate, p => p.SingleOrDefault(a => a.Id == id))
 0
Author: Sergey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-15 04:32:58

Si establece su contexto en AsNoTracking (), esto detendrá el seguimiento de los cambios de la entidad en la memoria por parte de aspmvc (que es lo que desea de todos modos en la web).

_dbContext.Products.AsNoTracking().Find(id);  

Te recomendaría leer más sobre esto en http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-an-mvc-web-application

 0
Author: Yashvit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-23 17:52:44

Separar la entidad encontrada (ver attachedEntity en la solución de Ladislav) y volver a unir la modificada funcionó muy bien para mí.

El razonamiento detrás de esto es simple: si algo es inmutable, entonces reemplácelo (como un todo, entidad) desde donde pertenece con el deseado.

Aquí hay un ejemplo de cómo hacer esto:

var set = this.Set<T>();
if (this.Entry(entity).State == EntityState.Detached)
{
    var attached = set.Find(id);
    if (attached != null) { this.Entry(attached).State = EntityState.Detached; }
    this.Attach(entity);
}

set.Update(entity);

Por supuesto, uno puede descubrir fácilmente que este fragmento es parte de un método genérico, de ahí el uso de T, que es un parámetro de plantilla, y Set<T>().

 0
Author: Alexander Christov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-13 06:38:27

Esa respuesta anterior puede ser EF 4.1+. Para aquellos en 4.0, pruebe este método simple...realmente no probado, pero adjuntó y guardó mis cambios.

    public void UpdateRiskInsight(RiskInsight item)
    {
        if (item == null)
        {
            throw new ArgumentException("Cannot add a null entity.");
        }

        if (item.RiskInsightID == Guid.Empty)
        {
            _db.RiskInsights.AddObject(item);
        }
        else
        {
            item.EntityKey = new System.Data.EntityKey("GRC9Entities.RiskInsights", "RiskInsightID", item.RiskInsightID);
            var entry = _db.GetObjectByKey(item.EntityKey) as RiskInsight;
            if (entry != null)
            {
                _db.ApplyCurrentValues<RiskInsight>("GRC9Entities.RiskInsights", item);
            }

        }

        _db.SaveChanges();

    }
 -2
Author: thomas gathings,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-28 03:46:05

Puede que te hayas olvidado de instalar el objeto fBLL = new FornecedorBLL(); en algún lugar

 -3
Author: Junior Ramoty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-25 01:27:06