ConnectionStrings 配置项必须运行时动态获取而非硬编码,应存于 appsettings.json 等配置文件,通过 IConfiguration.GetConnectionString() 按键读取;连接字符串构造须用 SqlConnectionStringBuilder,避免拼接与明文密码;DbContext 应短生命周期、按需创建,不可复用或切换连接。

ConnectionStrings 配置项不能硬编码在代码里
动态切换数据库,本质是运行时决定用哪个连接字符串,而不是编译期写死。硬编码 connectionString 会导致每次换库都要改代码、重新部署,完全失去“动态”意义。
正确做法是把不同环境的连接信息放在配置文件中,比如 appsettings.json:
{
"ConnectionStrings": {
"ProdDb": "Server=prod-sql;Database=Main;...",
"TestDb": "Server=test-sql;Database=Staging;...",
"LocalDb": "Data Source=(localdb)\mssqllocaldb;..."
}
}
- 用
IConfiguration.GetConnectionString("ProdDb")按键读取,不拼接、不硬写 - 键名(如
"ProdDb")应由业务逻辑或外部输入决定,比如用户选择的环境、租户 ID、请求头标识 - 避免直接从 HTTP 请求参数里拼接连接字符串——这是严重安全风险,可能触发 SQL 注入或服务端请求伪造(SSRF)
DbContext 构造函数必须支持运行时传入连接字符串
EF Core 默认从 DbContextOptionsBuilder 读取配置,而该对象通常在 Startup 或 Program 中注册为单例。如果想动态换库,就不能依赖默认注册方式。
推荐方案:不注册 DbContext 类型本身,而是注册工厂或封装构造逻辑:
- 定义一个工厂方法,例如
Func<string mydbcontext></string>,内部用new DbContextOptionsBuilder<mydbcontext>().UseSqlServer(connectionString)</mydbcontext> - 每次需要上下文时,先获取目标
connectionString,再调用工厂创建新实例 - 注意:每个
DbContext实例应短生命周期,用完即释放(using或 DI 的Scoped生命周期),否则连接池和变更跟踪会出问题 - 别试图复用同一个
DbContext实例去切换连接——它初始化后连接不可变,强行改会抛InvalidOperationException
多租户场景下容易误用 ConnectionString 缓存
当按租户隔离数据库(每个租户独享一个 DB),连接字符串往往来自租户元数据表或缓存。这时最常踩的坑是:缓存没设过期、没做租户维度隔离、或缓存键拼错。
- 缓存键必须包含租户唯一标识,比如
"connstr_" + tenantId,不能只用数据库名 - 连接字符串内容若含密码,不要存进内存缓存(尤其是共享缓存如 Redis),更不能打日志;可考虑用 Windows 身份验证或 Azure AD Token 替代明文密码
- 首次加载失败时,别静默 fallback 到默认库——要明确抛异常,否则数据写错库很难排查
- EF Core 连接池默认按连接字符串全等区分,两个看似相同但空格/分号顺序不同的字符串,会被视为两个独立连接池,浪费资源
SqlConnectionStringBuilder 是拼接连接字符串的唯一安全方式
即使必须构造连接字符串(比如从 UI 输入服务器地址),也不能用 string.Format 或 $"" 直接拼接。用户输入的任意字符都可能破坏连接语法,甚至注入恶意参数。
- 必须用
SqlConnectionStringBuilder,它会自动转义值、校验关键字、合并重复项 - 示例:
var builder = new SqlConnectionStringBuilder { DataSource = userInputServer, InitialCatalog = userInputDb };<br>if (!string.IsNullOrEmpty(userInputUser)) { builder.UserID = userInputUser; builder.Password = userInputPassword; }<br>string connStr = builder.ConnectionString; - 不要手动设置
builder["Encrypt"] = "true"这类非标准属性——它们可能被忽略,应优先使用强类型属性(如builder.Encrypt = true) - 生成后的
ConnectionString建议记录脱敏日志(如只记DataSource和InitialCatalog),不记凭据部分
动态连接真正的复杂点不在怎么连,而在连接生命周期管理、错误传播路径、以及连接字符串来源的可信边界。很多问题直到压测或切生产库才暴露,比如连接池耗尽、跨租户缓存污染、或某个分支没处理连接失败的回滚逻辑。


















