ThunD3eR
ThunD3eR

Reputation: 3456

EntityType ''IdentityUserRole' has no key defined error

Working with entity framework code first.

Trying to use the IdentityRole id as a foreign key to a custom class.

the relationship is correct but when I try to add data to my custom table i get this error.

EntityType 'IdentityUserRole' has no key defined. Define the key for this EntityType. IdentityUserRoles: EntityType: EntitySet 'IdentityUserRoles' is based on type 'IdentityUserRole' that has no keys defined.

Code:

Custom table/class

public class UserRoleAccess
{
    [Key]
    public long UserRoleAccessId { get; set; }

    public string UserRoleId { get; set; }

    public virtual ApplicationRole ApplicationRole { get; set; }

    public bool IsActive { get; set; }

    public string Name { get; set; }

    public int UserRoleAccessType { get; set; }
}

IdentityRole:

public class ApplicationRole : IdentityRole
{
    public virtual ICollection<UserRoleAccess> UserRoleAccess { get; set; }
}

Bindings:

    protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();


        modelBuilder.Entity<IdentityUser>().ToTable("Users").Property(p => p.Id).HasColumnName("UserId");
        modelBuilder.Entity<ApplicationUser>().ToTable("Users").Property(p => p.Id).HasColumnName("UserId");
        modelBuilder.Entity<IdentityUserRole>().ToTable("UserRoles");
        modelBuilder.Entity<IdentityUserLogin>().ToTable("UserLogins");
        modelBuilder.Entity<IdentityUserClaim>().ToTable("UserClaims");
        modelBuilder.Entity<IdentityRole>().ToTable("Roles");
        modelBuilder.Entity<ApplicationRole>().ToTable("Roles");
        modelBuilder.Entity<UserRoleAccess>().ToTable("UserRoleAccess");

        modelBuilder.Entity<ApplicationUser>().HasRequired(p => p.Person)
            .WithMany(b => b.Users);

        modelBuilder.Entity<ApplicationUser>().HasRequired(p => p.Person)
            .WithMany(b => b.Users)
            .HasForeignKey(p => p.PersonId);

        modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
        modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
        modelBuilder.Entity<ApplicationRole>().HasKey<string>(r => r.Id);
        modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });


    }

Relationship in db(correct):

enter image description here

Tried creating an id property and setting a [Key] attribute but it did not work:

public class ApplicationRole : IdentityRole
{
    [Key]
    public string Id { get; set; }

    public virtual ICollection<UserRoleAccess> UserRoleAccess { get; set; }
}

Run this code when adding data:

    public static void CreateUserRoleAccess(string name, int type, string id)
    {

        using (var context = new UserRoleAccessContext())
        {
            var userRoleAccess = new UserRoleAccess();

            userRoleAccess.ApplicationRole = new ApplicationRole { Id = id };

            userRoleAccess.UserRoleId = id;
            userRoleAccess.IsActive = true;

            userRoleAccess.UserRoleAccessType = type;

            userRoleAccess.Name = name;

            context.UserRoleAccess.Add(userRoleAccess);

            context.SaveChanges();
        }
    }

What am I missing?

EDIT:

IdentityRole class:

IdentityRole

Upvotes: 3

Views: 5428

Answers (1)

Diana
Diana

Reputation: 2226

So, you are using MVC 5 with ASP.NET Identity 2.0. I guess your project has the Nuget package for Microsoft.AspNet.Identity.EntityFramework and the right tables have been created in your database.

Perhaps your problem is because you are redefining the mappings for the standard Identity tables. Do you really need to map explicitly the table names and keys? I would try to keep the original mappings, and only add explicit mappings for your custom entities and fields. I think you don't need the ToTable mappings, as the base entities have a mapping somewhere in its code, and your entities extend them.

