ASP.NET Core Identity中加载并更新继承自ApplicationUser的Employee/Applicant类属性的实现方案
Hey, let's work through why your current code isn't updating the derived class properties and how to fix it. The core issues are twofold:
- When you use
_userManager.GetUserAsync(User), it only returns an instance of the baseApplicationUserclass—even if the user is actually an Employee or Applicant in the database. This means the derived properties are hidden and can't be accessed/updated directly. - Your
CreateUsermethod always spawns anApplicationUser, so new users never get stored as Employee or Applicant types in the first place.
Let's go through the fixes step by step:
一、先确保EF Core的继承映射配置正确
First, we need to make sure Entity Framework Core properly handles your inheritance hierarchy. By default, EF Core uses Table-Per-Hierarchy (TPH) (all types share one table with a discriminator column), but we can explicitly configure it to avoid surprises:
public partial class AppIdentityDbContext : IdentityDbContext<ApplicationUser> { public AppIdentityDbContext(DbContextOptions<AppIdentityDbContext> options) : base(options) { } // Add DbSets for your derived types to make queries easier public virtual DbSet<Employee> Employees { get; set; } public virtual DbSet<Applicant> Applicants { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // Explicitly configure TPH inheritance (optional but clear) modelBuilder.Entity<ApplicationUser>() .HasDiscriminator<string>("UserType") .HasValue<ApplicationUser>("StandardUser") .HasValue<Employee>("Employee") .HasValue<Applicant>("Applicant"); // Configure the Employee-Department relationship modelBuilder.Entity<Employee>() .HasOne(e => e.Department) .WithMany(d => d.Employees) .HasForeignKey(e => e.DepartmentId); } }
二、修改注册逻辑,创建正确的派生类实例
Your CreateUser method was only making ApplicationUser instances. We need to adjust it to create the right type based on user selection (like a dropdown in your registration page):
// Update your CreateUser method to accept a user type parameter private ApplicationUser CreateUser(string userType) { try { return userType switch { "Employee" => Activator.CreateInstance<Employee>(), "Applicant" => Activator.CreateInstance<Applicant>(), _ => Activator.CreateInstance<ApplicationUser>() }; } catch { throw new InvalidOperationException($"Can't create an instance of the selected user type. Make sure the type has a parameterless constructor."); } }
Then in your registration page's OnPostAsync, call it with the user-selected type:
// Assume your InputModel has a UserType field (from a dropdown) var user = CreateUser(Input.UserType); // Continue with your existing registration logic...
三、修改LoadAsync方法,加载派生类属性
Instead of relying on UserManager to get the user, query the DbContext directly to fetch the actual derived type:
First, update your InputModel to include derived properties:
public class InputModel { public string? PhoneNumber { get; set; } public string? Address { get; set; } // Applicant properties public DateOnly? Birthday { get; set; } public int? ExperienceYears { get; set; } // Employee properties public string? DepartmentId { get; set; } }
Then rewrite the LoadAsync method:
private async Task LoadAsync() { var userId = _userManager.GetUserId(User); // Query the DbContext to get the actual user type (Employee/Applicant) var user = await _context.Users .Include(e => (e as Employee)?.Department) // Load Department if it's an Employee .FirstOrDefaultAsync(u => u.Id == userId); if (user == null) return; Username = await _userManager.GetUserNameAsync(user); Input = new InputModel { PhoneNumber = await _userManager.GetPhoneNumberAsync(user), Address = user.Address, // Map derived properties based on user type Birthday = (user as Applicant)?.Birthday, ExperienceYears = (user as Applicant)?.ExperienceYears, DepartmentId = (user as Employee)?.DepartmentId }; }
四、修改OnPostAsync方法,更新派生类属性
Again, fetch the actual user type from the DbContext, update the properties, and save via DbContext (since UserManager.UpdateAsync only handles base class properties):
public async Task<IActionResult> OnPostAsync() { var userId = _userManager.GetUserId(User); var user = await _context.Users .FirstOrDefaultAsync(u => u.Id == userId); if (user == null) { return NotFound($"Unable to load user with ID '{userId}'."); } if (!ModelState.IsValid) { await LoadAsync(); return Page(); } // Update base class properties user.Address = Input.Address; var currentPhone = await _userManager.GetPhoneNumberAsync(user); if (Input.PhoneNumber != currentPhone) { var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber); if (!setPhoneResult.Succeeded) { StatusMessage = "Unexpected error when updating phone number."; return RedirectToPage(); } } // Update derived class properties if (user is Applicant applicant) { applicant.Birthday = Input.Birthday; applicant.ExperienceYears = Input.ExperienceYears; } else if (user is Employee employee) { employee.DepartmentId = Input.DepartmentId; } // Save all changes via DbContext (includes derived properties) await _context.SaveChangesAsync(); await _signInManager.RefreshSignInAsync(user); StatusMessage = "Your profile has been updated successfully!"; return RedirectToPage(); }
关键注意事项
- Avoid using UserManager for derived types:
UserManageris designed for the baseApplicationUserclass, so it can't return or update derived properties directly. Always use the DbContext to fetch the actual user type. - Ensure correct user type on registration: If you don't create the right derived type during registration, the user will be stored as
ApplicationUserforever, and their derived properties will never exist in the database. - Inheritance strategy: We used TPH here (single table with discriminator) because it's the most common and efficient approach. If you need separate tables for each type (TPT), you can adjust the EF Core configuration, but TPH is usually sufficient.
备注:内容来源于stack exchange,提问作者trongtoannguyen

