Given the fact that you want to set an alternative connectionstring in the constructor, suggests that it is a known value.
The thing to solve is how to do this with DI. The first hint is the code that is generated when scaffolding the context:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
//#warning To protect potentially sensitive information in your
// connection string, you should move it out of source code.
// See http://go.microsoft.com/fwlink/?LinkId=723263 for guidance on storing connection strings.
optionsBuilder.UseSqlServer("Server=.\\SQLEXPRESS;Database=MyDb;Trusted_Connection=True;");
}
}
This means that you can either use the default configuration (optionsBuilder.IsConfigured), setting the value at startup. But also use an alternative on construction.
The code would then look like this:
public partial class MyContext : DbContext
{
private readonly string _connectionString;
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
public MyContext(IOptions<DbConnectionInfo> dbConnectionInfo)
{
_connectionString = dbConnectionInfo.Value.MyContext;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(_connectionString);
}
}
}
Where the helper class looks like this:
public class DbConnectionInfo
{
public string MyContext { get; set; }
}
Example appsettings.json:
"ConnectionStrings": {
"MyContext": "Server=.\\SQLEXPRESS;Database=MyDb;Trusted_Connection=True;"
},
And register both in startup:
services.Configure<DbConnectionInfo>(settings => configuration.GetSection("ConnectionStrings").Bind(settings));
services.AddScoped<MyContext>();
If you don't want to read the connectionstring from the configuration but rather set it depending on middleware (e.g. per tenant), then you can use the same approach.
Just update the value before the context is constructed.
Update:
With dependency injection you do not construct the objects yourself but you pass the registered object / service as a paramater. DI will figure out what objects need to be created in what order. In the same way the objects will be disposed by DI after use.
The fact that the controller 'knows' the context, is because DI adds it automatically as the parameter. The fact that the context 'knows' the DbConnectionInfo is because it is registered to DI.
If you want to change the DbConnectionInfo then you need to add it in a proper way. In your case you could do something like this:
// Added as part of the example
services.AddHttpContextAccessor();
// Replace registration with this line:
services.AddScoped<DbConnectionInfo>();
// Register the DbContext
services.AddScoped<MyContext>();
Where the alternative version of the class is:
public class DbConnectionInfo
{
public string MyContext { get; set; }
// Example injecting IHttpContextAccessor
// On creating this class DI will inject
// the HttpContextAccessor as parameter
public DbConnectionInfo(IHttpContextAccessor httpContextAccessor)
{
// Access the current request
var request = httpContextAccessor.HttpContext.Request;
// Access the current user (if authenticated)
var user = httpContextAccessor.HttpContext.User;
// Now you could get a value from a header, claim,
// querystring or path and use that to set the value:
MyContext = "";
}
}
And in the DbContext a small change, we don't use IOptions in this case:
public partial class MyContext : DbContext
{
private readonly string _connectionString;
public MyContext(DbConnectionInfo dbConnectionInfo)
{
_connectionString = dbConnectionInfo.MyContext;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(_connectionString);
}
}
}
Now on each request the value will be set prior to creating MyContext.