使用ASP.Net Identity时是否应在业务库存放额外用户信息?
ASP.NET Identity 双库架构下的用户存储方案
不建议将全部用户信息存入Identity数据库并完全弃用业务库用户表,跨库场景下优先做职责拆分+逻辑关联,以下是可落地的方案和注意事项:
方案对比与选择
方案1:身份库/业务库职责拆分(推荐,匹配你当前的架构)
这是分库场景下的标准实践,核心思路是两个库各管各的,不做数据库级强关联:
- Identity库只存纯身份认证/授权数据:自定义用户类继承
IdentityUser,仅保留账号安全、权限相关字段:你原模型里的登录名(直接映射基类自带的UserName属性)、密码(不需要自己存明文/哈希,Identity自动维护PasswordHash)、邮箱、手机号、账号锁定状态、角色关联、双因子配置这类数据全部放这个库,不要放业务属性。
自定义IdentityUser示例:public class AppIdentityUser : IdentityUser { // 仅扩展身份相关字段,比如实名认证信息、账号注册渠道等 } - 业务库存放全量业务关联数据:保留精简的业务用户表,不需要重复存密码、邮箱这类身份字段,核心字段包含三类:
- 业务库自身的用户主键(用自增int即可,和你原有模型兼容)
IdentityUserId:字段类型和AppIdentityUser的主键类型保持一致(默认是string/guid,如果你改了Identity主键为int就对应设为int),作为两个库关联的逻辑外键- 所有业务属性:包括用户类型(提问者/答主)、提问总数、回答总数这类业务统计字段,以及和Question等业务实体的导航关联
业务用户表示例:
public enum UserType { Poster = 1, Answerer = 2 } public class BusinessUser { public int Id { get; set; } public string IdentityUserId { get; set; } // 关联Identity库用户ID public UserType UserType { get; set; } // 原有业务字段 public int Total_posted { get; set; } public int Total_answered { get; set; } // 业务实体导航属性,直接在业务库配数据库级外键 public List<Question> PostedQuestions { get; set; } public List<Question> AnsweredQuestions { get; set; } }
跨库关联的处理方式
不要配置跨库物理外键,不仅多数数据库支持度差,后续迁库、分库的时候坑非常多,一致性靠两层逻辑保证:
- 写入一致性:用户注册时,先在Identity库创建身份用户拿到生成的用户ID,再在业务库插入对应的
BusinessUser记录,两步操作可放在同一个事务中执行(同数据库实例下EF Core原生支持跨上下文事务),避免出现身份用户存在但业务用户不存在的脏数据。 - 校验逻辑:所有涉及用户的业务操作,先在业务库校验业务用户存在,必要时再调用Identity模块校验账号状态(是否封禁、是否有权限),校验通过再执行业务逻辑,示例代码:
public async Task<Question> CreateQuestion(int bizUserId, string title, string content) { // 先校验业务用户存在且有对应权限 var bizUser = await _bizDbContext.BusinessUsers.FirstOrDefaultAsync(u => u.Id == bizUserId && u.UserType == UserType.Poster); if (bizUser == null) throw new ArgumentException("用户不存在或无提问权限"); // 可选:校验身份账号状态 var idUser = await _userManager.FindByIdAsync(bizUser.IdentityUserId); if (idUser == null || idUser.LockoutEnd >= DateTimeOffset.Now) throw new UnauthorizedAccessException("账号异常,无法发布内容"); var question = new Question { PosterId = bizUserId, Title = title, Content = content }; _bizDbContext.Questions.Add(question); await _bizDbContext.SaveChangesAsync(); return question; } - 查询处理:需要同时展示身份信息(比如头像、昵称、邮箱)和业务数据时,先查业务库拿到业务数据集合,批量提取所有
IdentityUserId,再去Identity库批量查询对应用户信息,在内存中做映射即可,中小项目数据量下性能完全足够,不要写跨库连表SQL。
方案2:单库合并(适合小型项目,不想维护多库)
如果项目体量小、后续没有拆分微服务/独立身份服务的计划,可以直接去掉独立的Identity库,让Identity的相关表全部生成在业务库中:
- 自定义
AppUser : IdentityUser<int>,将Identity默认的字符串主键改为int,和你原有用户表主键类型对齐 - 直接把所有业务字段(提问总数、回答总数、用户类型等)全部加在
AppUser类上 Question等业务实体直接通过导航属性关联AppUser,数据库级外键可以正常配置,没有跨库问题
这个方案的缺点是后续如果要拆分身份模块,改造成本极高,仅适合快速迭代的小型项目。
不推荐的做法:全量用户数据存Identity库、弃用业务用户表
如果硬要采用这个方案,外键只能做逻辑关联:
- 所有业务实体(比如Question)只加一个
OwnerId字段,类型和Identity用户主键类型一致,存储对应用户的ID,不配置数据库外键 - 所有关联校验、级联操作(比如删除用户时关联的内容处理)全部写在业务代码层,没有数据库层面的兜底校验,很容易产生脏数据(比如Identity库删除用户后,业务库还残留大量关联该用户ID的内容)
- 无法使用EF Core的导航属性自动做连表查询,每次需要用户信息都要单独请求Identity库,开发效率低,新手维护很容易出问题。
核心原则:ASP.NET Identity的核心定位是身份认证与授权组件,它的存储只需要承载和账号安全、权限校验相关的数据,业务属性、业务关联关系尽量放在业务侧维护,后续不管是升级Identity版本、替换身份组件、拆分业务架构,都不会互相耦合。
内容的提问来源于stack exchange,提问作者Azetaris
相关产品推荐
相关产品推荐

