C# 动态更改Asp.Net核心中的连接字符串

C# 动态更改Asp.Net核心中的连接字符串,c#,asp.net,asp.net-core,entity-framework-core,C#,Asp.net,Asp.net Core,Entity Framework Core,我想更改控制器中的sql连接字符串,而不是ApplicationDbContext中的sql连接字符串。我正在使用Asp.Net核心和实体框架核心 例如: public class MyController : Controller { private readonly ApplicationDbContext _dbContext public MyController(ApplicationDbContext dbContext) { _dbConte

我想更改控制器中的sql连接字符串,而不是ApplicationDbContext中的sql连接字符串。我正在使用Asp.Net核心和实体框架核心

例如:

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

我怎样才能做到这一点呢?

我们有一个与您类似的案例。我们所做的是在Startup类的ConfigureServices方法中使用IServiceCollection的实现工厂重载,如下所示:

//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());
实现后,您可以像在构造函数中一样在控制器中插入正确的ApplicationDbContext:

public MyController(ApplicationDbContext dbContext)
{
    _dbContext = dbContext;
}
public class ERPContext : DbContext
{
    private readonly HttpContext _httpContext;

    public ERPContext(DbContextOptions<ERPContext> options, IHttpContextAccessor httpContextAccessor = null)
        : base(options)
    {
        _httpContext = httpContextAccessor?.HttpContext;
    }

    //..

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            var clientClaim = _httpContext?.User.Claims.Where(c => c.Type == ClaimTypes.GroupSid).Select(c => c.Value).SingleOrDefault();
            if (clientClaim == null) clientClaim = "DEBUG"; // Let's say there is no http context, like when you update-database from PMC
            optionsBuilder.UseSqlServer(RetrieveYourBeautifulClientConnection(clientClaim));
        }
    }

    //..
}
或控制器中的操作方法:

public IActionResult([FromServices] ApplicationDbContext dbContext){
}
无论您如何实现细节,关键在于每次注入时,实现工厂都会构建ApplicationDbContext

如果您在实施此解决方案时需要更多帮助,请告诉我

更新#1 Yuriy N.问AddTransient和AddDbContext之间有什么区别,这是一个有效的问题。。。但事实并非如此。让我解释一下

这与原来的问题无关

但是。。。话虽如此,在这种情况下,使用实体框架实现您自己的“实现工厂”(这是我的答案中需要注意的最重要的一点)可能比我们需要的要复杂一些

然而,有了这样的问题,我们现在可以幸运地查看GitHub中的源代码,所以我查看了具体的功能。而且。。。这其实并不难。记住,这些“添加”(和“使用”)扩展方法只不过是方便的方法。因此,您需要添加AddDbContext提供的所有服务,以及选项。也许您甚至可以重用AddDbContext扩展方法,只需使用实现工厂添加您自己的重载

那么,回到你的问题上来。AddDbContext做一些特定于EF的事情。正如您所见,它们将允许您在以后的版本中度过一生(瞬态、单例)。AddTransient是Asp.Net核心,允许您添加所需的任何服务。你需要一个实现工厂


这是否更清楚?

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

Startup.cs#ConfigureServices
方法中:
services.AddDbContext()

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

    private IConfigurationRoot _config;
    private HttpContext _httpContext;

    public MyDbContext(DbContextOptions options, IConfigurationRoot config, IHttpContextAccessor httpContextAccessor) 
          : base(options)
    {
        _config = config;
        _httpContext = httpContextAccessor.HttpContext;
    }
然后覆盖配置:

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

        optionsBuilder.UseSqlServer(connString);
    }
这对我来说很有用:

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;
}
public void配置服务(IServiceCollection服务)
{
// .....
services.TryAddSingleton();
services.AddTransient(提供者=>
{
返回ResolveDbContext(提供程序,hostingEnv);
});
// ..
}
私有MyDbContext ResolveDbContext(IServiceProvider提供程序,IHostingEnvironment HostingEnvironment)
{
string connectionString=Configuration.GetConnectionString(“DefaultConnection”);
字符串SOME_DB_identifier=httpContextAccessor.HttpContext.User.Claims
.Where(c=>c.Type==“[SOME_DB_identifier]”)。选择(c=>c.Value)。FirstOrDefault();
if(!string.IsNullOrWhiteSpace(某些\u DB\u标识符))
{
connectionString=connectionString.Replace(“[DB_NAME]”,$”{SOME_DB_identifier}DB”);
}
var dbContext=new DefaultDbContextFactory().CreateDbContext(connectionString);
// ....
返回dbContext;
}

