locked
Extending Identity causes error on Migration RRS feed

  • Question

  • User1374623307 posted

    Hello,

        I am working on implementing Identity into a project.  So following the article (https://docs.microsoft.com/en-us/aspnet/core/security/authentication/customize-identity-model?view=aspnetcore-3.1), I copied and pasted the information from Change Table/column names and facets into my ApplicationDbContext and made the modifications to the table names.  I have also referenced my Users class which extends the IdentityUser.  So I have the following:

    User.cs:

    public class Users : IdentityUser<Guid>
        {
            [Required]
            [Display(Name = "First Name")]
            [MaxLength(50)]
            public string FirstName { get; set; }
    
            [Display(Name ="Middle Initial")]
            [MaxLength(1)]
            public string MiddleInitial { get; set; }
    
            [Required]
            [Display(Name ="Last Name")]
            [MaxLength(50)]
            public string LastName { get; set; }
    
            [Display(Name ="Suffix")]
            [MaxLength(4)]
            public string Suffix { get; set; }
        }

    ApplicationDbContext.cs

    public class ApplicationDbContext : IdentityDbContext<Users, IdentityRole<Guid>, Guid>
        {
            public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
                : base(options)
            {
            }
            public DbSet<Movie> Movie { get; set; }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
    
                modelBuilder.Entity<Users>(b =>
                {
                    b.ToTable("ApplicationUsers");
                });
    
                modelBuilder.Entity<IdentityUserClaim<string>>(b =>
                {
                    b.ToTable("ApplicationUserClaims");
                });
    
                modelBuilder.Entity<IdentityUserLogin<string>>(b =>
                {
                    b.ToTable("ApplicationUserLogins");
                });
    
                modelBuilder.Entity<IdentityUserToken<string>>(b =>
                {
                    b.ToTable("ApplicationUserTokens");
                });
    
                modelBuilder.Entity<IdentityRole>(b =>
                {
                    b.ToTable("ApplicationRoles");
                });
    
                modelBuilder.Entity<IdentityRoleClaim<string>>(b =>
                {
                    b.ToTable("ApplicationRoleClaims");
                });
    
                modelBuilder.Entity<IdentityUserRole<string>>(b =>
                {
                    b.ToTable("ApplicationUserRoles");
                });
            }
        }

    Error:

    Build started...
    Build succeeded.
    System.InvalidOperationException: The entity type 'IdentityUserRole<string>' requires a primary key to be defined. If you intended to use a keyless entity type call 'HasNoKey()'.
       at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidateNonNullPrimaryKeys(IModel model, IDiagnosticsLogger`1 logger)
       at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
       at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
       at Microsoft.EntityFrameworkCore.SqlServer.Internal.SqlServerModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
       at Microsoft.EntityFrameworkCore.Metadata.Conventions.ValidatingConvention.ProcessModelFinalized(IConventionModelBuilder modelBuilder, IConventionContext`1 context)
       at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelFinalized(IConventionModelBuilder modelBuilder)
       at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnModelFinalized(IConventionModelBuilder modelBuilder)
       at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.FinalizeModel()
       at Microsoft.EntityFrameworkCore.ModelBuilder.FinalizeModel()
       at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder)
       at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder)
       at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
       at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
       at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__7_3(IServiceProvider p)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
       at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
       at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
       at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
       at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
       at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
       at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
       at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService[TService](IInfrastructure`1 accessor)
       at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
       at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(Func`1 factory)
       at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType)
       at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType)
       at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType)
       at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_0.<.ctor>b__0()
       at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0()
       at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
    The entity type 'IdentityUserRole<string>' requires a primary key to be defined. If you intended to use a keyless entity type call 'HasNoKey()'.

    If I comment out the ApplicationDbContext as such the migration will pass, but it does not give me the tables names that I am looking for, I end up with the silly default names for those tables.

    public class ApplicationDbContext : IdentityDbContext<Users, IdentityRole<Guid>, Guid>
        {
            public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
                : base(options)
            {
            }
            public DbSet<Movie> Movie { get; set; }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
    
                modelBuilder.Entity<Users>(b =>
                {
                    b.ToTable("ApplicationUsers");
                });
    
                modelBuilder.Entity<IdentityUserClaim<string>>(b =>
                {
                    b.ToTable("ApplicationUserClaims");
                });
    
                //modelBuilder.Entity<IdentityUserLogin<string>>(b =>
                //{
                //    b.ToTable("ApplicationUserLogins");
                //});
    
                //modelBuilder.Entity<IdentityUserToken<string>>(b =>
                //{
                //    b.ToTable("ApplicationUserTokens");
                //});
    
                modelBuilder.Entity<IdentityRole>(b =>
                {
                    b.ToTable("ApplicationRoles");
                });
    
                modelBuilder.Entity<IdentityRoleClaim<string>>(b =>
                {
                    b.ToTable("ApplicationRoleClaims");
                });
    
                //modelBuilder.Entity<IdentityUserRole<string>>(b =>
                //{
                //    b.ToTable("ApplicationUserRoles");
                //});
            }
        }

    If I do not have that modelBuilder section in there at all it works perfectly fine without the table names I am looking for.  Though I just noticed that looking at the migration, this could be a potential bug.

    As you may notice in my ApplicationDbContext for IdentityRole I have entity.ToTable("ApplicationRoles").  Looking in the Migration Up I can see that it has a table for ApplicationRoles with a string for the Id, while a little further down it has AspNetRoles with a Guid for the Id.

    protected override void Up(MigrationBuilder migrationBuilder)
            {
                migrationBuilder.CreateTable(
                    name: "ApplicationRoleClaims",
                    columns: table => new
                    {
                        Id = table.Column<int>(nullable: false)
                            .Annotation("SqlServer:Identity", "1, 1"),
                        RoleId = table.Column<string>(nullable: true),
                        ClaimType = table.Column<string>(nullable: true),
                        ClaimValue = table.Column<string>(nullable: true)
                    },
                    constraints: table =>
                    {
                        table.PrimaryKey("PK_ApplicationRoleClaims", x => x.Id);
                    });
    
                //This follows the modelBuilder.Entity<IdentityRole>(b => {b.ToTable("ApplicationRoles");}); in naming alone, table built incorrectly.
    
                migrationBuilder.CreateTable(
                    name: "ApplicationRoles",
                    columns: table => new
                    {
                        Id = table.Column<string>(nullable: false),
                        Name = table.Column<string>(nullable: true),
                        NormalizedName = table.Column<string>(nullable: true),
                        ConcurrencyStamp = table.Column<string>(nullable: true)
                    },
                    constraints: table =>
                    {
                        table.PrimaryKey("PK_ApplicationRoles", x => x.Id);
                    });
    
                // Trimmed for space
    
                migrationBuilder.CreateTable(
                    name: "ApplicationUsers",
                    columns: table => new
                    {
                        Id = table.Column<Guid>(nullable: false),
                        UserName = table.Column<string>(maxLength: 256, nullable: true),
                        NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
                        Email = table.Column<string>(maxLength: 256, nullable: true),
                        NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
                        EmailConfirmed = table.Column<bool>(nullable: false),
                        PasswordHash = table.Column<string>(nullable: true),
                        SecurityStamp = table.Column<string>(nullable: true),
                        ConcurrencyStamp = table.Column<string>(nullable: true),
                        PhoneNumber = table.Column<string>(nullable: true),
                        PhoneNumberConfirmed = table.Column<bool>(nullable: false),
                        TwoFactorEnabled = table.Column<bool>(nullable: false),
                        LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
                        LockoutEnabled = table.Column<bool>(nullable: false),
                        AccessFailedCount = table.Column<int>(nullable: false),
                        FirstName = table.Column<string>(maxLength: 50, nullable: false),
                        MiddleInitial = table.Column<string>(maxLength: 1, nullable: true),
                        LastName = table.Column<string>(maxLength: 50, nullable: false),
                        Suffix = table.Column<string>(maxLength: 4, nullable: true)
                    },
                    constraints: table =>
                    {
                        table.PrimaryKey("PK_ApplicationUsers", x => x.Id);
                    });
    
                //This does not follow the modelBuilder.Entity<IdentityRole>(b => {b.ToTable("ApplicationRoles");}); but builds out the table correctly.
                migrationBuilder.CreateTable(
                    name: "AspNetRoles",
                    columns: table => new
                    {
                        Id = table.Column<Guid>(nullable: false),
                        Name = table.Column<string>(maxLength: 256, nullable: true),
                        NormalizedName = table.Column<string>(maxLength: 256, nullable: true),
                        ConcurrencyStamp = table.Column<string>(nullable: true)
                    },
                    constraints: table =>
                    {
                        table.PrimaryKey("PK_AspNetRoles", x => x.Id);
                    });
    
                // Trimmed for space
            }

    Am I right that this might be buggy and I did what the docs call for, but the migration is finding it's own errors.  Or did I miss something that is preventing this from using the slightly modified table names, one extended class, and the rest being stock options?

    Tuesday, June 9, 2020 3:17 AM

