11

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.

Community
  • 1
  • 1
devuxer
  • 39,573
  • 46
  • 163
  • 281

2 Answers2

7

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> { }
devuxer
  • 39,573
  • 46
  • 163
  • 281
  • I would say make the `DbContextLocator` a generic class, i.e. `DbContextLocator 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](http://pastebin.com/KRqQvAie) – Shimmy Weitzhandler May 03 '12 at 22:06
  • Good idea...I guess I never optimized to that point because I only tend to write one of the concrete classes per project. – devuxer May 03 '12 at 22:28
  • Once you get used to generics, this is not an optimization, this is default code design :) – Shimmy Weitzhandler May 03 '12 at 22:55
  • I use them quite a bit actually, just didn't occur to me at the time I wrote this. – devuxer May 03 '12 at 22:59
  • 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()` over simply `new TContext()`? – devuxer May 04 '12 at 18:21
  • 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 May 04 '12 at 18:56
  • 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.) – devuxer May 04 '12 at 19:07
  • Regarding the `Activator.CreateInstance`, you'd be better of using `new TContext()`, `Activator.CreateInstance` uses reflection to call the first constructor. Check [this](http://stackoverflow.com/a/1649100/75500) post for more. – Shimmy Weitzhandler May 05 '12 at 19:36
5

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

Community
  • 1
  • 1
Ian Mercer
  • 35,804
  • 6
  • 87
  • 121
  • Thanks for your answer. Please see my response to your comment here: http://stackoverflow.com/questions/5533917/how-do-you-minimize-the-performance-hit-when-upgrading-to-ef-4-1-from-linq-to-sql/5632059#5632059. – devuxer Apr 13 '11 at 07:26
  • I don't know if this still is true just look at EF Core thay now pool the DbContext to improve performance! – Peter Oct 20 '18 at 13:32