What goes into DbContextOptions when invoking a new DbContext?

Entity Frameworkasp.net CoreEntity Framework-Core

Entity Framework Problem Overview


I am not using DI and simply want to invoke a DbContext from within my controller. I am struggling to figure out what the 'options' should be?

ApplicationDbContext.cs

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{

    public DbSet<Gig> Gigs { get; set; }
    public DbSet<Genre> Genres { get; set; }


    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);
    }
}

GigsController.cs

    public class GigsController : Controller
{
    private ApplicationDbContext _context;

    public GigsController()
    {
        _context = new ApplicationDbContext();
    }


    public IActionResult Create()
    {
        var viewModel = new GigFormViewModel
        {
            Genres = _context.Genres.ToList()
        };


        return View(viewModel);
    }
}

The issue is stemming in my GigsController constructor:

_context = new ApplicationDbContext();

I am erroring out because I need to pass something into the ApplicationDbContext. There is no argument given that corresponds to the required formal parameter 'options' of 'ApplicationDbContext.ApplicationDbContext(DbContextOptions)'

I tried creating a default constructor in ApplicationDbContext derived from base(), but that didn't work either.

In my startup.cs, I have configured the ApplicationDbContext

        public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        services.AddMvc();

        // Add application services.
        services.AddTransient<IEmailSender, AuthMessageSender>();
        services.AddTransient<ISmsSender, AuthMessageSender>();
    }

Entity Framework Solutions


Solution 1 - Entity Framework

If you really want to create the context manually, then you can configure it like this:

var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseSqlServer(Configuration.GetConnectionStringSecureValue("DefaultConnection"));
_context = new ApplicationDbContext(optionsBuilder.Options); 

