Entity Framework 5 Aktualisieren eines Datensatzes

870

Ich habe verschiedene Methoden zum Bearbeiten / Aktualisieren eines Datensatzes in Entity Framework 5 in einer ASP.NET MVC3-Umgebung untersucht, aber bisher hat keine davon alle von mir benötigten Kästchen angekreuzt. Ich werde erklären warum.

Ich habe drei Methoden gefunden, bei denen ich die Vor- und Nachteile erwähnen werde:

Methode 1 - Originaldatensatz laden, jede Eigenschaft aktualisieren

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    original.BusinessEntityId = updatedUser.BusinessEntityId;
    original.Email = updatedUser.Email;
    original.EmployeeId = updatedUser.EmployeeId;
    original.Forename = updatedUser.Forename;
    original.Surname = updatedUser.Surname;
    original.Telephone = updatedUser.Telephone;
    original.Title = updatedUser.Title;
    original.Fax = updatedUser.Fax;
    original.ASPNetUserId = updatedUser.ASPNetUserId;
    db.SaveChanges();
}    

Vorteile

  • Kann angeben, welche Eigenschaften sich ändern
  • Ansichten müssen nicht jede Eigenschaft enthalten

Nachteile

  • 2 x Abfragen in der Datenbank, um das Original zu laden und dann zu aktualisieren

Methode 2 - Originaldatensatz laden, geänderte Werte einstellen

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    db.Entry(original).CurrentValues.SetValues(updatedUser);
    db.SaveChanges();
}

Vorteile

  • Es werden nur geänderte Eigenschaften an die Datenbank gesendet

Nachteile

  • Ansichten müssen jede Eigenschaft enthalten
  • 2 x Abfragen in der Datenbank, um das Original zu laden und dann zu aktualisieren

Methode 3 - Aktuellen Datensatz anhängen und Status auf EntityState.Modified setzen

db.Users.Attach(updatedUser);
db.Entry(updatedUser).State = EntityState.Modified;
db.SaveChanges();

Vorteile

  • 1 x Abfrage der zu aktualisierenden Datenbank

Nachteile

  • Es kann nicht angegeben werden, welche Eigenschaften sich ändern
  • Ansichten müssen jede Eigenschaft enthalten

Frage

Meine Frage an euch; Gibt es einen sauberen Weg, wie ich diese Ziele erreichen kann?

  • Kann angeben, welche Eigenschaften sich ändern
  • Ansichten müssen nicht jede Eigenschaft enthalten (z. B. Passwort!)
  • 1 x Abfrage der zu aktualisierenden Datenbank

Ich verstehe, dass dies nur eine Kleinigkeit ist, aber ich vermisse möglicherweise eine einfache Lösung dafür. Wenn nicht Methode wird man sich durchsetzen ;-)

Stokedout
quelle
13
Verwenden Sie ViewModels und eine gute Mapping-Engine? Sie erhalten nur "zu aktualisierende Eigenschaften", um Ihre Ansicht zu füllen (und dann zu aktualisieren). Es werden immer noch die 2 Abfragen zum Aktualisieren sein (Original erhalten + aktualisieren), aber ich würde dies nicht als "Con" bezeichnen. Wenn das dein einziges Leistungsproblem ist, bist du ein glücklicher Mann;)
Raphaël Althaus
Danke @ RaphaëlAlthaus, sehr gültiger Punkt. Ich könnte dies tun, aber ich muss eine CRUD-Operation für eine Reihe von Tabellen erstellen, damit ich nach einer Methode suche, die direkt mit dem Modell arbeiten kann, damit ich für jedes Modell kein n-1 ViewModel erstellen kann.
Stokedout
3
Nun, in meinem aktuellen Projekt (auch viele Entitäten) haben wir mit der Arbeit an Modellen begonnen, weil wir dachten, wir würden Zeit verlieren, mit ViewModels zu arbeiten. Wir gehen jetzt zu ViewModels und mit (nicht zu vernachlässigenden) Infrastrukturarbeiten am Anfang ist es jetzt viel, viel, viel klarer und einfacher zu warten. Und sicherer (keine Angst vor böswilligen "versteckten Feldern" oder ähnlichen Dingen)
Raphaël Althaus
1
Und keine (schrecklichen) ViewBags mehr zum Auffüllen Ihrer DropDownLists (wir haben mindestens eine DropDownList in fast allen unseren CRU (D) -Ansichten ...)
Raphaël Althaus
Ich denke, Sie haben Recht, mein schlechtes für den Versuch, ViewModels zu übersehen. Ja, ViewBag scheint manchmal nur ein bisschen schmutzig zu sein. Normalerweise gehe ich laut Dino Espositos Blog noch einen Schritt weiter und erstelle auch InputModels, einen kleinen Gürtel und Hosenträger, aber es funktioniert ganz gut. Bedeutet nur 2 zusätzliche Modelle pro Modell - doh ;-)
Stokedout

