Machen Sie Änderungen in Entity Framework-Entitäten rückgängig

116

Dies mag eine triviale Frage sein, aber: Da das ADO.NET-Entitätsframework Änderungen (in generierten Entitäten) automatisch verfolgt und daher die ursprünglichen Werte beibehält, wie kann ich Änderungen an den Entitätsobjekten rückgängig machen?

Ich habe ein Formular, mit dem der Benutzer eine Reihe von "Kunden" -Entitäten in einer Rasteransicht bearbeiten kann.

Jetzt habe ich zwei Schaltflächen "Akzeptieren" und "Zurücksetzen": Wenn auf "Akzeptieren" geklickt wird, rufe ich auf Context.SaveChanges()und die geänderten Objekte werden in die Datenbank zurückgeschrieben. Wenn auf "Zurücksetzen" geklickt wird, möchte ich, dass alle Objekte ihre ursprünglichen Eigenschaftswerte erhalten. Was wäre der Code dafür?

Vielen Dank

MartinStettner
quelle

Antworten:

69

In EF gibt es keinen Vorgang zum Zurücksetzen oder Abbrechen von Änderungen. Jede Entität hat ObjectStateEntryin ObjectStateManager. Der Statuseintrag enthält Original- und Istwerte, sodass Sie Originalwerte verwenden können, um aktuelle Werte zu überschreiben. Sie müssen dies jedoch manuell für jede Entität tun. Änderungen an Navigationseigenschaften / -beziehungen werden nicht zurückgesetzt.

Die übliche Methode zum "Zurücksetzen von Änderungen" besteht darin, den Kontext zu entsorgen und Entitäten neu zu laden. Wenn Sie ein erneutes Laden vermeiden möchten, müssen Sie Klone von Entitäten erstellen und diese Klone im neuen Objektkontext ändern. Wenn Benutzer Änderungen abbrechen, haben Sie immer noch ursprüngliche Entitäten.

Ladislav Mrnka
quelle
4
@LadislavMrnka Ist sicherlich Context.Refresh()ein Gegenbeispiel zu Ihrer Behauptung, dass es keine Rücksetzoperation gibt? Die Verwendung Refresh()scheint ein besserer Ansatz zu sein (dh leichter auf bestimmte Entitäten abzuzielen), als den Kontext zu entsorgen und alle nachverfolgten Änderungen zu verlieren.
Rob
14
@robjb: Nein. Durch Aktualisieren können nur einzelne Entitäten oder Sammlungen von Entitäten aktualisiert werden, die Sie manuell definieren. Die Aktualisierungsfunktion wirkt sich jedoch nur auf einfache Eigenschaften (keine Beziehungen) aus. Es löst auch kein Problem mit hinzugefügten oder gelöschten Entitäten.
Ladislav Mrnka
153

Fragen Sie den ChangeTracker von DbContext nach schmutzigen Elementen ab. Setzen Sie den Status gelöschter Elemente auf unverändert und hinzugefügte Elemente auf getrennt. Verwenden Sie für geänderte Elemente Originalwerte und legen Sie die aktuellen Werte des Eintrags fest. Setzen Sie den Status des geänderten Eintrags schließlich auf unverändert:

public void RollBack()
{
    var context = DataContextFactory.GetDataContext();
    var changedEntries = context.ChangeTracker.Entries()
        .Where(x => x.State != EntityState.Unchanged).ToList();

    foreach (var entry in changedEntries)
    {
        switch(entry.State)
        {
            case EntityState.Modified:
                entry.CurrentValues.SetValues(entry.OriginalValues);
                entry.State = EntityState.Unchanged;
                break;
            case EntityState.Added:
                entry.State = EntityState.Detached;
                break;
            case EntityState.Deleted:
                entry.State = EntityState.Unchanged;
                break;
        }
    }
 }
Taher Rahgooy
quelle
3
Danke - das hat mir wirklich geholfen!
Matt
5
Sie sollten die ursprünglichen Werte wahrscheinlich auch auf gelöschte Einträge setzen. Möglicherweise haben Sie zuerst ein Element geändert und danach gelöscht.
Bas de Raad
22
Die Einstellung Stateauf EntityState.Unchanged überschreibt auch alle Werte mit, Original Valuessodass keine SetValuesMethode aufgerufen werden muss.
Abolfazl Hosnoddin
10
Sauberere
Jerther
1
Kumpel, das ist großartig! Ich habe nur die generische Version von Entries <T> () geändert, damit sie für meine Repositorys funktioniert. Dies gibt mir mehr Kontrolle und ich kann ein Rollback pro Entitätstyp durchführen. Vielen Dank!
Daniel Mackay
33
dbContext.Entry(entity).Reload();

Nach MSDN :

