ASP.NET Core 中的标识模型自定义Identity model customization in ASP.NET Core

本文内容

作者: Arthur Vickers

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

标识和 EF Core 迁移Identity and EF Core Migrations

在检查模型之前,了解身份如何与EF Core 迁移来创建和更新数据库,这一点很有用。在顶级,此过程如下:

  • 在代码中定义或更新数据模型。
  • 添加迁移,将此模型转换为可应用于数据库的更改。
  • 检查迁移是否正确表示你的意图。
  • 应用迁移以更新数据库,使其与模型保持同步。
  • 重复步骤1到4,进一步优化模型并使数据库保持同步。
    使用以下方法之一来添加和应用迁移:
  • 如果使用 Visual Studio,则为包管理器控制台(PMC)窗口。有关详细信息,请参阅EF CORE PMC 工具
  • 使用命令行时的 .NET Core CLI。有关详细信息,请参阅EF Core .net 命令行工具
  • 当应用运行时,单击 "错误" 页上的 "应用迁移" 按钮。

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

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

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

对模型进行更改时重复前面的步骤。

标识模型The Identity model

实体类型Entity types

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

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

实体类型关系Entity type relationships

实体类型通过以下方式彼此相关:

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

默认模型配置Default model configuration

标识定义了许多继承自DbContext上下文类来配置和使用模型。此配置是使用上下文类的OnModelCreating方法中的EF CORE Code First 熟知 API完成的。默认配置为:

  1. builder.Entity<TUser>(b =>
  2. {
  3. // Primary key
  4. b.HasKey(u => u.Id);
  5. // Indexes for "normalized" username and email, to allow efficient lookups
  6. b.HasIndex(u => u.NormalizedUserName).HasName("UserNameIndex").IsUnique();
  7. b.HasIndex(u => u.NormalizedEmail).HasName("EmailIndex");
  8. // Maps to the AspNetUsers table
  9. b.ToTable("AspNetUsers");
  10. // A concurrency token for use with the optimistic concurrency checking
  11. b.Property(u => u.ConcurrencyStamp).IsConcurrencyToken();
  12. // Limit the size of columns to use efficient database types
  13. b.Property(u => u.UserName).HasMaxLength(256);
  14. b.Property(u => u.NormalizedUserName).HasMaxLength(256);
  15. b.Property(u => u.Email).HasMaxLength(256);
  16. b.Property(u => u.NormalizedEmail).HasMaxLength(256);
  17. // The relationships between User and other entity types
  18. // Note that these relationships are configured with no navigation properties
  19. // Each User can have many UserClaims
  20. b.HasMany<TUserClaim>().WithOne().HasForeignKey(uc => uc.UserId).IsRequired();
  21. // Each User can have many UserLogins
  22. b.HasMany<TUserLogin>().WithOne().HasForeignKey(ul => ul.UserId).IsRequired();
  23. // Each User can have many UserTokens
  24. b.HasMany<TUserToken>().WithOne().HasForeignKey(ut => ut.UserId).IsRequired();
  25. // Each User can have many entries in the UserRole join table
  26. b.HasMany<TUserRole>().WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
  27. });
  28. builder.Entity<TUserClaim>(b =>
  29. {
  30. // Primary key
  31. b.HasKey(uc => uc.Id);
  32. // Maps to the AspNetUserClaims table
  33. b.ToTable("AspNetUserClaims");
  34. });
  35. builder.Entity<TUserLogin>(b =>
  36. {
  37. // Composite primary key consisting of the LoginProvider and the key to use
  38. // with that provider
  39. b.HasKey(l => new { l.LoginProvider, l.ProviderKey });
  40. // Limit the size of the composite key columns due to common DB restrictions
  41. b.Property(l => l.LoginProvider).HasMaxLength(128);
  42. b.Property(l => l.ProviderKey).HasMaxLength(128);
  43. // Maps to the AspNetUserLogins table
  44. b.ToTable("AspNetUserLogins");
  45. });
  46. builder.Entity<TUserToken>(b =>
  47. {
  48. // Composite primary key consisting of the UserId, LoginProvider and Name
  49. b.HasKey(t => new { t.UserId, t.LoginProvider, t.Name });
  50. // Limit the size of the composite key columns due to common DB restrictions
  51. b.Property(t => t.LoginProvider).HasMaxLength(maxKeyLength);
  52. b.Property(t => t.Name).HasMaxLength(maxKeyLength);
  53. // Maps to the AspNetUserTokens table
  54. b.ToTable("AspNetUserTokens");
  55. });
  56. builder.Entity<TRole>(b =>
  57. {
  58. // Primary key
  59. b.HasKey(r => r.Id);
  60. // Index for "normalized" role name to allow efficient lookups
  61. b.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex").IsUnique();
  62. // Maps to the AspNetRoles table
  63. b.ToTable("AspNetRoles");
  64. // A concurrency token for use with the optimistic concurrency checking
  65. b.Property(r => r.ConcurrencyStamp).IsConcurrencyToken();
  66. // Limit the size of columns to use efficient database types
  67. b.Property(u => u.Name).HasMaxLength(256);
  68. b.Property(u => u.NormalizedName).HasMaxLength(256);
  69. // The relationships between Role and other entity types
  70. // Note that these relationships are configured with no navigation properties
  71. // Each Role can have many entries in the UserRole join table
  72. b.HasMany<TUserRole>().WithOne().HasForeignKey(ur => ur.RoleId).IsRequired();
  73. // Each Role can have many associated RoleClaims
  74. b.HasMany<TRoleClaim>().WithOne().HasForeignKey(rc => rc.RoleId).IsRequired();
  75. });
  76. builder.Entity<TRoleClaim>(b =>
  77. {
  78. // Primary key
  79. b.HasKey(rc => rc.Id);
  80. // Maps to the AspNetRoleClaims table
  81. b.ToTable("AspNetRoleClaims");
  82. });
  83. builder.Entity<TUserRole>(b =>
  84. {
  85. // Primary key
  86. b.HasKey(r => new { r.UserId, r.RoleId });
  87. // Maps to the AspNetUserRoles table
  88. b.ToTable("AspNetUserRoles");
  89. });

