11

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.

Community
  • 1
  • 1
CuddleBunny
  • 1,941
  • 2
  • 23
  • 45

1 Answers1

10

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.

Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
  • This is a much more acceptable workaround than moving the entire namespace into the main project. Migration add worked perfect, hit a bump with apply: Since I was using a custom `DataDirectory` I had to do some context detection. When run from the command line `Path.GetFullPath("..\\MainProject")` gives me "C:\Path\To\My\Project\src\MainProject", but when running the actual project I get "C:\Program Files (x86)\MainProject" instead! So I added a check to see if the path contains the text "src" and it works now. This should also port to production since environment vars will overwrite it all. – CuddleBunny Jun 17 '15 at 14:20
  • I'll accept this as the answer until something less hacky comes along. – CuddleBunny Jun 17 '15 at 14:21
  • @CuddleBunny I'm glad it worked out and am looking forward to seeing something less hacky. Maybe someone from the EF team will contribute an answer. – Shaun Luttin Jun 17 '15 at 15:45
  • 3
    @ShaunLuttin with beta5 they changed the Config interface to require a base path, which I would usually get from IApplicationEnvironment.ApplicationBasePath. The problem is that when I add IApplicationEnvironment to the constructor, I get an error that there is no default constructor when I run "ef migration add". I wonder if it would be so bad to hardcode the path or the connection string. This all is very hacky and unfortunate though :( Who would put their context in the same project as web? Why such an oversight? – Ryan Langton Jul 22 '15 at 00:21
  • @RyanLangton for the BasePath value you can use relative path syntax @".\"; `var configurationBuilder = new ConfigurationBuilder(@".\", null);` or @"..\MyStartupProject\" for example. – ulty4life Jul 30 '15 at 22:51