Antworten:

681

Du suchst nach:

db.Users.Attach(updatedUser);
var entry = db.Entry(updatedUser);
entry.Property(e => e.Email).IsModified = true;
// other changed properties
db.SaveChanges();
Ladislav Mrnka
quelle
59
hi @Ladislav Mrnka, wenn ich alle Eigenschaften gleichzeitig aktualisieren möchte, kann ich den folgenden Code verwenden? db.Departments.Attach (Abteilung); db.Entry (Abteilung) .State = EntityState.Modified; db.SaveChanges ();
Foyzul Karim
23
@Foysal: Ja das kannst du.
Ladislav Mrnka
5
Eines der Probleme bei diesem Ansatz ist, dass Sie db.Entry () nicht verspotten können, was eine ernsthafte PITA ist. EF hat anderswo eine ziemlich gute spöttische Geschichte - es ist ziemlich ärgerlich, dass sie (soweit ich das beurteilen kann) hier keine haben.
Ken Smith
23
@Foysal Doing context.Entry (entity) .State = EntityState.Modified allein reicht aus, ohne dass das Anhängen erforderlich ist. Es wird automatisch als geändert angehängt ...
HelloWorld
4
@ Sandman4, das heißt, jede andere Eigenschaft muss vorhanden sein und auf den aktuellen Wert gesetzt werden. In einigen Anwendungsdesigns ist dies nicht möglich.
Dan Esparza
176

Ich mag die akzeptierte Antwort wirklich. Ich glaube, es gibt noch einen anderen Weg, dies zu erreichen. Angenommen, Sie haben eine sehr kurze Liste von Eigenschaften, die Sie niemals in eine Ansicht aufnehmen möchten. Wenn Sie also die Entität aktualisieren, werden diese weggelassen. Angenommen, diese beiden Felder sind Kennwort und SSN.

db.Users.Attach(updatedUser);

var entry = db.Entry(updatedUser);
entry.State = EntityState.Modified;

entry.Property(e => e.Password).IsModified = false;
entry.Property(e => e.SSN).IsModified = false;   

db.SaveChanges();   

In diesem Beispiel können Sie Ihre Geschäftslogik im Wesentlichen in Ruhe lassen, nachdem Sie Ihrer Benutzertabelle und Ihrer Ansicht ein neues Feld hinzugefügt haben.

