I honestly cannot believe how hard this is...first off the requirements that I am going for:
IDesignTimeDbContex
I am a bit confused with your question. Are you using dependency injection for the DbContext
or are you trying to initialize and construct the context ad hoc?
I am doing what you have described in one of my solutions. Here is my solution structure:
Startup.cs
public Startup(IHostingEnvironment env)
{
IConfigurationBuilder builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json")
.AddEnvironmentVariables();
// ...
}
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<MyDbContext>(
options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
sqlOptions => sqlOptions.EnableRetryOnFailure()));
// SQL configuration for non-injected dbcontext
DbContextOptionsBuilder<MyDbContext> builder = new DbContextOptionsBuilder<MyDbContext>();
builder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
services.AddSingleton(builder.Options);
// ...
}
MyDbContext.cs
public class MyDbContext : IdentityDbContext<ApplicationUser>
{
public MyDbContext(DbContextOptions options) : base(options) { }
}
If you are not using dependency injection to pass the DbContext, you can access the SQL properties by injecting DbContextOptions<MyDbContext>
instead.
In this example, the appsettings file is only every read once and everything just works.
If you are looking for solution to get database connection string from your custom settings class initialized from appsettings.json
file - that is how you can do this. Unfortunatelly you can't inject IOptions
via DI
to your IDesignTimeDbContextFactory
implementation constructor.
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppContext>
{
public AppContext CreateDbContext(string[] args)
{
// IDesignTimeDbContextFactory is used usually when you execute EF Core commands like Add-Migration, Update-Database, and so on
// So it is usually your local development machine environment
var envName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
// Prepare configuration builder
var configuration = new ConfigurationBuilder()
.SetBasePath(Path.Combine(Directory.GetCurrentDirectory()))
.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile($"appsettings.{envName}.json", optional: false)
.Build();
// Bind your custom settings class instance to values from appsettings.json
var settingsSection = configuration.GetSection("Settings");
var appSettings = new AppSettings();
settingsSection.Bind(appSettings);
// Create DB context with connection from your AppSettings
var optionsBuilder = new DbContextOptionsBuilder<AppContext>()
.UseMySql(appSettings.DefaultConnection);
return new AppContext(optionsBuilder.Options);
}
}
Of course in your AppSettings
class and appsettings.json
you could have even more sophisticated logic of building the connection string. For instance, like this:
public class AppSettings
{
public bool UseInMemory { get; set; }
public string Server { get; set; }
public string Port { get; set; }
public string Database { get; set; }
public string User { get; set; }
public string Password { get; set; }
public string BuildConnectionString()
{
if(UseInMemory) return null;
// You can set environment variable name which stores your real value, or use as value if not configured as environment variable
var server = Environment.GetEnvironmentVariable(Host) ?? Host;
var port = Environment.GetEnvironmentVariable(Port) ?? Port;
var database = Environment.GetEnvironmentVariable(Database) ?? Database;
var user = Environment.GetEnvironmentVariable(User) ?? User;
var password = Environment.GetEnvironmentVariable(Password) ?? Password;
var connectionString = $"Server={server};Port={port};Database={database};Uid={user};Pwd={password}";
return connectionString;
}
}
With just values stored in appsettings.json
:
{
"Settings": {
"UseInMemory": false,
"Server": "myserver",
"Port": "1234",
"Database": "mydatabase",
"User": "dbuser",
"Password": "dbpassw0rd"
}
}
With password and user stored in environment variables:
{
"Settings": {
"UseInMemory": false,
"Server": "myserver",
"Port": "1234",
"Database": "mydatabase",
"User": "MY-DB-UID-ENV-VAR",
"Password": "MY-DB-PWD-ENV-VAR"
}
}
In this case you should use it this way:
// Create DB context with connection from your AppSettings
var optionsBuilder = new DbContextOptionsBuilder<AppContext>();
if(appSettings.UseInMemory) {
optionsBuilder = appSettings.UseInMemory
? optionsBuilder.UseInMemoryDatabase("MyInMemoryDB")
: optionsBuilder.UseMySql(appSettings.BuildConnectionString());
return new AppContext(optionsBuilder.Options);