在 ASP.NET 身份中添加与 ApplicationUser class 的关系(数据库优先)

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

我在 ASP.NET MVC 应用程序中使用 ASP.NET 身份(数据库优先)。我按照说明 here,使用数据库优先方法设置 ASP.NET 身份。

我的 AspNetUsers table 与员工 table 有关系(员工 table 有一个 UserId 外键,AspNetUsers 实体有一个 ICollection<Employee> 属性).

我想将 ICollection<Employee> 属性 添加到 ApplicationUser,如下所示:

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

但是,当我这样做时,我收到以下错误消息:

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.

为什么我会收到此错误消息?我该如何解决?

我无法重现该问题,即使我在另一个没有键和关系的数据库中创建 table 也是如此。所以我确定你的模型有问题。不幸的是你没有添加我可以比较的代码,所以我不能说出有什么不同并直接回答问题。我唯一能做的就是展示什么对我有用。不过,首先我要说几句。


我认为你不应该关注这篇文章。因为没有理由将上下文添加到现有数据库。

就像 Ivan Stoev 提到的,您不应该混合上下文。身份上下文旨在对用户进行身份验证。它存储凭据、用户角色和声明。其中声明旨在添加有关用户的身份信息。

事实上,ApplicationUser 模板的默认 Hometown 字段可以删除,因为它是一个身份声明,应该存储在 AspNetUserClaims table 中。不需要扩展 ApplicationUser 的东西。实际上我想不出任何扩展 ApplicationUser 的理由。

关于角色,这些并不是真正的声明,因为它们没有说明身份,而是用于授权。这就是将它们存储在 AspNetUserRoles table 中的原因。不幸的是,角色作为角色声明添加到身份中,这让事情变得混乱。

请注意,身份信息出现在声明中。这意味着应用程序不必调用标识上下文。例如。 User.IsInRole 检查当前身份的角色声明,而不是 table 中存储的角色。

关于不同的上下文,另一个上下文(我通常称之为业务模型)与身份上下文没有任何共同之处。电子邮件和其他字段不是部分,对业务模型也没有意义。您可能认为那些字段是多余的,但实际上它们不是。我可以使用 google 帐户登录,但对于企业,请使用我的工作电子邮件地址。

保持上下文分离有多种原因。

  • 关注点分离。假设您希望将来与另一个身份验证框架交换。如果您想支持单点登录 (SSO),请实施 IdentityServer。
  • 如果另一个应用程序需要相同的登录名,则不能将用户 table 移动到另一个数据库。因此,您最终还将向数据库中添加其他上下文。
  • 迁移问题。如果混合上下文,则迁移将失败。
  • 它会让事情变得更容易。这是您遇到的第一个问题,不是最后一个。

文章中也提到:

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.

如果您不应该从您的应用程序访问身份上下文,那么如何管理这些信息?

对于当前用户,您不需要访问用户 table。所有信息都存在于身份声明中。访问身份上下文的唯一原因是允许用户登录。除了用户管理。

您可以通过添加对用户(用户标识)的引用来满足要求。如果您需要在报告中显示其他用户的信息(如姓名),请在您的业务上下文中创建一个用户 table 来存储信息。您可以将关系添加到此 table,因为它是同一上下文的一部分。

如果您对此方法有任何疑问,请告诉我。


现在是适合我的代码。就像其他人提到的那样,添加行的可能性不大:

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

是原因。如果没有 virtual 关键字,我认为它甚至会被忽略(保持为空)。

当我按照文章的步骤进行操作时,我得到了以下模型:

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

然后我添加 Employee class 并取消注释上面 ApplicationUser class 中的行:

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

    public string Name { get; set; }

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

    public string ApplicationUserId { get; set; }
}

在数据库中我添加了 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]

您可以使用 [ForeignKey] 属性来使用不同的字段名称。

您可以试试这个,或者选择将两个上下文分开。

关注:


我很清楚你的负担在这里。是的,微软,一个深奥的邪教,在提供与身份建立关系的信息方面做得很差(Entity Framework)。

贡献:
Ruard van Elburg post 于 8 月 24 日在 16:31 上对此事给出了很好的见解;但是,我注意到他的代码中缺少一个关键组件,即需要放置在 IdentityModels 的 DBContext 中的 DbSet。

技术堆栈:
我提供我的技术堆栈,这样如果这不适用于旧版本的软件,您就会知道我用什么来解决这个问题。

  • Visual Studio 2017 MVC 5。仅供参考,MVC 5 内置于最新的 VS 中。
  • SQL 服务器 17
  • MS SQL 管理工作室 17


解决方案:


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

在这里,我提供了有关如何执行此操作的演练。请确保您的代码顶部有所有依赖项。

第 1 步:添加 public virtual DbSet<ModelNameOfInterest> ModelNameOfInterest { get; set; }public class ApplicationDbContext : IdentityDbContext<ApplicationUser>{} 如下面的代码所示。

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



    }
}

第 2 步:将 public virtual ApplicationUser ApplicationUser { get; set; } 添加到您要与其建立关系的模型,如下面的代码所示。

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

第 3 步:如果您为数据库设置了连接字符串,则需要生成迁移。包管理器控制台路径:工具->NuGet Packer 管理器->包管理器控制台

  • 如果根目录中没有迁移文件夹,则启用迁移:在 PM> 后键入 Enable-Migrations 您应该会看到一个包含两个文件的迁移文件夹。
  • 启用迁移后:在 PM> 后键入 Update-Database 您现在应该会在数据库中看到 table。
  • 要添加另一个迁移:在 PM> 之后键入 Add-MigrationName: 之后键入 InitialCreateYour model of interest 您应该会看到 table现在在你的数据库中。您现在应该在数据库中看到 tables。


第 4 步:仔细检查感兴趣的模型的外键是否正确引用了 AspNetUser table。在 MS Management Studio 中,您可以创建关系图来显示引用。您可以在 google 上找到如何执行此操作。

第 5 步:一如既往地保持冷静、冷静和镇定。