Aggiorna riga se esiste Altrimenti Inserisci logica con Entity Framework


179

Qualcuno ha suggerimenti sul modo più efficiente per implementare la logica "aggiorna riga se esiste altrimenti inserisci" utilizzando Entity Framework?


2
Questo è qualcosa che dovrebbe essere fatto a livello di motore di database, in una procedura memorizzata. Altrimenti, dovrai racchiudere il rilevamento / aggiornamento / inserimento in una transazione.
Stephen Chung,

1
@Stephen: Questo, in effetti, è quello che ho finito per fare. Grazie.
Jonathan Wood,

Jonathan, la tua domanda mi è molto utile. Perché sei passato a una procedura memorizzata?
anar khalilov,

2
@Anar: è stato più semplice e mi aspetto molto più efficiente.
Jonathan Wood,

Devi scrivere una procedura memorizzata per ogni tabella?
martedì

Risposte:


174

Se stai lavorando con un oggetto collegato (oggetto caricato dalla stessa istanza del contesto) puoi semplicemente usare:

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

// Attached object tracks modifications automatically

context.SaveChanges();

Se puoi usare qualsiasi conoscenza della chiave dell'oggetto, puoi usare qualcosa del genere:

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

context.SaveChanges();

Se non riesci a decidere l'esistenza dell'oggetto in base al suo ID, devi eseguire la query di ricerca:

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();

Grazie. Sembra quello di cui ho bisogno. Posso farti una domanda che mi dà fastidio da un po '? Normalmente, inserisco il mio contesto in un breve usingblocco. Va bene lasciare il contesto in memoria per un po '? Ad esempio, durante la vita di un modulo di Windows? Di solito provo a ripulire gli oggetti del database per garantire il carico minimo sul database. Non ci sono problemi in attesa di distruggere il mio contesto EF?
Jonathan Wood,

Controlla questo: stackoverflow.com/questions/3653009/… il contesto dell'oggetto dovrebbe essere il più breve possibile, ma in caso di winforms o wpf questo può significare che il contesto continua a vivere quanto il presentatore. La domanda collegata contiene il collegamento all'articolo msdn sull'uso della sessione proibita in winforms. Lo stesso approccio può essere utilizzato per il contesto.
Ladislav Mrnka,

1
Ma cosa succede se devo farlo con un elenco di oggetti ... nel mio database c'è un elenco di righe con lo stesso ID e voglio sostituirlo se esiste o inserire se non .. come lo faccio? Grazie!
Phoenix_uy,

1
Questa risposta È fantastica, ma sto riscontrando questo problema durante l'aggiornamento: un oggetto con la stessa chiave esiste già in ObjectStateManager. ObjectStateManager non può tracciare più oggetti con la stessa chiave.
John Zumbrum,

1
Sembra che avessi solo un piccolo problema con il recupero dell'oggetto esistente in modo da recuperare la sua chiave prima di eseguire l'aggiornamento; staccare prima l'oggetto di ricerca ha contribuito a risolverlo.
John Zumbrum,

33

A partire da Entity Framework 4.3, esiste un AddOrUpdatemetodo nello spazio dei nomi System.Data.Entity.Migrations:

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

che dal doc :

Aggiunge o aggiorna le entità per chiave quando viene chiamato SaveChanges. Equivale a un'operazione "upsert" dalla terminologia del database. Questo metodo può essere utile durante il seeding dei dati utilizzando Migrations.


Per rispondere al commento di @ Smashing1978 , incollerò le parti pertinenti dal link fornito da @Colin

Il compito di AddOrUpdate è quello di assicurarsi di non creare duplicati quando si seminano dati durante lo sviluppo.

Innanzitutto, eseguirà una query nel database alla ricerca di un record in cui tutto ciò che è stato fornito come chiave (primo parametro) corrisponde al valore (o ai valori) della colonna mappata fornito in AddOrUpdate. Quindi questo è un po 'ficcanaso per l'abbinamento ma perfettamente perfetto per seminare i dati del tempo di progettazione.

Ancora più importante, se viene trovata una corrispondenza, l'aggiornamento aggiornerà tutto e annullerà quelli non presenti in AddOrUpdate.

