Add relationships to the ApplicationUser class in ASP.NET Identity (Database First)

依然范特西╮ 提交于 2019-12-01 23:43:02

问题


I'm using ASP.NET Identity (Database First) in my ASP.NET MVC application. I followed the instructions here, to set up the ASP.NET Identity with database first approach.

My AspNetUsers table has a relationship with the Employee table (The Employee table has a UserId foreign key, and the AspNetUsers entity has an ICollection<Employee> property).

I would like to add the ICollection<Employee> property to the ApplicationUser, like below:

public class ApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
    public ICollection<Employee> Employees { get; set; }

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, int> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        return userIdentity;
    }
}

But, when I do that, I get the following error message:

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

Why am I getting this error message? How can I fix that?


回答1:


I cannot reproduce the issue, even when I create the tables in another database without keys and relations. So I'm sure that there is a problem with your model. Unfortunately you didn't add code which I can compare, so I can't tell what is different and answer the question directly. The only thing I can do is to show what works for me. However, first I have some remarks.


I think you shouldn't follow the article. As there is no reason to add the context to an existing database.

Like Ivan Stoev mentioned you are not supposed to mix contexts. The Identity context is meant to authenticate the user. It stores the credentials, the roles of the user and claims. Where claims are meant to add identity information about the user.

In fact, the default Hometown field of the ApplicationUser template can be removed, as it is an identity claim which should be stored in the AspNetUserClaims table. Not something you need to extend the ApplicationUser for. Actually I can't think of any reason to extend the ApplicationUser.

About the roles, these are not really claims, as they tell nothing about the identity but rather are used for authorization. That's why it's fine that they are stored in the AspNetUserRoles table. Unfortunately roles are added to the identity as role claims, which makes things confusing.

Please note that the Identity information is present in the claims. This means that the application doesn't have to call the Identity context. E.g. User.IsInRole checks the role claims of the current identity, not the roles stored in the table.

About the different contexts, the other context (which I usually call the business model) has nothing in common with the Identity context. Email and other fields are not part, nor have meaning to the business model. You may think that those fields are redundant, but in fact they are not. I could login using a google account, but for the business use my work email address.

There are several reasons to keep the context seperated.

  • Seperation of concerns. Suppose you want to exchange the authentication framework in the future with another one. Like implement IdentityServer in case you want to support single sign-on (SSO).
  • You can't move the users table to another database if another application needs the same logins. So you'll end up adding other contexts as well to the database.
  • Trouble with migrations. If you mix the contexts then migrations will fail.
  • It'll makes things far more easier. This is the first problem you've encountered, not the last.

As also mentioned in the article:

At this point if you need to add any relationships (E.g. foreign keys) from your own tables to these tables you are welcome to do so but do not modify any of the Entity Framework 2.0 tables directly or later on any of their POCO classes. Doing so will result in errors based upon feedback I’ve received.

So how to manage the information if you shouldn't access the identity context from your application?

For the current user you don't need to access the users table. All the information is present in the identity claims. The only reason to access the identity context is to allow a user to login. Besides user management.

You can suffice by adding a reference to the user (the userid). If you need to show information of other users (like name) in a report, then create a user table in your business context to store the information. You can add relations to this table, as it is part of the same context.

Please let me know if you have questions about this approach.


Now the code that works for me. Like others have mentioned, it is not likely that adding the line:

public ICollection<Employee> Employees { get; set; }

is the cause. Without the virtual keyword I think it is even ignored (remains null).

When I follow the steps of the article then I end up with the following model:

public class ApplicationUser : IdentityUser
{
    public string Hometown { get; set; }

    //public virtual ICollection<Employee> Employees { get; set; }

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        return userIdentity;
    }
}

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base("DefaultConnection", throwIfV1Schema: false)
    {
        // Disable migrations
        //Database.SetInitializer<ApplicationDbContext>(null);
    }

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

And then I add the Employee class and uncomment the line in the ApplicationUser class above:

public class Employee
{
    public int Id { get; set; }

    public string Name { get; set; }

    //public virtual ApplicationUser ApplicationUser { get; set; }

    public string ApplicationUserId { get; set; }
}

In the database I added the table:

CREATE TABLE [dbo].[Employees](
    [Id] [int] NOT NULL,
    [Name] [varchar](50) NOT NULL,
    [ApplicationUserId] [nvarchar](128) NOT NULL,
PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

You can use the [ForeignKey] attribute to use a different field name.

You can try this or choose to keep both contexts seperated instead.




回答2:


Concern:


I know exactly what your burden is here. Yes, Microsoft, an esoteric cult, did very poor job in providing the information to this matter that is creating a relationship with the Identity (Entity Framework).

Contribution:
Ruard van Elburg post on Aug 24 at 16:31 gives good insight on this matter; however, there was one key component that I noticed was missing in his code which was DbSet that needed to be placed in the DBContext of IdentityModels.

Tech Stack:
I provide my tech stack so that if this does not work with older versions of software, you will know what I used to get this issue resolved.
  • Visual Studio 2017 MVC 5. FYI, MVC 5 is built in into most recent VS.
  • SQL Server 17
  • MS SQL Management Studio 17


Solution:


Disclaimer!!! I understand that the concern is for database first; however, this solution is only for code first approach. But hey, it works!

Here I provide a walk through on how to do this. Please make sure you have all the dependencies in top margin of your code.

Step 1: Add public virtual DbSet<ModelNameOfInterest> ModelNameOfInterest { get; set; } to public class ApplicationDbContext : IdentityDbContext<ApplicationUser>{} as seen in code below.

using System.Data.Entity;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
    using System.ComponentModel.DataAnnotations.Schema;

namespace AwesomeCode.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {

        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }
    }

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
        }
        //A virtul DbSet in order to interact with the autogenerated code the identity framewrok produces.
        public virtual DbSet<ModelNameOfInterest> ModelNameOfInterest { get; set; }

        public static ApplicationDbContext Create()
        {

            return new ApplicationDbContext();
        }



    }
}

Step 2: Add public virtual ApplicationUser ApplicationUser { get; set; } to your model that you want to create a relationship with as seen code below.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;

namespace AwesomeCode.Models
{
    public class WorkExp
    {
        [Key]
        public int Id { get; set; }
        public string JobTitle { get; set; }

        //Create foreign key with reference to ApplicationUser_Id that was auto-generated by entity framework.
        public virtual ApplicationUser ApplicationUser { get; set; }
    }
}

Step 3: Given that you set up your connection string for your database, you need to produce a migration. Path to Package Manager Console: Tools->NuGet Packer Manager->Package Manager Console

  • Enable a migration if migration folder is absent in root: After PM>, type Enable-Migrations you should see a migration folder with two files.
  • Once migration is enabled: After PM>, type Update-Database You should see tables in your database now.
  • To add another migration: After PM>, type Add-Migration After Name:, type InitialCreate or Your model of interest You should see tables in your database now. You should see tables in your database now.


Step 4: Double check that the model of interest's foreign key is properly referenced to the AspNetUser table. In MS Management Studio, you can create a relational diagram to show the references. You can find how to do that on google.

Step 5: As always stay cool, calm, and collected.



来源:https://stackoverflow.com/questions/51934680/add-relationships-to-the-applicationuser-class-in-asp-net-identity-database-fir

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!