Annulla le modifiche nelle entità del framework di entità


116

questa potrebbe essere una domanda banale ma: poiché il framework di entità ADO.NET tiene automaticamente traccia delle modifiche (nelle entità generate) e quindi mantiene i valori originali, come posso ripristinare le modifiche apportate agli oggetti entità?

Ho un modulo che consente all'utente di modificare un insieme di entità "Cliente" in una visualizzazione a griglia.

Ora ho due pulsanti "Accetta" e "Ripristina": se si fa clic su "Accetta", chiamo Context.SaveChanges()e gli oggetti modificati vengono riscritti nel database. Se si fa clic su "Ripristina", desidero che tutti gli oggetti ottengano i valori delle proprietà originali. Quale sarebbe il codice per questo?

Grazie

Risposte:


69

Non vi è alcuna operazione di ripristino o annullamento delle modifiche in EF. Ogni entità ha ObjectStateEntryin ObjectStateManager. La voce di stato contiene i valori originali ed effettivi, quindi puoi utilizzare i valori originali per sovrascrivere i valori correnti, ma devi farlo manualmente per ciascuna entità. Non ripristinerà le modifiche nelle proprietà / relazioni di navigazione.

Il modo comune per "annullare le modifiche" è disporre il contesto e ricaricare le entità. Se vuoi evitare di ricaricare, devi creare cloni di entità e modificare quei cloni nel nuovo contesto dell'oggetto. Se l'utente annulla le modifiche, avrai ancora le entità originali.


4
@LadislavMrnka Sicuramente Context.Refresh()è un controesempio alla tua affermazione che non esiste un'operazione di ripristino? L'utilizzo Refresh()sembra un approccio migliore (cioè più facilmente mirato a entità specifiche) rispetto alla disposizione del contesto e alla perdita di tutte le modifiche rilevate.
Rob il

14
@robjb: No. L'aggiornamento è in grado di aggiornare solo una singola entità o una raccolta di entità che si definiscono manualmente, ma l'aggiornamento della funzionalità influisce solo sulle proprietà semplici (non sulle relazioni). Inoltre non risolve il problema con le entità aggiunte o eliminate.
Ladislav Mrnka

153

Query ChangeTracker di DbContext per elementi sporchi. Imposta lo stato degli elementi eliminati su invariato e gli elementi aggiunti su scollegati. Per gli elementi modificati, utilizzare i valori originali e impostare i valori correnti della voce. Infine imposta lo stato della voce modificata su invariato:

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

3
Grazie, questo mi ha davvero aiutato!
Matt

5
Probabilmente dovresti impostare anche i valori originali sulle voci eliminate. È possibile che tu abbia prima modificato un elemento e successivamente cancellato.
Bas de Raad

22
L'impostazione Statesu EntityState.Unchanged sovrascriverà anche tutti i valori con Original Valuesquindi non è necessario chiamare il SetValuesmetodo.
Abolfazl Hosnoddin

10
La versione più pulita di questa risposta: stackoverflow.com/a/22098063/2498426
Jerther

1
Amico, è fantastico! L'unica modifica che ho apportato è quella di utilizzare la versione generica di Entries <T> () in modo che funzioni per i miei repository. Questo mi dà più controllo e posso eseguire il rollback per tipo di entità. Grazie!
Daniel Mackay

33
dbContext.Entry(entity).Reload();

Accroding a MSDN :

Ricarica l'entità dal database sovrascrivendo eventuali valori di proprietà con valori dal database. L'entità sarà nello stato Non modificato dopo aver chiamato questo metodo.

Si noti che il ripristino tramite la richiesta al database presenta alcuni inconvenienti:

  • traffico di rete
  • Sovraccarico DB
  • l'aumento del tempo di risposta dell'applicazione

17

Questo ha funzionato per me:

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

Dove si itemtrova l'entità cliente da ripristinare.


12

Modo semplice senza tenere traccia delle modifiche. Dovrebbe essere più veloce che guardare tutte le entità.

public void Rollback()
{
    dataContext.Dispose();
    dataContext= new MyEntities(yourConnection);
}

Tempo di creazione di un singolo oggetto entitity ... che è un paio di ms (50 ms). Il ciclo attraverso la raccolta può essere più veloce o più lungo a seconda delle sue dimensioni. Per quanto riguarda le prestazioni, O (1) è raramente un problema rispetto a O (n). Notazione Big O
Guish

Non ti sto seguendo - esecuzione dell'eliminazione e della ricreazione della connessione. L'ho testato su un progetto esistente ed è terminato un po 'più velocemente della Rollbackprocedura sopra , il che lo rende una scelta di gran lunga migliore se si desidera ripristinare l'intero stato del database. Il rollback potrebbe scegliere comunque.
Majkinetor

