2

I would like to configure the connection string from the controller, but I don't find any function to do it.

I have:

public class tstController : Controller
{
    private readonly contextTst _context;

    public tstController(contextTst context)
    {
        _context = context;
        _context.Database.**????**
    }
}

It's possible?

Thanks a lot!

Tasos K.
  • 7,979
  • 7
  • 39
  • 63
user3682831
  • 45
  • 1
  • 1
  • 5

3 Answers3

7

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.

  • To update the value before the context is constructed, from the controler I'm trying: DbConnectionInfo conInfo = new DbConnectionInfo(); conInfo.MyContext = ; _context = new contextTst(conInfo); But I get the error for the constructor of conInfo: "Argument 1: Unable to convert..." T'hanks a lot, – user3682831 Feb 03 '19 at 08:45
  • @user3682831 You need to use Dependency Injection in order to create the new object. Do not use the `new` keyword here. I've updated the answer. –  Feb 03 '19 at 19:42
  • T'hanks, but the constructor "MyContext(IOptions dbConnectionInfo)" gives me the error: "DbConnectionInfo must be non-abstract type with a public parameterless constructor...", so I can't compile. – user3682831 Feb 05 '19 at 20:51
  • @user3682831 I've updated the answer. I'd forgotten to update the MyContext class. IOptions is used in the first part of the answer, because services.Configure uses IOptions. But in the second part DbConnectionInfo is registered without IOptions. –  Feb 06 '19 at 01:57
4

You should register DbContext during startup only.

There only you should specify the connection string.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnectionString")));

    services.AddMvc();
}

This is very important if you are using dependency injection (from MSDN):

Entity Framework contexts Entity Framework contexts should be added to the service container using the scoped lifetime. This is handled automatically with a call to the AddDbContext method when registering the database context. Services that use the database context should also use the scoped lifetime.

Hope this helps.

Manoj Choudhari
  • 5,277
  • 2
  • 26
  • 37
0

.Net Core 6

 public MyContext(DbContextOptions<MyContext> options)
        : base(options)
    {
        SqlServerOptionsExtension sqlServerOptionsExtension = (SqlServerOptionsExtension)options.Extensions.FirstOrDefault(p => p is SqlServerOptionsExtension);
        if (sqlServerOptionsExtension != null)
        {
            _connectionString = sqlServerOptionsExtension.ConnectionString;
        }
    }
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103