使用IEntityTypeConfiguration<ApplicationUser>初始化用户种子数据异常
问题描述
我需要向系统插入若干种子用户,于是编写了ApplicationUserConfiguration类,并在ApplicationDbContext的OnModelCreating方法中注册了这个类。种子值能正常生效,但存在两个问题:
- 每次执行
Add-Migration生成新迁移时,即便用户已经在首次迁移时插入数据库,迁移的Up和Down方法里还是会包含这些用户信息; - 修改用户密码后能正常使用,但每次创建新迁移并执行
Update-Database时,密码会被迁移里的值重置。
现有代码
public class ApplicationUserConfiguration : IEntityTypeConfiguration<ApplicationUser> { public void Configure(EntityTypeBuilder<ApplicationUser> builder) { PasswordHasher<ApplicationUser> passwordHasher = new PasswordHasher<ApplicationUser>(); // User1 ApplicationUser user = new ApplicationUser { Id = "f3a6223f-ac2d-47d9-87d7-e297ea1b78e4", ConcurrencyStamp = "f3a6223f-ac2d-47d9-87d7-e297ea1b78e4", Email = "abc@gmail.com", NormalizedEmail = "ABC@GMAIL.COM", EmailConfirmed = true, FirstName = "User1", LastName = "XYZ", UserName = "abc@gmail.com", NormalizedUserName = "ABC@GMAIL.COM", }; user.PasswordHash = passwordHasher.HashPassword(user, "Apple@123"); builder.HasData(user); // User 2 user = new ApplicationUser { Id = "c061e936-bfc7-4b2a-95d1-644f2c19037a", ConcurrencyStamp = "c061e936-bfc7-4b2a-95d1-644f2c19037a", Email = "def@gmail.com", NormalizedEmail = "DEF@GMAIL.COM", EmailConfirmed = true, FirstName = "User2", LastName = "XYZ", UserName = "def@gmail.com", NormalizedUserName = "DEF@GMAIL.COM", }; user.PasswordHash = passwordHasher.HashPassword(user, "Banana@123"); builder.HasData(user); } } public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.ApplyConfiguration(new ApplicationUserConfiguration()); } }
解决方案
问题根源
builder.HasData配置的种子数据会被EF Core视为模型的一部分,每次生成迁移时都会校验这些数据,且执行迁移时会强制覆盖数据库中对应主键的记录,这就是两个问题的核心原因。
解决步骤
1. 移除实体配置类中的种子数据
修改ApplicationUserConfiguration,删除所有builder.HasData相关代码,这个类仅用于配置实体的属性映射逻辑(如果有需要),不再存放种子数据:
public class ApplicationUserConfiguration : IEntityTypeConfiguration<ApplicationUser> { public void Configure(EntityTypeBuilder<ApplicationUser> builder) { // 这里只保留实体属性映射配置,比如字段长度、索引等 // 移除所有builder.HasData代码 } }
2. 新增条件式种子初始化逻辑
在ApplicationDbContext中添加种子初始化方法,通过检查数据库中是否存在目标用户,决定是否插入,避免重复操作和覆盖现有数据:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.ApplyConfiguration(new ApplicationUserConfiguration()); } // 种子用户初始化方法 public async Task SeedUsersAsync(UserManager<ApplicationUser> userManager) { var passwordHasher = new PasswordHasher<ApplicationUser>(); // 检查User1是否存在,不存在则创建 var user1 = await userManager.FindByIdAsync("f3a6223f-ac2d-47d9-87d7-e297ea1b78e4"); if (user1 == null) { user1 = new ApplicationUser { Id = "f3a6223f-ac2d-47d9-87d7-e297ea1b78e4", ConcurrencyStamp = Guid.NewGuid().ToString(), Email = "abc@gmail.com", NormalizedEmail = "ABC@GMAIL.COM", EmailConfirmed = true, FirstName = "User1", LastName = "XYZ", UserName = "abc@gmail.com", NormalizedUserName = "ABC@GMAIL.COM", }; user1.PasswordHash = passwordHasher.HashPassword(user1, "Apple@123"); await userManager.CreateAsync(user1); } // 检查User2是否存在,不存在则创建 var user2 = await userManager.FindByIdAsync("c061e936-bfc7-4b2a-95d1-644f2c19037a"); if (user2 == null) { user2 = new ApplicationUser { Id = "c061e936-bfc7-4b2a-95d1-644f2c19037a", ConcurrencyStamp = Guid.NewGuid().ToString(), Email = "def@gmail.com", NormalizedEmail = "DEF@GMAIL.COM", EmailConfirmed = true, FirstName = "User2", LastName = "XYZ", UserName = "def@gmail.com", NormalizedUserName = "DEF@GMAIL.COM", }; user2.PasswordHash = passwordHasher.HashPassword(user2, "Banana@123"); await userManager.CreateAsync(user2); } } }
3. 在程序启动时执行种子初始化
在Program.cs中,添加启动时的种子数据执行逻辑:
var app = builder.Build(); // 执行种子数据初始化 using (var scope = app.Services.CreateScope()) { var services = scope.ServiceProvider; var userManager = services.GetRequiredService<UserManager<ApplicationUser>>(); var dbContext = services.GetRequiredService<ApplicationDbContext>(); // 确保数据库已创建 await dbContext.Database.EnsureCreatedAsync(); // 执行用户种子初始化 await dbContext.SeedUsersAsync(userManager); } // 其他中间件配置... app.Run();
4. 清理现有迁移中的用户数据
手动修改之前生成的迁移文件,删除Up和Down方法中关于用户的插入/删除代码,避免后续执行迁移时再次操作这些用户。
效果说明
- 不再通过
HasData绑定用户数据到模型,生成新迁移时EF Core不会再检测这些数据,解决了迁移文件重复包含用户信息的问题; - 种子初始化时先检查用户是否存在,仅在不存在时插入,不会覆盖已存在的用户记录,修改后的密码不会被重置。
内容的提问来源于stack exchange,提问作者Simant
相关产品推荐
相关产品推荐

