识模型自定义

在 ASP.NET Core 中的标识模型自定义

通过Arthur Vickers

ASP.NET Core 标识提供了一个框架,用于管理和存储在 ASP.NET Core 应用中的用户帐户。 标识添加到项目时单个用户帐户选择作为身份验证机制。 默认情况下,标识,可以使用的 Entity Framework (EF) Core 数据模型。 本文介绍如何自定义的身份标识模型。

标识和 EF Core 迁移

之前检查模型,它是有助于了解如何标识配合EF Core 迁移创建和更新数据库。 最高级别的过程是:

  1. 定义或更新代码中的数据模型
  2. 添加迁移,以将此模型转换为可以应用到数据库的更改。
  3. 检查迁移正确表示你的意图。
  4. 将应用迁移来更新要与模型同步的数据库。
  5. 重复步骤 1 至 4 以进一步优化模型并使数据库保持同步。

使用以下方法之一来添加和应用迁移:

  • 程序包管理器控制台(PMC) 窗口,如果使用 Visual Studio。 有关详细信息,请参阅EF Core PMC 工具
  • 使用命令行在.NET Core CLI。 有关详细信息,请参阅EF Core.NET 命令行工具
  • 单击应用迁移时运行该应用程序错误页上的按钮。

ASP.NET Core 具有开发时间错误页处理程序。 当应用运行时,该处理程序可以将应用迁移。 生产应用程序通常从迁移生成 SQL 脚本和部署数据库更改受控制的应用程序和数据库部署的一部分。

创建新的应用使用标识时,已经完成上述步骤 1 和 2。 也就是说,初始数据模型已存在,并已向项目添加初始迁移。 初始迁移仍需要将应用到数据库。 可以通过以下方法之一应用初始迁移:

  • 运行Update-Database在 PMC 中。
  • 运行dotnet ef database update命令行界面中。
  • 单击应用迁移时运行该应用程序错误页上的按钮。

根据对模型进行更改,请重复前面的步骤。

标识模型

实体类型

标识模型包含以下实体类型。

实体类型 描述
User 表示的用户。
Role 表示一个角色。
UserClaim 表示一个用户拥有的声明。
UserToken 表示用户的身份验证令牌。
UserLogin 将用户与登录名相关联。
RoleClaim 表示一个角色内的所有用户授予的声明。
UserRole 联接实体相关联的用户和角色。

实体的类型关系

实体类型彼此相关的以下方面:

  • 每个User都可以具有许多UserClaims
  • 每个User都可以具有许多UserLogins
  • 每个User都可以具有许多UserTokens
  • 每个Role都可以具有许多关联RoleClaims
  • 每个User都可以具有许多关联Roles,和每个Role可与许多关联Users 这是需要在数据库中的联接表的多对多关系。 联接表由UserRole实体。

默认模型配置

标识定义许多上下文类继承自DbContext若要配置和使用模型。 进行此配置使用EF Core 代码 First Fluent APIOnModelCreating上下文类的方法。 默认配置是:

builder.Entity<TUser>(b =>
{
    // Primary key
    b.HasKey(u => u.Id);

    // Indexes for "normalized" username and email, to allow efficient lookups
    b.HasIndex(u => u.NormalizedUserName).HasName("UserNameIndex").IsUnique();
    b.HasIndex(u => u.NormalizedEmail).HasName("EmailIndex");

    // Maps to the AspNetUsers table
    b.ToTable("AspNetUsers");

    // A concurrency token for use with the optimistic concurrency checking
    b.Property(u => u.ConcurrencyStamp).IsConcurrencyToken();

    // Limit the size of columns to use efficient database types
    b.Property(u => u.UserName).HasMaxLength(256);
    b.Property(u => u.NormalizedUserName).HasMaxLength(256);
    b.Property(u => u.Email).HasMaxLength(256);
    b.Property(u => u.NormalizedEmail).HasMaxLength(256);

    // The relationships between User and other entity types
    // Note that these relationships are configured with no navigation properties

    // Each User can have many UserClaims
    b.HasMany<TUserClaim>().WithOne().HasForeignKey(uc => uc.UserId).IsRequired();

    // Each User can have many UserLogins
    b.HasMany<TUserLogin>().WithOne().HasForeignKey(ul => ul.UserId).IsRequired();

    // Each User can have many UserTokens
    b.HasMany<TUserToken>().WithOne().HasForeignKey(ut => ut.UserId).IsRequired();

    // Each User can have many entries in the UserRole join table
    b.HasMany<TUserRole>().WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
});