smd
quelle
Trotzdem erhalte ich eine Fehlermeldung, wenn ich keinen Wert für die SSN-Eigenschaft angegeben habe, obwohl ich IsModified auf false gesetzt habe, wird die Eigenschaft dennoch anhand der Modellregeln überprüft. Wenn die Eigenschaft als NOT NULL markiert ist, schlägt dies fehl, wenn ich keinen anderen Wert als null setze.
RolandoCC
Sie erhalten keine Fehlermeldung, da diese Felder nicht in Ihrem Formular enthalten sind. Sie lassen die Felder weg, die Sie definitiv nicht aktualisieren werden, holen den Eintrag aus der Datenbank mithilfe des Formulars, das durch Anhängen zurückgegeben wurde, und teilen dem Eintrag mit, dass diese Felder nicht geändert werden. Die Modellvalidierung wird im ModelState gesteuert, nicht im Kontext. In diesem Beispiel wird auf einen vorhandenen Benutzer verwiesen, daher "aktualisierter Benutzer". Wenn Ihre SSN ein Pflichtfeld ist, wäre sie bei der ersten Erstellung vorhanden gewesen.
SMD
4
Wenn ich das richtig verstehe, ist "aktualisierter Benutzer" eine Instanz eines Objekts, das bereits mit FirstOrDefault () oder ähnlichem gefüllt ist. Daher aktualisiere ich nur die Eigenschaften, die ich geändert habe, und setze andere auf ISModified = false. Das funktioniert gut. Ich versuche jedoch, ein Objekt zu aktualisieren, ohne es zuerst zu füllen, ohne FirstOrDefault () vor dem Update zu erstellen. In diesem Fall erhalte ich eine Fehlermeldung, wenn ich nicht für alle erforderlichen Felder einen Wert angegeben habe, obwohl ich für diese Eigenschaften ISModified = false festgelegt habe. entry.Property (e => e.columnA) .IsModified = false; Ohne diese Zeile schlägt ColumnA fehl.
RolandoCC
Was Sie beschreiben, ist das Erstellen einer neuen Entität. Dies gilt nur für die Aktualisierung.
smd
1
RolandoCC, setze db.Configuration.ValidateOnSaveEnabled = false; vor dem db.SaveChanges ();
Wilky
28
foreach(PropertyInfo propertyInfo in original.GetType().GetProperties()) {
    if (propertyInfo.GetValue(updatedUser, null) == null)
        propertyInfo.SetValue(updatedUser, propertyInfo.GetValue(original, null), null);
}
db.Entry(original).CurrentValues.SetValues(updatedUser);
db.SaveChanges();
Stefano Camisassi
quelle
Dies scheint eine wirklich schöne Lösung zu sein - kein Muss oder Aufhebens; Sie müssen die Eigenschaften nicht manuell angeben und es werden alle OP-Aufzählungszeichen berücksichtigt. Gibt es einen Grund, warum dies nicht mehr Stimmen hat?
Nocarrier
Das tut es aber nicht. Es hat eines der größten "Nachteile", mehr als einen Treffer in der Datenbank. Sie müssten immer noch das Original mit dieser Antwort laden.
smd
1
@smd warum sagt man, dass es die Datenbank mehr als einmal trifft? Ich sehe das nicht, es sei denn, die Verwendung von SetValues ​​() hat diesen Effekt, aber das scheint nicht wahr zu sein.
Parlament
@parliament Ich denke, ich muss geschlafen haben, als ich das geschrieben habe. Entschuldigung. Das eigentliche Problem besteht darin, einen beabsichtigten Nullwert zu überschreiben. Wenn der aktualisierte Benutzer nicht mehr auf etwas verweist, ist es nicht richtig, es durch den ursprünglichen Wert zu ersetzen, wenn Sie es löschen möchten.
smd
22

Ich habe meiner Repository-Basisklasse eine zusätzliche Aktualisierungsmethode hinzugefügt, die der von Scaffolding generierten Aktualisierungsmethode ähnelt. Anstatt das gesamte Objekt auf "geändert" zu setzen, werden einzelne Eigenschaften festgelegt. (T ist ein generischer Klassenparameter.)

public void Update(T obj, params Expression<Func<T, object>>[] propertiesToUpdate)
{
    Context.Set<T>().Attach(obj);

    foreach (var p in propertiesToUpdate)
    {
        Context.Entry(obj).Property(p).IsModified = true;
    }
}

Und dann zum Beispiel anrufen:

public void UpdatePasswordAndEmail(long userId, string password, string email)
{
    var user = new User {UserId = userId, Password = password, Email = email};

    Update(user, u => u.Password, u => u.Email);

    Save();
}

