使用Bogus为EF Core含外键数据播种时遇唯一约束错误
解决EF Core用Bogus播种时的SQLite唯一约束错误
错误根源
你遇到的UNIQUE constraint failed: Vaccines.Id错误,核心问题是跨DbContext的实体跟踪冲突:
- DataGenerator里新建了临时
IvbContext加载疫苗数据,这些疫苗实体属于这个临时上下文。 - 当把生成的
DailyImmunization添加到主上下文_context时,EF会把这些来自临时上下文的疫苗实体识别为新实体,尝试重新插入数据库,导致主键重复违反唯一约束。
另外你的映射配置里,DailyImmunization和Vaccine的关系被重复定义了两次,这也可能导致EF的关系元数据混乱。
修复步骤
1. 重构DataGenerator,避免跨上下文加载实体
把主上下文的疫苗列表传入生成方法,不要在方法内新建上下文:
public static class DataGenerator { // 传入已加载的疫苗列表,而非自己创建上下文 public static List<DailyImmunization> GenerateDailyImmunizations(int count, List<Vaccine> vaccines) { var dosageValues = new List<short> { 1, 2, 3 }; var dailyImmunizations = new Faker<DailyImmunization>() .RuleFor(di => di.Id, f => f.Random.Guid()) .RuleFor(di => di.Date, f => f.Date.PastDateOnly(2)) // 只赋值VaccineId,避免引入外部上下文的实体 .RuleFor(di => di.VaccineId, f => f.PickRandom(vaccines).Id) .RuleFor(di => di.Dosage, f => f.PickRandom(dosageValues)) // 不需要赋值Vaccine导航属性,EF会通过VaccineId自动关联 .Generate(count); return dailyImmunizations; } }
2. 修改保存方法,传入主上下文的疫苗数据
从主上下文加载疫苗,再传给生成方法:
private void GenerateDailyImmunization() { // 从当前上下文加载疫苗,确保实体属于当前跟踪上下文 var vaccines = _context.Vaccines.ToList(); var dailyImmunizations = DataGenerator.GenerateDailyImmunizations(50, vaccines); _context.DailyImmunizations.AddRange(dailyImmunizations); _context.SaveChanges(); }
3. 清理重复的关系映射配置
你的RelationshipsMapping方法里,DailyImmunization和Vaccine的关系被定义了两次,保留其中一组即可,去掉重复的那行避免EF元数据冲突:
修改后的映射代码:
private void RelationshipsMapping(ModelBuilder modelBuilder) { modelBuilder.Entity<Vaccine>() .HasOne(x => x.VaccineType) .WithMany(op => op.Vaccines) .OnDelete(DeleteBehavior.Restrict) .HasForeignKey("VaccineTypeId") .IsRequired(); modelBuilder.Entity<Vaccine>() .HasOne(x => x.TargetDisease) .WithMany(op => op.Vaccines) .OnDelete(DeleteBehavior.Restrict) .HasForeignKey("TargetDiseaseId") .IsRequired(); modelBuilder.Entity<Vaccine>() .HasOne(x => x.RouteOfAdministration) .WithMany(op => op.Vaccines) .OnDelete(DeleteBehavior.Restrict) .HasForeignKey("RouteOfAdministrationId") .IsRequired(); // 只保留这一组关系定义即可 modelBuilder.Entity<DailyImmunization>() .HasOne(x => x.Vaccine) .WithMany(op => op.DailyImmunizations) .OnDelete(DeleteBehavior.Restrict) .HasForeignKey("vaccineId") .IsRequired(); modelBuilder.Entity<VaccineType>() .HasMany(x => x.Vaccines) .WithOne(op => op.VaccineType) .OnDelete(DeleteBehavior.Restrict) .HasForeignKey("VaccineTypeId") .IsRequired(); modelBuilder.Entity<TargetDisease>() .HasMany(x => x.Vaccines) .WithOne(op => op.TargetDisease) .OnDelete(DeleteBehavior.Restrict) .HasForeignKey("TargetDiseaseId") .IsRequired(); modelBuilder.Entity<RouteOfAdministration>() .HasMany(x => x.Vaccines) .WithOne(op => op.RouteOfAdministration) .OnDelete(DeleteBehavior.Restrict) .HasForeignKey("RouteOfAdministrationId") .IsRequired(); }
额外说明
如果一定要赋值导航属性Vaccine,可以在保存方法里添加一行代码,标记疫苗实体为已存在,避免EF尝试插入:
// 告诉EF这些疫苗是已存在的,不需要重新插入 _context.AttachRange(vaccines);
但更推荐直接赋值VaccineId的方式,更简洁且避免跟踪问题。
内容的提问来源于stack exchange,提问作者MSH
相关产品推荐
相关产品推荐

