71 votes

Un objet avec la même clé existe déjà dans l'ObjectStateManager. L'ObjectStateManager ne peut pas suivre plusieurs objets avec la même clé.

J'utilise EF5 avec un modèle de référentiel générique et ninject pour l'injection de dépendances et je rencontre un problème lorsque j'essaie de mettre à jour une entité dans la base de données en utilisant des procs stockés avec mon edmx.

ma mise à jour dans DbContextRepository.cs est :

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

Dans mon AddressService.cs qui renvoie à mon référentiel j'ai :

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

    _repo.SaveChanges();

    return address.address_pk;
}

Quand il atteint l'attachement et EntityState.Modified il vomit avec l'erreur :

Un objet avec la même clé existe déjà dans l'ObjectStateManager. L'ObjectStateManager ne peut pas suivre plusieurs objets avec la même clé.

J'ai examiné de nombreuses suggestions dans la pile et sur Internet et je n'ai rien trouvé qui permette de résoudre le problème. Toute solution de contournement serait appréciée.

Merci !

125voto

Ladislav Mrnka Points 218632

Editar : Réponse originale utilisée Find au lieu de Local.SingleOrDefault . Cela a fonctionné en combinaison avec @Juan's Save mais cela pourrait provoquer des requêtes inutiles à la base de données et else n'a probablement jamais été exécutée (l'exécution de la partie else provoquerait une exception car Find a déjà interrogé la base de données et n'a pas trouvé l'entité, qui ne peut donc pas être mise à jour). Merci à @BenSwayne d'avoir trouvé le problème.

Vous devez vérifier si une entité avec la même clé est déjà suivie par le contexte et modifier cette entité au lieu de rattacher l'entité actuelle :

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

Comme vous pouvez le voir, le problème principal est que SingleOrDefault doit connaître la clé pour trouver l'entité. Vous pouvez créer une interface simple exposant la clé ( IEntity dans mon exemple) et l'implémenter dans toutes les entités que vous souhaitez traiter de cette manière.

8voto

Geoff Wells Points 161

Je ne voulais pas polluer mes classes EF générées automatiquement par l'ajout d'interfaces ou d'attributs. Cette solution est donc un peu différente de certaines des réponses précédentes (le mérite en revient à Ladislav Mrnka). Cela a fourni une solution simple pour moi.

J'ai ajouté un func à la méthode de mise à jour qui trouve la clé entière de l'entité.

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

}

Ensuite, lorsque vous appelez votre code, vous pouvez utiliser

repository.Update(entity, key => key.myId);

7voto

Toffee Points 699

Vous pouvez en fait récupérer l'Id par réflexion, voir l'exemple ci-dessous :

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

2voto

Toffee Points 699

@ serj-sagan vous devriez le faire de cette façon :

**Notez que YourDb doit être une classe dérivée 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
           }
       }
    }

}

1voto

Andrés S. Points 1

Une autre solution (basée sur la réponse de @Sergey) pourrait être :

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

Et puis vous l'appelleriez comme ça :

Update(EntitytoUpdate, key => key.Id == id)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X