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

19,606

Solution 1

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

Solution 2

Keeping an ObjectContext open for the lifetime of the application is generally a bad idea.

ObjectContext (or DbContext in this case) is for a Unit of Work.

See Entity Framework and Connection Pooling

Solution 3

Entity Framework Core 5.0 now has a ChangeTracker.Clear() method which does this. As mentioned in the documentation, it's still best to create and dispose DbContext instances as needed, but sometimes that's not practical.

DbContext is designed to have a short lifetime where a new instance is created for each unit-of-work. This manner means all tracked entities are discarded when the context is disposed at the end of each unit-of-work. However, clearing all tracked entities using this method may be useful in situations where creating a new context instance is not practical.

Share:
19,606

Related videos on Youtube

devuxer
Author by

devuxer

Updated on May 28, 2022

Comments

  • devuxer
    devuxer almost 2 years

    I recently refactored a WPF app so that it no longer wraps each use of the DbContext in a using clause (see this question). Instead, my app just uses the same DbContext singleton throughout.

    This works great except for one small problem. I have a routine that rebuilds the database from scratch and inserts some default data. This routine uses ADO.NET directly (not the DbContext), so the DbContext is unaware that the database is now completely different.

    Is there a method to reset the DbContext without disposing it? I'd like to avoid disposing if possible because this would break several references to the original singleton throughout the app.

  • devuxer
    devuxer about 13 years
    Thanks for your answer. Please see my response to your comment here: stackoverflow.com/questions/5533917/….
  • Shimmy Weitzhandler
    Shimmy Weitzhandler almost 12 years
    I would say make the DbContextLocator a generic class, i.e. DbContextLocator<TContext> where TContext : DbContext, then you don't have to make a derived class, or you can make the derived class just to reduce verbosity without declaring anything in it, see example here
  • devuxer
    devuxer almost 12 years
    Good idea...I guess I never optimized to that point because I only tend to write one of the concrete classes per project.
  • Shimmy Weitzhandler
    Shimmy Weitzhandler almost 12 years
    Once you get used to generics, this is not an optimization, this is default code design :)
  • devuxer
    devuxer almost 12 years
    I use them quite a bit actually, just didn't occur to me at the time I wrote this.
  • devuxer
    devuxer almost 12 years
    Just got around to making my locator class generic. Since you already have new() in the where clause, is there any reason to prefer Activator.CreateInstance<TContext>() over simply new TContext()?
  • devuxer
    devuxer almost 12 years
    I just noticed an issue with making the class generic. I had some dependencies on the original base class that couldn't know what TContext was until runtime. I thought I could just make an interface IDbContextLocator with a DbContext Current { get; } property, but then the generic class has to return DbContext rather than TContext, forcing users of the generic class to perform a cast. The only solution I can think of is to go back to having the concrete class define its own Current property that performs the cast, but this makes the generic class nearly useless.
  • devuxer
    devuxer almost 12 years
    Ahh, figured it out, I just needed to use an explicit interface implementation for Current. Code: DbContext IDbContextLocator.Current { get { return _dbContext; } }. Sorry for all the comments :) (I'm still curious about Activator vs. new(), though.)
  • Shimmy Weitzhandler
    Shimmy Weitzhandler almost 12 years
    Regarding the Activator.CreateInstance<T>, you'd be better of using new TContext(), Activator.CreateInstance<T> uses reflection to call the first constructor. Check this post for more.
  • Raven
    Raven over 5 years
    I don't know if this still is true just look at EF Core thay now pool the DbContext to improve performance!
  • Gregory Ledray
    Gregory Ledray almost 2 years
    There is also context.tableName.Local.Clear() which performs this operation on a single table. I found this to be very helpful when executing raw SQL with context.Database.ExecuteSqlRaw() and then wanting to reset the affected table's change tracker.