.NET Core 6迁移外部登录报错:无法使用AspNetRoleClaims表
我在.NET Core 3.1项目里实现了一套外部登录逻辑:通过查询字符串接收包含用户邮箱的令牌,验证令牌有效后完成用户登录。升级到.NET Core 6后,这套逻辑在3.1环境正常运行,但在6.0环境调用SignInManager.SignInAsync时抛出异常。
报错信息
InvalidOperationException: Cannot use table 'AspNetRoleClaims' for entity type 'IdentityRoleClaim<string>' since it is being used for entity type 'AspNetRoleClaims' and potentially other entity types, but there is no linking relationship. Add a foreign key to 'IdentityRoleClaim<string>' on the primary key properties and pointing to the primary key on another entity type mapped to 'AspNetRoleClaims'.
登录控制器代码
public class SignInController : Controller { private SignInManager<IdentityUser> _signInManager; private MyProjectContext _context; public TokenController(SignInManager<IdentityUser> signInManager, MyProjectContext context) { _signInManager = signInManager; _context = context; } public async Task<IActionResult> SignIn([FromQuery] string token) { string emailFromToken = MyTokenParser.GetEmail(token); IdentityUser user = new() { UserName = emailFromToken, Email = emailFromToken, EmailConfirmed = true }; await _signInManager.SignInAsync(user, isPersistent: false, null); return RedirectToAction("Index", "Home"); } }
上下文类代码(EF Core Power Tools重建)
public partial class MyProjectContext : IdentityDbContext<IdentityUser> { public MyProjectContext() { } public MyProjectContext(DbContextOptions<MyProjectContext> options) : base(options) { } public virtual DbSet<AspNetRoleClaims> AspNetRoleClaims { get; set; } public virtual DbSet<AspNetRoles> AspNetRoles { get; set; } public virtual DbSet<AspNetUserClaims> AspNetUserClaims { get; set; } public virtual DbSet<AspNetUserLogins> AspNetUserLogins { get; set; } public virtual DbSet<AspNetUserRoles> AspNetUserRoles { get; set; } public virtual DbSet<AspNetUserTokens> AspNetUserTokens { get; set; } public virtual DbSet<AspNetUsers> AspNetUsers { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<AspNetRoleClaims>(entity => { entity.HasIndex(e => e.RoleId); entity.Property(e => e.RoleId).IsRequired(); entity.HasOne(d => d.Role) .WithMany(p => p.AspNetRoleClaims) .HasForeignKey(d => d.RoleId); }); modelBuilder.Entity<AspNetRoles>(entity => { entity.HasIndex(e => e.NormalizedName, "RoleNameIndex") .IsUnique() .HasFilter("([NormalizedName] IS NOT NULL)"); }); modelBuilder.Entity<AspNetUserLogins>(entity => { entity.HasKey(e => new { e.LoginProvider, e.ProviderKey }); }); modelBuilder.Entity<AspNetUserRoles>(entity => { entity.HasKey(e => new { e.UserId, e.RoleId }); }); modelBuilder.Entity<AspNetUserTokens>(entity => { entity.HasKey(e => new { e.UserId, e.LoginProvider, e.Name }); }); modelBuilder.Entity<AspNetUsers>(entity => { entity.HasIndex(e => e.NormalizedUserName, "UserNameIndex") .IsUnique() .HasFilter("([NormalizedUserName] IS NOT NULL)"); }); } partial void OnModelCreatingPartial(ModelBuilder modelBuilder); protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { var envName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); IConfigurationRoot configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .Build(); optionsBuilder.UseSqlServer(configuration.GetConnectionString("MyConnectionString")); } }
尝试的错误修改代码
modelBuilder.Entity<AspNetRoleClaims>(entity => { entity.HasIndex(e => e.RoleId); entity.Property(e => e.RoleId).IsRequired(); entity.HasOne(d => d.Role) .WithMany(p => p.AspNetRoleClaims) .HasForeignKey(d => d.RoleId); //Tried to declare the relationship here entity.HasOne(d => d.RoleId) .WithOne(p => p.*) //compiler error regardless of entry .HasForeignKey(d => d.*) //compiler error regardless of entry });
请问我遗漏了什么?该如何解决这个问题?
这个问题的核心是:你的MyProjectContext继承了IdentityDbContext<IdentityUser>,而IdentityDbContext已经内置了对IdentityRoleClaim<string>、IdentityRole等Identity默认实体的映射,对应数据库里的AspNetRoleClaims、AspNetRoles等表。但你又通过EF Core Power Tools生成了自己的AspNetRoleClaims、AspNetRoles等实体类,导致EF Core认为同一个表被两个不同的实体类型映射,从而引发冲突。
解决步骤如下:
删除手动生成的Identity相关实体类
直接删除AspNetRoleClaims、AspNetRoles、AspNetUserClaims、AspNetUserLogins、AspNetUserRoles、AspNetUserTokens、AspNetUsers这些类,因为IdentityDbContext已经提供了对应的默认实体(IdentityRoleClaim<TKey>、IdentityRole<TKey>、IdentityUserClaim<TKey>等)。修改上下文类,移除自定义的Identity相关配置
把MyProjectContext里的自定义Identity相关DbSet和OnModelCreating里的对应配置删除,只保留业务实体相关代码。修改后的上下文示例:public partial class MyProjectContext : IdentityDbContext<IdentityUser> { public MyProjectContext() { } public MyProjectContext(DbContextOptions<MyProjectContext> options) : base(options) { } // 仅保留你的业务实体DbSet,例如: // public virtual DbSet<Order> Orders { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 仅添加业务实体的映射配置,删除所有AspNet开头的实体配置 partial void OnModelCreatingPartial(ModelBuilder modelBuilder); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { var envName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); IConfigurationRoot configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .Build(); optionsBuilder.UseSqlServer(configuration.GetConnectionString("MyConnectionString")); } }如需自定义Identity实体,采用继承方式
如果需要扩展Identity实体(比如给用户添加额外字段),不要手动生成实体类,而是继承默认Identity实体实现:public class ApplicationUser : IdentityUser { // 自定义字段 public string FullName { get; set; } }然后修改上下文继承
IdentityDbContext<ApplicationUser>,并在Program.cs中配置Identity使用自定义实体:builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<MyProjectContext>();修正登录逻辑(可选)
你的代码中直接创建新的IdentityUser对象调用SignInAsync,这种"无密码登录"逻辑通常应该基于数据库中已存在的用户。建议先从数据库查询用户再执行登录:var user = await _context.Users.FirstOrDefaultAsync(u => u.Email == emailFromToken); if (user != null) { await _signInManager.SignInAsync(user, isPersistent: false); } // 处理用户不存在的异常情况
内容的提问来源于stack exchange,提问作者N.MORR

