Aktualisieren Sie die Zeile, wenn sie vorhanden ist. Fügen Sie die Logik mit Entity Framework ein


179

Hat jemand Vorschläge zur effizientesten Implementierung der Logik "Zeile aktualisieren, falls vorhanden, sonst einfügen" mithilfe von Entity Framework?


2
Dies sollte auf der Ebene des Datenbankmoduls in einer gespeicherten Prozedur erfolgen. Andernfalls müssen Sie das Erkennen / Aktualisieren / Einfügen in eine Transaktion einschließen.
Stephen Chung

1
@ Stephen: Dies ist in der Tat, was ich am Ende getan habe. Vielen Dank.
Jonathan Wood

Jonathan, deine Frage ist sehr nützlich für mich. Warum haben Sie zu einer gespeicherten Prozedur gewechselt?
Anar Khalilov

2
@ Anar: Es war einfach einfacher und ich erwarte viel effizienter.
Jonathan Wood

Müssen Sie für jede Tabelle eine gespeicherte Prozedur schreiben?
Tofutim

Antworten:


174

Wenn Sie mit angehängten Objekten arbeiten (Objekte, die aus derselben Instanz des Kontexts geladen wurden), können Sie einfach Folgendes verwenden:

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

// Attached object tracks modifications automatically

context.SaveChanges();

Wenn Sie Kenntnisse über den Schlüssel des Objekts verwenden können, können Sie Folgendes verwenden:

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

context.SaveChanges();

Wenn Sie die Existenz des Objekts nicht anhand seiner ID bestimmen können, müssen Sie eine Suchabfrage ausführen:

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

Vielen Dank. Sieht aus wie was ich brauche. Kann ich Ihnen eine Frage stellen, die mich schon eine Weile beschäftigt? Normalerweise setze ich meinen Kontext in einen kurzen usingBlock. Ist es in Ordnung, den Kontext für eine Weile im Gedächtnis zu lassen? Zum Beispiel während der Lebensdauer eines Windows-Formulars? Normalerweise versuche ich, Datenbankobjekte zu bereinigen, um eine minimale Belastung der Datenbank sicherzustellen. Gibt es kein Problem damit, meinen EF-Kontext zu zerstören?
Jonathan Wood

Überprüfen Sie Folgendes : stackoverflow.com/questions/3653009/… Der Objektkontext sollte so kurz wie möglich sein. Bei Winforms oder wpf kann dies jedoch bedeuten, dass der Kontext so lange wie der Präsentator lebt. Die verknüpfte Frage enthält einen Link zu einem msdn-Artikel über die Verwendung der nhibernate-Sitzung in Winforms. Der gleiche Ansatz kann für den Kontext verwendet werden.
Ladislav Mrnka

1
Aber was ist, wenn ich dies mit einer Liste von Objekten tun muss ... in meiner Datenbank gibt es eine Liste von Zeilen mit derselben ID und ich möchte sie ersetzen, wenn sie existieren, oder einfügen, wenn sie nicht vorhanden sind. Wie mache ich das? Vielen Dank!
Phoenix_uy

1
Diese Antwort sieht fantastisch aus, aber ich stoße beim Update auf dieses Problem: Ein Objekt mit demselben Schlüssel ist bereits im ObjectStateManager vorhanden. Der ObjectStateManager kann nicht mehrere Objekte mit demselben Schlüssel verfolgen.
John Zumbrum

1
Es sieht so aus, als hätte ich nur ein kleines Problem beim Abrufen des vorhandenen Objekts, um dessen Schlüssel vor dem Update abzurufen. Das Entfernen des Suchobjekts half zuerst, das Problem zu beheben.
John Zumbrum

33

Ab Entity Framework 4.3 gibt es AddOrUpdateim Namespace eine Methode System.Data.Entity.Migrations:

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

welche von der doc :

Fügt Entitäten nach Schlüssel hinzu oder aktualisiert sie, wenn SaveChanges aufgerufen wird. Entspricht einer "Upsert" -Operation aus der Datenbankterminologie. Diese Methode kann beim Seeding von Daten mithilfe von Migrationen hilfreich sein.


Um den Kommentar von @ Smashing1978 zu beantworten , füge ich relevante Teile aus dem von @Colin bereitgestellten Link ein

Die Aufgabe von AddOrUpdate besteht darin, sicherzustellen, dass Sie keine Duplikate erstellen, wenn Sie Daten während der Entwicklung festlegen.

Zunächst wird eine Abfrage in Ihrer Datenbank ausgeführt, in der nach einem Datensatz gesucht wird, bei dem alles, was Sie als Schlüssel (erster Parameter) angegeben haben, mit dem im AddOrUpdate angegebenen zugeordneten Spaltenwert (oder den zugeordneten Werten) übereinstimmt. Dies ist also ein wenig locker für das Matching, aber perfekt für das Seeding von Entwurfszeitdaten.

