自定义AspNetCore Identity数据库遇IdentityRole影子状态异常求助
这个异常的核心原因是Entity Framework无法找到某个实体类型对应的CLR类,进入了"影子状态"(shadow-state)。在你的场景中,这是因为你自定义了Identity的实体(比如Role、RoleClaim等),但关联实体的配置或继承关系有错误,导致EF仍然试图加载默认的IdentityRole类型,和你自定义的Role类产生了冲突。
下面是具体的排查和修复步骤:
1. 确认自定义Identity实体的继承关系
首先要确保你的所有自定义Identity实体都正确继承了对应的Identity基类,示例如下:
// User类 public class User : IdentityUser<int> { // 你的自定义属性,比如Registered、LastVisit public DateTime Registered { get; set; } public DateTime LastVisit { get; set; } } // Role类 public class Role : IdentityRole<int> { // 自定义属性(如果有的话) } // RoleClaim类 public class RoleClaim : IdentityRoleClaim<int> { // 自定义属性(如果有的话) } // UserClaim类 public class UserClaim : IdentityUserClaim<int> { // 自定义属性(如果有的话) } // Login类(对应IdentityUserLogin) public class Login : IdentityUserLogin<int> { // 自定义属性(如果有的话) } // UserRole类(对应IdentityUserRole) public class UserRole : IdentityUserRole<int> { // 自定义属性(如果有的话) }
2. 修正OnModelCreating中的配置错误
你的配置里有几处导航属性和键的错误,导致EF无法正确关联实体:
修正RoleClaim配置
把错误的HasOne(D => D.Claim)改成HasOne(d => d.Role)(IdentityRoleClaim的导航属性是Role,不是Claim):
ModelBuilder.Entity<RoleClaim>(B => { B.Property<int>(P => P.Id) .HasColumnName("ClaimId") .ValueGeneratedOnAdd(); B.Property<string>("ClaimType") .HasMaxLength(128); B.Property<string>("ClaimValue") .HasMaxLength(128); B.Property<int>("RoleId") .IsRequired(); B.HasIndex(P => P.RoleId) .HasName("IX_RoleClaims_RoleId"); // 修正导航属性 B.HasOne(d => d.Role) .WithMany() .HasForeignKey(p => p.RoleId) .OnDelete(DeleteBehavior.Cascade); B.ToTable("RoleClaims"); });
修正UserClaim配置
把HasOne(D => D.Account)改成HasOne(d => d.User)(IdentityUserClaim的导航属性是User):
ModelBuilder.Entity<UserClaim>(B => { B.Property<int>(P => P.Id) .HasColumnName("ClaimId") .ValueGeneratedOnAdd(); B.Property<string>("ClaimType") .HasMaxLength(128); B.Property<string>("ClaimValue") .HasMaxLength(128); B.Property<int>(P => P.UserId) .HasColumnName("AccountId") .IsRequired(); B.HasIndex("AccountId") .HasName("IX_UserClaims_AccountId"); // 修正导航属性 B.HasOne(d => d.User) .WithMany() .HasForeignKey(p => p.UserId) .OnDelete(DeleteBehavior.Cascade); B.ToTable("UserClaims"); });
修正Login配置
把HasOne(D => D.Account)改成HasOne(d => d.User)(IdentityUserLogin的导航属性是User),同时注意IdentityUserLogin的主键是复合键,不是自增ID:
ModelBuilder.Entity<Login>(B => { // 配置复合主键(IdentityUserLogin的默认主键) B.HasKey(l => new { l.LoginProvider, l.ProviderKey }); B.Property<string>("LoginProvider") .HasMaxLength(450) .IsRequired(); B.Property<string>("ProviderKey") .HasMaxLength(450) .IsRequired(); B.Property<string>("ProviderDisplayName"); B.Property<int>("UserId") .HasColumnName("AccountId") .IsRequired(); B.HasIndex("AccountId") .HasName("IX_Logins_AccountId"); // 修正导航属性 B.HasOne(d => d.User) .WithMany() .HasForeignKey(p => p.UserId) .OnDelete(DeleteBehavior.Cascade); B.ToTable("Logins"); });
修正UserRole配置
IdentityUserRole的主键是复合键(UserId, RoleId),需要正确配置并修正导航属性:
ModelBuilder.Entity<UserRole>(B => { // 配置复合主键 B.HasKey(ur => new { ur.UserId, ur.RoleId }); B.Property<int>(ur => ur.UserId) .HasColumnName("AccountId"); B.Property<int>(ur => ur.RoleId) .HasColumnName("RoleId"); B.HasIndex(ur => ur.RoleId) .HasName("IX_UserRoles_RoleId"); B.HasIndex(ur => ur.UserId) .HasName("IX_UserRoles_AccountId"); // 配置导航属性 B.HasOne(ur => ur.User) .WithMany() .HasForeignKey(ur => ur.UserId) .OnDelete(DeleteBehavior.Cascade); B.HasOne(ur => ur.Role) .WithMany() .HasForeignKey(ur => ur.RoleId) .OnDelete(DeleteBehavior.Cascade); B.ToTable("UserRoles"); });
3. 确保DbContext和Identity服务配置正确
- 在
ApplicationDbContext中,添加所有自定义实体的DbSet(可选,但能让EF更明确识别实体):public DbSet<User> Users { get; set; } public DbSet<Role> Roles { get; set; } public DbSet<RoleClaim> RoleClaims { get; set; } public DbSet<UserClaim> UserClaims { get; set; } public DbSet<Login> Logins { get; set; } public DbSet<UserRole> UserRoles { get; set; } - 在Startup.cs(或Program.cs)中配置Identity时,指定你的自定义用户和角色类型:
services.AddIdentity<User, Role>(options => /* 你的配置 */) .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders();
做完这些修改后,EF就能正确识别所有自定义实体,不会再试图加载默认的IdentityRole,也就解决了shadow-state的异常。
内容的提问来源于stack exchange,提问作者derpasaurus

