将关系添加到ASP.NET Identity(数据库优先)中的ApplicationUser类

时间:2018-08-20 16:09:58

标签: c# asp.net-mvc asp.net-identity-2 ef-database-first

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

我的AspNetUsers表与Employee表有关系(Employee表具有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'没有定义键。定义密钥   此EntityType。 AspNetUserLogins:EntityType:EntitySet   “ AspNetUserLogins”基于类型“ AspNetUserLogins”,没有   键已定义。

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

2 个答案:

答案 0 :(得分:5)

即使我在没有键和关系的另一个数据库中创建表,也无法重现该问题。因此,我确定您的模型有问题。不幸的是,您没有添加我可以比较的代码,因此我无法分辨出不同之处并直接回答问题。我唯一能做的就是展示对我有用的东西。但是,首先我要说一些话。


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

就像伊万·斯托耶夫(Ivan Stoev)提到的那样,您不应混合使用上下文。身份上下文旨在对用户进行身份验证。它存储凭据,用户角色和声明。声明旨在添加有关用户的身份信息。

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

关于角色,这些并不是真正的要求,因为它们不提供任何有关身份的信息,而是用于授权。这就是将它们存储在AspNetUserRoles表中的原因。不幸的是,角色作为角色声明被添加到身份中,这使事情变得混乱。

请注意,身份信息存在于权利要求中。这意味着应用程序不必调用Identity上下文。例如。 User.IsInRole检查当前身份的角色声明,而不是表中存储的角色。

关于不同的上下文,另一个上下文(我通常将其称为业务模型)与Identity上下文没有任何共同之处。电子邮件和其他字段不属于业务模式,也没有意义。您可能会认为这些字段是多余的,但实际上并非如此。我可以使用Google帐户登录,但对于公司,请使用我的工作电子邮件地址。

有几个原因可以使上下文分开。

  • 关注点分离。假设您将来希望与另一个交换认证框架。像实施IdentityServer一样,以防您想要支持单点登录(SSO)。
  • 如果另一个应用程序需要相同的登录名,则不能将users表移动到另一个数据库。因此,您最终还将向数据库中添加其他上下文。
  • 迁移时遇到问题。如果您混合使用上下文,则迁移将失败。
  • 这将使事情变得更加轻松。这是您遇到的第一个问题,而不是最后一个。

如文章中所述:

  

此时,如果您需要添加任何关系(例如外键)   从您自己的表到这些表,欢迎您这样做,但是   不要直接或稍后修改任何Entity Framework 2.0表   他们的任何POCO课程。这样做会基于以下原因导致错误   我收到的反馈。

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

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

您只需添加对用户的引用(用户ID)即可。如果您需要在报表中显示其他用户的信息(例如姓名),请在您的业务上下文中创建一个用户表来存储信息。您可以向该表添加关系,因为它是同一上下文的一部分。

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


现在适合我的代码。像其他人提到的那样,不太可能添加以下行:

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

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

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

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类并取消注释上面ApplicationUser类中的行:

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

    public string Name { get; set; }

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

    public string ApplicationUserId { get; set; }
}

在数据库中,我添加了表:

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]属性使用其他字段名称。

您可以尝试这样做,也可以选择将两个上下文分开。

答案 1 :(得分:0)

关注点:


我确切地知道您的负担在这里。是的,深奥的微软公司在提供与身份(实体框架)建立关系的信息方面做得很差。

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

技术堆栈:
我提供了技术堆栈,以便在旧版本的软件中不起作用时,您将知道我用来解决此问题的方式。

  • Visual Studio 2017 MVC5。仅供参考,最新的VS中内置了MVC 5。
  • SQL Server 17
  • MS SQL Management Studio 17


解决方案:


  

免责声明!!!!我了解到首先要关注数据库。但是,此解决方案仅适用于代码优先方法。但是,嘿,它有效!

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

步骤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,您现在应该在数据库中看到表。
  • 要添加另一个迁移,请执行以下操作:在PM>之后,键入Add-MigrationName:之后,键入InitialCreateYour model of interest您现在应该在数据库中看到表。您现在应该在数据库中看到表。


步骤4:仔细检查感兴趣的模型的外键是否正确引用了AspNetUser表。在MS Management Studio中,您可以创建一个关系图以显示参考。您可以在Google上找到操作方法。

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