2010-06-30 7 views
3

Le code ci-dessous tente de mettre à jour une ligne dans la base de données, mais jette une exception à la place:LINQ to SQL - DuplicateKeyException lors de la mise à jour

System.Data.Linq.DuplicateKeyException: Cannot add an entity with a key that is already in use

La plupart des exemples que j'ai vu interroger la base pour obtenir une instance de une entité, modifiez certaines propriétés de l'instance, puis mettez-la à jour. Ici, je récupère entièrement l'objet d'une source différente (il est analysé à partir d'un fichier XML) et interroge pour voir s'il y a déjà une ligne pour ces données. Si c'est le cas, je définis la clé primaire et tente d'exécuter une mise à jour. Quelle est la bonne façon de faire cela?

est ici la version dégrossi du code:

Customer customer = new Customer(); // Customer has a database generated 
            // identity column called CustomerId 

// populate customer object 
customer.Name = "Mr. X"; 
customer.Email = "[email protected]"; 
// etc. 

// is customer already in database? 
// identify customer by email 
var results = ctx.Where(c => c.Email == customer.Email); // ctx is a DataContext 

if (results.Any()) 
{ 
    Customer existing = results.Single(); 

    // set primary key to match existing one 
    customer.CustomerId = existing.CustomerId; 

    // update database 
    customerTable.Attach(customer); // customerTable is a Table<Customer> 
    ctx.SubmitChanges(); 
} 

// otherwise do insert 
// ... 

Répondre

0

Apparemment, ce n'est pas un nouveau problème.Voici un échantillonnage de quelques-uns des messages qui traitent de cette question:

http://www.west-wind.com/weblog/posts/134095.aspx

http://www.codeproject.com/KB/linq/linq-to-sql-detach.aspx

http://social.msdn.microsoft.com/forums/en-US/linqprojectgeneral/thread/3848c02c-464e-40ff-87b6-813bff7b1263/

Je l'ai travail en créant un nouveau DataContext et le tableau avant de faire la mise à jour. Mon code modifié ressemble à ceci:

Customer customer = new Customer(); // Customer has a database generated 
            // identity column called CustomerId 

// populate customer object 
customer.Name = "Mr. X"; 
customer.Email = "[email protected]"; 
// etc. 

// is customer already in database? 
// identify customer by email 
var results = ctx.Where(c => c.Email == customer.Email); // ctx is a DataContext 

if (results.Any()) 
{ 
    Customer existing = results.Single(); 

    // set primary key to match existing one 
    customer.CustomerId = existing.CustomerId; 

    // **** CODE CHANGES HERE **** 
    // create new DataContext and table to avoid DuplicateKeyException errors 
    var ctx = new DataContext(customerTable.Context.Connection.ConnectionString); 
    customerTable = ctx.GetTable<Customer>(); 

    // update database 
    customerTable.Attach(customer); // customerTable is a Table<Customer> 

    // **** ANOTHER CODE CHANGE **** 
    // without this line the data won't be updated with the new values 
    ctx.Refresh(RefreshMode.KeepCurrentValues, customer); 

    ctx.SubmitChanges(); 
} 

// otherwise do insert 
// ... 

La façon dont je comprends est que le DataContext ne peut contenir qu'une seule instance de chaque entité unique. Tenter d'attacher une nouvelle entité avec la même clé primaire provoque l'erreur, car il y aura maintenant deux instances de la même entité. Le nouveau DataContext ne connaît pas l'entité existante et n'a donc aucun problème pour joindre le nouveau.


MISE À JOUR: Il ressemble à cette question has already been answered.


MISE À JOUR: N'utilisez pas mon exemple de code tel quel. It caused me other problems.

1

Faire ce changement:

customerTable.Attach(customer, existing); 

^Je ne sais pas pourquoi ne fonctionnerait pas ci-dessus. Le deuxième argument est "l'état original" de l'entité, peut-être parce que c'est une référence différente à une instance différente, L2S pense qu'il doit insérer un nouvel objet entier.

Je pense qu'il serait préférable de faire quelque chose comme:

var customer = ctx.Where(...).SingleOrDefault(); 
if (customer == null) 
{ 
    customer = new Customer() 
    { 
    Name = name, 
    Email = email 
    }; 
    customerTable.InsertOnSubmit(customer); 
} 
else 
{ 
    customer.Name = name; 
    customer.Email = email; 
} 

ctx.SubmitChanges(); 
+0

Intéressant. Je suppose que cela fait ce que j'ai suggéré (mais dans le code). Pouvez-vous expliquer un peu plus comment cela fonctionne pour un débutant de LINQ to SQL? – JasCav

+0

J'ai essayé ça. Cela me donne la même erreur. – MCS

+0

Il me semble aussi que customerTable.Attach (client, existant) devrait fonctionner. Mais si je ne mets pas customer.CustomerId, j'obtiens l'erreur: System.InvalidOperationException: La valeur du membre 'CustomerId' d'un objet de type 'Client' a changé. Un membre définissant l'identité de l'objet ne peut pas être modifié. Et quand je mets Customer.CustomerId je reçois le DuplicateKeyException! Allez comprendre. – MCS

1

Je suis nouveau à LINQ to SQL, donc si quelqu'un de plus malin que moi voit cela est faux, s'il vous plaît me corriger. Mais, je crois que votre problème est que lorsque vous entrez dans l'instruction if, vous obtenez l'entité à partir des résultats (via results.Single()) et vous définissez la valeur à l'objet client NEW. Lorsque vous tentez de soumettre l'objet client à la base de données, la clé primaire existe déjà, vous recevez donc l'erreur.

A la place, vous souhaitez mettre à jour le client existant et le renvoyer à la base de données.

0

La façon dont je fais est la suivante

je tirerais le client qui existe déjà comme vous ne puis mettez à jour l'élément avec l'id correspondant que vous utilisez, avec les valeurs que vous avez tiré de xml . Puis, quand vous appelez la méthode datacontext.SubmitChanges(), elle fera la mise à jour pour vous.

Ou vous pouvez utiliser la méthode Attach, dans votre cas votre code ressemblerait à quelque chose comme

customerTable.Attach(customer, existing);

Attach a été créé exactement pour ce cas.

Modifier

Pourquoi ne pas changer l'ordre des choses que vous faites dans, au lieu de faire un nouveau client, peuplant ce client, quelque chose comme ça

var results = ctx.Where(c => c.Email == customer.Email); 

Customer customer = (results.Any ? results.Single : new Customer) 

Ensuite, votre requête xml pour remplir le client puis faites votre insertion/mise à jour.

+0

Voulez-vous dire définir toutes les propriétés existantes pour correspondre à celles du client? Quelque chose comme existing.name = customer.name et existing.email = customer.email etc. etc. Il semble qu'il devrait y avoir un moyen plus simple de faire cela. – MCS

+0

Oui, cependant, Attach fera cela pour vous. Cependant, regardez mon poste pour un edit. – msarchet

1

Quelque part dans le Web, je trouve cette solution:

static void CopyProperties<T>(ref T Target, T Source) 
     { 
      foreach (PropertyInfo PI in Target.GetType().GetProperties()) 
      { 
       if (PI.CanWrite && PI.CanRead) 
       { 
        PI.SetValue(Target, PI.GetValue(Source, null), null); 
       } 
      } 
     } 

....

static void Save(Test_TableData ChangedData) 
     { 
      using (DataClasses1DataContext D = new DataClasses1DataContext()) 
      { 
       Test_TableData UpdateTarget = D.Test_TableDatas.SingleOrDefault(i => i.ID == ChangedData.ID); 
       if (UpdateTarget != null) 
       { 
        CopyProperties<Test_TableData>(ref UpdateTarget, ChangedData); 
       } 
       else 
       { 
        D.Test_TableDatas.InsertOnSubmit(ChangedData); 
       } 
       D.SubmitChanges(); 
      } 
    } 
Questions connexes