Mettre à jour la ligne si elle existe autrement Insérer une logique avec Entity Framework


125

Est-ce que quelqu'un a des suggestions sur la manière la plus efficace d'implémenter la mise à jour de la ligne "Mettre la ligne si elle existe ailleurs" avec Entity Framework?

+2

C'est quelque chose qui devrait être fait au niveau du moteur de base de données, dans une procédure stockée. Sinon, vous devrez envelopper la détection/mise à jour/insertion dans une transaction. 06 avril. 112011-04-06 09:12:11

+1

@Stephen: C'est en fait ce que j'ai fini par faire. Merci. 06 avril. 112011-04-06 14:18:43

  0

Jonathan, votre question est très utile pour moi. Pourquoi avez-vous basculé vers une procédure stockée? 15 janv.. 142014-01-15 09:54:35

+2

@Anar: C'était juste plus facile et je m'attends à beaucoup plus efficace. 15 janv.. 142014-01-15 16:58:03

  0

Devez-vous écrire une procédure stockée pour chaque table? 03 janv.. 182018-01-03 05:00:58

130

Si vous travaillez avec un objet attaché (objet chargé de la même instance du contexte), vous pouvez simplement utiliser:

if (context.ObjectStateManager.GetObjectStateEntry(myEntity).State == EntityState.Detached) 
{ 
    context.MyEntities.AddObject(myEntity); 
} 

// Attached object tracks modifications automatically 

context.SaveChanges(); 

Si vous pouvez utiliser les connaissances sur la clé de l'objet que vous pouvez utiliser quelque chose comme ça :

if (myEntity.Id != 0) 
{ 
    context.MyEntities.Attach(myEntity); 
    context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified); 
} 
else 
{ 
    context.MyEntities.AddObject(myEntity); 
} 

context.SaveChanges(); 

Si vous ne pouvez pas décider existance de l'objet par son Id vous devez exectue requête de recherche:

var id = myEntity.Id; 
if (context.MyEntities.Any(e => e.Id == id)) 
{ 
    context.MyEntities.Attach(myEntity); 
    context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified); 
} 
else 
{ 
    context.MyEntities.AddObject(myEntity); 
} 

context.SaveChanges(); 
  0

Merci. On dirait ce dont j'ai besoin. Puis-je vous poser une question qui me dérange depuis un moment? Normalement, je place mon contexte dans un court bloc 'using'. Est-il acceptable de laisser le contexte en mémoire pendant un moment? Par exemple, pendant la vie d'un formulaire Windows?J'essaie normalement de nettoyer les objets de base de données pour assurer une charge minimale sur la base de données. N'y a-t-il aucun problème à attendre pour détruire mon contexte EF? 06 avril. 112011-04-06 02:16:58

  0

Vérifiez cela: http://stackoverflow.com/questions/3653009/entity-framework-and-connection-pooling/3653392#3653392 contexte d'objet devrait vivre aussi court que possible mais dans le cas de winforms ou wpf cela peut signifier que le contexte est vivre aussi longtemps que présentateur. La question liée contient un lien vers l'article msdn sur l'utilisation de la session nhibernate dans les winforms. La même approche peut être utilisée pour le contexte. 06 avril. 112011-04-06 05:34:14

  0

Mais que faire si je dois le faire avec une liste d'objets ... dans ma base de données il ya une liste de lignes avec le même id et je veux remplacer si elles existent ou insèrent si elles ne le font pas. Merci! 14 oct.. 112011-10-14 11:45:52

+1

Cette réponse est géniale, mais je suis confronté à ce problème lors de la mise à jour: Un objet avec la même clé existe déjà dans ObjectStateManager. ObjectStateManager ne peut pas suivre plusieurs objets avec la même clé. 29 nov.. 122012-11-29 21:58:14

+1

On dirait que j'avais juste un peu un problème avec l'extraction de l'objet existant afin de récupérer sa clé avant de faire la mise à jour; détacher cet objet de recherche a d'abord aidé à le réparer. 29 nov.. 122012-11-29 22:15:28

  0

@JohnZ alors vous vous êtes détaché puis attaché, ai-je raison? Un tel comportement est-il coûteux en termes de performances? J'ai le même problème. 15 janv.. 142014-01-15 09:56:36

  0

@Anar oui, j'ai fait un fetch pour obtenir la version de la base de données de l'objet et en retirer certaines de ses valeurs, puis j'ai détaché cette version, puis attaché l'objet passé dans la méthode, changé son état d'objet et sauvé. Et oui, c'est assez cher, parce que vous faites un choix de la base de données pour chaque élément que vous mettez à jour (mais seulement dans la façon dont je le fais). 15 janv.. 142014-01-15 15:49:03

  0