A couple of questions:

  • What is the meaning of UserRoleAccess.UserRoleId? Is it a user id, or a role id? If it is meant to contain the UserRole id, it is a composed key, not a single value, so it is not likely that a String property would do. Perhaps you should rename it.

  • What do you want to do exactly in the method CreateUserRoleAccess: adding a new UserRoleAccess for a role and user that already exist and are already related (that is, the UserRole relation already exists)? Or either adding a new UserRole and UserRoleAccess for a user and role that already exist but are not related? I assume you do not want to create the Role or the User at this point, they already exist, am I wrong?

  • Anyway, in your CreateUserRoleAccess method, in this line:

    userRoleAccess.ApplicationRole = new ApplicationRole { Id = id };
    

    Unless you are creating a new Role in this same moment you should do a Find or other Linq query and get the existing Role from the context. Perhaps just changing this would make your code work:

    //userRoleAccess.ApplicationRole = new ApplicationRole { Id = id };
    userRoleAccess.ApplicationRole = context.Roles.Find(id) as ApplicationRole;
    

I created a MVC project and tried your code with several changes:

I did not include any explicit EF bindings or mappings, just to try with the original Identity model and see if it works.

I extended Identity DbContext just adding a DbSet for UserRoleAccess:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public virtual IDbSet<UserRoleAccess> UserRoleAccesses { get; set; }

    public ApplicationDbContext()
        : base("DefaultConnection", throwIfV1Schema: false)
    {
    }

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }
}  

After adding your custom entities ApplicationRole and UserRoleAccess, generating a migration and updating the database I got the same UserRoleAccess table than you, but also a new column Discriminator in the Roles table:

enter image description here

This means EF is using TPH (Table Per Hierarchy) when you extend the IdentityRole entity with your ApplicationRole entity. They share the table. This is implicit, no need to write bindings (in fact, unneeded explicit bindings tend to interfere).

So, if your roles table is missing this field this can be a probable cause for you error.

I run the MVC web, and created a new user with the Register link in the page header. I run the following code (by adding it to the index controller), and it worked:

    public void CreateUserRoleAccess(string name, int type, string id)
    {
        //Insert role (run only once)
        using (var context = new ApplicationDbContext())
        {
            var role = new ApplicationRole { Id = id, Name = "Role1" };
            var user = context.Users.FirstOrDefault<ApplicationUser>();
            //user.Roles.Add(role);
            role.Users.Add(new IdentityUserRole { RoleId = role.Id, UserId = user.Id });

            context.Roles.Add(role);
            context.SaveChanges();
        }

        using (var context = new ApplicationDbContext())
        {
            var userRoleAccess = new UserRoleAccess();

            //userRoleAccess.ApplicationRole = new ApplicationRole { Id = id };
            userRoleAccess.ApplicationRole = context.Roles.Find(id) as ApplicationRole;

            //Is this a userId or a roleId? Does not seem OK to assign id param here
            userRoleAccess.UserRoleId = id;
            userRoleAccess.IsActive = true;

            userRoleAccess.UserRoleAccessType = type;

            userRoleAccess.Name = name;

            context.UserRoleAccesses.Add(userRoleAccess);

            context.SaveChanges();
        }
    }

Note that I create first a new Role with a different context scope, to make sure the Role exists when I get to the interesting code. I use that role later getting it from the context.

Hope this helps. I can send you the whole solution code if you want.

EDIT - UserRoleAccess table:

UserRoleAccess table

        CreateTable(
            "dbo.UserRoleAccesses",
            c => new
                {
                    UserRoleAccessId = c.Long(nullable: false, identity: true),
                    UserRoleId = c.String(),
                    IsActive = c.Boolean(nullable: false),
                    Name = c.String(),
                    UserRoleAccessType = c.Int(nullable: false),
                    ApplicationRole_Id = c.String(maxLength: 128),
                })
            .PrimaryKey(t => t.UserRoleAccessId)
            .ForeignKey("dbo.AspNetRoles", t => t.ApplicationRole_Id)
            .Index(t => t.ApplicationRole_Id);

Useful links:

Introduction to ASP.NET Identity - I created the simple MVC solution using this info.

ASP.NET Identity, extract from book Pro ASP.NET MVC 5

Upvotes: 2

Related Questions