Lädt die Entität aus der Datenbank neu und überschreibt alle Eigenschaftswerte mit Werten aus der Datenbank. Die Entität befindet sich nach dem Aufrufen dieser Methode im Status "Unverändert".

Beachten Sie, dass das Zurücksetzen der Anforderung zur Datenbank einige Nachteile hat:

  • Netzwerktraffic
  • DB-Überlastung
  • die erhöhte Reaktionszeit der Anwendung
Lu55
quelle
17

Das hat bei mir funktioniert:

dataContext.customer.Context.Refresh(RefreshMode.StoreWins, item);

Wo itemsoll die Kundeneinheit zurückgesetzt werden?

Matyas
quelle
12

Einfacher Weg ohne Änderungen nachzuverfolgen. Es sollte schneller sein, als alle Entitäten zu betrachten.

public void Rollback()
{
    dataContext.Dispose();
    dataContext= new MyEntities(yourConnection);
}
Guish
quelle
Zeitpunkt der Erstellung eines einzelnen Berechtigungsobjekts ... das sind einige ms (50 ms). Das Durchlaufen der Sammlung kann je nach Größe schneller oder länger dauern. In Bezug auf die Leistung ist O (1) im Vergleich zu O (n) selten ein Problem. Big O Notation
Guish
Nicht folgen - Leistung beim Entsorgen und Wiederherstellen der Verbindung. Ich habe es an einem vorhandenen Projekt getestet und es endete etwas schneller als oben beschrieben Rollback, was es zu einer weitaus besseren Wahl macht, wenn man den gesamten Datenbankstatus zurücksetzen möchte. Rollback könnte Kirsche pflücken.
Majkinetor
'n' bedeutet die Anzahl der Objekte. Das Wiederherstellen der Verbindung dauert ungefähr 50 ms ... O (1) bedeutet, dass es immer dieselbe Zeit ist 50ms+0*n= 50ms. O (n) bedeutet, dass die Leistung von der Anzahl der Objekte beeinflusst wird ... die Leistung ist möglicherweise 2ms+0.5ms*n... also unter 96 Objekten wäre es schneller, aber die Zeit würde linear mit der Datenmenge zunehmen.
Guish
Wenn Sie nicht auswählen möchten, was zurückgerollt wird, ist dies der richtige Weg, vorausgesetzt, Sie machen sich keine Sorgen um die Bandbreite.
Anthony Nichols
6
// Undo the changes of all entries. 
foreach (DbEntityEntry entry in context.ChangeTracker.Entries()) 
{ 
    switch (entry.State) 
    { 
        // Under the covers, changing the state of an entity from  
        // Modified to Unchanged first sets the values of all  
        // properties to the original values that were read from  
        // the database when it was queried, and then marks the  
        // entity as Unchanged. This will also reject changes to  
        // FK relationships since the original value of the FK  
        // will be restored. 
        case EntityState.Modified: 
            entry.State = EntityState.Unchanged; 
            break; 
        case EntityState.Added: 
            entry.State = EntityState.Detached; 
            break; 
        // If the EntityState is the Deleted, reload the date from the database.   
        case EntityState.Deleted: 
            entry.Reload(); 
            break; 
        default: break; 
    } 
} 

Es hat bei mir funktioniert. Sie müssen jedoch Ihre Daten aus dem Kontext neu laden, um die alten Daten zu erhalten. Quelle hier

Alejandro del Río
quelle
3

"Das hat bei mir funktioniert:

dataContext.customer.Context.Refresh(RefreshMode.StoreWins, item);

Wo itemsoll die Kundeneinheit zurückgesetzt werden? "


Ich habe Tests mit ObjectContext.Refresh in SQL Azure durchgeführt, und "RefreshMode.StoreWins" löst für jede Entität eine Abfrage für die Datenbank aus und verursacht einen Leistungsverlust. Basierend auf der Microsoft-Dokumentation ():

ClientWins: Eigenschaftsänderungen an Objekten im Objektkontext werden nicht durch Werte aus der Datenquelle ersetzt. Beim nächsten Aufruf von SaveChanges werden diese Änderungen an die Datenquelle gesendet.

StoreWins: Eigenschaftsänderungen, die an Objekten im Objektkontext vorgenommen wurden, werden durch Werte aus der Datenquelle ersetzt.

ClientWins ist auch keine gute Idee, da durch das Auslösen von .SaveChanges "verworfene" Änderungen an der Datenquelle vorgenommen werden.

Ich weiß noch nicht, was der beste Weg ist, da das Entsorgen des Kontexts und das Erstellen eines neuen Kontexts eine Ausnahme mit der Meldung "Der zugrunde liegende Anbieter ist beim Öffnen fehlgeschlagen" verursacht, wenn ich versuche, eine Abfrage für einen neu erstellten Kontext auszuführen.

Grüße,

Henrique Clausing