如果要根据活动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);
        });
使用Microsoft.AspNetCore.Http;
//..
services.TryAddSingleton();
services.AddDbContext((serviceProvider,options)=>
{
var httpContext=serviceProvider.GetService().httpContext;
var httpRequest=httpContext.Request;
var connection=GetConnection(httpRequest);
选项。使用SQLServer(连接);
});
更新

大约一年后,我的解决方案看起来像其他答案中的零碎部分,所以请允许我为您总结一下

您可以在启动文件中添加一个单一的HttpContextAccessor:

services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddDbContext<ERPContext>();
services.TryAddSingleton();
services.AddDbContext();
这将解决上下文构造函数上的注入问题:

public MyController(ApplicationDbContext dbContext)
{
    _dbContext = dbContext;
}
public class ERPContext : DbContext
{
    private readonly HttpContext _httpContext;

    public ERPContext(DbContextOptions<ERPContext> options, IHttpContextAccessor httpContextAccessor = null)
        : base(options)
    {
        _httpContext = httpContextAccessor?.HttpContext;
    }

    //..

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            var clientClaim = _httpContext?.User.Claims.Where(c => c.Type == ClaimTypes.GroupSid).Select(c => c.Value).SingleOrDefault();
            if (clientClaim == null) clientClaim = "DEBUG"; // Let's say there is no http context, like when you update-database from PMC
            optionsBuilder.UseSqlServer(RetrieveYourBeautifulClientConnection(clientClaim));
        }
    }

    //..
}
public类上下文:DbContext
{
私有只读HttpContext\u HttpContext;
公共ERPContext(DbContextOptions选项,IHttpContextAccessor httpContextAccessor=null)
:基本(选项)
{
_httpContext=httpContextAccessor?.httpContext;
}
//..
配置时受保护的覆盖无效(DBContextOptions Builder Options Builder)
{
如果(!optionBuilder.IsConfigured)
{
var clientClaim=_httpContext?.User.Claims.Where(c=>c.Type==ClaimTypes.GroupSid)。选择(c=>c.Value.SingleOrDefault();
if(clientClaim==null)clientClaim=“DEBUG”;//假设不存在http上下文,比如从PMC更新数据库时
optionsBuilder.UseSqlServer(检索YourBeautifulClientConnection(clientClaim));
}
}
//..
}
这将为您提供一种干净的方式来访问和提取索赔,并决定您的连接

如注释中所述,将为创建的上下文的每个实例调用OnConfiguring()

注意可选的访问器和
!optionsBuilder.IsConfigured

您将需要它们来简化您的测试,因为您将覆盖您的上下文配置。

关于@ginalx和@jcmordan的答案非常适合我的用例。我喜欢这些答案的一点是,我可以在
Startup.cs
中完成这一切,并保持所有其他类没有构造代码。我想向Web Api请求提供一个可选的querystring参数,并将其替换到创建DbContext的基本连接字符串中。我将基本字符串保存在appsettings.json中,并根据传入的参数或默认值(如果未提供)对其进行格式化。
    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"
            });
        });
    }
public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddDbContext<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);
        }
    }

    ...
[
  {
    "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"
  }
]
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;


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);
}

    internal static class Converter
    {
        public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
        {
            MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
            DateParseHandling = DateParseHandling.None,
            Converters =
            {
                new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
            },
        };
    }
}
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;
    }
}
public static class DbManager
{
    public static string DbName;

    public static string GetDbConnectionString(string dbName)
    {
        return DbConnectionManager.GetConnectionString(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");
}
services.AddScoped<MyContext>(_ => new MyContext(Configuration.GetConnectionString("myDB")));
using (var _context = new MyContext(@"server=....){
context.Table1....
}
public MyContext(string connectionString) : base(GetOptions(connectionString))
{
}

private static DbContextOptions GetOptions(string connectionString)
{
    return SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), connectionString).Options;
}
_dbContext.Database.GetDbConnection().ConnectionString = "NEW_CONN_STRING";