模型泛型类型Model generic types

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

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

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

将标识与支持角色一起使用时,应使用 IdentityDbContext 类。例如:

  1. // Uses all the built-in Identity types
  2. // Uses `string` as the key type
  3. public class IdentityDbContext
  4. : IdentityDbContext<IdentityUser, IdentityRole, string>
  5. {
  6. }
  7. // Uses the built-in Identity types except with a custom User type
  8. // Uses `string` as the key type
  9. public class IdentityDbContext<TUser>
  10. : IdentityDbContext<TUser, IdentityRole, string>
  11. where TUser : IdentityUser
  12. {
  13. }
  14. // Uses the built-in Identity types except with custom User and Role types
  15. // The key type is defined by TKey
  16. public class IdentityDbContext<TUser, TRole, TKey> : IdentityDbContext<
  17. TUser, TRole, TKey, IdentityUserClaim<TKey>, IdentityUserRole<TKey>,
  18. IdentityUserLogin<TKey>, IdentityRoleClaim<TKey>, IdentityUserToken<TKey>>
  19. where TUser : IdentityUser<TKey>
  20. where TRole : IdentityRole<TKey>
  21. where TKey : IEquatable<TKey>
  22. {
  23. }
  24. // No built-in Identity types are used; all are specified by generic arguments
  25. // The key type is defined by TKey
  26. public abstract class IdentityDbContext<
  27. TUser, TRole, TKey, TUserClaim, TUserRole, TUserLogin, TRoleClaim, TUserToken>
  28. : IdentityUserContext<TUser, TKey, TUserClaim, TUserLogin, TUserToken>
  29. where TUser : IdentityUser<TKey>
  30. where TRole : IdentityRole<TKey>
  31. where TKey : IEquatable<TKey>
  32. where TUserClaim : IdentityUserClaim<TKey>
  33. where TUserRole : IdentityUserRole<TKey>
  34. where TUserLogin : IdentityUserLogin<TKey>
  35. where TRoleClaim : IdentityRoleClaim<TKey>
  36. where TUserToken : IdentityUserToken<TKey>

