Ausgabe
Lage
Ich verwende Identity ASP.NET Core 3.1 mit Angular 8 Template. Ich möchte die ASPNETUserRoles-Tabelle erweitern und ihr eine weitere benutzerdefinierte Schlüsselspalte CompanyId hinzufügen.
Standardmäßig bietet Identität:
public virtual TKey UserId { get; set; }
public virtual TKey RoleId { get; set; }
Als ich meinen DbContext von UserId (string) zu UserId (long) geändert habe, sieht DbContext so aus:
public class CompanyDBContext : KeyApiAuthorizationDbContext<User, Role, UserRole, long>
{
public CompanyDBContext(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
public DbSet<Company> Companies { get; set; }
}
KeyApiAuthorizationDbContext
public class KeyApiAuthorizationDbContext<TUser, TRole, IdentityUserRole, TKey> : IdentityDbContext<TUser, TRole, TKey, IdentityUserClaim<TKey>, IdentityUserRole<TKey>, IdentityUserLogin<TKey>, IdentityRoleClaim<TKey>, IdentityUserToken<TKey>>, IPersistedGrantDbContext
where TUser : IdentityUser<TKey>
where TRole : IdentityRole<TKey>
where IdentityUserRole : IdentityUserRole<TKey>
where TKey : IEquatable<TKey>
{
private readonly IOptions<OperationalStoreOptions> _operationalStoreOptions;
/// <summary>
/// Initializes a new instance of <see cref="ApiAuthorizationDbContext{TUser, TRole, TKey}"/>.
/// </summary>
/// <param name="options">The <see cref="DbContextOptions"/>.</param>
/// <param name="operationalStoreOptions">The <see cref="IOptions{OperationalStoreOptions}"/>.</param>
public KeyApiAuthorizationDbContext(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions)
: base(options)
{
_operationalStoreOptions = operationalStoreOptions;
}
/// <summary>
/// Gets or sets the <see cref="DbSet{PersistedGrant}"/>.
/// </summary>
public DbSet<PersistedGrant> PersistedGrants { get; set; }
/// <summary>
/// Gets or sets the <see cref="DbSet{DeviceFlowCodes}"/>.
/// </summary>
public DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; }
Task<int> IPersistedGrantDbContext.SaveChangesAsync() => base.SaveChangesAsync();
/// <inheritdoc />
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value);
}
}
Entitäten
public class User : IdentityUser<long> {}
public class Role : IdentityRole<long> {}
public class UserRole : IdentityUserRole<long>
{
public long CompanyId { get; set; }
}
Problem auftreten
Wenn ich meinen Benutzer registriert habe und er true zurückgibt, füge ich einen aktuellen Benutzer in der UserRole-Tabelle wie unten hinzu, aber wenn mein Debugger die await _context.SaveChangesAsync();
Methode erreicht hat, zeigt er mir eine Ausnahme
if (result.Succeeded)
{
foreach (var role in model.Roles.Where(x => x.IsChecked = true))
{
var entity = new Core.Entities.Identity.UserRole()
{
UserId = model.User.Id,
RoleId = role.Id,
CompanyId = companycode
};
_context.UserRoles.Add(entity);
}
await _context.SaveChangesAsync();
}
Ich weiß nicht, wo ich meinen Fehler mache? Wenn die obigen Schritte zum Überschreiben der Benutzerrolle falsch sind, helfen Sie mir dabei.
Ich teile auch meine Migrationsdetails zu Ihrer Information, möglicherweise mache ich dort etwas falsch.
Migrationen
migrationBuilder.CreateTable(
name: "Companies",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(nullable: false),
Code = table.Column<string>(nullable: false),
Logo = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Companies", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetUserRoles",
columns: table => new
{
UserId = table.Column<long>(nullable: false),
RoleId = table.Column<long>(nullable: false),
CompanyId = table.Column<long>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId, x.CompanyId });
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_Companies_CompanyId",
column: x => x.CompanyId,
principalTable: "Companies",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
Migrations-Snapshot
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<long>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<long>("RoleId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
Lösung
Jetzt habe ich eine Antwort auf meine eigene Frage gefunden.
Rückruffrage
I want to extend the User Role (IdentityUserRole) and adding a few foreign keys, but faced two errors:
Error 1
Invalid column name ‘discriminator’
Error 2
A key cannot be configured on ‘UserRole’ because it is a derived type. The key must be configured on the root type ‘IdentityUserRole’. If you did not intend for ‘IdentityUserRole’ to be included in the model, ensure that it is not included in a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation property on a type that is included in the model.
This is because if you look into the definition of IdentityUserRole, you’ll find out that it already has primary keys:
public virtual TKey UserId { get; set; }
public virtual TKey RoleId { get; set; }
So, what’s the solution?
To fix this, we have to override the user role implementation.
Now if you see the code:
public class CompanyDBContext : IdentityDbContext<User, Role, long, UserClaim, UserRole, UserLogin, RoleClaim, UserToken>, IPersistedGrantDbContext
{
private readonly IOptions<OperationalStoreOptions> _operationalStoreOptions;
public CompanyDBContext(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options)
{
_operationalStoreOptions = operationalStoreOptions;
}
Task<int> IPersistedGrantDbContext.SaveChangesAsync() => base.SaveChangesAsync();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value);
modelBuilder.Entity<UserRole>(b =>
{
b.HasKey(ur => new { ur.UserId, ur.RoleId, ur.CompanyId });
});
public DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; }
public DbSet<PersistedGrant> PersistedGrants { get; set; }
}
Das ist es. Wie @EduCielo vorgeschlagen hat, überdenke ich meinen Code und führe die richtige Zuordnung und Konfiguration durch und überschreibe den Modelbuilder, um die richtige Beziehung für die Benutzerrolle abzubilden.
Für neue Entwickler teile ich Entitätsklassen, die ich oben verwendet habe, damit es vollständig mit Ihrem Code funktioniert.
public class User : IdentityUser<long> { //Add new props to modify ASP.NETUsers Table }
public class Role : IdentityRole<long> { }
public class UserClaim: IdentityUserClaim<long> { }
public class UserRole : IdentityUserRole<long>
{
[Key, Column(Order = 2)]
public long CompanyId { get; set; }
public Company Company { get; set; }
}
public class UserLogin: IdentityUserLogin<long> { }
public class RoleClaim : IdentityRoleClaim<long> { }
public class UserToken : IdentityUserToken<long> { }
Fazit
add-migration FirstMigration -context CompanyDbContext
update-database -context CompanyDbContext
Viel Spaß beim Codieren 🙂
Beantwortet von – Ahmer Ali Ahsan
Antwort geprüft von – David Goodson (FixError Volunteer)