"n" indica il numero di oggetti. La ricreazione della connessione richiede circa 50 ms ... O (1) significa che è sempre la stessa ora 50ms+0*n= 50ms. O (n) significa che le prestazioni sono influenzate dal numero di oggetti ... le prestazioni sono forse 2ms+0.5ms*n... quindi sotto 96 oggetti sarebbe più veloce ma il tempo aumenterebbe linearmente con la quantità di dati.
Guish

Se non hai intenzione di scegliere ciò che è (non) ripristinato, questa è la strada da percorrere a condizione che non siate preoccupati per la larghezza di banda.
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; 
    } 
} 

Ha funzionato per me. Tuttavia è necessario ricaricare i dati dal contesto per portare i vecchi dati. Fonte qui


3

"Questo ha funzionato per me:

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

Dov'è iteml'entità cliente da ripristinare. "


Ho effettuato test con ObjectContext.Refresh in SQL Azure e "RefreshMode.StoreWins" avvia una query sul database per ciascuna entità e causa una perdita di prestazioni. Basato sulla documentazione di Microsoft ():

ClientWins: le modifiche alle proprietà apportate agli oggetti nel contesto dell'oggetto non vengono sostituite con i valori dell'origine dati. Alla successiva chiamata a SaveChanges, queste modifiche vengono inviate all'origine dati.

StoreWins: le modifiche alle proprietà apportate agli oggetti nel contesto dell'oggetto vengono sostituite con i valori dell'origine dati.

ClientWins non è nemmeno una buona ideia, perché sparando .SaveChanges commetterà modifiche "scartate" all'origine dati.

Non so ancora qual è il modo migliore, perché l'eliminazione del contesto e la creazione di uno nuovo è causata da un'eccezione con il messaggio: "Il provider sottostante non è riuscito all'apertura" quando provo a eseguire una query su un nuovo contesto creato.

Saluti,

Henrique Clausing


2

Per quanto mi riguarda, il metodo migliore per farlo è impostare EntityState.Unchangedsu ogni entità su cui si desidera annullare le modifiche. Ciò garantisce che le modifiche vengano ripristinate su FK e abbia una sintassi un po 'più chiara.


4
Nota: le modifiche verranno ripristinate se l'entità viene modificata di nuovo.
Nick Whaley

2

Ho scoperto che funziona bene nel mio contesto:

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


1
Credo che ciò impedirà alle modifiche all'entità di persistere dopo la chiamata DbContext.SaveChanges(), ma non restituirà i valori dell'entità ai valori originali. E se lo stato dell'entità viene modificato da una modifica successiva, forse tutte le modifiche precedenti verranno mantenute dopo il salvataggio?
Carl G

1
Controlla questo link code.msdn.microsoft.com/How-to-undo-the-changes-in-00aed3c4 Dice che l'impostazione di un'entità sullo stato Unchaged ripristina i valori originali "sotto le coperte".
Hannish

2

Questo è un esempio di ciò di cui parla Mrnka. Il metodo seguente sovrascrive i valori correnti di un'entità con i valori originali e non richiama il database. A tale scopo, viene utilizzata la proprietà OriginalValues ​​di DbEntityEntry e la riflessione per impostare i valori in modo generico. (Funziona a partire da 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]); 
        }
    }
}

1

Stiamo usando EF 4, con il contesto dell'oggetto legacy. Nessuna delle soluzioni di cui sopra ha risposto direttamente a questo per me, anche se ha risposto a lungo termine spingendomi nella giusta direzione.

Non possiamo semplicemente eliminare e ricostruire il contesto perché alcuni degli oggetti che abbiamo in giro in memoria (accidenti a quel caricamento pigro !!) sono ancora attaccati al contesto ma hanno figli che devono ancora essere caricati. Per questi casi è necessario riportare tutto ai valori originali senza martellare il database e senza interrompere la connessione esistente.

Di seguito è la nostra soluzione a questo stesso problema:

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

Spero che questo aiuti gli altri.


0

Alcune buone idee sopra, ho scelto di implementare ICloneable e quindi un semplice metodo di estensione.

Trovato qui: come faccio a clonare un elenco generico in C #?

Da utilizzare come:

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

In questo modo sono stato in grado di clonare il mio elenco di entità di prodotto, applicare uno sconto a ogni articolo e non devo preoccuparmi di annullare eventuali modifiche sull'entità originale. Non c'è bisogno di parlare con DBContext e chiedere un aggiornamento o lavorare con ChangeTracker. Si potrebbe dire che non sto facendo pieno uso di EF6, ma questa è un'implementazione molto bella e semplice ed evita un colpo di DB. Non posso dire se questo abbia o meno un calo delle prestazioni.

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.