也可以使用不带角色的标识(仅限声明),在这种情况下,应使用 IdentityUserContext<TUser> 类:

  1. // Uses the built-in non-role Identity types except with a custom User type
  2. // Uses `string` as the key type
  3. public class IdentityUserContext<TUser>
  4. : IdentityUserContext<TUser, string>
  5. where TUser : IdentityUser
  6. {
  7. }
  8. // Uses the built-in non-role Identity types except with a custom User type
  9. // The key type is defined by TKey
  10. public class IdentityUserContext<TUser, TKey> : IdentityUserContext<
  11. TUser, TKey, IdentityUserClaim<TKey>, IdentityUserLogin<TKey>,
  12. IdentityUserToken<TKey>>
  13. where TUser : IdentityUser<TKey>
  14. where TKey : IEquatable<TKey>
  15. {
  16. }
  17. // No built-in Identity types are used; all are specified by generic arguments, with no roles
  18. // The key type is defined by TKey
  19. public abstract class IdentityUserContext<
  20. TUser, TKey, TUserClaim, TUserLogin, TUserToken> : DbContext
  21. where TUser : IdentityUser<TKey>
  22. where TKey : IEquatable<TKey>
  23. where TUserClaim : IdentityUserClaim<TKey>
  24. where TUserLogin : IdentityUserLogin<TKey>
  25. where TUserToken : IdentityUserToken<TKey>
  26. {
  27. }

自定义模型Customize the model

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

上下文用于通过两种方式配置模型:

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

重写 OnModelCreating时,应首先调用 base.OnModelCreating;接下来应调用重写配置。EF Core 通常具有配置的最后一个 wins 策略。例如,如果先使用一个表名称调用实体类型的 ToTable 方法,然后使用不同的表名称再次调用该方法,则使用第二个调用中的表名。

自定义用户数据Custom user data

自定义用户数据是通过从 IdentityUser继承来支持的。建议将此类型命名 ApplicationUser

  1. public class ApplicationUser : IdentityUser
  2. {
  3. public string CustomTag { get; set; }
  4. }

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

  1. public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
  2. {
  3. public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
  4. : base(options)
  5. {
  6. }
  7. protected override void OnModelCreating(ModelBuilder builder)
  8. {
  9. base.OnModelCreating(builder);
  10. }
  11. }

不需要重写 ApplicationDbContext 类中的 OnModelCreatingEF Core 按约定映射 CustomTag 属性。但是,需要更新数据库以创建新的 CustomTag 列。若要创建该列,请添加迁移,然后根据标识和 EF Core 迁移中所述更新数据库。

更新Pages/Shared/_LoginPartial ,并将 IdentityUser 替换为 ApplicationUser

  1. @using Microsoft.AspNetCore.Identity
  2. @using WebApp1.Areas.Identity.Data
  3. @inject SignInManager<ApplicationUser> SignInManager
  4. @inject UserManager<ApplicationUser> UserManager

更新Areas/Identity/IdentityHostingStartupStartup.ConfigureServices,并将 IdentityUser 替换为 ApplicationUser

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

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

更改主键类型Change the primary key type

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

按照以下步骤更改 PK 类型:

  • 如果数据库是在 PK 更改之前创建的,请运行 Drop-Database (PMC)或 dotnet ef database drop (.NET Core CLI)以删除它。

  • 确认删除数据库后,删除带有 Remove-Migration (PMC)或 dotnet ef migrations remove (.NET Core CLI)的初始迁移。

  • 更新 ApplicationDbContext 类,使其从 IdentityDbContext派生。为 TKey指定新的密钥类型。例如,若要使用 Guid 密钥类型:

  1. public class ApplicationDbContext
  2. : IdentityDbContext<IdentityUser<Guid>, IdentityRole<Guid>, Guid>
  3. {
  4. public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
  5. : base(options)
  6. {
  7. }
  8. }

在前面的代码中,必须指定泛型类 IdentityUserIdentityRole 才能使用新的密钥类型。

在前面的代码中,必须指定泛型类 IdentityUserIdentityRole 才能使用新的密钥类型。

