web-dev-qa-db-fra.com

La liaison d'une entité de type 'X' a échoué car une autre entité du même type

Je suis tombé sur un étrange bug dans mon code. Ce qui fonctionnait auparavant, mais fonctionne parfois maintenant.

J'utilise EF6 pour éditer une entité avec quelques relations . Pour ne pas éditer les relations je les attache (voir exemple de code).

public void EditA(A ThisIsA, B ThisIsB)
    {
        using (var Context = new LDZ_DEVEntities())
        {
            Context.As.Attach(ThisIsA);

            var b = Context.Bs.FirstOrDefault(x => x.BId == ThisIsB.BId);
            //var b = Context.Bs.Find(ThisIsB.BId);

            if (b != null)
                Context.Bs.Attach(b);
            else
                b = ThisIsB;

            if (b.C != null)
                Context.Cs.Attach(b.C);

            ThisIsA.Bs.Add(b);

            Context.SaveChanges();

        }
    }

J'ai édité les noms pour rester simple.

La ligne suivante

Context.Cs.Attach(b.C);

jette cette erreur:

La liaison d'une entité de type 'C' a échoué car une autre entité du même type a déjà la même valeur de clé primaire. Cela peut se produire lorsque vous utilisez la méthode 'Attach' ou que vous définissez l'état d'une entité sur 'Inchangé' ou 'Modifié' si l'une des entités du graphique a des valeurs de clé en conflit. Cela peut être dû au fait que certaines entités sont nouvelles et n'ont pas encore reçu de valeurs de clé générées par la base de données. Dans ce cas, utilisez la méthode "Ajouter" ou l'état de l'entité "Ajouté" pour suivre le graphique, puis définissez l'état des entités non nouvelles sur "Inchangé" ou "Modifié" selon le cas.

Cette ligne a été introduite car toutes les entités C sont des entités statiques. Je ne veux jamais qu'un C soit créé. Si je supprime cette ligne, chaque fois que j'ajouterai un B à A; un C est créé. Ce qui n'est pas souhaitable.

Informaitons supplémentaires:
A a une liste de B
B a un C

Cette méthode EditA () est appelée à plusieurs endroits de mon logiciel. Cette erreur n'apparaît que lorsque la méthode est appelée dans une boucle (importation). Il n'y a pas de problèmes en travaillant sur le premier disque. Mais je reçois l’erreur dans les enregistrements après le premier.

J'ai lu ces questions plus les réponses mais elles ne fonctionnaient pas pour moi:

  1. ASP.NET MVC - La connexion 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

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

17
Robin Gordijn

Je l'ai corrigé.

Dans sa réponse, Fabio Luz dit:

// si A a été chargé à partir du contexte
// ne l'attache pas
// s'il a été créé en dehors du contexte
//Context.Entry(ThisIsA).State = EntityState.Modified;

Cela m'a fait réfléchir, alors j'ai modifié mon code pour ceci:

public void EditA(A ThisIsA, B ThisIsB)
{
    using (var Context = new LDZ_DEVEntities())
    {
        var a = Context.As.Find(ThisIsA.AId);

        //var b = Context.Bs.FirstOrDefault(x => x.BId == ThisIsB.BId);
        var b = Context.Bs.Find(ThisIsB.BId);

        if (b != null)
            Context.Bs.Attach(b);
        else
            b = ThisIsB;

        if (b.C != null)
            Context.Cs.Attach(b.C);

        a.Bs.Add(b);

        Context.SaveChanges();

    }
}

Sommaire des changements:

  • FirstOrDefault modifié pour trouver
  • Obtenir un du contexte

Au début, j'ai supprimé l'attachement de C, ce qui a entraîné la création d'une nouvelle entité… .. J'ai donc inversé ce changement.

Un merci spécial à Fabio Luz. Je n'aurais pas pu faire cela sans votre aide!

19
Robin Gordijn

Consultez le lien suivant https://msdn.Microsoft.com/en-us/data/jj592676.aspx

Si vous connaissez une entité qui existe déjà dans la base de données mais sur laquelle des modifications ont été apportées, vous pouvez indiquer au contexte de lier l'entité et de définir son état sur Modifié. Par exemple:

var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" }; 

using (var context = new BloggingContext()) 
{ 
    context.Entry(existingBlog).State = EntityState.Modified; 

    // Do some more work...  

    context.SaveChanges(); 
}

NOTE: vous n’avez pas à faire cela avec tous les objets (A, B et C), mais seulement avec A. 

EDIT 1

En fonction de votre commentaire, essayez ceci:

//check if 
var _b = Context.Bs.Find(ThisIsB.BId);

if (_b != null)
  //b doesn't exist, then add to the context
  //make sure that the primary key of A is set.
  //_b.PrimaryKeyOfA = someValue;
  Context.Bs.Add(_b);
else
 //b already exists, then modify the properties
 //make sure that the primary key of A is set.

Context.SaveChanges();

EDIT 2

Je n'ai pas testé mais ça devrait marcher.

public void EditA(A ThisIsA, B ThisIsB)
{
    using (var Context = new LDZ_DEVEntities())
    {
        //if A has been loaded from context
        //dont attach it
        //if it has been created outside of the context
        //Context.Entry(ThisIsA).State = EntityState.Modified;

        var _b = Context.Bs.Find(ThisIsB.BId);

        if (_b == null)
        { 
            _b = ThisIsB;
        }

        ThisIsA.Bs.Add(_b);

        Context.SaveChanges();

    }
}
5
Fabio Luz

Selon votre situation, vous pouvez également simplement détacher l’État de l’entité.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Modify(Model model)
{

if (model.Image == null)
{
Model item = db.Model.Find(model.Name);

// Get the Content needed:
model.Image = item.Image;

// Detach the Comparison State:
db.Entry(item).State = EntityState.Detached;
}

if (ModelState.IsValid)
{
db.Entry(model).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}

return View(model);
}

En faisant ceci: db.Entry(item).State = EntityState.Detached; l'état de EntityFramework est toujours intact et vous pouvez enregistrer les modifications dans la base de données (base de données).

J'espère que cela t'aides! 

2
Rusty Nail