首页 > 解决方案 > 实体类型“Microsoft.AspNetCore.Identity.IdentityRole”处于阴影状态

问题描述

我一直在玩这个快速入门示例并且一直在尝试查看我可以自定义数据库多远(我有一个现有的数据库,我一直在尝试复制一半)。

我设法触发了下面的异常并且无法修复它,部分原因是我不明白消息告诉我什么。

InvalidOperationException:实体类型“Microsoft.AspNetCore.Identity.IdentityRole”处于阴影状态。一个有效的模型要求所有实体类型都有对应的 CLR 类型。

ApplicationDbContext的如下:

using IdentityServerWithAspIdAndEF.Models;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using System;

namespace IdentityServerWithAspIdAndEF.Data
{
    public class ApplicationDbContext : IdentityDbContext<User, Role, int>
    {
        public ApplicationDbContext(
            DbContextOptions<ApplicationDbContext> Options
        ) : base(Options) { }

        protected override void OnModelCreating(ModelBuilder ModelBuilder)
        {
            base.OnModelCreating(ModelBuilder);

            // Customisations

            // "IdentityServer4AspNetIdentity.Models.ApplicationUser"
            ModelBuilder.Entity<User>(B =>
            {
                B.Property<int>(P => P.Id)
                    .HasColumnName("AccountId")
                    .ValueGeneratedOnAdd();

                B.Property<string>("ConcurrencyStamp")
                    .HasMaxLength(512)
                    .IsConcurrencyToken();

                B.Property<string>("Email")
                    .HasMaxLength(512)
                    .IsRequired();

                B.Property<bool>("EmailConfirmed")
                    .ValueGeneratedOnAdd();

                B.Property<string>("NormalisedEmail")
                    .HasMaxLength(512)
                    .IsRequired();

                B.Property<string>("NormalisedUserName")
                    .HasMaxLength(256)
                    .IsRequired();

                B.Property<string>("PasswordHash");

                B.Property<string>("SecurityStamp")
                    .IsRequired();

                B.Property<bool>("TwoFactorEnabled")
                    .ValueGeneratedOnAdd();

                B.Property<string>("UserName")
                    .HasMaxLength(256)
                    .IsRequired();

                B.Property<DateTime>("Registered")
                    .ValueGeneratedOnAdd();

                B.Property<DateTime>("LastVisit")
                    .IsRequired();

                B.HasKey("AccountId");

                B.HasIndex("NormalisedEmail")
                    .HasName("IX_Users_NormalisedEmail");

                B.HasIndex("NormalisedUserName")
                    .IsUnique()
                    .HasName("IX_Users_NormalisedUserName");

                B.ToTable("Users");
            });

            // "Microsoft.AspNetCore.Identity.IdentityRole"
            ModelBuilder.Entity<Role>(B =>
            {
                B.Property<int>(P => P.Id)
                    .HasColumnName("RoleId")
                    .ValueGeneratedOnAdd();

                B.Property<string>("ConcurrencyStamp")
                    .HasMaxLength(512)
                    .IsConcurrencyToken();

                B.Property<string>("Name")
                    .HasMaxLength(256);

                B.Property<string>("NormalisedName")
                    .HasMaxLength(256);

                B.HasKey(P => P.Id);

                B.HasIndex("NormalisedName")
                    .IsUnique()
                    .HasName("IX_Roles_NormalisedName");

                B.ToTable("Roles");
            });

            // "Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>"
            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.Claim)
                    .WithMany()
                    .HasForeignKey(P => P.RoleId)
                    .OnDelete(DeleteBehavior.Cascade);

