Dbcontext Discard Changes Without Disposing

DbContext discard changes without disposing

How about wrapping it in a transaction?

    using(var scope = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted })){

// Do something
context.SaveChanges();
// Do something else
context.SaveChanges();

scope.Complete();
}

Undo changes in entity framework entities

There is no revert or cancel changes operation in EF. Each entity has ObjectStateEntry in ObjectStateManager. State entry contains original and actual values so you can use original values to overwrite current values but you must do it manually for each entity. It will not reveret changes in navigation properties / relations.

The common way to "revert changes" is disposing context and reload entities. If you want to avoid reloading you must create clones of entities and modify those clones in new object context. If user cancel changes you will still have original entities.

How to discard changes to context in EF Core

The code below worked for me. However, if someone posts a cleaner solution (I still expect there to be something out of the box with EF) I will accept it.

private void ResetContextState() => _context.ChangeTracker.Entries()
.Where(e => e.Entity != null).ToList()
.ForEach(e => e.State = EntityState.Detached);

What if I call DbContext.Dispose without calling DbContext.SaveChanges?

what happens if I simply dispose of my dbContext instance without calling dbContext.SaveChanges() or manipulating the entity states

Nothing. The instances that were previously attached to the now disposed DbContext instance continue to exist as any normal instances would assuming there is a handle on those instances somewhere by something. If there is not the memory will be release and eventually garbage collected as any other normal instance of something would if there is no handle to it. The state of the entities in memory stays as is, nothing in memory is automatically reverted back. The database store also stays "as is" meaning there is no call from the DbContext to the datastore.

am I leaving anything in an unstable state by rejecting changes in this way

No, at least not in the data store. In memory it is hard to say, it depends on where the code left off and what the dependencies were on the modifications up until that point. Lets assume its a stateless asp.net application, maybe the request simply ends and in this case nothing unstable should happen with any of the following requests as they should retrieve anything needed back from the data store.

If its something more long lived like a windows app then you might have to manually make sure that any pointers/handles to instances that were previously being tracked are either updated with the now reverted in-memory state or release those pointers.

As far as any new DbContext instances are concerned they all operate independent of each other so there is no continuation between them. The new DbContext does not know about state being tracked or state that had been tracked by any other DbContext instance.

Entity Framework Core dbContext behavior after rejecting changes

The Change Tracker is scoped to a single DbContext, which should be scoped to a single API request. Also you don't create a new ChangeTracker. The DbContext already has one.

var ChangeTracker = _context.ChangeTracker;

Does Entity Framework's DbContext save changes if no changes were made?

It uses EntityState to determine that there is nothing to commit and so does not waste resources.

http://msdn.microsoft.com/en-us/library/system.data.entitystate%28v=vs.110%29.aspx

Using EF Core with WPF and I'd like to revert to initial state when a user press Cancel instead of OK on a dialog, is that possible and how?

Q: Is that possible and how?

A: Sure.

  1. The most straightforward way is to only "SaveChanges()" when you're "Done" (when the user clicks OK).

  2. Another approach might be to make copies of the original objects, and perform in-process operations on the copies.

  3. If you're continually updating and saving work-in-progress to the DB (possibly an unwise design), then you can also leverage transactions, and rollback if the user hits Cancel.

Example:

https://learn.microsoft.com/en-us/ef/core/saving/transactions

using var context = new BloggingContext();
using var transaction = context.Database.BeginTransaction();

try
{
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
context.SaveChanges();

context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
context.SaveChanges();

var blogs = context.Blogs
.OrderBy(b => b.Url)
.ToList();

// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
transaction.Commit();
}
catch (Exception)
{
// TODO: Handle failure
}

Is there a way to reset a DbContext without disposing and reinstantiating it?

I was not able to come up with a method to reset the global DbContext. I was able to solve my problem, however, by injecting a DbContextLocator into any class that needs a DbContext instead of passing the DbContext itself.

My goal was to maintain a global DbContext, but allow it to be reset whenever needed (such as after a database rebuild or import).

My solution uses an abstract base class and a concrete class.

Base Class

using System.Data.Entity;

namespace CommonLibrary.Database
{
public abstract class DbContextLocator
{
private DbContext _dbContext;

public DbContext Current
{
get { return _dbContext; }
}

public DbContextLocator()
{
_dbContext = GetNew();
}

public virtual void Reset()
{
_dbContext.Dispose();
_dbContext = GetNew();
}

protected abstract DbContext GetNew();
}
}

Concrete Class

using System.Data.Entity;
using CommonLibrary.Database;
using ExperimentBase.EntityModels;

namespace MainProject.Models
{
public class MainDbContextLocator : DbContextLocator
{
public new MainDbContext Current
{
get { return (MainDbContext)base.Current; }
}

protected override DbContext GetNew()
{
return new MainDbContext();
}
}
}

Usage

Get the current DbContext:

var dbContext = dbContextLocator.Current;

Reset the DbContext:

dbContextLocator.Reset();
//Note: normally followed by code that re-initializes app data

Edit

Based on Shimmy's feedback, I made DbContextLocatorBase into a generic. (I'm also now implementing IDisposable.)

Base Class

public class DbContextLocator<TContext> : IDisposable
where TContext : DbContext, new()
{
private TContext _dbContext;

public TContext Current
{
get { return _dbContext; }
}

public DbContextLocator()
{
_dbContext = GetNew();
}

public virtual void Reset()
{
_dbContext.Dispose();
_dbContext = GetNew();
}

protected virtual TContext GetNew()
{
return new TContext();
}

public void Dispose()
{
_dbContext.Dispose();
}
}

Concrete Class (optional, since the base class is no longer abstract)

public class MainDbContextLocator : DbContextLocator<MainDbContext> { }


Related Topics



Leave a reply



Submit