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

179

Quelqu'un a-t-il des suggestions sur la manière la plus efficace d'implémenter la logique «mise à jour de la ligne s'il existe, sinon insérer» à l'aide d'Entity Framework?

Jonathan Wood
la source
2
C'est quelque chose qui doit être fait au niveau du moteur de base de données, dans une procédure stockée. Sinon, vous devrez encapsuler la détection / mise à jour / insertion dans une transaction.
Stephen Chung
1
@Stephen: C'est en fait ce que j'ai fini par faire. Merci.
Jonathan Wood
Jonathan, votre question m'est très utile. Pourquoi avez-vous basculé vers une procédure stockée?
anar khalilov
2
@Anar: C'était juste plus facile et j'attends beaucoup plus efficace.
Jonathan Wood
Devez-vous écrire une procédure stockée pour chaque table?
tofutim le

Réponses:

174

Si vous travaillez avec un objet attaché (objet chargé à partir 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 des connaissances sur la clé de l'objet, vous pouvez utiliser quelque chose comme ceci:

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 de l'existence de l'objet par son identifiant, vous devez exécuter une 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();
Ladislav Mrnka
la source
Merci. On dirait ce dont j'ai besoin. Puis-je vous poser une question qui me tracasse depuis un moment? Normalement, je place mon contexte dans un petit usingbloc. Est-il acceptable de laisser le contexte en mémoire pendant un moment? Par exemple, pendant la durée de 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 de détruire mon contexte EF?
Jonathan Wood
Vérifiez ceci: le contexte de l'objet stackoverflow.com/questions/3653009/… doit vivre aussi court que possible, mais dans le cas de winforms ou wpf, cela peut signifier que le contexte vit aussi longtemps que le présentateur. La question liée contient un lien vers l'article msdn sur l'utilisation de la session nhibernate dans winforms. La même approche peut être utilisée pour le contexte.
Ladislav Mrnka
1
Mais que faire si je dois le faire avec une liste d'objets ... dans ma base de données il y a une liste de lignes avec le même identifiant et je veux remplacer si elles existent ou insérer si elles ne le font pas .. comment je le fais? Merci!
Phoenix_uy
1
Cette réponse a l'air géniale, mais je rencontre 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é.
John Zumbrum
1
On dirait que j'avais juste un petit problème avec la récupération de l'objet existant afin de récupérer sa clé avant de faire la mise à jour; Le détachement de cet objet de recherche a d'abord aidé à le résoudre.
John Zumbrum
33

Depuis Entity Framework 4.3, il existe une AddOrUpdateméthode dans 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 le doc :

Ajoute ou met à jour des entités par clé lorsque SaveChanges est appelé. Équivaut à une opération "upsert" à partir de la terminologie de la base de données. Cette méthode peut être utile lors de l'amorçage de données à l'aide de Migrations.


Pour répondre au commentaire de @ Smashing1978 , je vais coller les parties pertinentes du lien fourni par @Colin

Le travail d'AddOrUpdate est de s'assurer que vous ne créez pas de doublons lorsque vous amorcez des données pendant le développement.

Tout d'abord, il exécutera une requête dans votre base de données à la recherche d'un enregistrement où tout ce que vous avez fourni comme clé (premier paramètre) correspond à la ou aux valeurs de colonne mappée fournies dans AddOrUpdate. C'est donc un peu lâche pour la correspondance, mais parfaitement adapté pour l'ensemencement des données de conception.

Plus important encore, si une correspondance est trouvée, la mise à jour mettra à jour tout et annulera tout ce qui ne figurait pas dans votre AddOrUpdate.

Cela dit, j'ai une situation dans laquelle je tire des données d'un service externe et j'insère ou mets à jour des valeurs existantes par clé primaire (et mes données locales pour les consommateurs sont en lecture seule) - j'utilise AddOrUpdateen production depuis plus de 6 mois maintenant et donc loin aucun problème.

Erki M.
la source
7
L'espace de noms System.Data.Entity.Migrations contient des classes liées aux migrations basées sur du code et à leurs configurations. Y a-t-il une raison pour laquelle nous ne devrions pas l'utiliser dans nos référentiels pour l'entité non-migration AddOrUpdates?
Matt Lengenfelder
10
Attention avec la méthode AddOrUpdate: thedatafarm.com/data-access/…
Colin
1
Cet article explique pourquoi AddOrUpdate ne doit pas être utilisé michaelgmccarthy.com/2016/08/24/…
Nolmë Informatique
11

La magie se produit lors de l'appel SaveChanges()et dépend du courant EntityState. Si l'entité a un EntityState.Added, elle sera ajoutée à la base de données, si elle en a un EntityState.Modified, elle sera mise à jour dans la base de données. Vous pouvez donc implémenter une InsertOrUpdate()méthode 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(); 
    } 
}

En savoir plus sur EntityState

Si vous ne pouvez pas vérifier Id = 0s'il s'agit d'une nouvelle entité ou non, vérifiez la réponse de Ladislav Mrnka .

Empilé
la source
8