                B.ToTable("RoleClaims");
            });

            // "Microsoft.AspNetCore.Identity.IdentityUserClaim<string>"
            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.Account)
                    .WithMany()
                    .HasForeignKey(P => P.AccountId)
                    .OnDelete(DeleteBehavior.Cascade);

                B.ToTable("UserClaims");
            });

            // "Microsoft.AspNetCore.Identity.IdentityUserLogin<string>"
            ModelBuilder.Entity<Login>(B =>
            {
                B.Property<int>(P => P.UserId)
                    .HasColumnName("LoginId")
                    .ValueGeneratedOnAdd();

                B.Property<string>("LoginProvider")
                    .HasMaxLength(450)
                    .IsRequired();

                B.Property<string>("ProviderKey")
                    .HasMaxLength(450)
                    .IsRequired();

                B.Property<string>("ProviderDisplayName");

                B.Property<int>("AccountId")
                    .IsRequired();

                B.HasIndex("LoginProvider")
                    .HasName("IX_Logins_LoginProvider");

                B.HasIndex("ProviderKey")
                    .HasName("IX_Logins_ProviderKey");

                B.HasIndex("AccountId")
                    .HasName("IX_Logins_AccountId");

                B.HasOne(D => D.Account)
                    .WithMany()
                    .HasForeignKey(P => P.AccountId)
                    .OnDelete(DeleteBehavior.Cascade);

                B.ToTable("Logins");
            });

            // "Microsoft.AspNetCore.Identity.IdentityUserRole<string>"
            ModelBuilder.Entity<UserRole>(B =>
            {
                B.Property<int>(P => P.UserId)
                    .HasColumnName("AccountId")
                    .IsRequired();

                B.Property<int>("RoleId")
                    .IsRequired();

                B.HasIndex("AccountId")
                    .HasName("IX_RoleMap_AccountId");

                B.HasIndex("RoleId")
                    .HasName("IX_RoleMap_RoleId");

                B.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
                    .WithMany()
                    .HasForeignKey("RoleId")
                    .OnDelete(DeleteBehavior.Cascade);

                B.HasOne(P => P.Account)
                    .WithMany()
                    .HasForeignKey("AccountId")
                    .OnDelete(DeleteBehavior.Cascade);

                B.ToTable("RoleMap");
            });

            // "Microsoft.AspNetCore.Identity.IdentityUserToken<string>"
            ModelBuilder.Entity<Token>(B =>
            {
                B.Property<int>(P => P.UserId)
                    .HasColumnName("AccountId")
                    .IsRequired();

                B.Property<string>("LoginProvider")
                    .HasMaxLength(128)
                    .IsRequired();

                B.Property<string>("Name")
                    .HasMaxLength(64);

                B.Property<string>("Value");

                B.HasOne(P => P.Account)
                    .WithMany()
                    .HasForeignKey(P => P.AccountId)
                    .OnDelete(DeleteBehavior.Cascade);

                B.ToTable("UserTokens");
            });

            // Non-identity extras

            /* snipped */
        }
    }
}

与这些 DbSet 对应的实体如下:

using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;

namespace IdentityServerWithAspIdAndEF.Models
{
    public class User : IdentityUser<int>
    {
        public int AccountId
        {
            get => base.Id;
            set => base.Id = value;
        }
        public string NormalisedEmail
        {
            get => base.NormalizedEmail;
            set => base.NormalizedEmail = value;
        }
        public string NormalisedUserName
        {
            get => base.NormalizedUserName;
            set => base.NormalizedUserName = value;
        }
        public DateTime Registered { get; set; }
        public DateTime LastVisit { get; set; }

        public AccountDetail UserDetails { get; set; }
        public AccountLockout Lockout { get; set; }
        public PasswordReset PasswordResetRequested { get; set; }
        public Concierge ConciergeAccountFlag { get; set; }
        public NotValidated AccountValidatation { get; set; }

        public ICollection<UserRole> AssignedRoles { get; set; }
        public ICollection<UserClaim> ClaimsCollection { get; set; }
        public ICollection<Login> Logins { get; set; }
        public ICollection<LoginAttempt> LoginAttempts { get; set; }
        public ICollection<Token> TokenCollection { get; set; }