builder.Entity<TUserClaim>(b =>
{
    // Primary key
    b.HasKey(uc => uc.Id);

    // Maps to the AspNetUserClaims table
    b.ToTable("AspNetUserClaims");
});

builder.Entity<TUserLogin>(b =>
{
    // Composite primary key consisting of the LoginProvider and the key to use
    // with that provider
    b.HasKey(l => new { l.LoginProvider, l.ProviderKey });

    // Limit the size of the composite key columns due to common DB restrictions
    b.Property(l => l.LoginProvider).HasMaxLength(128);
    b.Property(l => l.ProviderKey).HasMaxLength(128);

    // Maps to the AspNetUserLogins table
    b.ToTable("AspNetUserLogins");
});

builder.Entity<TUserToken>(b =>
{
    // Composite primary key consisting of the UserId, LoginProvider and Name
    b.HasKey(t => new { t.UserId, t.LoginProvider, t.Name });

    // Limit the size of the composite key columns due to common DB restrictions
    b.Property(t => t.LoginProvider).HasMaxLength(maxKeyLength);
    b.Property(t => t.Name).HasMaxLength(maxKeyLength);

    // Maps to the AspNetUserTokens table
    b.ToTable("AspNetUserTokens");
});

builder.Entity<TRole>(b =>
{
    // Primary key
    b.HasKey(r => r.Id);

    // Index for "normalized" role name to allow efficient lookups
    b.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex").IsUnique();

    // Maps to the AspNetRoles table
    b.ToTable("AspNetRoles");

    // A concurrency token for use with the optimistic concurrency checking
    b.Property(r => r.ConcurrencyStamp).IsConcurrencyToken();

    // Limit the size of columns to use efficient database types
    b.Property(u => u.Name).HasMaxLength(256);
    b.Property(u => u.NormalizedName).HasMaxLength(256);

    // The relationships between Role and other entity types
    // Note that these relationships are configured with no navigation properties

    // Each Role can have many entries in the UserRole join table
    b.HasMany<TUserRole>().WithOne().HasForeignKey(ur => ur.RoleId).IsRequired();

    // Each Role can have many associated RoleClaims
    b.HasMany<TRoleClaim>().WithOne().HasForeignKey(rc => rc.RoleId).IsRequired();
});

builder.Entity<TRoleClaim>(b =>
{
    // Primary key
    b.HasKey(rc => rc.Id);

    // Maps to the AspNetRoleClaims table
    b.ToTable("AspNetRoleClaims");
});

builder.Entity<TUserRole>(b =>
{
    // Primary key
    b.HasKey(r => new { r.UserId, r.RoleId });

    // Maps to the AspNetUserRoles table
    b.ToTable("AspNetUserRoles");
});

模型的泛型类型

标识定义默认值公共语言运行时上面列出的每个实体类型 (CLR) 类型。 这些类型带有前缀标识:

  • IdentityUser
  • IdentityRole
  • IdentityUserClaim
  • IdentityUserToken
  • IdentityUserLogin
  • IdentityRoleClaim
  • IdentityUserRole

而不是直接使用这些类型,类型可以用作基类,这些类对于应用自己的类型。 DbContext定义标识的类是通用的这样不同的 CLR 类型可以用于一个或多个模型中的实体类型。 这些泛型类型还允许User主键 (PK) 数据类型更改。

对于角色,支持使用标识时IdentityDbContext应使用类。 例如:

// Uses all the built-in Identity types
// Uses `string` as the key type
public class IdentityDbContext
    : IdentityDbContext<IdentityUser, IdentityRole, string>
{
}

// Uses the built-in Identity types except with a custom User type
// Uses `string` as the key type
public class IdentityDbContext<TUser>
    : IdentityDbContext<TUser, IdentityRole, string>
        where TUser : IdentityUser
{
}

