在Asp.Net Core中动态更改连接字符串

时间:2016-04-23 20:46:59

标签: c# asp.net asp.net-core entity-framework-core

我想在控制器中更改sql连接字符串,而不是在ApplicationDbContext中。我使用的是Asp.Net Core和Entity Framework Core。

例如:

public class MyController : Controller {
    private readonly ApplicationDbContext _dbContext
    public MyController(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }
    private void ChangeConnectionString()
    {
    // So, what should be here?
    } }

我该怎么做?

9 个答案:

答案 0 :(得分:10)

我可以通过将连接字符串逻辑移动到DbContext的OnConfiguring方法来更改每个请求的连接字符串。

Startup.cs#ConfigureServices方法中: services.AddDbContext<MyDbContext>();

在MyDbContext.cs中,我添加了我需要注入构造函数的服务。

    private IConfigurationRoot _config;
    private HttpContext _httpContext;

    public MyDbContext(DbContextOptions options, IConfigurationRoot config, IHttpContextAccessor httpContextAccessor) 
          : base(options)
    {
        _config = config;
        _httpContext = httpContextAccessor.HttpContext;
    }

然后覆盖OnConfiguring:

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var connString = BuildConnectionString(); // Your connection string logic here

        optionsBuilder.UseSqlServer(connString);
    }

答案 1 :(得分:8)

如果您想根据活动的http请求参数为每个http请求选择一个连接字符串,这就足够了。

    using Microsoft.AspNetCore.Http;

    //..

    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

    services.AddDbContext<ERPContext>((serviceProvider, options) =>
        {
            var httpContext = serviceProvider.GetService<IHttpContextAccessor>().HttpContext;
            var httpRequest = httpContext.Request;
            var connection = GetConnection(httpRequest);
            options.UseSqlServer(connection);
        });

您无法轻易访问用户,也无法获得他的声明。除非你手动完成。

How to extract and get a claim from token?

答案 2 :(得分:7)

我们有一个类似于你的案例。我们所做的是使用 Startup ConfigureServices 方法中 IServiceCollection implementationfactory 重载上课,像这样:

//First register a custom made db context provider
services.AddTransient<ApplicationDbContextFactory>();
//Then use implementation factory to get the one you need
services.AddTransient(provider => provider.GetService<ApplicationDbContextFactory>().CreateApplicationDbContext());

现在我很难为你实现CreateApplicationDbContext,因为它完全取决于你想要什么。但是,一旦你认为你要完全按照自己的意愿去做,那么该方法的基础知识应该是这样的:

public ApplicationDbContext CreateApplicationDbContext(){
  //TODO Something clever to create correct ApplicationDbContext with ConnectionString you need.
} 

实现此功能后,您可以像在构造函数中一样在控制器中注入正确的ApplicationDbContext:

public MyController(ApplicationDbContext dbContext)
{
    _dbContext = dbContext;
}

或控制器中的操作方法:

public IActionResult([FromServices] ApplicationDbContext dbContext){
}

然而,您实现了详细信息,诀窍是实现工厂将在每次注入时构建ApplicationDbContext。

告诉我您是否需要更多帮助来实施此解决方案。

更新#1 Yuriy N.询问了AddTransient和AddDbContext之间的区别,这是一个有效的问题......而且它不是。让我解释一下。

这与原始问题无关。

但是......话虽如此,实施自己的实施工厂&#39; (关于我的答案,这是最重要的注意事项)在这种情况下,实体框架可能比我们需要的更棘手。

但是,有了这些问题,我们现在可以幸运地查看GitHub中的源代码,所以我查看了AddDbContext的确切内容。好吧......这并不难。这些&#39;添加&#39; (和&#39;使用&#39;)扩展方法只不过是方便的方法,请记住。因此,您需要添加AddDbContext所做的所有服务以及选项。也许你甚至可以重用AddDbContext扩展方法,只需用实现工厂添加你自己的重载。

所以,回到你的问题。 AddDbContext执行一些EF特定的东西。正如您所看到的,它们将允许您在以后的版本中使用一生(瞬态,单例)。 AddTransient是Asp.Net Core,允许您添加所需的任何服务。而且你需要一个实施工厂。

