Entity Framework 7 Migration Scaffolding in Class Library with Configuration

asp.net-core code-first-migrations dnx entity-framework-core visual-studio-2015

Question

Trying to add migrations to an EF7 Model that lives in an ASP.NET 5 class library. When running dnx . ef migration add mymigration fails with different results depending on which project I run it on.

If I run it in the folder of the main project, it cannot locate a DbContext, this makes sense because the DbContext is in the shared project and ef commands likely don't care about dependencies.

If I run it in the folder of the shared project, it does not have access to the connection string specified in the startup.cs. I have gleaned from questions like this that it does work from the shared project if you specify the connection string in the OnConfiguring method of the DbContext but I really would like to keep this code separate from the configuration.

I came across some issue logs in the EF7 repository that mention they implemented command line options for specifying a project and a context but there are no examples and I couldn't figure out how to use it from looking at the source code in the commit history.

Accepted Answer

Here is an approach that might work for you.

If I run it in the folder of the shared project, it does not have access to the connection string specified in the startup.cs.

Startup.cs

I'm assuming that in your Startup.cs, you're specifying the connection string by accessing Configuration rather than by hard coding it. Further, I'm assuming that in your Startup.cs file's constructor, you're setting up configuration from a few sources. In other words, your Startup.cs might look something like this:

public class Startup
{
    public IConfiguration Config { get; set; }

    public Startup(IHostingEnvironment env)
    {
        var config = new Configuration()
            .AddJsonFile("config.json")
            .AddUserSecrets()
            .AddEnvironmentVariables();

        Config = config;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddEntityFramework()
            .AddSqlServer()
            .AddDbContext<MyDbContext>(options =>
            {
                options.UseSqlServer(Config["ConnectionStrings:MyDbContext"]);
            });
    }

    public void Configure(IApplicationBuilder app, IServiceProvider serviceProvider)
    {
        var db = serviceProvider.GetRequiredService<MyDbContext>();
        db.Database.AsSqlServer().EnsureCreated();

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
}

config.json

Further, I'm assuming that you're adding the connection string to a config.json in the root of your project (or that you adding it via user secrets or environmental variables.) Your config.json might look something like this:

{
  "ConnectionStrings": {
    "MyDbContext": "Some-Connection-String"
  }
}

If you're not doing it that way, it might be worth trying it.

I have gleaned from questions like this that it does work from the shared project if you specify the connection string in the OnConfiguring method of the DbContext but I really would like to keep this code separate from the configuration.

DbContext

If my assumptions above are correct, then you can access the connection string in the DbContext by using the same pattern that you used in the Startup class. That is, in the DbContext constructor, setup the IConfiguration. Then, in OnConfiguring, access the connection string. It might look something like this:

public class MyDbContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<SomeModel>().Key(e => e.Id);
        base.OnModelCreating(builder);
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var connString = Config["ConnectionStrings:MyDbContext"];
        optionsBuilder.UseSqlServer(connString);
    }

    public IConfiguration Config { get; set; }

    public MyDbContext()
    {
        var config = new Configuration()
            .AddJsonFile("config.json")
            .AddEnvironmentVariables();

        Config = config;
    }
}

Project Structure

You'll of course need to have a config.json file in the root of your shared projects folder too. So, your projects' structure could look something like this:

SharedDataContext
    Migrations
    config.json
    project.json

WebApp
    config.json
    project.json
    Startup.cs

In the above, both config.json files contain a connection string setting for the DbContext.

Some Thoughts

If you don't like duplicating the config.json connection string stuff, then you can use environmental variables or user secrets instead.



Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why