// Uses the built-in Identity types except with custom User and Role types
// The key type is defined by TKey
public class IdentityDbContext<TUser, TRole, TKey> : IdentityDbContext<
    TUser, TRole, TKey, IdentityUserClaim<TKey>, IdentityUserRole<TKey>,
    IdentityUserLogin<TKey>, IdentityRoleClaim<TKey>, IdentityUserToken<TKey>>
        where TUser : IdentityUser<TKey>
        where TRole : IdentityRole<TKey>
        where TKey : IEquatable<TKey>
{
}

// No built-in Identity types are used; all are specified by generic arguments
// The key type is defined by TKey
public abstract class IdentityDbContext<
    TUser, TRole, TKey, TUserClaim, TUserRole, TUserLogin, TRoleClaim, TUserToken>
    : IdentityUserContext<TUser, TKey, TUserClaim, TUserLogin, TUserToken>
         where TUser : IdentityUser<TKey>
         where TRole : IdentityRole<TKey>
         where TKey : IEquatable<TKey>
         where TUserClaim : IdentityUserClaim<TKey>
         where TUserRole : IdentityUserRole<TKey>
         where TUserLogin : IdentityUserLogin<TKey>
         where TRoleClaim : IdentityRoleClaim<TKey>
         where TUserToken : IdentityUserToken<TKey>

还有可能在这种情况下使用而无需角色 (仅声明),标识IdentityUserContext<TUser>应使用类:

// Uses the built-in non-role Identity types except with a custom User type
// Uses `string` as the key type
public class IdentityUserContext<TUser>
    : IdentityUserContext<TUser, string>
        where TUser : IdentityUser
{
}

// Uses the built-in non-role Identity types except with a custom User type
// The key type is defined by TKey
public class IdentityUserContext<TUser, TKey> : IdentityUserContext<
    TUser, TKey, IdentityUserClaim<TKey>, IdentityUserLogin<TKey>,
    IdentityUserToken<TKey>>
        where TUser : IdentityUser<TKey>
        where TKey : IEquatable<TKey>
{
}

// No built-in Identity types are used; all are specified by generic arguments, with no roles
// The key type is defined by TKey
public abstract class IdentityUserContext<
    TUser, TKey, TUserClaim, TUserLogin, TUserToken> : DbContext
        where TUser : IdentityUser<TKey>
        where TKey : IEquatable<TKey>
        where TUserClaim : IdentityUserClaim<TKey>
        where TUserLogin : IdentityUserLogin<TKey>
        where TUserToken : IdentityUserToken<TKey>
{
}

自定义模型

模型自定义的起始点是派生自相应的上下文类型。 请参阅泛型类型的模型部分。 此上下文类型通常称为ApplicationDbContext和创建的 ASP.NET Core 模板。

使用的上下文来将模型配置为通过两种方式:

  • 提供实体和键类型作为泛型类型参数。
  • 重写OnModelCreating若要修改这些类型的映射。

重写时OnModelCreatingbase.OnModelCreating应首先调用; 重写配置,应调用下一步。 EF Core 通常具有配置的最后一个 wins 策略。 例如,如果ToTable的实体类型的方法首先调用具有一个表名称,并用再说一遍更高版本使用不同的表名称,第二次调用中的表名。

自定义用户数据

自定义用户数据支持通过继承IdentityUser 此类型命名惯例做法ApplicationUser:

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

使用ApplicationUser作为上下文的泛型参数的类型:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
    }
}

无需重写OnModelCreatingApplicationDbContext类。 EF Core 映射CustomTag按照约定的属性。 但是,需要更新,以创建新数据库CustomTag列。 若要创建列,请添加迁移时,并如中所述,然后更新数据库标识和 EF Core 迁移

更新Pages/Shared/_LoginPartial.cshtml和替换IdentityUserApplicationUser:

@using Microsoft.AspNetCore.Identity
@using WebApp1.Areas.Identity.Data
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager

更新Areas/Identity/IdentityHostingStartup.csStartup.ConfigureServices和替换IdentityUserApplicationUser

services.AddDefaultIdentity<ApplicationUser>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultUI();