(The DbContextOptionsBuilder<ApplicationDbContext> class is the type of options argument in services.AddDbContext<ApplicationDbContext>(options =>). But in the controller, you don't have access to Configuration object, so you would have to expose it as a static field in Startup.cs or use some other trick, which is all bad practice.

The best way to obtain ApplicationDbContext is to get it through DI:

public GigsController(ApplicationDbContext context)
{
    _context = context;
}

The DI container will take care of instantiating and disposing of ApplicationDbContext. Note that you have everything correctly configured in Startup.cs:

services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

That's configuring DI, so why not just use it?

One more note about the default constructor of DbContext: In EF6 it was done like this: public ApplicationDbContext(): base("DefaultConnection") {}. Then the base object would use System.Configuration.ConfigurationManager static class to obtain the connection string named DefaultConnection from web.config. The new Asp.net Core and EF Core is designed to be as much decoupled as possible, so it should not take dependencies on any configuration system. Instead, you just pass a DbContextOptions object - creating that object and configuring it is a separate concern.

Solution 2 - Entity Framework

This is how I would do it:

public class GigsController : Controller
{
    private readonly IConfiguration _configuration;
    private string _connectionString;
    DbContextOptionsBuilder<ApplicationDbContext> _optionsBuilder;

    public GigsController (IConfiguration configuration)
    {
        _configuration = configuration;
        _optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
        _connectionString = _configuration.GetConnectionString("DefaultConnection");
        _optionsBuilder.UseSqlServer(_connectionString);
    }

    public IActionResult Index()
    {
        using(ApplicationDbContext _context = new ApplicationDbContext(_optionsBuilder.Options))
        {
             // .....Do something here
        }
    }
}

Recently I was migrating a very large dataset into database(around 10 million) and one context instance will quick eat up all my memory. Thus I had to create a new Context instance and dispose the old one after certain threshold to release memory.

This is not an elegant solution but worked for me.

Solution 3 - Entity Framework

I personally don't understand why you wouldn't want to use DI and just let it be created on your behalf by specifying (ApplicationDbContext db) in your controller's constructor, in fact you are registering it in DI with your code anyway:

services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

If you absolutely want to call new ApplicationDbContext(...) explicitly, keep in mind that it's a bad idea to call Configuration within your controller, but you need the configuration in order to get the connection details as you must provide DbContextOptions as an argument to the context. I suggest removing the services.AddDbContext altogether because are never intending on resolving it. Built in DI doesn't seem to have a clean way of doing "factory" registration. I use Autofac and Simple Injector which provide really clean ways in a lambda expression to do it along these lines:

containerBuilder.Register(c =>
{
	var optionsBuilder = new DbContextOptionsBuilder<EntityDbContext>()
	.UseSqlServer(Configuration.GetConnectionStringSecureValue("DefaultConnection"));

	return optionsBuilder.Options;
});

You would then simply do this:

public GigsController(DbContextOptionsBuilder<EntityDbContext> dbContextOptions)
{
    _context = new ApplicationDbContext(dbContextOptions);
}

So if you were to integrate Autofac that's one way.

I've just managed to get my head around all this injection stuff and configuration and have a nice clean solution which would solve your issue including reading configuration. The idea is you read the configuration from appsettings.json the assign to a connection string property on a configuration class:

public interface IDatabaseConfig
{
    string ConnectionString { get; set; }
}

public class DatabaseConfig : IDatabaseConfig
{
    public DatabaseConfig()
    {
        IConfiguration configuration = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .Build();

        ConnectionString = configuration.GetSection("Database").GetValue<string>("ConnectionString");
    }

    public string ConnectionString { get; set; }

}

then you register the interface in ConfigureServices:

services.AddTransient<IDatabaseConfig, DatabaseConfig>();

and use the interface as a controller constructor argument then you can create the options:

public GigsController(IDatabaseConfig dbConfig)
{
    var dbContextOptions = new DbContextOptions<ApplicationDbContext>().UseSqlServer(dbConfig.ConnectionString);
    _context = new ApplicationDbContext(dbContextOptions);
}

I'm not a fan of creating the configuration builder directly on that class. ASP.NET Core provides one already and all this should be done in the Startup class. Best practice would be to deserialise the DatabaseConfig from the appsettings with:

var databaseConfig = configuration.GetSection("Database").Get<DatabaseConfig>();

but as I can't see a way to then register that instance or defer that to a DI factory style registration it's not an option.

Really you're simply better off using serices.AddDbContext<ApplicationDbContext>(...) like you originally had and inject it as a constructor argument on your controller and the problem is soved.

The way I personally tackle this whole scenario giving freedom to configure the options as you like as well as even switch out the SqlServer connection for an in memory db for running integration tests where you don't have access to a real db as part on a CI build, is as follows...

I have a DatabaseConfig object graph:

public class Config
{
    public DatabaseConfig Database { get; set; }

}

public interface IDatabaseConfig
{
    InMemoryConfig InMemory { get; set; }
    string ConnectionString { get; set; }
}

public class DatabaseConfig : IDatabaseConfig
{
    public InMemoryConfig InMemory { get; set; }
    public string ConnectionString { get; set; }

}

public class InMemoryConfig
{
    public bool Enabled { get; set; }
    public string Name { get; set; }

}

which is aligned with this structure and deserialized from appsettings.json:

"Database": {
    "InMemory": {
      "Enabled": true,
      "Name": "Snoogans"
    },
    "ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=SomeDb;Trusted_Connection=True;"
  }

An out of the box option is to do this

var databaseConfig = configuration.GetSection("Database").Get<DatabaseConfig>();

but I use Autofac and there's a sweet nuget package called Divergic.Configuration.Autofac which allows you to do this in the ConfigureContainer method:

builder.RegisterModule<ConfigurationModule<JsonResolver<Config>>>();

If a property on the Config graph implements an interface then a registration is made with Autofac with the settings deserialized onto the service instance. That in itself is enough to inject IDatabaseConfig as a constructor argument on any controller and you can then new it up yourself, but it's really best done in one place otherwise you have to keep repeating the DbContextOptionsBuilder logic everywhere yo use it.

So I follow the ConfigurationModule registration with a factory registration which creates my db context with the options from the appsettings.json:

containerBuilder.Register(c =>
{
    var optionsBuilder = new DbContextOptionsBuilder<EntityDbContext>();
    optionsBuilder = databaseConfig.InMemory.Enabled
        ? optionsBuilder.UseInMemoryDatabase(databaseConfig.InMemory.Name)
        : optionsBuilder.UseSqlServer(databaseConfig.ConnectionString);

        return optionsBuilder.Options;
});

This is a clean solution and responsibilities don't leak into areas they shouldn't. Your controller shouldn't be responsible for database ORM creation. It should just be given one pre-created to use otherwise it's too hard to change later. Consider if you have 500 controllers where you manually create them in all cases vs passing in a pre-created instance where the creation code is done in one place. Take it one step further where my db context implements IWriteEntities and IReadEntities then it's even more abstract and you could switch in another whole DbContext subclass and you simplify the re-work to a single line where you register the db context.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRezaView Question on Stackoverflow
Solution 1 - Entity FrameworkqbikView Answer on Stackoverflow
Solution 2 - Entity FrameworkAvenView Answer on Stackoverflow
Solution 3 - Entity FrameworkStephen YorkView Answer on Stackoverflow