Henrique Clausing Cunha
quelle
2

Für mich ist es besser, für EntityState.Unchangedjede Entität festzulegen, für die Sie Änderungen rückgängig machen möchten. Dies stellt sicher, dass Änderungen in FK rückgängig gemacht werden und eine etwas klarere Syntax haben.

Naz
quelle
4
Hinweis: Die Änderungen werden zurückgegeben, wenn die Entität erneut geändert wird.
Nick Whaley
2

Ich fand, dass dies in meinem Kontext gut funktioniert:

Context.ObjectStateManager.ChangeObjectState(customer, EntityState.Unchanged);

Alex Pop
quelle
1
Ich glaube, dies verhindert, dass Änderungen an der Entität beim Aufruf bestehen bleiben DbContext.SaveChanges(), aber es werden die Entitätswerte nicht auf die ursprünglichen Werte zurückgesetzt. Und wenn der Entitätsstatus aufgrund einer späteren Änderung geändert wird, bleiben möglicherweise alle vorherigen Änderungen beim Speichern erhalten?
Carl G
1
Überprüfen Sie diesen Link code.msdn.microsoft.com/How-to-undo-the-changes-in-00aed3c4 Es besagt , dass ein Unternehmen zu unchaged Zustand versetzt die ursprünglichen Werte wieder her „unter der Decke“.
Hannish
2

Dies ist ein Beispiel dafür, wovon Mrnka spricht. Die folgende Methode überschreibt aktuelle Werte des Unternehmens mit den ursprünglichen Werten und nicht rufen Sie die Datenbank aus. Dazu verwenden wir die OriginalValues-Eigenschaft von DbEntityEntry und verwenden die Reflexion, um Werte generisch festzulegen. (Dies funktioniert ab EntityFramework 5.0)

/// <summary>
/// Undoes any pending updates 
/// </summary>
public void UndoUpdates( DbContext dbContext )
{
    //Get list of entities that are marked as modified
    List<DbEntityEntry> modifiedEntityList = 
        dbContext.ChangeTracker.Entries().Where(x => x.State == EntityState.Modified).ToList();

    foreach(  DbEntityEntry entity in modifiedEntityList ) 
    {
        DbPropertyValues propertyValues = entity.OriginalValues;
        foreach (String propertyName in propertyValues.PropertyNames)
        {                    
            //Replace current values with original values
            PropertyInfo property = entity.Entity.GetType().GetProperty(propertyName);
            property.SetValue(entity.Entity, propertyValues[propertyName]); 
        }
    }
}
BizarroDavid
quelle
1

Wir verwenden EF 4 mit dem Legacy-Objektkontext. Keine der oben genannten Lösungen hat dies direkt für mich beantwortet - obwohl es auf lange Sicht beantwortet wurde, indem es mich in die richtige Richtung drängte.

Wir können den Kontext nicht einfach entsorgen und neu erstellen, da einige der Objekte, die wir im Speicher haben (verdammt noch mal das faule Laden !!), immer noch mit dem Kontext verbunden sind, aber untergeordnete Elemente haben, die noch geladen werden müssen. In diesen Fällen müssen wir alles auf die ursprünglichen Werte zurücksetzen, ohne die Datenbank zu hämmern und ohne die bestehende Verbindung zu trennen.

Nachfolgend finden Sie unsere Lösung für dasselbe Problem:

    public static void UndoAllChanges(OurEntities ctx)
    {
        foreach (ObjectStateEntry entry in
            ctx.ObjectStateManager.GetObjectStateEntries(~EntityState.Detached))
        {
            if (entry.State != EntityState.Unchanged)
            {
                ctx.Refresh(RefreshMode.StoreWins, entry.Entity);
            }
        }
    }

Ich hoffe das hilft anderen.

Jerry
quelle
0

Einige gute Ideen oben, ich habe mich entschieden, ICloneable und dann eine einfache Erweiterungsmethode zu implementieren.

Hier gefunden: Wie klone ich eine generische Liste in C #?

Zu verwenden als:

ReceiptHandler.ApplyDiscountToAllItemsOnReciept(LocalProductsOnReciept.Clone(), selectedDisc);

Auf diese Weise konnte ich meine Produktentitätsliste klonen, auf jeden Artikel einen Rabatt gewähren und musste mich nicht darum kümmern, Änderungen an der ursprünglichen Entität zurückzusetzen. Sie müssen nicht mit dem DBContext sprechen und nach einer Aktualisierung fragen oder mit dem ChangeTracker arbeiten. Man könnte sagen, ich nutze EF6 nicht vollständig, aber dies ist eine sehr schöne und einfache Implementierung, die einen DB-Treffer vermeidet. Ich kann nicht sagen, ob dies einen Leistungseinbruch hat oder nicht.

IbrarMumtaz
quelle