在 ASP.NET Core 2.1 或更高版本,作为一个 Razor 类库提供标识。 有关详细信息,请参阅 ASP.NET Core 项目中的基架标识 因此,前面的代码需要调用AddDefaultUI 如果标识基架用于标识文件添加到项目,删除对调用AddDefaultUI 有关详细信息,请参见:

更改主键类型

为 PK 列的数据类型创建数据库后更改是很多数据库系统有问题。 更改 PK 通常涉及删除并重新创建表。 因此,密钥类型时,应指定在初始迁移创建的数据库。

请按照下列步骤更改 PK 类型:

  1. 如果数据库已创建的 PK 更改之前,运行Drop-Database(PMC) 或dotnet ef database drop(.NET Core CLI) 将其删除。

  2. 在确认删除数据库,删除以进行初始迁移Remove-Migration(PMC) 或dotnet ef migrations remove(.NET Core CLI)。

  3. 更新ApplicationDbContext类进行派生IdentityDbContext<TUser,TRole,TKey> 指定的新类型TKey 例如,若要使用Guid密钥类型:

    public class ApplicationDbContext
        : IdentityDbContext<IdentityUser<Guid>, IdentityRole<Guid>, Guid>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }
    

    在前面的代码中,泛型类IdentityUser<TKey>IdentityRole<TKey>必须指定要使用新的密钥类型。

    在前面的代码中,泛型类IdentityUser<TKey>IdentityRole<TKey>必须指定要使用新的密钥类型。

    Startup.ConfigureServices 必须更新为使用一般用户:

    services.AddDefaultIdentity<IdentityUser<Guid>>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();
    
    services.AddIdentity<IdentityUser<Guid>, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();
    
    services.AddIdentity<IdentityUser<Guid>, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext, Guid>()
            .AddDefaultTokenProviders();
    
  4. 如果自定义ApplicationUser正在使用类中,更新要继承的类IdentityUser 例如:

    using System;
    using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    
    public class ApplicationUser : IdentityUser<Guid>
    {
        public string CustomTag { get; set; }        
    }
    
    using System;
    using Microsoft.AspNetCore.Identity;
    
    public class ApplicationUser : IdentityUser<Guid>
    {
        public string CustomTag { get; set; }
    }
    

    更新ApplicationDbContext来引用自定义ApplicationUser类:

    public class ApplicationDbContext
        : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }
    

    注册自定义数据库上下文类添加中的标识服务时Startup.ConfigureServices:

    services.AddDefaultIdentity<ApplicationUser>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultUI()
            .AddDefaultTokenProviders();
    

    通过分析来推断主键的数据类型DbContext对象。

    在 ASP.NET Core 2.1 或更高版本,作为一个 Razor 类库提供标识。 有关详细信息,请参阅 ASP.NET Core 项目中的基架标识 因此,前面的代码需要调用AddDefaultUI 如果标识基架用于标识文件添加到项目,删除对调用AddDefaultUI

    services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();
    

    通过分析来推断主键的数据类型DbContext对象。

    services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext, Guid>()
            .AddDefaultTokenProviders();
    

    AddEntityFrameworkStores方法接受TKey,该值指示主键的数据类型的类型。

  5. 如果自定义ApplicationRole正在使用类中,更新要继承的类IdentityRole<TKey> 例如:

    using System;
    using Microsoft.AspNetCore.Identity;
    
    public class ApplicationRole : IdentityRole<Guid>
    {
        public string Description { get; set; }
    }
    

    更新ApplicationDbContext来引用自定义ApplicationRole类。 例如,下面的类引用自定义ApplicationUser和自定义ApplicationRole:

    using System;
    using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore;
    
    public class ApplicationDbContext :
        IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }
    

    注册自定义数据库上下文类添加中的标识服务时Startup.ConfigureServices:

    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<CookiePolicyOptions>(options =>
        {
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });
    
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));
    
        services.AddIdentity<ApplicationUser, ApplicationRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultUI()
                .AddDefaultTokenProviders();
    
        services.AddMvc()
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }
    

    通过分析来推断主键的数据类型DbContext对象。

    在 ASP.NET Core 2.1 或更高版本,作为一个 Razor 类库提供标识。 有关详细信息,请参阅 ASP.NET Core 项目中的基架标识 因此,前面的代码需要调用AddDefaultUI 如果标识基架用于标识文件添加到项目,删除对调用AddDefaultUI

    using System;
    using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore;
    
    public class ApplicationDbContext : 
        IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }
    

    注册自定义数据库上下文类添加中的标识服务时Startup.ConfigureServices:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));
    
        services.AddIdentity<ApplicationUser, ApplicationRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();
    
        services.AddMvc()
            .AddRazorPagesOptions(options =>
            {
                options.Conventions.AuthorizeFolder("/Account/Manage");
                options.Conventions.AuthorizePage("/Account/Logout");
            });
    
        services.AddSingleton<IEmailSender, EmailSender>();
    }
    

    通过分析来推断主键的数据类型DbContext对象。

    using System;
    using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore;
    
    public class ApplicationDbContext : 
        IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }
    

    注册自定义数据库上下文类添加中的标识服务时Startup.ConfigureServices:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options => 
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));
    
        services.AddIdentity<ApplicationUser, ApplicationRole>()
                .AddEntityFrameworkStores<ApplicationDbContext, Guid>()
                .AddDefaultTokenProviders();
    
        services.AddMvc();
    
        services.AddTransient<IEmailSender, AuthMessageSender>();
        services.AddTransient<ISmsSender, AuthMessageSender>();
    }
    

    AddEntityFrameworkStores方法接受TKey,该值指示主键的数据类型的类型。

