多用户类型身份——DbContext设计
Multiple user type Identity - DbContext design
我正在尝试将 .NET Core 的 Identity 包与扩展 IdentityUser<Guid>
的多个 classes 一起使用,但使用单个 UserRole
class.
我有多个 class 扩展 UserStore<T>
每个用户类型和一个 class 扩展 RoleStore<UserRole>
.
以下是我的startup.cs
:
services.AddIdentity<InternalUser, UserRole>(IdentityOptions)
.AddDefaultTokenProviders()
.AddUserStore<InternalUserStore>()
.AddRoleStore<GenericUserRoleStore>();
services.AddIdentityCore<Contractor>(IdentityOptions)
.AddRoles<UserRole>()
.AddDefaultTokenProviders()
.AddUserStore<ContractorUserStore>()
.AddRoleStore<GenericUserRoleStore>();
services.AddIdentityCore<Homeowner>(IdentityOptions)
.AddRoles<UserRole>()
.AddDefaultTokenProviders()
.AddUserStore<HomeownerUserStore>()
.AddRoleStore<GenericUserRoleStore>();
我的 DbContext
没有扩展 IdentityDbContext
:
public sealed class EntityDbContext: DbContext { }
我遇到了多个错误,所以我将以下内容添加到 DbContext
但我将其注释掉了:
public DbSet<IdentityUserClaim<Guid>> UserClaims { get; set; }
public DbSet<IdentityUserRole<Guid>> UserRoles { get; set; }
我收到许多不同的错误:
build Error on Instance 'Dal.IdentityStores.InternalUserStore'
for PluginType IUserStore
- and Instance 'RoleManager' for PluginType Microsoft.AspNetCore.Identity.RoleManager1[Models.Entities.Users.UserRole]
- and Instance 'Dal.IdentityStores.GenericUserRoleStore' for PluginType
Microsoft.AspNetCore.Identity.IRoleStore
1[Models.Entities.Users.UserRole]
- and Instance 'Dal.IdentityStores.GenericUserRoleStore' for PluginType
Microsoft.AspNetCore.Identity.IRoleStore1[Models.Entities.Users.UserRole]
- and Instance 'Dal.IdentityStores.ContractorUserStore' for PluginType
Microsoft.AspNetCore.Identity.IUserStore
1[Models.Entities.Contractors.Contractor]
- and Instance 'UserClaimsPrincipalFactory' for PluginType
Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory1[Models.Entities.Contractors.Contractor]
- and Instance 'UserClaimsPrincipalFactory<Contractor, UserRole>' for PluginType
Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory
1[Models.Entities.Contractors.Contractor]
- and Instance 'UserManager' for PluginType Microsoft.AspNetCore.Identity.UserManager1[Models.Entities.Homeowners.Homeowner]
- and Instance 'UserClaimsPrincipalFactory<Homeowner>' for PluginType Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory
1[Models.Entities.Homeowners.Homeowner]
我重现了您的问题,下面是解决方案,但我会再次考虑为不同的用户角色创建多个表。
以下是反对多用户表的两个主要原因:
- 当您想通过 ID 查找用户时(假设您不知道角色),您将需要 运行 针对不同的表进行多次查询,这会降低性能并增加代码的复杂性。
- 这也可能会增加数据库的复杂性,因为您将需要为其他表设置多个外键。
如果您仍想为不同的用户角色创建多个表,这里有一点 "hack"。您只需要覆盖 OnModelCreating 方法并配置实体:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Contractor>(b =>
{
b.HasMany<IdentityUserRole<Guid>>().WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
});
builder.Entity<UserRole>(b =>
{
b.HasKey(r => r.Id);
b.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex").IsUnique();
b.ToTable("AspNetRoles");
b.Property(r => r.ConcurrencyStamp).IsConcurrencyToken();
b.Property(u => u.Name).HasMaxLength(256);
b.Property(u => u.NormalizedName).HasMaxLength(256);
b.HasMany<IdentityUserRole<Guid>>().WithOne().HasForeignKey(ur => ur.RoleId).IsRequired();
b.HasMany<IdentityRoleClaim<Guid>>().WithOne().HasForeignKey(rc => rc.RoleId).IsRequired();
});
builder.Entity<IdentityRoleClaim<Guid>>(b =>
{
b.HasKey(rc => rc.Id);
b.ToTable("AspNetRoleClaims");
});
builder.Entity<IdentityUserRole<Guid>>(b =>
{
b.HasKey(r => new { r.UserId, r.RoleId });
b.ToTable("AspNetUserRoles");
});
builder.Entity<UserRole>().ToTable("Roles");
builder.Entity<IdentityUserRole<Guid>>().ToTable("UserRoles");
builder.Entity<IdentityRoleClaim<Guid>>().ToTable("RoleClaims");
builder.Entity<IdentityUserClaim<Guid>>().ToTable("UserClaims");
}
之后,您应该可以登录了。
根据 OP 在评论中解释的要求以及 OP 表示 he/she 没有数据库设计经验,我将尝试提供一个答案,希望能帮助使 OP 的项目更轻松,并且软件开发职业:
首先,你想要一个 "Users" table:
|---------------------|------------------|------------------|
| FirstName | LastName | Role |
|---------------------|------------------|------------------|
| Joe | Black | Contractor |
|---------------------|------------------|------------------|
您对项目没有多个用户 table 的要求,一般来说,任何项目或软件产品都不需要这样的事情。 When/IF 一个用户 table 扩展到 N 数十亿条记录,而不是为了性能而跨分区分片,and/or 非规范化读取与写入,但它在逻辑上仍然是一个 table 在许多规模化的复杂软件系统设计中。
您说:
How can I have different user metadata for different types of a user?
A contractor has it's own properties, a homeowner has its own set of
properties and etc. I don't want to have a giant table with all
possible properties
你担心的是你最终不会成为一个巨人 table。简而言之,这不是您关心的问题。您还没有一个巨大的 table,如果您添加了 30 列(如您所说的 "properties"),那将不是一个巨大的 table。
创建单个用户 table。为特定承包商属性添加可为空的列,为特定房主属性添加可为空的列。当 属性 适用于所有用户时,使列不为空。
适用于所有用户的列是您的用户的角色列,因此您有一个名为 "Role"
的不可空列
请注意,如果您使用的是关系数据库,则无需创建包含角色行记录的角色 table。您可以这样做并且您的课程讲师可能会期望这样做,但您也可以在后端创建一个常量字符串 class 或枚举来表示可能的角色,这是一种为这些角色提供完整性的持久性形式。我之所以提到这一点,是因为这是一个开发人员很快就会在关系数据库设计中陷入参照完整性矫枉过正的领域。
我正在尝试将 .NET Core 的 Identity 包与扩展 IdentityUser<Guid>
的多个 classes 一起使用,但使用单个 UserRole
class.
我有多个 class 扩展 UserStore<T>
每个用户类型和一个 class 扩展 RoleStore<UserRole>
.
以下是我的startup.cs
:
services.AddIdentity<InternalUser, UserRole>(IdentityOptions)
.AddDefaultTokenProviders()
.AddUserStore<InternalUserStore>()
.AddRoleStore<GenericUserRoleStore>();
services.AddIdentityCore<Contractor>(IdentityOptions)
.AddRoles<UserRole>()
.AddDefaultTokenProviders()
.AddUserStore<ContractorUserStore>()
.AddRoleStore<GenericUserRoleStore>();
services.AddIdentityCore<Homeowner>(IdentityOptions)
.AddRoles<UserRole>()
.AddDefaultTokenProviders()
.AddUserStore<HomeownerUserStore>()
.AddRoleStore<GenericUserRoleStore>();
我的 DbContext
没有扩展 IdentityDbContext
:
public sealed class EntityDbContext: DbContext { }
我遇到了多个错误,所以我将以下内容添加到 DbContext
但我将其注释掉了:
public DbSet<IdentityUserClaim<Guid>> UserClaims { get; set; }
public DbSet<IdentityUserRole<Guid>> UserRoles { get; set; }
我收到许多不同的错误:
build Error on Instance 'Dal.IdentityStores.InternalUserStore' for PluginType IUserStore - and Instance 'RoleManager' for PluginType Microsoft.AspNetCore.Identity.RoleManager
1[Models.Entities.Users.UserRole] - and Instance 'Dal.IdentityStores.GenericUserRoleStore' for PluginType Microsoft.AspNetCore.Identity.IRoleStore
1[Models.Entities.Users.UserRole] - and Instance 'Dal.IdentityStores.GenericUserRoleStore' for PluginType Microsoft.AspNetCore.Identity.IRoleStore1[Models.Entities.Users.UserRole] - and Instance 'Dal.IdentityStores.ContractorUserStore' for PluginType Microsoft.AspNetCore.Identity.IUserStore
1[Models.Entities.Contractors.Contractor] - and Instance 'UserClaimsPrincipalFactory' for PluginType Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory1[Models.Entities.Contractors.Contractor] - and Instance 'UserClaimsPrincipalFactory<Contractor, UserRole>' for PluginType Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory
1[Models.Entities.Contractors.Contractor] - and Instance 'UserManager' for PluginType Microsoft.AspNetCore.Identity.UserManager1[Models.Entities.Homeowners.Homeowner] - and Instance 'UserClaimsPrincipalFactory<Homeowner>' for PluginType Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory
1[Models.Entities.Homeowners.Homeowner]
我重现了您的问题,下面是解决方案,但我会再次考虑为不同的用户角色创建多个表。
以下是反对多用户表的两个主要原因:
- 当您想通过 ID 查找用户时(假设您不知道角色),您将需要 运行 针对不同的表进行多次查询,这会降低性能并增加代码的复杂性。
- 这也可能会增加数据库的复杂性,因为您将需要为其他表设置多个外键。
如果您仍想为不同的用户角色创建多个表,这里有一点 "hack"。您只需要覆盖 OnModelCreating 方法并配置实体:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Contractor>(b =>
{
b.HasMany<IdentityUserRole<Guid>>().WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
});
builder.Entity<UserRole>(b =>
{
b.HasKey(r => r.Id);
b.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex").IsUnique();
b.ToTable("AspNetRoles");
b.Property(r => r.ConcurrencyStamp).IsConcurrencyToken();
b.Property(u => u.Name).HasMaxLength(256);
b.Property(u => u.NormalizedName).HasMaxLength(256);
b.HasMany<IdentityUserRole<Guid>>().WithOne().HasForeignKey(ur => ur.RoleId).IsRequired();
b.HasMany<IdentityRoleClaim<Guid>>().WithOne().HasForeignKey(rc => rc.RoleId).IsRequired();
});
builder.Entity<IdentityRoleClaim<Guid>>(b =>
{
b.HasKey(rc => rc.Id);
b.ToTable("AspNetRoleClaims");
});
builder.Entity<IdentityUserRole<Guid>>(b =>
{
b.HasKey(r => new { r.UserId, r.RoleId });
b.ToTable("AspNetUserRoles");
});
builder.Entity<UserRole>().ToTable("Roles");
builder.Entity<IdentityUserRole<Guid>>().ToTable("UserRoles");
builder.Entity<IdentityRoleClaim<Guid>>().ToTable("RoleClaims");
builder.Entity<IdentityUserClaim<Guid>>().ToTable("UserClaims");
}
之后,您应该可以登录了。
根据 OP 在评论中解释的要求以及 OP 表示 he/she 没有数据库设计经验,我将尝试提供一个答案,希望能帮助使 OP 的项目更轻松,并且软件开发职业:
首先,你想要一个 "Users" table:
|---------------------|------------------|------------------|
| FirstName | LastName | Role |
|---------------------|------------------|------------------|
| Joe | Black | Contractor |
|---------------------|------------------|------------------|
您对项目没有多个用户 table 的要求,一般来说,任何项目或软件产品都不需要这样的事情。 When/IF 一个用户 table 扩展到 N 数十亿条记录,而不是为了性能而跨分区分片,and/or 非规范化读取与写入,但它在逻辑上仍然是一个 table 在许多规模化的复杂软件系统设计中。
您说:
How can I have different user metadata for different types of a user? A contractor has it's own properties, a homeowner has its own set of properties and etc. I don't want to have a giant table with all possible properties
你担心的是你最终不会成为一个巨人 table。简而言之,这不是您关心的问题。您还没有一个巨大的 table,如果您添加了 30 列(如您所说的 "properties"),那将不是一个巨大的 table。
创建单个用户 table。为特定承包商属性添加可为空的列,为特定房主属性添加可为空的列。当 属性 适用于所有用户时,使列不为空。
适用于所有用户的列是您的用户的角色列,因此您有一个名为 "Role"
的不可空列请注意,如果您使用的是关系数据库,则无需创建包含角色行记录的角色 table。您可以这样做并且您的课程讲师可能会期望这样做,但您也可以在后端创建一个常量字符串 class 或枚举来表示可能的角色,这是一种为这些角色提供完整性的持久性形式。我之所以提到这一点,是因为这是一个开发人员很快就会在关系数据库设计中陷入参照完整性矫枉过正的领域。