Answers

  • User1374623307 posted

    Well I figured out what was wrong and will post it here in case anyone else has the issue.  My ApplicationDBContext was indeed wrong, but that also lies in fact that the understanding of the reference page had me messed up.  Putting string next to the Identity class was claiming that the representing key of the other table was a string.  As my IdentityUser key was a Guid that was causing the issue.  As the IdentityRole was a Guid, I do not know why it worked with string there.  So I changed the items to Guid and it worked perfectly.

    public class ApplicationDbContext : IdentityDbContext<Users, Roles, Guid>
        {
            public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
                : base(options)
            {
            }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
    
                modelBuilder.Entity<Users>(b =>
                {
                    b.ToTable("ApplicationUsers");
                });
    
                modelBuilder.Entity<IdentityUserClaim<Guid>>(b =>
                {
                    b.ToTable("ApplicationUserClaims");
                });
    
                modelBuilder.Entity<IdentityUserLogin<Guid>>(b =>
                {
                    b.ToTable("ApplicationUserLogins");
                });
    
                modelBuilder.Entity<IdentityUserToken<Guid>>(b =>
                {
                    b.ToTable("ApplicationUserTokens");
                });
    
                modelBuilder.Entity<Roles>(b =>
                {
                    b.ToTable("ApplicationRoles");
                });
    
                modelBuilder.Entity<IdentityRoleClaim<Guid>>(b =>
                {
                    b.ToTable("ApplicationRoleClaims");
                });
    
                modelBuilder.Entity<IdentityUserRole<Guid>>(b =>
                {
                    b.ToTable("ApplicationUserRoles");
                });
            }
        }

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, June 9, 2020 5:16 PM