添加导航属性

更改关系的模型配置可能会更难于进行其他更改。 若要替换现有关系,而不是创建新的其他关系,必须格外小心。 具体而言,已更改的关系必须指定相同的外键 (FK) 属性作为现有的关系。 例如,之间的关系UsersUserClaims是,默认情况下,按如下所示指定:

builder.Entity<TUser>(b =>
{
    // Each User can have many UserClaims
    b.HasMany<TUserClaim>()
     .WithOne()
     .HasForeignKey(uc => uc.UserId)
     .IsRequired();
});

此关系的 FK 指定为UserClaim.UserId属性。 HasManyWithOne调用不带参数,以创建不使用导航属性的关系。

导航属性添加到ApplicationUser,允许关联UserClaims若要从用户中引用:

public class ApplicationUser : IdentityUser
{
    public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
}

TKeyIdentityUserClaim<TKey>是用户的 PK 为指定的类型。 在这种情况下,TKeystring因为正在使用默认值。 它具有的 PK 类型UserClaim实体类型。

现在,已存在的导航属性,它必须在配置OnModelCreating:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<ApplicationUser>(b =>
        {
            // Each User can have many UserClaims
            b.HasMany(e => e.Claims)
                .WithOne()
                .HasForeignKey(uc => uc.UserId)
                .IsRequired();
        });
    }
}

请注意,完全按照以前,只能使用导航属性调用中指定配置关系HasMany

在 EF 模型中,不是数据库仅存在导航属性。 因为关系 fk 后未发生更改,这种类型的模型更改不需要更新的数据库。 可以通过添加迁移后进行更改选中此项。 UpDown方法为空。

添加所有用户导航属性

下面的示例作为指南使用上的一节中,在用户配置单向导航属性的所有关系:

public class ApplicationUser : IdentityUser
{
    public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
    public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; }
    public virtual ICollection<IdentityUserToken<string>> Tokens { get; set; }
    public virtual ICollection<IdentityUserRole<string>> UserRoles { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<ApplicationUser>(b =>
        {
            // Each User can have many UserClaims
            b.HasMany(e => e.Claims)
                .WithOne()
                .HasForeignKey(uc => uc.UserId)
                .IsRequired();

            // Each User can have many UserLogins
            b.HasMany(e => e.Logins)
                .WithOne()
                .HasForeignKey(ul => ul.UserId)
                .IsRequired();

            // Each User can have many UserTokens
            b.HasMany(e => e.Tokens)
                .WithOne()
                .HasForeignKey(ut => ut.UserId)
                .IsRequired();

            // Each User can have many entries in the UserRole join table
            b.HasMany(e => e.UserRoles)
                .WithOne()
                .HasForeignKey(ur => ur.UserId)
                .IsRequired();
        });
    }
}

添加用户和角色导航属性