Si vous savez que vous utilisez le même contexte et que vous ne détachez aucune entité, vous pouvez créer une version générique comme celle-ci:

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 rendue statique et une extension, mais ce sont les bases.

ciscoheat
la source
4

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). J'ai étendu mon contexte de données avec ma méthode on AddOrUpdate et jusqu'à présent, cela semble bien fonctionner avec les 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);
      }
  }
[....]
cdonner
la source
AddOrUpdate existe également en tant que méthode d'extension dans System.Data.Entity.Migrations, donc si j'étais vous, j'éviterais de réutiliser le même nom de méthode pour votre propre méthode.
AFract
2

À mon avis, il vaut la peine de dire qu'avec le tout nouveau EntityGraphOperations pour Entity Framework Code First, vous pouvez vous éviter d'écrire des codes répétitifs pour définir les états de toutes les entités du 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 à être téléchargé) et nuget .

Il définira automatiquement l'état des entités sur Addedou Modified. Et vous choisirez manuellement quelles entités doivent être supprimées si elles n'existent plus.

L'exemple:

Disons que j'ai un Personobjet. Personpourrait avoir plusieurs 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 souhaite déterminer l'état de toutes les entités incluses 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 plus, comme vous le savez, les propriétés de clé uniques pourraient jouer un rôle lors de la définition de l'état de l'entité Phone. Pour ces fins spéciales, nous avons la ExtendedEntityTypeConfiguration<>classe, qui hérite de EntityTypeConfiguration<>. Si nous voulons utiliser de telles configurations spéciales, nous devons hériter de nos classes de mappage ExtendedEntityTypeConfiguration<>plutôt que de 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.

Farhad Jabiyev
la source
2

Insérer autre mettre à jour les deux

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();
}
Sharad Chougale
la source
J'ai utilisé cette approche mais et vérifié (après le premier ou par défaut) si (query == null)
Patrick
2

Vérifiez la ligne existante avec Any.

    public static void insertOrUpdateCustomer(Customer customer)
    {
        using (var db = getDb())
        {

            db.Entry(customer).State = !db.Customer.Any(f => f.CustomerId == customer.CustomerId) ? EntityState.Added : EntityState.Modified;
            db.SaveChanges();

        }

    }
Ali Osman Yavuz
la source
1

Alternative pour la réponse @LadislavMrnka. Cela vaut pour Entity Framework 6.2.0.

Si vous avez un DbSetélément spécifique et un élément qui doit être mis à jour ou créé:

var name = getNameFromService();

var current = _dbContext.Names.Find(name.BusinessSystemId, name.NameNo);
if (current == null)
{
    _dbContext.Names.Add(name);
}
else
{
    _dbContext.Entry(current).CurrentValues.SetValues(name);
}
_dbContext.SaveChanges();

Cependant, cela peut également être utilisé pour un générique DbSetavec une seule clé primaire ou une clé primaire composite.

var allNames = NameApiService.GetAllNames();
GenericAddOrUpdate(allNames, "BusinessSystemId", "NameNo");

public virtual void GenericAddOrUpdate<T>(IEnumerable<T> values, params string[] keyValues) where T : class
{
    foreach (var value in values)
    {
        try
        {
            var keyList = new List<object>();

            //Get key values from T entity based on keyValues property
            foreach (var keyValue in keyValues)
            {
                var propertyInfo = value.GetType().GetProperty(keyValue);
                var propertyValue = propertyInfo.GetValue(value);
                keyList.Add(propertyValue);
            }

            GenericAddOrUpdateDbSet(keyList, value);
            //Only use this when debugging to catch save exceptions
            //_dbContext.SaveChanges();
        }
        catch
        {
            throw;
        }
    }
    _dbContext.SaveChanges();
}

public virtual void GenericAddOrUpdateDbSet<T>(List<object> keyList, T value) where T : class
{
    //Get a DbSet of T type
    var someDbSet = Set(typeof(T));

    //Check if any value exists with the key values
    var current = someDbSet.Find(keyList.ToArray());
    if (current == null)
    {
        someDbSet.Add(value);
    }
    else
    {
        Entry(current).CurrentValues.SetValues(value);
    }
}
Ogglas
la source
-1

Corrigée

public static void InsertOrUpdateRange<T, T2>(this T entity, List<T2> updateEntity) 
        where T : class
        where T2 : class
        {
            foreach(var e in updateEntity)
            {
                context.Set<T2>().InsertOrUpdate(e);
            }
        }


        public static void InsertOrUpdate<T, T2>(this T entity, T2 updateEntity) 
        where T : class
        where T2 : class
        {
            if (context.Entry(updateEntity).State == EntityState.Detached)
            {
                if (context.Set<T2>().Any(t => t == updateEntity))
                {
                   context.Set<T2>().Update(updateEntity); 
                }
                else
                {
                    context.Set<T2>().Add(updateEntity);
                }

            }
            context.SaveChanges();
        }
Vadim Rychkow
la source
2
Veuillez utiliser modifier au lieu de publier une autre réponse
Suraj Rao