Detto questo, ho una situazione in cui sto estraendo i dati da un servizio esterno e inserendo o aggiornando i valori esistenti tramite chiave primaria (ei miei dati locali per i consumatori sono di sola lettura) - sono AddOrUpdatein uso da più di 6 mesi ormai e così di gran lunga nessun problema.


7
Lo spazio dei nomi System.Data.Entity.Migrations contiene classi correlate alle migrazioni basate su codice e alle loro configurazioni. C'è qualche motivo per cui non dovremmo usarlo nei nostri repository per AddOrUpdates di entità non migranti?
Matt Lengenfelder,

10
Abbi cura del metodo AddOrUpdate: thedatafarm.com/data-access/…
Colin,

1
Questo articolo descrive perché AddOrUpdate non dovrebbe essere usato michaelgmccarthy.com/2016/08/24/…
Nolmë Informatique

11

La magia accade quando si chiama SaveChanges()e dipende dalla corrente EntityState. Se l'entità ha un EntityState.Added, verrà aggiunta al database, se ha un EntityState.Modified, verrà aggiornata nel database. Quindi puoi implementare un InsertOrUpdate()metodo come segue:

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

        context.SaveChanges(); 
    } 
}

Ulteriori informazioni su EntityState

Se non riesci a verificare Id = 0se si tratta di una nuova entità o meno, controlla la risposta di Ladislav Mrnka .


8

Se sai che stai utilizzando lo stesso contesto e non stai staccando alcuna entità, puoi creare una versione generica come questa:

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 può ovviamente essere un campo di classe o il metodo può essere reso statico e un'estensione, ma questa è la base.


4

La risposta di Ladislav è stata vicina ma ho dovuto apportare un paio di modifiche per farlo funzionare in EF6 (prima il database). Ho esteso il mio contesto di dati con il mio metodo on AddOrUpdate e finora questo sembra funzionare bene con oggetti distaccati:

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);
      }
  }
[....]

AddOrUpdate esiste anche come metodo di estensione in System.Data.Entity.Migrations, quindi se fossi in te eviterei di riutilizzare lo stesso nome di metodo per il tuo metodo.
AFract

2

A mio avviso, vale la pena di dire che con il nuovo EntityGraphOperations per Entity Framework Code in primo luogo è possibile salvarsi dalla scrittura di alcuni codici ripetitivi per la definizione degli stati di tutte le entità nel grafico. Sono l'autore di questo prodotto. E l'ho pubblicato su github , code-project ( include una dimostrazione passo-passo e un progetto di esempio è pronto per il download) e nuget .

Sarà impostare automaticamente lo stato delle entità a Addedo Modified. E sceglierai manualmente quali entità devono essere eliminate se non esistono più.

L'esempio:

Diciamo che ho un Personoggetto. Personpotrebbe avere molti telefoni, un documento e potrebbe avere un coniuge.

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; }
}

Voglio determinare lo stato di tutte le entità che è incluso nel grafico.

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();
       });

Inoltre, come sapete, proprietà chiave uniche potrebbero svolgere un ruolo durante la definizione dello stato dell'entità Telefono. Per tali scopi speciali abbiamo ExtendedEntityTypeConfiguration<>classe, che eredita da EntityTypeConfiguration<>. Se vogliamo usare configurazioni così speciali, dobbiamo ereditare le nostre classi di mappatura ExtendedEntityTypeConfiguration<>piuttosto che EntityTypeConfiguration<>. Per esempio:

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

È tutto.


2

Inserisci altro aggiorna entrambi

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();
}

Ho usato questo approccio ma e verificato (dopo il primo o il default) se (query == null)
Patrick

2

Controlla la riga esistente con Qualsiasi.

    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();

        }

    }

1

Alternativa alla risposta di @LadislavMrnka. Questo se per Entity Framework 6.2.0.

Se si dispone di uno specifico DbSete un elemento che deve essere aggiornato o creato:

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();

Tuttavia, questo può essere utilizzato anche per un generico DbSetcon una chiave primaria singola o una chiave primaria composita.

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);
    }
}

-1

corretto

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();
        }

2
Si prega di utilizzare la modifica invece di pubblicare un'altra risposta
Suraj Rao,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.