下面的示例作为指南使用上的一节中,用户和角色上配置所有关系的导航的属性:

public class ApplicationUser : IdentityUser
{
    public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
    public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; }
    public virtual ICollection<IdentityUserToken<string>> Tokens { get; set; }
    public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
}

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

public class ApplicationUserRole : IdentityUserRole<string>
{
    public virtual ApplicationUser User { get; set; }
    public virtual ApplicationRole Role { get; set; }
}
public class ApplicationDbContext
    : IdentityDbContext<
        ApplicationUser, ApplicationRole, string,
        IdentityUserClaim<string>, ApplicationUserRole, IdentityUserLogin<string>,
        IdentityRoleClaim<string>, IdentityUserToken<string>>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<ApplicationUser>(b =>
        {
            // Each User can have many UserClaims
            b.HasMany(e => e.Claims)
                .WithOne()
                .HasForeignKey(uc => uc.UserId)
                .IsRequired();

            // Each User can have many UserLogins
            b.HasMany(e => e.Logins)
                .WithOne()
                .HasForeignKey(ul => ul.UserId)
                .IsRequired();

            // Each User can have many UserTokens
            b.HasMany(e => e.Tokens)
                .WithOne()
                .HasForeignKey(ut => ut.UserId)
                .IsRequired();

            // Each User can have many entries in the UserRole join table
            b.HasMany(e => e.UserRoles)
                .WithOne(e => e.User)
                .HasForeignKey(ur => ur.UserId)
                .IsRequired();
        });

        modelBuilder.Entity<ApplicationRole>(b =>
        {
            // Each Role can have many entries in the UserRole join table
            b.HasMany(e => e.UserRoles)
                .WithOne(e => e.Role)
                .HasForeignKey(ur => ur.RoleId)
                .IsRequired();
        });

    }
}

注意:

  • 此示例还包括UserRole联接实体,这将需要导航从用户到角色的多对多关系。
  • 请记得要更改类型的导航属性以反映该ApplicationXxx类型现在正在使用而不是IdentityXxx类型。
  • 请记住使用ApplicationXxx中泛型ApplicationContext定义。

添加所有导航属性

下面的示例作为指南使用上的一节中,所有实体类型上配置所有关系的导航的属性:

public class ApplicationUser : IdentityUser
{
    public virtual ICollection<ApplicationUserClaim> Claims { get; set; }
    public virtual ICollection<ApplicationUserLogin> Logins { get; set; }
    public virtual ICollection<ApplicationUserToken> Tokens { get; set; }
    public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
}

public class ApplicationRole : IdentityRole
{
    public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
    public virtual ICollection<ApplicationRoleClaim> RoleClaims { get; set; }
}

public class ApplicationUserRole : IdentityUserRole<string>
{
    public virtual ApplicationUser User { get; set; }
    public virtual ApplicationRole Role { get; set; }
}

public class ApplicationUserClaim : IdentityUserClaim<string>
{
    public virtual ApplicationUser User { get; set; }
}

public class ApplicationUserLogin : IdentityUserLogin<string>
{
    public virtual ApplicationUser User { get; set; }
}

public class ApplicationRoleClaim : IdentityRoleClaim<string>
{
    public virtual ApplicationRole Role { get; set; }
}

public class ApplicationUserToken : IdentityUserToken<string>
{
    public virtual ApplicationUser User { get; set; }
}
public class ApplicationDbContext
    : IdentityDbContext<
        ApplicationUser, ApplicationRole, string,
        ApplicationUserClaim, ApplicationUserRole, ApplicationUserLogin,
        ApplicationRoleClaim, ApplicationUserToken>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<ApplicationUser>(b =>
        {
            // Each User can have many UserClaims
            b.HasMany(e => e.Claims)
                .WithOne(e => e.User)
                .HasForeignKey(uc => uc.UserId)
                .IsRequired();

            // Each User can have many UserLogins
            b.HasMany(e => e.Logins)
                .WithOne(e => e.User)
                .HasForeignKey(ul => ul.UserId)
                .IsRequired();

            // Each User can have many UserTokens
            b.HasMany(e => e.Tokens)
                .WithOne(e => e.User)
                .HasForeignKey(ut => ut.UserId)
                .IsRequired();

            // Each User can have many entries in the UserRole join table
            b.HasMany(e => e.UserRoles)
                .WithOne(e => e.User)
                .HasForeignKey(ur => ur.UserId)
                .IsRequired();
        });

        modelBuilder.Entity<ApplicationRole>(b =>
        {
            // Each Role can have many entries in the UserRole join table
            b.HasMany(e => e.UserRoles)
                .WithOne(e => e.Role)
                .HasForeignKey(ur => ur.RoleId)
                .IsRequired();

            // Each Role can have many associated RoleClaims
            b.HasMany(e => e.RoleClaims)
                .WithOne(e => e.Role)
                .HasForeignKey(rc => rc.RoleId)
                .IsRequired();
        });
    }
}

