Entity Framework
Undo Changes
Entity State Management
.NET Development
C# Programming

Undo changes in entity framework entities

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Entity Framework tracks changes to loaded entities so it can generate the right SQL at save time. That same tracking data also gives you a way to undo edits when a user cancels a form, validation fails, or a workflow needs to discard in-memory changes.

How Entity Framework Tracks Changes

Every tracked entity has an EntityState, such as Unchanged, Modified, Added, or Deleted. EF keeps both current values and original values for tracked properties, which means reverting a change usually comes down to restoring values or changing the state.

The exact action depends on what kind of change happened:

  • A Modified entity should usually be reset to its original values.
  • An Added entity should usually be detached so EF stops treating it as pending insert.
  • A Deleted entity should usually be switched back to Unchanged.

Reverting a Single Entity

For a tracked entity, the simplest option is to inspect its entry and reset based on state.

csharp
1using Microsoft.EntityFrameworkCore;
2using Microsoft.EntityFrameworkCore.ChangeTracking;
3
4public static class DbContextExtensions
5{
6    public static void UndoChanges(this DbContext context)
7    {
8        foreach (var entry in context.ChangeTracker.Entries())
9        {
10            switch (entry.State)
11            {
12                case EntityState.Modified:
13                    entry.CurrentValues.SetValues(entry.OriginalValues);
14                    entry.State = EntityState.Unchanged;
15                    break;
16
17                case EntityState.Added:
18                    entry.State = EntityState.Detached;
19                    break;
20
21                case EntityState.Deleted:
22                    entry.State = EntityState.Unchanged;
23                    break;
24            }
25        }
26    }
27}

This is useful when the unit of work should be discarded entirely. After UndoChanges, SaveChanges will not persist those discarded edits.

Reloading from the Database

If you want the entity to match the database exactly, reload it. This is especially helpful when values may have changed in the database since the entity was first queried.

csharp
1public async Task CancelEditAsync(AppDbContext context, int id)
2{
3    var customer = await context.Customers.FindAsync(id);
4    if (customer == null)
5    {
6        return;
7    }
8
9    customer.Name = "Temporary UI change";
10    customer.Email = "[email protected]";
11
12    await context.Entry(customer).ReloadAsync();
13}

ReloadAsync discards current in-memory changes and fetches the latest row again. It only works for entities that already exist in the database. It does not help with newly added entities that have not been saved yet.

Undoing Changes for One Entry

Sometimes you do not want to reset the whole context. In that case, work with a single EntityEntry.

csharp
1public static void UndoEntry(EntityEntry entry)
2{
3    if (entry.State == EntityState.Modified)
4    {
5        entry.CurrentValues.SetValues(entry.OriginalValues);
6        entry.State = EntityState.Unchanged;
7        return;
8    }
9
10    if (entry.State == EntityState.Added)
11    {
12        entry.State = EntityState.Detached;
13        return;
14    }
15
16    if (entry.State == EntityState.Deleted)
17    {
18        entry.State = EntityState.Unchanged;
19    }
20}

That is a good fit for screen-level cancel actions where only one aggregate should be reverted.

Undo logic gets more subtle when a root entity has child collections or owned types. Resetting only the parent can leave child entities in Added or Deleted state. If your UI edits a graph, iterate through all tracked entries that belong to the workflow and revert them consistently.

In practice, many applications solve this by using a short-lived DbContext per request or per dialog. If the user cancels, the context is disposed and a fresh one is created next time. That is often simpler than building elaborate undo logic for long-lived contexts.

When AsNoTracking Is Better

If a view only needs to display data and not edit it, query with AsNoTracking. EF then skips change tracking entirely, which means there is nothing to undo and less memory overhead.

csharp
1var customers = await context.Customers
2    .AsNoTracking()
3    .Where(c => c.IsActive)
4    .ToListAsync();

This is not an undo feature, but it prevents the problem in read-only flows.

Common Pitfalls

Calling entry.State = EntityState.Unchanged on a modified entity without restoring original values can leave the current property values in memory, which may confuse later code.

Using ReloadAsync on an unsaved Added entity will fail because there is no database row to load.

Long-lived contexts make undo behavior harder because many unrelated tracked entities accumulate over time. Short-lived contexts are usually easier to maintain.

Navigation properties are easy to miss. If a parent and child were both edited, reverting only the parent leaves the graph inconsistent.

Undoing changes in memory is not the same as a database transaction rollback. Once SaveChanges has succeeded, you need a new update to reverse it.

Summary

  • EF change tracking makes undo possible by storing state and original values.
  • Revert Modified, Added, and Deleted entities differently.
  • Use ReloadAsync when you want to refresh an existing entity from the database.
  • Prefer short-lived contexts when canceling edits is a common workflow.
  • Treat entity graphs carefully so parent and child state stay aligned.

Course illustration
Course illustration

All Rights Reserved.