![](/img/trans.png)
[英]Dynamically change connection string in a controller with Asp.Net Core with Entity Framework and Dependency Injection
[英]Dynamically change connection string in 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?
} }
如果您想根据活动的 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);
});
更新
一年左右后,我的解决方案看起来像这里其他答案的点点滴滴,所以请允许我为您整理一下。
您可以在启动文件中添加 HttpContextAccessor 的单例:
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddDbContext<ERPContext>();
这将解决您的上下文构造函数中的注入:
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));
}
}
//..
}
这将为您提供一种干净的方式来访问和提取声明并确定您的连接。
正如@JamesWilkins在评论中所述,将为创建的每个上下文实例调用 OnConfiguring()。
注意可选的访问器和!optionsBuilder.IsConfigured
。 您将需要它们来简化您将覆盖上下文配置的测试。
我们有一个和你类似的案例。 我们所做的是在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 之间的区别是什么,这是一个有效的问题......事实并非如此。 让我解释。
这与原始问题无关。
但是......话虽如此,在这种情况下,使用实体框架实现你自己的“实现工厂”(这是我的答案中最重要的一点)可能比我们需要的更棘手。
然而,有了这样的问题,我们现在可以幸运地查看 GitHub 中的源代码,所以我查了一下AddDbContext到底做了什么。 嗯……这并不难。 这些“添加”(和“使用”)扩展方法只不过是方便的方法,请记住这一点。 因此,您需要添加 AddDbContext 所做的所有服务以及选项。 也许您甚至可以重用 AddDbContext 扩展方法,只需使用实现工厂添加您自己的重载。
所以,回到你的问题。 AddDbContext 做了一些 EF 特定的东西。 正如您所看到的,它们将允许您在以后的版本(瞬态、单例)中传递生命周期。 AddTransient 是 Asp.Net Core,它允许您添加任何您需要的服务。 你需要一个实现工厂。
这是否使它更清楚?
通过将连接字符串逻辑移动到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);
}
@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<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"
});
});
}
所有其他答案对我都不起作用。 所以我想与那些在运行时更改数据库连接字符串的人分享我的方法。
我的应用程序是使用Entity Framework和MySql使用asp.net core 2.2构建的。
启动文件
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"
}
]
数据库连接类
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 }
},
};
}
}
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;
}
}
数据库管理器类
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显式设置它。 希望这会帮助某人。
虽然晚了,但 EF Core 中最简单的技巧是使用 nuget Microsoft.EntityFrameworkCore.Relational :
_dbContext.Database.GetDbConnection().ConnectionString = "NEW_CONN_STRING";
当您的应用程序配置/设置中因任何原因不存在连接字符串或您想使用 DbContext 的一个实例处理具有相同结构的多个数据库时,这很有用(同样,出于任何原因)。
永久或临时取决于您为 DbContext 选择的注入生命周期类型。 如果您将其作为单例服务注入,它将是永久性的,不推荐这样做。
这对我有用:
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;
}
我去了这个解决方案:
代替
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;
用于静态连接的 Startup.cs
services.AddScoped<MyContext>(_ => new MyContext(Configuration.GetConnectionString("myDB")));
用于动态连接的 Repository.cs
using (var _context = new MyContext(@"server=....){
context.Table1....
}
表 1MyContext.cs
public MyContext(string connectionString) : base(GetOptions(connectionString))
{
}
private static DbContextOptions GetOptions(string connectionString)
{
return SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), connectionString).Options;
}
我创建了一个.net6 控制台<\/strong>应用程序并循环 1 到 10 以插入到 test1 数据库和 test2 数据库: Program.cs :<\/strong>
Console.WriteLine("Hello, World!");
for (int i = 1; i <= 10; i++)
{
if (i % 2 == 0)
{
var _context = new AppDbContext("Data Source=.\\SQLEXPRESS;Initial Catalog=test2;Integrated Security=True"); // test2
_context.Tbls.Add(new Tbl { Title = i.ToString() });
_context.SaveChanges();
}
else
{
var _context = new AppDbContext("Data Source=.\\SQLEXPRESS;Initial Catalog=test1;Integrated Security=True"); // test1
_context.Tbls.Add(new Tbl { Title = i.ToString() });
_context.SaveChanges();
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.