web-dev-qa-db-fra.com

ASP.NET MVC - La liaison d'une entité de type 'MODELNAME' a échoué car une autre entité du même type a déjà la même valeur de clé primaire

En un mot, l'exception est levée lors du modèle d'enveloppe de POST et le passage de l'état d'une entrée à "Modifié". Avant de modifier l'état, l'état est défini sur 'Détaché', mais l'appel de Attach () génère la même erreur. J'utilise EF6.

Veuillez trouver mon code ci-dessous (les noms de modèles ont été modifiés pour faciliter la lecture)

Modèle

// Wrapper classes
        public class AViewModel
        {
            public A a { get; set; }
            public List<B> b { get; set; }
            public C c { get; set; }
        }   

Manette

        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }

            if (!canUserAccessA(id.Value))
                return new HttpStatusCodeResult(HttpStatusCode.Forbidden);

            var aViewModel = new AViewModel();
            aViewModel.A = db.As.Find(id);

            if (aViewModel.Receipt == null)
            {
                return HttpNotFound();
            }

            aViewModel.b = db.Bs.Where(x => x.aID == id.Value).ToList();
            aViewModel.Vendor = db.Cs.Where(x => x.cID == aViewModel.a.cID).FirstOrDefault();

            return View(aViewModel);
        }

[HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit(AViewModel aViewModel)
        {
            if (!canUserAccessA(aViewModel.a.aID) || aViewModel.a.UserID != WebSecurity.GetUserId(User.Identity.Name))
                return new HttpStatusCodeResult(HttpStatusCode.Forbidden);

            if (ModelState.IsValid)
            {
                db.Entry(aViewModel.a).State = EntityState.Modified; //THIS IS WHERE THE ERROR IS BEING THROWN
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(aViewModel);
        }

Comme indiqué ci-dessus

db.Entry(aViewModel.a).State = EntityState.Modified;

lève exception:

La liaison d'une entité de type 'A' a échoué car une autre entité du le même type a déjà la même valeur de clé primaire. Cela peut arriver quand en utilisant la méthode 'Attach' ou en définissant l'état d'une entité sur 'Inchangé' ou 'Modifié' si l'une des entités du graphique a valeurs clés contradictoires. Cela peut être dû au fait que certaines entités sont nouvelles et n'ont pas encore reçu les valeurs de clé générées par la base de données. Dans ce cas, utilisez la méthode 'Add' ou l'état de l'entité 'Added' pour suivre le graphique et puis définissez l'état des entités non nouvelles sur "Unchanged" ou "Modified" en tant que approprié.

Quelqu'un voit-il quelque chose qui ne va pas dans mon code ou comprend-il dans quelles circonstances une telle erreur serait générée lors de la modification d'un modèle?

102
Chris Ciszak

Problème résolu!

La méthode Attach pourrait potentiellement aider quelqu'un, mais cela ne l'aiderait pas car le document faisait déjà l'objet d'un suivi alors qu'il était chargé dans la fonction de modification du contrôleur GET. Attach jetterait exactement la même erreur.

Le problème que j'ai rencontré ici est dû à la fonction canUserAccessA() qui charge l'entité A avant de mettre à jour l'état de l'objet a. Cela bousillait l'entité suivie et changeait l'état d'un objet en Detached.

La solution consistait à modifier canUserAccessA() afin que l'objet que je chargeais ne soit pas suivi. La fonction AsNoTracking() doit être appelée lors de l'interrogation du contexte.

// User -> Receipt validation
private bool canUserAccessA(int aID)
{
    int userID = WebSecurity.GetUserId(User.Identity.Name);
    int aFound = db.Model.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();

    return (aFound > 0); //if aFound > 0, then return true, else return false.
}

Pour une raison quelconque, je ne pouvais pas utiliser .Find(aID) avec AsNoTracking(), mais cela n'a pas vraiment d'importance, car je pouvais atteindre le même objectif en modifiant la requête.

J'espère que cela aidera tout le monde avec le même problème!

133
Chris Ciszak

Intéressant: 

_dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId);

Ou si vous n'êtes toujours pas générique:

_dbContext.Set<UserEntity>().AddOrUpdate(entityToBeUpdatedWithId);

semble résoudre mon problème en douceur.

90
guneysus

Il semble que l'entité que vous essayez de modifier ne soit pas suivie correctement et par conséquent, elle n'est pas reconnue comme modifiée, mais ajoutée à la place.

Au lieu de définir directement l'état, essayez de procéder comme suit:

//db.Entry(aViewModel.a).State = EntityState.Modified;
db.As.Attach(aViewModel.a); 
db.SaveChanges();

De plus, je voudrais vous avertir que votre code contient des failles de sécurité potentielles. Si vous utilisez une entité directement dans votre modèle de vue, vous risquez donc que quelqu'un modifie le contenu de cette entité en ajoutant des champs correctement nommés dans le formulaire soumis. Par exemple, si l'utilisateur a ajouté une zone de saisie avec le nom "A.FirstName" et que l'entité contenait ce champ, la valeur serait liée à viewmodel et enregistrée dans la base de données même si l'utilisateur n'était pas autorisé à modifier cela dans le fonctionnement normal de l'application. .

Mettre à jour:

Pour remédier à la vulnérabilité de sécurité mentionnée précédemment, vous ne devez jamais exposer votre modèle de domaine en tant que modèle de vue, mais utiliser plutôt un modèle de vue distinct. Ensuite, votre action recevrait un modèle de vue que vous pourriez associer à un modèle de domaine à l'aide d'un outil de cartographie tel qu'AutoMapper. Cela vous évitera de modifier des données sensibles. 

Voici l'explication étendue:

http://www.stevefenton.co.uk/Content/Blog/Date/201303/Blog/Why-You-Never-Expose-Your-Domain-Model-As-Your-MVC-Model/

14
Kaspars Ozols

Mon cas était que je n'avais pas d'accès direct au contexte EF depuis mon application MVC. 

Ainsi, si vous utilisez un type de référentiel pour la persistance d'entité, il peut être approprié de simplement détacher une entité explicitement chargée, puis de définir EntityState lié à Modifié.

Exemple de code (abstrait):

MVC

public ActionResult(A a)
{
  A aa = repo.Find(...);
  // some logic
  repo.Detach(aa);
  repo.Update(a);
}

Dépôt

void Update(A a)
{
   context.Entry(a).EntityState = EntityState.Modified;
   context.SaveChanges();
}

void Detach(A a)
{
   context.Entry(a).EntityState = EntityState.Detached;
}
10
sephirot

pour moi la copie locale était la source du problème ..__ ceci l'a résolu

var local = context.Set<Contact>().Local.FirstOrDefault(c => c.ContactId == contact.ContactId);
                if (local != null)
                {
                    context.Entry(local).State = EntityState.Detached;
                }
8
add-Naan

Essaye ça:

var local = yourDbContext.Set<YourModel>()
                         .Local
                         .FirstOrDefault(f => f.Id == yourModel.Id);
if (local != null)
{
  yourDbContext.Entry(local).State = EntityState.Detached;
}
yourDbContext.Entry(applicationModel).State = EntityState.Modified;

Je pensais partager mon expérience sur celui-ci, même si je me sentais un peu bête de ne pas m'en rendre compte plus tôt.

J'utilise le modèle de référentiel avec les instances de référentiel injectées dans mes contrôleurs. Les référentiels concrets instancient mon ModelContext (DbContext) qui dure toute la vie du référentiel, lequel est IDisposable et est éliminé par le contrôleur.

Le problème pour moi était que j'avais une version modifiée du tampon et de la ligne sur mes entités; je les obtenais donc en premier pour pouvoir les comparer aux en-têtes entrants. Bien sûr, cela chargeait et suivait l'entité qui était ensuite mise à jour.

Le correctif consistait simplement à changer le référentiel de la création d’un contexte une fois dans le constructeur à l’application des méthodes suivantes:

    private DbContext GetDbContext()
    {
        return this.GetDbContext(false);
    }


    protected virtual DbContext GetDbContext(bool canUseCachedContext)
    {
        if (_dbContext != null)
        {
            if (canUseCachedContext)
            {
                return _dbContext;
            }
            else
            {
                _dbContext.Dispose();
            }
        }

        _dbContext = new ModelContext();

        return _dbContext;
    }

    #region IDisposable Members

    public void Dispose()
    {
        this.Dispose(true);
    }

    protected virtual void Dispose(bool isDisposing)
    {
        if (!_isDisposed)
        {
            if (isDisposing)
            {
                // Clear down managed resources.

                if (_dbContext != null)
                    _dbContext.Dispose();
            }

            _isDisposed = true;
        }
    }

    #endregion

Cela permet aux méthodes de référentiel de renouveler leur instance de contexte à chaque utilisation en appelant GetDbContext ou d'utiliser une instance précédente si elles le souhaitent en spécifiant true.

3
Luke Puplett

J'ai ajouté cette réponse uniquement parce que le problème est expliqué sur la base d'un modèle de données plus complexe et que j'ai trouvé difficile à comprendre ici.

J'ai créé une application assez simple. Cette erreur s'est produite dans l'action Edit POST. L'action a accepté ViewModel en tant que paramètre d'entrée. Le ViewModel a été utilisé pour effectuer des calculs avant l'enregistrement de l'enregistrement.

Une fois que l'action a été validée, telle que if(ModelState.IsValid), mon action fautive était de projeter les valeurs de ViewModel dans une toute nouvelle instance de Entity. Je pensais que je devrais créer une nouvelle instance pour stocker les données mises à jour, puis sauvegarder une telle instance.

Ce que j’ai compris plus tard, c’était que je devais lire le dossier de la base de données:

Student student = db.Students.Find(s => s.StudentID == ViewModel.StudentID);

et mis à jour cet objet. Tout fonctionne maintenant.

2
Celdor

J'ai eu ce problème avec var local et je viens de le détacher comme ceci:

if (ModelState.IsValid)
{
    var old = db.Channel.Find(channel.Id);
    if (Request.Files.Count > 0)
    {
        HttpPostedFileBase objFiles = Request.Files[0];
        using (var binaryReader = new BinaryReader(objFiles.InputStream))
        {
            channel.GateImage = binaryReader.ReadBytes(objFiles.ContentLength);
        }

    }
    else
        channel.GateImage = old.GateImage;
    var cat = db.Category.Find(CatID);
    if (cat != null)
        channel.Category = cat;
    db.Entry(old).State = EntityState.Detached; // just added this line
    db.Entry(channel).State = EntityState.Modified;
    await db.SaveChangesAsync();
    return RedirectToAction("Index");
}
return View(channel);

Problèmes liés aux objets chargés avec la même clé. Nous allons donc d'abord détacher cet objet et effectuer la mise à jour pour éviter les conflits entre deux objets ayant la même clé. 

1
lvl4fi4

Je résous ce problème avec un bloc "using" 

using (SqlConnection conn = new SqlConnection(connectionString))

    {

       // stuff to do with data base
    }

    // or if you are using entity framework 
    using (DataBaseEntity data = new DataBaseEntity)
{

    }

C’est là que j’ai l’idée https://social.msdn.Microsoft.com/Forums/sqlserver/es-ES/b4b350ba-b0d5-464d-8656-8c117d55b2af/problema-al-modificar-en-entity-framework ? forum = vcses est en espagnol (cherchez la deuxième réponse)

1
Suzume

Utilisez AsNoTracking() à l'endroit où vous obtenez votre requête.

  var result = dbcontext.YourModel.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();
1
Abdus Salam Azad

Voici ce que j'ai fait dans le cas similaire.

Cette sitatuation signifie que la même entité a déjà existé dans le contexte. Ce qui suit peut aider

Première vérification de ChangeTracker si l'entité est dans le contexte

var trackedEntries=GetContext().ChangeTracker.Entries<YourEntityType>().ToList();

var isAlreadyTracked =
                    trackedEntries.Any(trackedItem => trackedItem.Entity.Id ==myEntityToSave.Id);

S'il existe 

  if (isAlreadyTracked)
            {
                myEntityToSave= trackedEntries.First(trackedItem => trackedItem.Entity.Id == myEntityToSave.Id).Entity;
            } 

else
{
//Attach or Modify depending on your needs
}
1
erhan355

Semblable à ce que dit Luke Puplett, le problème peut être causé par une mauvaise élimination ou création de votre contexte.

Dans mon cas, j'avais une classe qui acceptait un contexte appelé ContextService:

public class ContextService : IDisposable
{
    private Context _context;

    public void Dispose()
    {
        _context.Dispose();
    }
    public ContextService(Context context)
    {
        _context = context;
    }
//... do stuff with the context

Mon service de contexte avait une fonction qui met à jour une entité à l'aide d'un objet d'entité instanciée:

        public void UpdateEntity(MyEntity myEntity, ICollection<int> ids)
        {
            var item = _context.Entry(myEntity);
            item.State = EntityState.Modified;
            item.Collection(x => x.RelatedEntities).Load();
            myEntity.RelatedEntities.Clear();
            foreach (var id in ids)
            {
                myEntity.RelatedEntities.Add(_context.RelatedEntities.Find(id));
            }
            _context.SaveChanges();
        }

Tout cela se passait bien, le problème était lié à mon contrôleur sur lequel j'avais initialisé le service. Mon contrôleur ressemblait à l'origine à ceci:

    private static NotificationService _service = 
        new NotificationService(new NotificationContext());
    public void Dispose()
    {
    }

Je l'ai changé en ceci et l'erreur est partie:

    private static NotificationService _service;
    public TemplateController()
    {
        _service = new NotificationService(new NotificationContext());
    }
    public void Dispose()
    {
        _service.Dispose();
    }
1
Jared Beach

je parviens à résoudre le problème en mettant à jour l’état. lorsque vous déclenchez une recherche ou toute autre opération de requête sur le même enregistrement, l'état a été mis à jour avec modification; nous devons donc définir le statut sur Détaché pour que vous puissiez déclencher votre modification 

     ActivityEntity activity = new ActivityEntity();
      activity.name="vv";
    activity.ID = 22 ; //sample id
   var savedActivity = context.Activities.Find(22);

            if (savedActivity!=null)
            {
                context.Entry(savedActivity).State = EntityState.Detached;
                context.SaveChanges();

                activity.age= savedActivity.age;
                activity.marks= savedActivity.marks; 

                context.Entry(activity).State = EntityState.Modified;
                context.SaveChanges();
                return activity.ID;
            }
1
veeresh i

Ce problème peut également apparaître lors de la mappage de ViewModel à EntityModel (à l'aide de AutoMapper, etc.) et si vous tentiez d'inclure context.Entry().State et context.SaveChanges() tel qu'un bloc utilisant, comme indiqué ci-dessous, résoudrait le problème. N'oubliez pas que la méthode context.SaveChanges() est utilisée deux fois au lieu d'utiliser juste après if-block car elle doit également être utilisée avec block.

public void Save(YourEntity entity)
{
    if (entity.Id == 0)
    {
        context.YourEntity.Add(entity);
        context.SaveChanges();
    }
    else
    {
        using (var context = new YourDbContext())
        {
            context.Entry(entity).State = EntityState.Modified;
            context.SaveChanges(); //Must be in using block
        }
    }            
}

J'espère que cela t'aides...

1
Murat Yıldız

J'ai rencontré cette erreur où

  • deux méthodes, A et B, dans un seul contrôleur, utilisaient toutes deux la même instance d'un ApplicationDbContext, et
  • méthode A appelée méthode B
    private ApplicationDbContext db;
    // api methods
    public JsonResult methodA(string id){
        Resource resource = db.Resources.Find(g);
        db.Entry(resource).State = EntityState.Modified;
        db.SaveChanges();
        return methodB()
    }

    public JsonResult methodB(string id){
        Resource resource = db.Resources.Find(id);
        db.Entry(resource).State = EntityState.Modified;
        db.SaveChanges();
        return new JsonResult();
    }

J'ai changé de méthode B pour avoir une instruction using et ne compter que sur le db2 local. Après:

    private ApplicationDbContext db;    
    // api methods    
    public JsonResult methodA(string id){
        Resource resource = db.Resources.Find(g);
        db.Entry(resource).State = EntityState.Modified;
        db.SaveChanges();
        return methodB()
    }

    public JsonResult methodB(string id){
        using (var db2 = new ApplicationDbContext())
        {
            Resource resource = db2.Resources.Find(id);
            db2.Entry(resource).State = EntityState.Modified;
            db2.SaveChanges();
        }
        return new JsonResult();
    }
0
colbybhearn

J'ai eu un problème similaire. Après avoir sondé pendant 2 à 3 jours, "AsNoTracking" a dû être supprimé, car EF ne surveille pas les modifications et suppose qu'il n'y a aucune modification à moins qu'un objet ne soit attaché. De plus, si nous n'utilisons pas .AsNoTracking, EF sait automatiquement quel objet enregistrer/mettre à jour. Il n'est donc pas nécessaire d'utiliser Attach/Added.

0
Prem Kumar