在Asp.Net 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? } } 

我怎样才能做到这一点?

我们有一个类似于你的案例。 我们所做的是在Startup类的ConfigureServices方法中使用IServiceCollectionimplementationfactory重载,如下所示:

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

我现在很难为你实现CreateApplicationDbContext,因为它完全取决于你想要什么。 但是,一旦你确定了你想要如何完成它的部分,该方法的基础知识应该是这样的:

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

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

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

或者控制器中的动作方法:

 public IActionResult([FromServices] ApplicationDbContext dbContext){ } 

无论你实现细节,诀窍是实现工厂将在每次注入时构建ApplicationDbContext。

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

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

这与原始问题无关。

但是……话虽如此,实施自己的“实施工厂”(这是我要回答的最重要的事情),在这种情况下,entity framework可能比我们需要的更棘手。

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

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

这会让它更清楚吗?

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

Startup.cs#ConfigureServices services.AddDbContext();方法: 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; } 

然后重写OnConfiguring:

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

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

  using Microsoft.AspNetCore.Http; //.. services.TryAddSingleton(); services.AddDbContext((serviceProvider, options) => { var httpContext = serviceProvider.GetService().HttpContext; var httpRequest = httpContext.Request; var connection = GetConnection(httpRequest); options.UseSqlServer(connection); }); 

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

如何从令牌中提取并获得声明?

这对我有用:

 public void ConfigureServices(IServiceCollection services) { // ..... services.TryAddSingleton(); services.AddTransient(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; } 

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

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

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

  public void ConfigureServices(IServiceCollection services) { services.AddTransient(); services.AddDbContext(((serviceProvider, options) => { var httpContext = serviceProvider.GetService().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" }); }); }