这是否更清晰?

答案 3 :(得分:2)

那对我有用:

public void ConfigureServices(IServiceCollection services)
{
    // .....
    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    services.AddTransient<School360DbContext>(provider =>
    {
        return ResolveDbContext(provider, hostingEnv);
    });
    // ..
}

private MyDbContext ResolveDbContext(IServiceProvider provider, IHostingEnvironment hostingEnv)
{
    string connectionString = Configuration.GetConnectionString("DefaultConnection");

    string SOME_DB_IDENTIFYER = httpContextAccessor.HttpContext.User.Claims
        .Where(c => c.Type == "[SOME_DB_IDENTIFYER]").Select(c => c.Value).FirstOrDefault();
    if (!string.IsNullOrWhiteSpace(SOME_DB_IDENTIFYER))
    {
        connectionString = connectionString.Replace("[DB_NAME]", $"{SOME_DB_IDENTIFYER}Db");
    }

    var dbContext = new DefaultDbContextFactory().CreateDbContext(connectionString);

    // ....
    return dbContext;
}

答案 4 :(得分:2)

@ginalx和@jcmordan的答案非常适合我的用例。我喜欢这些答案的地方是,我可以在Startup.cs中完成所有操作,并保持所有其他类的构造代码清洁。我想为Web Api请求提供一个可选的querystring参数,并将其替换为创建DbContext的基本连接字符串。我将基本字符串保留在appsettings.json中,并根据传入的参数或如果未提供默认值的默认值对其进行格式化,即:

"IbmDb2Formatted": "DATABASE={0};SERVER=servername;UID=userId;PWD=password"

对我来说,最终ConfigureServices方法看起来像(观察。我正在连接到DB2而不是SQL,但这是偶然的):

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();

        services.AddDbContext<Db2Context>(((serviceProvider, options) =>
        {
            var httpContext = serviceProvider.GetService<IHttpContextAccessor>().HttpContext;
            var httpRequest = httpContext.Request;

            // Get the 'database' querystring parameter from the request (if supplied - default is empty).
           // TODO: Swap this out for an enum.
            var databaseQuerystringParameter = httpRequest.Query["database"].ToString();

            // Get the base, formatted connection string with the 'DATABASE' paramter missing.
            var db2ConnectionString = Configuration.GetConnectionString("IbmDb2Formatted");

            if (!databaseQuerystringParameter.IsNullOrEmpty())
            {
                // We have a 'database' param, stick it in.
                db2ConnectionString = string.Format(db2ConnectionString, databaseQuerystringParameter);
            }
            else
            {
                // We havent been given a 'database' param, use the default.
                var db2DefaultDatabaseValue = Configuration.GetConnectionString("IbmDb2DefaultDatabaseValue");
                db2ConnectionString = string.Format(db2ConnectionString, db2DefaultDatabaseValue);
            }

            // Build the EF DbContext using the built conn string.
            options.UseDb2(db2ConnectionString, p => p.SetServerInfo(IBMDBServerType.OS390));
        }));

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new Info
            {
                Title = "DB2 API",
                Version = "v1"
            });
        });
    }

答案 5 :(得分:1)

虽然晚了,但是EF Core中最简单的技巧是使用nuget Microsoft.EntityFrameworkCore.Relational

_dbContext.Database.GetDbConnection().ConnectionString = "NEW_CONN_STRING";

当由于任何原因在您的应用程序配置/设置中不存在连接字符串,或者您想使用一个DbContext实例(同样由于任何原因)处理具有相同结构的多个数据库时,这很有用。

永久临时取决于您为DbContext选择的注入生命周期的类型。如果您将其作为 Singleton 服务注入,则将是永久,建议

答案 6 :(得分:1)

我寻求这种解决方案:

代替

services.AddScoped<IMyDbContext, MyDbContext>();

我去了