若要使用一般用户,必须更新 Startup.ConfigureServices

  1. services.AddDefaultIdentity<IdentityUser<Guid>>()
  2. .AddEntityFrameworkStores<ApplicationDbContext>()
  3. .AddDefaultTokenProviders();
  1. services.AddIdentity<IdentityUser<Guid>, IdentityRole>()
  2. .AddEntityFrameworkStores<ApplicationDbContext>()
  3. .AddDefaultTokenProviders();
  1. services.AddIdentity<IdentityUser<Guid>, IdentityRole>()
  2. .AddEntityFrameworkStores<ApplicationDbContext, Guid>()
  3. .AddDefaultTokenProviders();
  • 如果正在使用自定义 ApplicationUser 类,请将类更新为从 IdentityUser继承。例如:
  1. using System;
  2. using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
  3. public class ApplicationUser : IdentityUser<Guid>
  4. {
  5. public string CustomTag { get; set; }
  6. }
  1. using System;
  2. using Microsoft.AspNetCore.Identity;
  3. public class ApplicationUser : IdentityUser<Guid>
  4. {
  5. public string CustomTag { get; set; }
  6. }

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

  1. public class ApplicationDbContext
  2. : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid>
  3. {
  4. public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
  5. : base(options)
  6. {
  7. }
  8. }

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

  1. services.AddDefaultIdentity<ApplicationUser>()
  2. .AddEntityFrameworkStores<ApplicationDbContext>()
  3. .AddDefaultUI()
  4. .AddDefaultTokenProviders();

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

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

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

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

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

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

  • 如果正在使用自定义 ApplicationRole 类,请将类更新为从 IdentityRole<TKey>继承。例如:
  1. using System;
  2. using Microsoft.AspNetCore.Identity;
  3. public class ApplicationRole : IdentityRole<Guid>
  4. {
  5. public string Description { get; set; }
  6. }

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

  1. using System;
  2. using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
  3. using Microsoft.EntityFrameworkCore;
  4. public class ApplicationDbContext :
  5. IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
  6. {
  7. public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
  8. : base(options)
  9. {
  10. }
  11. }

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

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.Configure<CookiePolicyOptions>(options =>
  4. {
  5. options.CheckConsentNeeded = context => true;
  6. options.MinimumSameSitePolicy = SameSiteMode.None;
  7. });
  8. services.AddDbContext<ApplicationDbContext>(options =>
  9. options.UseSqlServer(
  10. Configuration.GetConnectionString("DefaultConnection")));
  11. services.AddIdentity<ApplicationUser, ApplicationRole>()
  12. .AddEntityFrameworkStores<ApplicationDbContext>()
  13. .AddDefaultUI()
  14. .AddDefaultTokenProviders();
  15. services.AddMvc()
  16. .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
  17. }

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

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

  1. using System;
  2. using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
  3. using Microsoft.EntityFrameworkCore;
  4. public class ApplicationDbContext :
  5. IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
  6. {
  7. public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
  8. : base(options)
  9. {
  10. }
  11. }

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

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddDbContext<ApplicationDbContext>(options =>
  4. options.UseSqlServer(
  5. Configuration.GetConnectionString("DefaultConnection")));
  6. services.AddIdentity<ApplicationUser, ApplicationRole>()
  7. .AddEntityFrameworkStores<ApplicationDbContext>()
  8. .AddDefaultTokenProviders();
  9. services.AddMvc()
  10. .AddRazorPagesOptions(options =>
  11. {
  12. options.Conventions.AuthorizeFolder("/Account/Manage");
  13. options.Conventions.AuthorizePage("/Account/Logout");
  14. });
  15. services.AddSingleton<IEmailSender, EmailSender>();
  16. }

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

  1. using System;
  2. using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
  3. using Microsoft.EntityFrameworkCore;
  4. public class ApplicationDbContext :
  5. IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
  6. {
  7. public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
  8. : base(options)
  9. {
  10. }
  11. }

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

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddDbContext<ApplicationDbContext>(options =>
  4. options.UseSqlServer(
  5. Configuration.GetConnectionString("DefaultConnection")));
  6. services.AddIdentity<ApplicationUser, ApplicationRole>()
  7. .AddEntityFrameworkStores<ApplicationDbContext, Guid>()
  8. .AddDefaultTokenProviders();
  9. services.AddMvc();
  10. services.AddTransient<IEmailSender, AuthMessageSender>();
  11. services.AddTransient<ISmsSender, AuthMessageSender>();
  12. }

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

添加导航属性Add navigation properties

