Entity type 'Microsoft.AspNetCore.Identity.IdentityRole' is in shadow-state

asp.net-core-2.0 c# entity-framework-core identityserver4

Question

I've been playing around with this quickstart example & have been trying to see how far I can customise the database (I have an existing database I've been half-trying to replicate).

I've managed to trigger the exception below and am having trouble fixing it, partially because I don't understand what the message is telling me.

InvalidOperationException: Entity type 'Microsoft.AspNetCore.Identity.IdentityRole' is in shadow-state. A valid model requires all entity types to have corresponding CLR type.

My ApplicationDbContext is as follows:

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 */
        }
    }
}

The entities that correspond with these DbSets are as follows:

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; }
    }
}

I have read the posts "What does it mean for an entity type to be in “shadow state”?" and "Entity type 'type' is in shadow-state. A valid model requires all entity types to have corresponding CLR type"

Judging from the documentation I think that I may have missed, or mis-referenced, the Role entity somewhere, but it's not clear to me where.

Thanks in advance!


Edit:

Re-reading the shadow property documentation, the line "By convention, shadow properties are only created when a relationship is discovered but no foreign key property is found in the dependent entity class. In this case, a shadow foreign key property will be introduced." seems to support that I've mucked up the entity.

I've tried to rule out a property name mismatch by changing all property references in the Role and RoleClaim ModelBuilder entity declarations to expressions, to the following to see if hard referencing will help:

// "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");
});

But no luck so far.

1
2
5/23/2018 11:04:57 AM

Popular Answer

It appears as though I'd made several mistakes, the most pertinent is that I hadn't actually provided code for my UserRole object within the ApplicationDbContext sample; this is actually where the error was originating...

The code still referenced the original IdentityRole model:

// "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");
});

This was updated to reference properties, instead of using magic strings to specify the field names; which highlighted the error I'd made.

// "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");
});

And:

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
}

At this point the exception informing me that IdentityRole was operating in a shadow-state seems to have disappeared and been replaced by others.


Additionally (and I'm not 100% sure that this contributed to the exception I was seeing) I had misconfigured my RoleStore within Startup->ConfigureServices.

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

CustomerRoleStore also required an override to allow IdentityServer to understand the roles after my customisations, which looks as so:

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
        };
    }
}
1
5/17/2018 11:36:17 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow