ASP.NET Core run migration for a database context that reads from the configuration file (appsettings.json)

1.9k views Asked by At

I am toying with ASP.NET Core 1.1 application and I am trying to read connection string from the configuration file.

My database context class looks like this:

public class ApplicationDbContext : DbContext, IDbContextFactory<ApplicationDbContext>
{
    public ApplicationDbContext()
    {
    }

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

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
    }

    public ApplicationDbContext Create(DbContextFactoryOptions options)
    {
        var builder = new DbContextOptionsBuilder<ApplicationDbContext>();

        string connString = "some hardcoded connection string";
        builder.UseSqlServer(connString);
        return new ApplicationDbContext(builder.Options);
    }
}

Running migrations works fine (e.g. dotnet ef database update).

If I want to remove the hardcoded connection string, I am facing the following problem:

  • if I try to inject IConfiguration into ApplicationDbContext, migration will fail with No parameterless constructor defined for this object.
  • without injecting something I cannot find a way to read information from the configuration file

How should I proceed?

Thanks.

[EDIT]

In Startup.cs I have configured the connection string and it works correctly (runtime):

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    //services.AddApplicationInsightsTelemetry(Configuration);

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

    services.AddMvc();
}

The problem is that dotnet migrations seems to require a parameterless constructor in database context and to implement IDbContextFactory<> and I do not know how to inject the configuration.

Edit after trying IOptions<> with ApplicationDbContext

I have tried suggested solution, but I cannot make it work because dotnet migration will call the default constructor.

public class ApplicationDbContext : DbContext, IDbContextFactory<ApplicationDbContext>
{
    private IOptions<DefaultConnectionValue> ConnOptions;
    private string connectionString;

    public ApplicationDbContext()
    {
        Console.WriteLine("Default constructor was called");
    }

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IOptions<DefaultConnectionValue> connOptions) : base(options)
    {
        ConnOptions = connOptions;
        connectionString = connOptions.Value.Value;

        Console.WriteLine($"Injected connection string = {connectionString}");
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
    }

    public ApplicationDbContext Create(DbContextFactoryOptions options)
    {
        var builder = new DbContextOptionsBuilder<ApplicationDbContext>();

        //string connString = ConfigurationExtensions.GetConnectionString(null, "DefaultConnection");
        builder.UseSqlServer(connectionString);
        return new ApplicationDbContext(builder.Options, ConnOptions);
    }
1

There are 1 answers

2
Dawid Rutkowski On BEST ANSWER

I was facing the same problem and I resolved it in the following way:

  1. Register options with the connection string in the service collection
  2. Inject options into DBContext
  3. User connection string in the DBContext parameterless constructor

Thanks to that you will have parameterless constructor required by migrations.

Here is how you can register configuration settings:

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        // Set up configuration sources.
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); //let's assume that here you have your connection string

        Configuration = builder.Build();
    }

    public IConfigurationRoot Configuration { get; set; }

    public void ConfigureServices(IServiceCollection services)
    {
        // Adds services required for using options.
        services.AddOptions();

        // Register the IConfiguration instance which MyOptions binds against.
        services.Configure<MyOptions>(Configuration);

        ...
    }
}

And then you can use it as fallow (it's controller example but you can do the same with the DBContext):

public class HomeController : Controller
{
    private readonly MyOptions _optionsAccessor;

    public HomeController(IOptions<MyOptions> optionsAccessor)
    {
        //injection of your configuration object with connection string 
        _optionsAccessor = optionsAccessor.Value;
    }

    public IActionResult Index()
    {
        var option1 = _optionsAccessor.Option1;
        var option2 = _optionsAccessor.Option2;
        return Content($"option1 = {option1}, option2 = {option2}");
    }
}

For more informations about usage of configuration settings take a look here: Configuration.

UPDATE:

You can also try different approach - read configuration inside of the DBContext constructor:

public class ApplicationDbContext : DbContext, IDbContextFactory<ApplicationDbContext>
{
    private string connectionString;

    public ApplicationDbContext()
    {
        var builder = new ConfigurationBuilder().AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); //let's assume that here you have your connection string
        var configuration = builder.Build();

        connectionString = ""; //FILL THE CONNECTION STRING FROM THE CONFIGURATION OBJECT
    }

    ...
}