更改关系的模型配置可能比进行其他更改更难。必须小心替换现有关系,而不是创建新的其他关系。特别是,更改的关系必须指定与现有关系相同的外键(FK)属性。例如,默认情况下,UsersUserClaims 之间的关系按如下方式指定:

  1. builder.Entity<TUser>(b =>
  2. {
  3. // Each User can have many UserClaims
  4. b.HasMany<TUserClaim>()
  5. .WithOne()
  6. .HasForeignKey(uc => uc.UserId)
  7. .IsRequired();
  8. });

此关系的 FK 作为 UserClaim.UserId 属性指定。无需参数即可调用 HasManyWithOne 来创建不带导航属性的关系。

ApplicationUser 添加一个导航属性,该属性允许从用户引用关联的 UserClaims

  1. public class ApplicationUser : IdentityUser
  2. {
  3. public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
  4. }

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

由于导航属性存在,因此必须在 OnModelCreating中进行配置:

  1. public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
  2. {
  3. public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
  4. : base(options)
  5. {
  6. }
  7. protected override void OnModelCreating(ModelBuilder modelBuilder)
  8. {
  9. base.OnModelCreating(modelBuilder);
  10. modelBuilder.Entity<ApplicationUser>(b =>
  11. {
  12. // Each User can have many UserClaims
  13. b.HasMany(e => e.Claims)
  14. .WithOne()
  15. .HasForeignKey(uc => uc.UserId)
  16. .IsRequired();
  17. });
  18. }
  19. }

请注意,关系的配置与之前完全相同,只是在对的调用中指定的导航属性 HasMany

导航属性仅存在于 EF 模型中,而不存在于数据库中。由于关系的 FK 未更改,这种类型的模型更改不需要更新数据库。这可以通过在更改后添加迁移来进行检查。UpDown 方法为空。

添加所有用户导航属性Add all User navigation properties

以下示例使用上述部分作为指导,为用户上的所有关系配置单向导航属性:

  1. public class ApplicationUser : IdentityUser
  2. {
  3. public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
  4. public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; }
  5. public virtual ICollection<IdentityUserToken<string>> Tokens { get; set; }
  6. public virtual ICollection<IdentityUserRole<string>> UserRoles { get; set; }
  7. }
  1. public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
  2. {
  3. public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
  4. : base(options)
  5. {
  6. }
  7. protected override void OnModelCreating(ModelBuilder modelBuilder)
  8. {
  9. base.OnModelCreating(modelBuilder);
  10. modelBuilder.Entity<ApplicationUser>(b =>
  11. {
  12. // Each User can have many UserClaims
  13. b.HasMany(e => e.Claims)
  14. .WithOne()
  15. .HasForeignKey(uc => uc.UserId)
  16. .IsRequired();
  17. // Each User can have many UserLogins
  18. b.HasMany(e => e.Logins)
  19. .WithOne()
  20. .HasForeignKey(ul => ul.UserId)
  21. .IsRequired();
  22. // Each User can have many UserTokens
  23. b.HasMany(e => e.Tokens)
  24. .WithOne()
  25. .HasForeignKey(ut => ut.UserId)
  26. .IsRequired();
  27. // Each User can have many entries in the UserRole join table
  28. b.HasMany(e => e.UserRoles)
  29. .WithOne()
  30. .HasForeignKey(ur => ur.UserId)
  31. .IsRequired();
  32. });
  33. }
  34. }

添加用户和角色导航属性Add User and Role navigation properties

以下示例使用上述部分作为指导,为用户和角色上的所有关系配置导航属性:

  1. public class ApplicationUser : IdentityUser
  2. {
  3. public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
  4. public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; }
  5. public virtual ICollection<IdentityUserToken<string>> Tokens { get; set; }
  6. public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
  7. }
  8. public class ApplicationRole : IdentityRole
  9. {
  10. public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
  11. }
  12. public class ApplicationUserRole : IdentityUserRole<string>
  13. {
  14. public virtual ApplicationUser User { get; set; }
  15. public virtual ApplicationRole Role { get; set; }
  16. }
  1. public class ApplicationDbContext
  2. : IdentityDbContext<
  3. ApplicationUser, ApplicationRole, string,
  4. IdentityUserClaim<string>, ApplicationUserRole, IdentityUserLogin<string>,
  5. IdentityRoleClaim<string>, IdentityUserToken<string>>
  6. {
  7. public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
  8. : base(options)
  9. {
  10. }
  11. protected override void OnModelCreating(ModelBuilder modelBuilder)
  12. {
  13. base.OnModelCreating(modelBuilder);
  14. modelBuilder.Entity<ApplicationUser>(b =>
  15. {
  16. // Each User can have many UserClaims
  17. b.HasMany(e => e.Claims)
  18. .WithOne()
  19. .HasForeignKey(uc => uc.UserId)
  20. .IsRequired();
  21. // Each User can have many UserLogins
  22. b.HasMany(e => e.Logins)
  23. .WithOne()
  24. .HasForeignKey(ul => ul.UserId)
  25. .IsRequired();
  26. // Each User can have many UserTokens
  27. b.HasMany(e => e.Tokens)
  28. .WithOne()
  29. .HasForeignKey(ut => ut.UserId)
  30. .IsRequired();
  31. // Each User can have many entries in the UserRole join table
  32. b.HasMany(e => e.UserRoles)
  33. .WithOne(e => e.User)
  34. .HasForeignKey(ur => ur.UserId)
  35. .IsRequired();
  36. });
  37. modelBuilder.Entity<ApplicationRole>(b =>
  38. {
  39. // Each Role can have many entries in the UserRole join table
  40. b.HasMany(e => e.UserRoles)
  41. .WithOne(e => e.Role)
  42. .HasForeignKey(ur => ur.RoleId)
  43. .IsRequired();
  44. });
  45. }
  46. }

说明:

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

添加所有导航属性Add all navigation properties

以下示例使用上述部分作为指导,为所有实体类型上的所有关系配置导航属性:

  1. public class ApplicationUser : IdentityUser
  2. {
  3. public virtual ICollection<ApplicationUserClaim> Claims { get; set; }
  4. public virtual ICollection<ApplicationUserLogin> Logins { get; set; }
  5. public virtual ICollection<ApplicationUserToken> Tokens { get; set; }
  6. public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
  7. }
  8. public class ApplicationRole : IdentityRole
  9. {
  10. public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
  11. public virtual ICollection<ApplicationRoleClaim> RoleClaims { get; set; }
  12. }
  13. public class ApplicationUserRole : IdentityUserRole<string>
  14. {
  15. public virtual ApplicationUser User { get; set; }
  16. public virtual ApplicationRole Role { get; set; }
  17. }
  18. public class ApplicationUserClaim : IdentityUserClaim<string>
  19. {
  20. public virtual ApplicationUser User { get; set; }
  21. }
  22. public class ApplicationUserLogin : IdentityUserLogin<string>
  23. {
  24. public virtual ApplicationUser User { get; set; }
  25. }
  26. public class ApplicationRoleClaim : IdentityRoleClaim<string>
  27. {
  28. public virtual ApplicationRole Role { get; set; }
  29. }
  30. public class ApplicationUserToken : IdentityUserToken<string>
  31. {
  32. public virtual ApplicationUser User { get; set; }
  33. }
  1. public class ApplicationDbContext
  2. : IdentityDbContext<
  3. ApplicationUser, ApplicationRole, string,
  4. ApplicationUserClaim, ApplicationUserRole, ApplicationUserLogin,
  5. ApplicationRoleClaim, ApplicationUserToken>
  6. {
  7. public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
  8. : base(options)
  9. {
  10. }
  11. protected override void OnModelCreating(ModelBuilder modelBuilder)
  12. {
  13. base.OnModelCreating(modelBuilder);
  14. modelBuilder.Entity<ApplicationUser>(b =>
  15. {
  16. // Each User can have many UserClaims
  17. b.HasMany(e => e.Claims)
  18. .WithOne(e => e.User)
  19. .HasForeignKey(uc => uc.UserId)
  20. .IsRequired();
  21. // Each User can have many UserLogins
  22. b.HasMany(e => e.Logins)
  23. .WithOne(e => e.User)
  24. .HasForeignKey(ul => ul.UserId)
  25. .IsRequired();
  26. // Each User can have many UserTokens
  27. b.HasMany(e => e.Tokens)
  28. .WithOne(e => e.User)
  29. .HasForeignKey(ut => ut.UserId)
  30. .IsRequired();
  31. // Each User can have many entries in the UserRole join table
  32. b.HasMany(e => e.UserRoles)
  33. .WithOne(e => e.User)
  34. .HasForeignKey(ur => ur.UserId)
  35. .IsRequired();
  36. });
  37. modelBuilder.Entity<ApplicationRole>(b =>
  38. {
  39. // Each Role can have many entries in the UserRole join table
  40. b.HasMany(e => e.UserRoles)
  41. .WithOne(e => e.Role)
  42. .HasForeignKey(ur => ur.RoleId)
  43. .IsRequired();
  44. // Each Role can have many associated RoleClaims
  45. b.HasMany(e => e.RoleClaims)
  46. .WithOne(e => e.Role)
  47. .HasForeignKey(rc => rc.RoleId)
  48. .IsRequired();
  49. });
  50. }
  51. }