Ich mag eine Reise in die Datenbank. Es ist jedoch wahrscheinlich besser, dies mit Ansichtsmodellen zu tun, um zu vermeiden, dass sich Sätze von Eigenschaften wiederholen. Ich habe das noch nicht getan, weil ich nicht weiß, wie ich vermeiden kann, die Validierungsnachrichten auf meinen Ansichtsmodell-Validatoren in mein Domänenprojekt zu bringen.

Ian Warburton
quelle
Aha ... separates Projekt für Ansichtsmodelle und separates Projekt für Repositorys, die mit Ansichtsmodellen arbeiten.
Ian Warburton
11
public interface IRepository
{
    void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class;
}

public class Repository : DbContext, IRepository
{
    public void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class
    {
        Set<T>().Attach(obj);
        propertiesToUpdate.ToList().ForEach(p => Entry(obj).Property(p).IsModified = true);
        SaveChanges();
    }
}
Matthew Steven Monkan
quelle
Warum nicht einfach DbContext.Attach (obj); DbContext.Entry (obj) .State = EntityState.Modified;
Nelsontruran
Dies steuert den setTeil der Update-Anweisung.
Tanveer Badar
4

Nur um der Liste der Optionen hinzuzufügen. Sie können das Objekt auch aus der Datenbank abrufen und ein Auto-Mapping-Tool wie Auto Mapper verwenden , um die Teile des Datensatzes zu aktualisieren, die Sie ändern möchten.

Bostwick
quelle
3

Abhängig von Ihrem Anwendungsfall gelten alle oben genannten Lösungen. So mache ich es aber normalerweise:

Für serverseitigen Code (z. B. einen Stapelprozess) lade ich normalerweise die Entitäten und arbeite mit dynamischen Proxys. Normalerweise müssen Sie in Batch-Prozessen die Daten zum Zeitpunkt der Ausführung des Dienstes trotzdem laden. Ich versuche, die Daten stapelweise zu laden, anstatt die Suchmethode zu verwenden, um Zeit zu sparen. Je nach Prozess verwende ich eine optimistische oder pessimistische Parallelitätskontrolle (ich verwende immer optimistisch, außer bei Szenarien mit paralleler Ausführung, in denen ich einige Datensätze mit einfachen SQL-Anweisungen sperren muss, dies ist jedoch selten). Je nach Code und Szenario kann die Auswirkung auf nahezu Null reduziert werden.

Für clientseitige Szenarien haben Sie einige Optionen

  1. Verwenden Sie Ansichtsmodelle. Die Modelle sollten eine Eigenschaft UpdateStatus haben (unverändert-eingefügt-aktualisiert-gelöscht). Es liegt in der Verantwortung des Clients, abhängig von den Benutzeraktionen (Einfügen-Aktualisieren-Löschen) den richtigen Wert für diese Spalte festzulegen. Der Server kann entweder die Datenbank nach den ursprünglichen Werten abfragen, oder der Client sollte die ursprünglichen Werte zusammen mit den geänderten Zeilen an den Server senden. Der Server sollte die ursprünglichen Werte anhängen und die UpdateStatus-Spalte für jede Zeile verwenden, um zu entscheiden, wie mit den neuen Werten umgegangen werden soll. In diesem Szenario verwende ich immer optimistische Parallelität. Dies führt nur die Anweisungen zum Einfügen - Aktualisieren - Löschen und keine Auswahl aus. Möglicherweise ist jedoch ein cleverer Code erforderlich, um das Diagramm zu durchlaufen und die Entitäten zu aktualisieren (abhängig von Ihrem Szenario - Anwendung). Ein Mapper kann helfen, behandelt aber nicht die CRUD-Logik

  2. Verwenden Sie eine Bibliothek wie breeze.js, die den größten Teil dieser Komplexität verbirgt (wie in 1 beschrieben), und versuchen Sie, sie an Ihren Anwendungsfall anzupassen.

Ich hoffe es hilft

Chriss
quelle