Noch wichtiger ist, wenn eine Übereinstimmung gefunden wird, aktualisiert das Update alle und nullt alle, die nicht in Ihrem AddOrUpdate enthalten waren.

Ich habe jedoch eine Situation, in der ich Daten von einem externen Dienst abrufe und vorhandene Werte per Primärschlüssel einfüge oder aktualisiere (und meine lokalen Daten für Verbraucher schreibgeschützt sind) - seit AddOrUpdatemehr als 6 Monaten in der Produktion bisher keine probleme.


7
Der System.Data.Entity.Migrations-Namespace enthält Klassen, die sich auf codebasierte Migrationen und deren Konfigurationen beziehen. Gibt es einen Grund, warum wir dies nicht in unseren Repositorys für die Nichtmigrationsentität AddOrUpdates verwenden sollten?
Matt Lengenfelder

10
Seien Sie vorsichtig
Colin

1
Dieser Artikel beschreibt, warum AddOrUpdate nicht verwendet werden sollte michaelgmccarthy.com/2016/08/24/…
Nolmë Informatique

10

Die Magie passiert beim Anrufen SaveChanges()und hängt vom Strom ab EntityState. Wenn die Entität eine hat EntityState.Added, wird sie der Datenbank hinzugefügt. Wenn sie eine hat EntityState.Modified, wird sie in der Datenbank aktualisiert. Sie können also eine InsertOrUpdate()Methode wie folgt implementieren :

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

        context.SaveChanges(); 
    } 
}

Mehr über EntityState

Wenn Sie nicht überprüfen können Id = 0, ob es sich um eine neue Einheit handelt oder nicht, überprüfen Sie die Antwort von Ladislav Mrnka .


8

Wenn Sie wissen, dass Sie denselben Kontext verwenden und keine Entitäten trennen, können Sie eine generische Version wie die folgende erstellen:

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 kann natürlich ein Klassenfeld sein, oder die Methode kann statisch und eine Erweiterung gemacht werden, aber das sind die Grundlagen.


4

Ladislavs Antwort war knapp, aber ich musste einige Änderungen vornehmen, damit dies in EF6 (Datenbank zuerst) funktioniert. Ich habe meinen Datenkontext mit meiner Methode on AddOrUpdate erweitert, und bisher scheint dies mit getrennten Objekten gut zu funktionieren:

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 existiert auch als Erweiterungsmethode in System.Data.Entity.Migrations. Wenn ich Sie wäre, würde ich vermeiden, denselben Methodennamen für Ihre eigene Methode wiederzuverwenden.
Nach dem

2

Meiner Meinung nach ist es erwähnenswert, dass Sie sich mit dem neu veröffentlichten EntityGraphOperations for Entity Framework-Code zunächst das Schreiben einiger sich wiederholender Codes zum Definieren der Zustände aller Entitäten im Diagramm ersparen können. Ich bin der Autor dieses Produkts. Und ich habe es im Github , im Code-Projekt ( einschließlich einer schrittweisen Demonstration und zum Herunterladen eines Beispielprojekts) und im Nuget veröffentlicht .

Der Status der Entitäten wird automatisch auf Addedoder gesetzt Modified. Und Sie werden manuell auswählen, welche Entitäten gelöscht werden müssen, wenn sie nicht mehr vorhanden sind.

Das Beispiel:

Nehmen wir an, ich habe ein PersonObjekt. Personkönnte viele Telefone haben, ein Dokument und könnte einen Ehepartner haben.

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

Ich möchte den Status aller Entitäten bestimmen, die in der Grafik enthalten sind.

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

Wie Sie wissen, können beim Definieren des Status der Telefonentität eindeutige Schlüsseleigenschaften eine Rolle spielen. Für solche speziellen Zwecke haben wir eine ExtendedEntityTypeConfiguration<>Klasse, die von erbt EntityTypeConfiguration<>. Wenn wir solche speziellen Konfigurationen verwenden möchten, müssen wir unsere Zuordnungsklassen von erben ExtendedEntityTypeConfiguration<>und nicht von EntityTypeConfiguration<>. Beispielsweise:

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

Das ist alles.


2

Fügen Sie else ein, aktualisieren Sie beide

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

Ich habe diesen Ansatz aber verwendet und (nach dem ersten oder Standard) überprüft, ob (query == null)
Patrick

2

Überprüfen Sie die vorhandene Zeile mit Beliebig.

    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

Alternative für @LadislavMrnka Antwort. Dies gilt für Entity Framework 6.2.0.

Wenn Sie ein bestimmtes DbSetElement haben, das entweder aktualisiert oder erstellt werden muss:

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

Dies kann jedoch auch für ein Generikum DbSetmit einem einzelnen Primärschlüssel oder einem zusammengesetzten Primärschlüssel verwendet werden.

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

Korrigiert

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
Bitte verwenden Sie bearbeiten, anstatt eine andere Antwort zu veröffentlichen
Suraj Rao
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.