使用组合键Use composite keys

前面几节演示了如何更改标识模型中使用的密钥类型。不支持或建议将标识密钥模型更改为使用组合键。使用带有标识的组合键涉及更改标识管理器代码与模型交互的方式。此自定义超出了本文档的范围。

更改表/列名称和方面Change table/column names and facets

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

  1. protected override void OnModelCreating(ModelBuilder modelBuilder)
  2. {
  3. base.OnModelCreating(modelBuilder);
  4. modelBuilder.Entity<IdentityUser>(b =>
  5. {
  6. b.ToTable("MyUsers");
  7. });
  8. modelBuilder.Entity<IdentityUserClaim<string>>(b =>
  9. {
  10. b.ToTable("MyUserClaims");
  11. });
  12. modelBuilder.Entity<IdentityUserLogin<string>>(b =>
  13. {
  14. b.ToTable("MyUserLogins");
  15. });
  16. modelBuilder.Entity<IdentityUserToken<string>>(b =>
  17. {
  18. b.ToTable("MyUserTokens");
  19. });
  20. modelBuilder.Entity<IdentityRole>(b =>
  21. {
  22. b.ToTable("MyRoles");
  23. });
  24. modelBuilder.Entity<IdentityRoleClaim<string>>(b =>
  25. {
  26. b.ToTable("MyRoleClaims");
  27. });
  28. modelBuilder.Entity<IdentityUserRole<string>>(b =>
  29. {
  30. b.ToTable("MyUserRoles");
  31. });
  32. }

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

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

  1. protected override void OnModelCreating(ModelBuilder modelBuilder)
  2. {
  3. base.OnModelCreating(modelBuilder);
  4. modelBuilder.Entity<IdentityUser>(b =>
  5. {
  6. b.Property(e => e.Email).HasColumnName("EMail");
  7. });
  8. modelBuilder.Entity<IdentityUserClaim<string>>(b =>
  9. {
  10. b.Property(e => e.ClaimType).HasColumnName("CType");
  11. b.Property(e => e.ClaimValue).HasColumnName("CValue");
  12. });
  13. }

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

  1. protected override void OnModelCreating(ModelBuilder modelBuilder)
  2. {
  3. base.OnModelCreating(modelBuilder);
  4. modelBuilder.Entity<IdentityUser>(b =>
  5. {
  6. b.Property(u => u.UserName).HasMaxLength(128);
  7. b.Property(u => u.NormalizedUserName).HasMaxLength(128);
  8. b.Property(u => u.Email).HasMaxLength(128);
  9. b.Property(u => u.NormalizedEmail).HasMaxLength(128);
  10. });
  11. modelBuilder.Entity<IdentityUserToken<string>>(b =>
  12. {
  13. b.Property(t => t.LoginProvider).HasMaxLength(128);
  14. b.Property(t => t.Name).HasMaxLength(128);
  15. });
  16. }

映射到其他架构Map to a different schema

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

  1. protected override void OnModelCreating(ModelBuilder modelBuilder)
  2. {
  3. base.OnModelCreating(modelBuilder);
  4. modelBuilder.HasDefaultSchema("notdbo");
  5. }

延迟加载Lazy loading

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

可以通过多种方式使实体类型适用于延迟加载,如EF Core 文档中所述。为简单起见,请使用延迟加载代理,这需要:

  1. services
  2. .AddDbContext<ApplicationDbContext>(
  3. b => b.UseSqlServer(connectionString)
  4. .UseLazyLoadingProxies())
  5. .AddDefaultIdentity<ApplicationUser>()
  6. .AddEntityFrameworkStores<ApplicationDbContext>();

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

其他资源Additional resources