如何为AspNetUser表添加字段?基于公司ID切换多租户数据库
嘿,你已经走对了第一步——给ApplicationUser加上CompanyId字段,这正是实现按租户(公司)动态切换数据库连接的核心基础。接下来咱们一步步把整个方案落地,确保改造顺利完成:
1. 完善用户实体与数据库迁移
你已经在ApplicationUser里添加了CompanyId,接下来需要生成数据库迁移来更新AspNetUsers表:
- 打开终端或Package Manager Console,运行:
Add-Migration AddCompanyIdToAspNetUsers Update-Database - 确保用户创建/注册时
CompanyId被正确赋值(比如根据业务逻辑分配或让用户选择),示例代码:public async Task<IdentityResult> RegisterUserAsync(RegisterViewModel model, int assignedCompanyId) { var user = new ApplicationUser { UserName = model.Email, Email = model.Email, CompanyId = assignedCompanyId, // 这里根据你的业务逻辑赋值 LastVisitDate = DateTime.UtcNow }; return await _userManager.CreateAsync(user, model.Password); }
2. 配置多租户连接字符串
在appsettings.json里为每个公司配置对应的业务数据库连接字符串,建议按规则命名(比如Company_{Id}):
"ConnectionStrings": { "IdentityConnection": "Server=...;Database=IdentityDB;...", // 原身份库连接 "Company_1": "Server=...;Database=BusinessDB_Company1;...", "Company_2": "Server=...;Database=BusinessDB_Company2;...", // 更多公司的连接字符串... }
3. 实现动态连接字符串提供者
创建一个服务,用来根据当前登录用户的CompanyId获取对应的业务库连接字符串。这里需要用到IHttpContextAccessor来获取当前用户信息:
public interface IConnectionStringProvider { string GetCurrentBusinessConnectionString(); } public class ConnectionStringProvider : IConnectionStringProvider { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IConfiguration _configuration; public ConnectionStringProvider(IHttpContextAccessor httpContextAccessor, IConfiguration configuration) { _httpContextAccessor = httpContextAccessor; _configuration = configuration; } public string GetCurrentBusinessConnectionString() { var httpContext = _httpContextAccessor.HttpContext; if (httpContext == null || !httpContext.User.Identity.IsAuthenticated) { // 未认证用户的处理:返回默认业务库或抛出异常,根据你的需求调整 return _configuration.GetConnectionString("DefaultBusinessConnection"); } // 从用户Claims中获取CompanyId(需要在登录时把CompanyId加入Claims) var companyIdClaim = httpContext.User.FindFirst("CompanyId"); if (companyIdClaim == null || !int.TryParse(companyIdClaim.Value, out int companyId)) { throw new InvalidOperationException("当前用户未关联有效的CompanyId"); } var connectionStringName = $"Company_{companyId}"; var connectionString = _configuration.GetConnectionString(connectionStringName); if (string.IsNullOrEmpty(connectionString)) { throw new InvalidOperationException($"未找到CompanyId为{companyId}的数据库连接配置"); } return connectionString; } }
4. 登录时注入CompanyId到Claims
为了让上面的提供者能拿到CompanyId,需要在用户登录时把CompanyId加入身份Claims,示例代码(在登录逻辑里):
var user = await _userManager.FindByEmailAsync(model.Email); if (user != null && await _userManager.CheckPasswordAsync(user, model.Password)) { var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(ClaimTypes.Email, user.Email), new Claim("CompanyId", user.CompanyId.ToString()!) // 把CompanyId加入Claims }; var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity)); // 其他登录逻辑... }
5. 配置业务DbContext使用动态连接
在Program.cs(或Startup.cs)里注册业务DbContext时,使用咱们的连接字符串提供者来动态获取连接:
// 先注册连接字符串提供者 builder.Services.AddScoped<IConnectionStringProvider, ConnectionStringProvider>(); // 注册身份DbContext(固定连接到原身份库) builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("IdentityConnection"))); // 注册业务DbContext(动态切换连接) builder.Services.AddDbContext<BusinessDbContext>((serviceProvider, options) => { var connectionStringProvider = serviceProvider.GetRequiredService<IConnectionStringProvider>(); var connectionString = connectionStringProvider.GetCurrentBusinessConnectionString(); options.UseSqlServer(connectionString); // 如果你用的是其他数据库,替换成对应的UseXXX方法 });
6. 特殊场景处理
- 后台任务/非HTTP请求:如果有定时任务或控制台程序这类没有HttpContext的场景,需要手动传入
CompanyId来获取连接字符串,可以给IConnectionStringProvider加一个重载方法GetConnectionStringByCompanyId(int companyId)。 - 异常兜底:建议在全局异常过滤器里捕获连接字符串相关的异常,给用户友好的提示。
- CompanyId必填性:如果业务要求每个用户必须关联公司,可以把
CompanyId的类型从int?改成int,确保数据完整性。
这样改造完成后,用户登录后,所有业务DbContext的操作都会自动切换到对应的公司数据库,而身份相关的操作依然使用原来的共享数据库,完美实现你的需求~
内容的提问来源于stack exchange,提问作者akyj
相关产品推荐
相关产品推荐