@JohnZ J'ai un problème étrange où je récupère un enregistrement existant, change ses valeurs de champs, puis quand j'appelle 'SaveChanges()' est en quelque sorte n'envoie pas ces valeurs changées à la base de données. Au lieu de cela, il envoie l'objet avec des valeurs de champ inchangées. J'ai le sentiment que vous connaissez déjà une solution à ce problème. S'il vous plaît jeter un oeil, je l'apprécierais beaucoup. http://stackoverflow.com/questions/21088398/cannot-update-entity-framework-model 15 janv.. 142014-01-15 15:56:00

  0

N'échouerait-il pas si d'autres processus externes peuvent modifier/ajouter le même enregistrement juste après avoir récupéré dbcontext (à l'état connecté). 24 juin. 152015-06-24 07:39:53

  0

@LadislavMrnka merci mate - comment cela changerait-il maintenant que nous avons changetracker dans ef6? 14 déc.. 162016-12-14 05:05:45

  0

salut merci - une autre question pour le dernier exemple donné: va-t-il reconnaître les changements dans les objets enfants et mettre à jour ces objets? 20 déc.. 162016-12-20 05:01:06

  0

@BKSpurgeon: Non. Vous devez également détecter les modifications pour les objets enfants lorsque vous travaillez avec une arborescence d'entités détachées. 24 déc.. 162016-12-24 08:45:44


7

Si vous savez que vous utilisez le même contexte et ne détachez pas des entités, vous pouvez faire une version générique comme ceci:

public void InsertOrUpdate<T>(T entity, DbContext db) where T : class 
{ 
    if (db.Entry(entity).State == EntityState.Detached) 
     db.Set<T>().Add(entity); 

    // If an immediate save is needed, can be slow though 
    // if iterating through many entities: 
    db.SaveChanges(); 
} 

db peut bien sûr être un champ de classe, ou la méthode peut être faite statique et une extension, mais ce sont les bases.


25

Au Entity Framework 4.3, il existe une méthode AddOrUpdate à l'espace de noms System.Data.Entity.Migrations:

public static void AddOrUpdate<TEntity>(
    this IDbSet<TEntity> set, 
    params TEntity[] entities 
) 
where TEntity : class 

qui, par les doc:

entités permet d'ajouter ou mises à jour par clé lorsque SaveChanges est appelée. Équivalent à une opération "upsert" de la terminologie de la base de données. Cette méthode peut être utile lors de l'ensemencement de données à l'aide de migrations.


Pour répondre à la comment by @Smashing1978, je vais coller les parties pertinentes de lien fournies par @Colin

Le travail de AddOrUpdate est de faire en sorte que vous ne créez pas de doublons lorsque vous semez des données pendant le développement.

En premier lieu, il exécutera une requête dans la base de données à la recherche d'un enregistrement où tout ce que vous avez fourni une clé (premier paramètre) correspond à la valeur de la colonne mappée (ou valeurs) fourni dans le AddOrUpdate. Donc, ce est un peu lâche-goosey pour la correspondance, mais parfaitement bien pour l'ensemencement données de temps de conception. Plus important encore, si une correspondance est trouvée, la mise à jour mettra à jour tous les et annulera tous ceux qui n'étaient pas dans votre AddOrUpdate.

Cela dit, j'ai une situation où je tire des données d'un service externe et d'insérer ou de mettre à jour les valeurs existantes par clé primaire (et mes données locales pour les consommateurs est en lecture seule) - été en utilisant AddOrUpdate en production plus de 6 mois maintenant et jusqu'à présent aucun problème.

+6

L'espace de noms System.Data.Entity.Migrations contient des classes liées aux migrations basées sur le code et à leurs configurations. Y a-t-il une raison pour laquelle nous ne devrions pas utiliser cela dans nos dépôts pour l'entité AddOrUpdates? 24 févr.. 152015-02-24 20:58:55

+5

Faites attention avec la méthode AddOrUpdate: http://thedatafarm.com/data-access/take-care-with-ef-4-3-addorupdate-method/ 14 mai. 152015-05-14 08:43:46


3

La réponse de Ladislav était proche, mais j'ai dû faire quelques modifications pour que cela fonctionne dans EF6 (base de données d'abord).Je tendis mon contexte de données avec ma méthode sur AddOrUpdate et jusqu'à présent, cela semble bien fonctionner avec des objets détachés:

using System.Data.Entity; 

[....] 

public partial class MyDBEntities { 

    public void AddOrUpdate(MyDBEntities ctx, DbSet set, Object obj, long ID) { 
     if (ID != 0) { 
      set.Attach(obj); 
      ctx.Entry(obj).State = EntityState.Modified; 
     } 
     else { 
      set.Add(obj); 
     } 
    } 
[....] 

3

La magie se produit lors de l'appel SaveChanges() et dépend de la EntityState actuelle. Si l'entité a un EntityState.Added, il sera ajouté à la base de données, s'il a un EntityState.Modified, il sera mis à jour dans la base de données. Vous pouvez donc mettre en œuvre une méthode InsertOrUpdate() comme suit:

public void InsertOrUpdate(Blog blog) 
{ 
    using (var context = new BloggingContext()) 
    { 
     context.Entry(blog).State = blog.BlogId == 0 ? 
            EntityState.Added : 
            EntityState.Modified; 

     context.SaveChanges(); 
    } 
} 

More about EntityState

Si vous ne pouvez pas vérifier Id = 0 pour déterminer si elle est une nouvelle entité ou non, vérifiez la answer of Ladislav Mrnka.


1

À mon avis, il vaut la peine de dire qu'avec le EntityGraphOperations for Entity Framework Code First nouvellement publié, vous pouvez vous éviter d'écrire des codes répétitifs pour définir les états de toutes les entités dans le graphique. Je suis l'auteur de ce produit. Et je l'ai publié dans le github, code-project (comprend une démonstration étape par étape et un exemple de projet est prêt pour le téléchargement) et nuget.

Il régler automatiquement l'état des entités à Added ou Modified. Et vous choisirez manuellement quelles entités doivent être supprimées si elles n'existent plus.

L'exemple:

Disons que j'obtenir un objet Person. Person pourrait avoir de nombreux téléphones, un document et pourrait avoir un conjoint.

public class Person 
{ 
    public int Id { get; set; } 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public string MiddleName { get; set; } 
    public int Age { get; set; } 
    public int DocumentId {get; set;} 

    public virtual ICollection<Phone> Phones { get; set; } 
    public virtual Document Document { get; set; } 
    public virtual PersonSpouse PersonSpouse { get; set; } 
} 

Je veux déterminer l'état de toutes les entités qui est inclus dans le graphique.

context.InsertOrUpdateGraph(person) 
     .After(entity => 
     { 
      // Delete missing phones. 
      entity.HasCollection(p => p.Phones) 
       .DeleteMissingEntities(); 

      // Delete if spouse is not exist anymore. 
      entity.HasNavigationalProperty(m => m.PersonSpouse) 
        .DeleteIfNull(); 
     }); 

De même que vous le savez, des propriétés de clé uniques peuvent jouer un rôle lors de la définition de l'état de l'entité Phone. Pour ces fins spéciales, nous avons la classe ExtendedEntityTypeConfiguration<>, qui hérite de EntityTypeConfiguration<>. Si nous voulons utiliser de telles configurations spéciales, nous devons hériter de nos classes de mapping de ExtendedEntityTypeConfiguration<>, plutôt que EntityTypeConfiguration<>. Par exemple:

public class PhoneMap: ExtendedEntityTypeConfiguration<Phone> 
    { 
     public PhoneMap() 
     { 
      // Primary Key 
      this.HasKey(m => m.Id); 
       … 
      // Unique keys 
      this.HasUniqueKey(m => new { m.Prefix, m.Digits }); 
     } 
    } 

C'est tout.


2

Insérer autre mise à jour à la fois

public void InsertUpdateData() 
{ 
//Here TestEntities is the class which is given from "Save entity connection setting in web.config" 
TestEntities context = new TestEntities(); 

var query = from data in context.Employee 
      orderby data.name 
      select data; 

foreach (Employee details in query) 
{ 
    if (details.id == 1) 
    { 
     //Assign the new values to name whose id is 1 
     details.name = "Sanjay"; 
     details. Surname="Desai"; 
     details.address=" Desiwadi"; 
    } 
    else if(query==null) 
    { 
    details.name="Sharad"; 
    details.surname=" Chougale "; 
    details.address=" Gargoti"; 
} 

//Save the changes back to database. 
context.SaveChanges(); 
} 
  0

J'ai utilisé cette approche mais et vérifié (après première ou par défaut) if (requête == null) 27 janv.. 182018-01-27 16:40:00