services.AddTransient<IMyDbContext, MyDbContext>(resolver =>
{
    var context= resolver.GetService<MyDbContext>();
    var config = resolver.GetService<IConfiguration>();
    var connectionString = config.GetConnectionString("MyDb");
    context.GetDbConnection().ConnectionString = connectionString;
    return context;
});

在运行时覆盖设置:

Configuration["ConnectionStrings:MyDb"] = newConnectionString;

答案 7 :(得分:0)

我知道其他人已经回答了。但是,我想与那些想在运行时更改数据库连接字符串的人分享我的方法。

我的应用程序是使用 asp.net core 2.2 Entity Framework MySql

StartUp.cs

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddDbContext<MyDbContext>();

    ...

MyDbContext类

public partial class MyDbContext : DbContext
{
    public MyDbContext()
    {
    }

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

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (DbManager.DbName != null && !optionsBuilder.IsConfigured)
        {
            var dbName = DbManager.DbName;
            var dbConnectionString = DbManager.GetDbConnectionString(dbName);
            optionsBuilder.UseMySql(dbConnectionString);
        }
    }

    ...

Json -具有连接信息的文件

[
  {
    "name": "DB1",
    "dbconnection": "server=localhost;port=3306;user=username;password=password;database=dbname1"
  },
  {
    "name": "DB2",
    "dbconnection": "server=localhost;port=3306;user=username;password=password;database=dbname2"
  }
]

DbConnection类

public class DbConnection
{
    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("dbconnection")]
    public string Dbconnection { get; set; }

    public static List<DbConnection> FromJson(string json) => JsonConvert.DeserializeObject<List<DbConnection>>(json, Converter.Settings);
}

DbConnectionManager类

public static class DbConnectionManager
{
    public static List<DbConnection> GetAllConnections()
    {
        List<DbConnection> result;
        using (StreamReader r = new StreamReader("myjsonfile.json"))
        {
            string json = r.ReadToEnd();
            result = DbConnection.FromJson(json);
        }
        return result;
    }

    public static string GetConnectionString(string dbName)
    {
        return GetAllConnections().FirstOrDefault(c => c.Name == dbName)?.Dbconnection;
    }
}

DbManager类

public static class DbManager
{
    public static string DbName;

    public static string GetDbConnectionString(string dbName)
    {
        return DbConnectionManager.GetConnectionString()dbName;
    }
}

然后,您将需要一些控制器来设置 dbName

控制器类

[Route("dbselect/{dbName}")]
public IActionResult DbSelect(string dbName)
{
    // Set DbName for DbManager.
    DbManager.DbName = dbName;

    dynamic myDynamic = new System.Dynamic.ExpandoObject();
    myDynamic.DbName = dbName;
    var json = JsonConvert.SerializeObject(myDynamic);
    return Content(json, "application/json");
}

您可能需要在这里和那里做一些技巧。但您会得到想法。在应用程序的开头,它没有连接详细信息。因此您必须使用Controller对其进行显式设置。希望这会帮助某人。

答案 8 :(得分:0)

用于静态连接的Startup.cs

customElements.define('user-card', class extends HTMLElement {
  connectedCallback() {
    this.attachShadow({mode: 'open'})
        .innerHTML = `
      <style>
        div.name {
          font-size: 24px;
          width: 200px;
          background-color: yellow;
          display: inline-block;
        }
        ::slotted(span[slot="username"]) {
          display: inline-block; 
          width: 100%;
        }
      </style>
      <div class="name">Name :
        <slot name="username"></slot>
      </div>
      <div class="dob">Birthday:
        <slot name="birthday"></slot>
      </div>
    `
  }
})

function abc(event) {
  console.log('Expected: ', this.document.getElementsByTagName('user-card')[0].shadowRoot.querySelector('.name').offsetWidth)
  console.log('Result: ', event.target.parentElement.offsetWidth)
}

用于动态连接的Repository.cs

<user-card>
  <span slot="username">
    <div onclick="abc(event)">Ritesh Rajput</div>
  </span>
  <span slot="birthday">01.01.2001</span>
</user-card>

Table1MyContext.cs

services.AddScoped<MyContext>(_ => new MyContext(Configuration.GetConnectionString("myDB")));