        public new int Id => throw new NotImplementedException();
        public override string NormalizedEmail => throw new NotImplementedException();
        public override string NormalizedUserName => throw new NotImplementedException();
    }

    public class Role : IdentityRole<int>
    {
        public int RoleId
        {
            get => base.Id;
            set => base.Id = value;
        }
        public string NormalisedName
        {
            get => base.NormalizedName;
            set => base.NormalizedName = value;
        }

        public ICollection<RoleClaim> ClaimsCollection { get; set; }

        private new int Id => throw new NotImplementedException();
        private new int NormalizedName => throw new NotImplementedException();
    }

    public class RoleClaim : IdentityRoleClaim<int>
    {
        public int ClaimId
        {
            get => base.Id;
            set => base.Id = value;
        }

        public Role Claim { get; set; }

        private new int Id => throw new NotImplementedException();
    }

    public class UserClaim : IdentityUserClaim<int>
    {
        public int ClaimId
        {
            get => base.Id;
            set => base.Id = value;
        }
        public int AccountId
        {
            get => base.UserId;
            set => base.UserId = value;
        }

        public User Account { get; set; }

        private new int Id => throw new NotImplementedException();
        private new int UserId => throw new NotImplementedException();
    }

    public class Login : IdentityUserLogin<int>
    {
        public int AccountId
        {
            get => base.UserId;
            set => base.UserId = value;
        }

        public User Account { get; set; }

        private new int UserId => throw new NotImplementedException();
    }

    public class UserRole : IdentityUserRole<int>
    {
        public int AccountId
        {
            get => base.UserId;
            set => base.UserId = value;
        }

        public User Account { get; set; }

        private new int UserId => throw new NotImplementedException();
    }

    public class Token : IdentityUserToken<int>
    {
        public int AccountId
        {
            get => base.UserId;
            set => base.UserId = value;
        }

        private new int UserId => throw new NotImplementedException();

        public User Account { get; set; }
    }
}

我已阅读帖子“实体类型处于“影子状态”意味着什么?” “实体类型‘类型’处于阴影状态。有效的模型要求所有实体类型都具有相应的 CLR 类型”

文档来看,我认为我可能在Role某处错过或错误引用了该实体,但我不清楚在哪里。

提前致谢!


编辑:

重新阅读影子属性文档,“按照惯例,影子属性仅在发现关系但在依赖实体类中找不到外键属性时才创建。在这种情况下,将引入影子外键属性。 ” 似乎支持我破坏了实体。

我试图通过将RoleRoleClaimModelBuilder 实体声明中的所有属性引用更改为表达式来排除属性名称不匹配,以查看硬引用是否会有所帮助:

// "Microsoft.AspNetCore.Identity.IdentityRole"
ModelBuilder.Entity<Role>(B =>
{
    B.Property(P => P.RoleId)
        .ValueGeneratedOnAdd();

    B.Property(E => E.ConcurrencyStamp)
        .HasMaxLength(512)
        .IsConcurrencyToken();

    B.Property(E => E.Name)
        .HasMaxLength(256);

    B.Property(E => E.NormalisedName)
        .HasMaxLength(256);

    B.HasKey(P => P.Id);

    B.HasIndex(E => E.NormalisedName)
        .IsUnique()
        .HasName("IX_Roles_NormalisedName");

    B.ToTable("Roles");
});

// "Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>"
ModelBuilder.Entity<RoleClaim>(B =>
{
    B.Property(P => P.ClaimId)
        .ValueGeneratedOnAdd();

    B.Property(E => E.ClaimType)
        .HasMaxLength(128);

    B.Property(E => E.ClaimValue)
        .HasMaxLength(128);

    B.Property(E => E.RoleId)
        .IsRequired();

    B.HasIndex(P => P.RoleId)
        .HasName("IX_RoleClaims_RoleId");

    B.HasOne(D => D.Claim)
        .WithMany()
        .HasForeignKey(P => P.RoleId)
        .OnDelete(DeleteBehavior.Cascade);

    B.ToTable("RoleClaims");
});