使用复合键

前面几节所示更改的标识模型中使用的密钥类型。 更改要使用的复合键的标识密钥模型不支持或建议。 使用复合密钥与标识涉及更改标识管理器代码与模型交互的方式。 此自定义已超出本文的讨论范围。

更改表/列名称和分面

若要更改表和列的名称,请调用base.OnModelCreating 然后,添加配置以重写任何默认值。 例如,若要更改所有标识表的名称:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<IdentityUser>(b =>
    {
        b.ToTable("MyUsers");
    });

    modelBuilder.Entity<IdentityUserClaim<string>>(b =>
    {
        b.ToTable("MyUserClaims");
    });

    modelBuilder.Entity<IdentityUserLogin<string>>(b =>
    {
        b.ToTable("MyUserLogins");
    });

    modelBuilder.Entity<IdentityUserToken<string>>(b =>
    {
        b.ToTable("MyUserTokens");
    });

    modelBuilder.Entity<IdentityRole>(b =>
    {
        b.ToTable("MyRoles");
    });

    modelBuilder.Entity<IdentityRoleClaim<string>>(b =>
    {
        b.ToTable("MyRoleClaims");
    });

    modelBuilder.Entity<IdentityUserRole<string>>(b =>
    {
        b.ToTable("MyUserRoles");
    });
}

这些示例使用默认标识类型。 如果使用的应用类型,如ApplicationUser,配置该类型而不是默认类型。

下面的示例更改某些列名称:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<IdentityUser>(b =>
    {
        b.Property(e => e.Email).HasColumnName("EMail");
    });

    modelBuilder.Entity<IdentityUserClaim<string>>(b =>
    {
        b.Property(e => e.ClaimType).HasColumnName("CType");
        b.Property(e => e.ClaimValue).HasColumnName("CValue");
    });
}

某些类型的数据库列可以配置某些方面(例如,最大值string允许的长度)。 下面的示例设置多个列的最大长度string模型中的属性:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<IdentityUser>(b =>
    {
        b.Property(u => u.UserName).HasMaxLength(128);
        b.Property(u => u.NormalizedUserName).HasMaxLength(128);
        b.Property(u => u.Email).HasMaxLength(128);
        b.Property(u => u.NormalizedEmail).HasMaxLength(128);
    });

    modelBuilder.Entity<IdentityUserToken<string>>(b =>
    {
        b.Property(t => t.LoginProvider).HasMaxLength(128);
        b.Property(t => t.Name).HasMaxLength(128);
    });
}

将映射到不同的架构

跨数据库提供程序,架构的行为可能有所不同。 对于 SQL Server,默认值是创建中的所有表dbo架构。 可以在不同的架构中创建的表。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.HasDefaultSchema("notdbo");
}

延迟加载

在本部分中,添加了支持的身份标识模型中的延迟加载代理。 延迟加载很有用,因为它允许使用而无需首先确保它们正在加载导航属性。

实体类型可适用于采用多种方式的延迟加载中所述EF Core 文档 为简单起见,使用该软件需要延迟加载代理:

下面的示例演示如何调用UseLazyLoadingProxiesStartup.ConfigureServices:

services
    .AddDbContext<ApplicationDbContext>(
        b => b.UseSqlServer(connectionString)
              .UseLazyLoadingProxies())
    .AddDefaultIdentity<ApplicationUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

请参阅前面的示例将导航属性添加到实体类型有关的指南。

其他资源

目录