8

I'm attempting to use migrations EFCore2.0 in a .NET standard 2.0 class library, and so far I have something like

public class SomeContextFactory : IDesignTimeDbContextFactory<SomeContext>
{
    private Configuration _configuration;

    public SomeContextFactory(Configuration configuration)
    {
        _configuration = configuration;
    }

    public SomeContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder<SomeContext>();

        optionsBuilder.UseSqlServer(_configuration.ConnectionString);

        return new SomeContext(optionsBuilder.Options);
    }
}

public class SomeContext : DbContext
{
    public DbSet<SomeDbModel> Some { get; set; }

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

The point is that the connection string is different depending on the environment (dev,test,prod), and migrations should be performed on the database specified by the Configuration.

How do instruct migrations to inject Configuration into SomeContextFactory?

kasperhj
  • 10,052
  • 21
  • 63
  • 106

2 Answers2

8

By design, the IDesignTimeDbContextFactory isn't supposed to work with DI. One way to achieve this would be:

public class AppDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
  public AppDbContext CreateDbContext(params string[] args)
  {
    var options = new DbContextOptionsBuilder<AppDbContext>();
    var config = GetAppConfiguration();
    options.UseSqlServer(config.GetConnectionString("DesignTimeAppDbConnection"));

    return new AppDbContext(options.Options);
  }

  IConfiguration GetAppConfiguration()
  {
    var environmentName =
              Environment.GetEnvironmentVariable(
                  "ASPNETCORE_ENVIRONMENT");

    var dir = Directory.GetParent(AppContext.BaseDirectory);      

    if(EnvironmentName.Development.Equals(environmentName, 
        StringComparison.OrdinalIgnoreCase))
    {                  
      var depth = 0;
      do
        dir = dir.Parent;
      while (++depth < 5 && dir.Name != "bin");
      dir = dir.Parent;
    }

    var path = dir.FullName;

    var builder = new ConfigurationBuilder()
            .SetBasePath(path)
            .AddJsonFile("appsettings.json")
            .AddJsonFile($"appsettings.{environmentName}.json", true)
            .AddEnvironmentVariables();

    return builder.Build();
  }
}
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
  • Instead of going up from the basedirectory to the parent of the bin dir to find your appsettings, doesn't it make more sense to include the appsettings in the bin to avoid that logic by setting them as EmbeddedResource and CopyToOuptuDirectory: always as explained here: https://stackoverflow.com/questions/38178340/how-can-i-ensure-that-appsettings-dev-json-gets-copied-to-the-output-folder? Or is there a reason not to do that? – Brecht De Rooms Feb 17 '22 at 11:04
-6

In Startup.cs

services.AddTransient<SomeContextFactory>();

And your factory:

public class SomeContextFactory : 
IDesignTimeDbContextFactory<SomeContext>
{
    private readonly IHostingEnvironment environment;
    private readonly IConfigurationRoot config;

    public SomeContextFactory(IConfigurationRoot config, 
IHostingEnvironment environment)
    {
        this.environment = environment;
        this.config = config;
    }

    public SomeContext CreateDbContext()
    {
        return CreateDbContext(null);
    }

    public SomeContext CreateDbContext(string[] args)
    {
        var builder = new DbContextOptionsBuilder<SomeContext>();
        var connectionString = config.GetConnectionString("Default");

        builder.UseSqlServer(connectionString);

        return new SomeContext(builder.Options);
    }
}
  • 4
    Not working for me. I get a 'No parameterless constructor defined for this object.' error when running code migration's 'Update-Database' command – Wade Bee May 21 '18 at 16:12