但到目前为止还没有运气。

标签: c#entity-framework-coreasp.net-core-2.0identityserver4

解决方案


看起来好像我犯了几个错误,最相关的是我实际上没有UserRole在 ApplicationDbContext 示例中为我的对象提供代码;这实际上是错误的根源......

代码仍然引用了原始IdentityRole模型:

// "Microsoft.AspNetCore.Identity.IdentityUserRole<string>"
ModelBuilder.Entity<UserRole>(E =>
{
    E.Property<int>(P => P.UserId)
        .HasColumnName("AccountId")
        .IsRequired();

    E.Property<int>("RoleId")
        .IsRequired();

    E.HasIndex("AccountId")
        .HasName("IX_RoleMap_AccountId");

    E.HasIndex("RoleId")
        .HasName("IX_RoleMap_RoleId");

    E.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")    // Argh!
        .WithMany()
        .HasForeignKey("RoleId")
        .OnDelete(DeleteBehavior.Cascade);

    E.HasOne(P => P.Account)
        .WithMany()
        .HasForeignKey("AccountId")
        .OnDelete(DeleteBehavior.Cascade);

    E.ToTable("RoleMap");
});

这已更新为引用属性,而不是使用魔术字符串来指定字段名称;这突出了我犯的错误。

// "Microsoft.AspNetCore.Identity.IdentityUserRole<string>"
ModelBuilder.Entity<UserRole>(E =>
{
    E.Property(P => P.UserId)
        .HasColumnName("AccountId")
        .IsRequired();

    E.Property(P => P.RoleId)
        .IsRequired();

    E.HasIndex(P => P.AccountId)
        .HasName("IX_RoleMap_AccountId");

    E.HasIndex(P => P.RoleId)
        .HasName("IX_RoleMap_RoleId");

    E.HasOne(P => P.Role)            // 'UserRole' does not contain a definition for 'Role'
        .WithMany()
        .HasForeignKey(P => P.RoleId)
        .OnDelete(DeleteBehavior.Cascade);

    E.HasOne(P => P.Account)
        .WithMany()
        .HasForeignKey(P => P.AccountId)
        .OnDelete(DeleteBehavior.Cascade);

    E.ToTable("RoleMap");
});

和:

public class UserRole : IdentityUserRole<int>
{
    public int AccountId
    {
        get => base.UserId;
        set => base.UserId = value;
    }

    public User Account { get; set; }
    public Role Role { get; set; }    // Addition
}

在这一点上,通知我 IdentityRole 在影子状态下运行的异常似乎已经消失并被其他人取代。


此外(而且我不是 100% 确定这导致了我看到的异常)我在 Startup->ConfigureServices 中错误地配置了我的 RoleStore。

Services.AddIdentity<User, Role>()
    .AddUserStore<CustomerUserStore>()
    .AddUserManager<CustomerManager>()
    .AddRoleStore<Role>()                // Should have been CustomerRoleStore
    .AddRoleManager<RoleManager>()
    .AddSignInManager<CustomerSignInManager>()
    .AddDefaultTokenProviders();

CustomerRoleStore还需要覆盖以允许 IdentityServer 在我的自定义之后理解角色,如下所示:

public class CustomerRoleStore
    : RoleStore<Role, ApplicationDbContext, int, UserRole, RoleClaim>
{
    public CustomerRoleStore(
        ApplicationDbContext context,
        IdentityErrorDescriber describer = null
    ) : base(
        context,
        describer
    )
    { }

    protected override RoleClaim CreateRoleClaim(Role role, Claim claim)
    {
        return new RoleClaim
        {
            RoleId = role.RoleId,
            ClaimType = claim.Type,
            ClaimValue = claim.Value
        };
    }
}

推荐阅读