EF Core 2.0使用多个同类型值对象时抛出异常的问题排查
这个问题我之前在EF Core 2.0的项目里也碰到过,确实是这个版本的一个局限导致的,咱们来一步步拆解原因和解决办法:
问题根源
EF Core 2.0在处理多个通过OwnsOne配置的Owned Entity时,存在一个已知bug:当Owned Entity是带有TypeConverter的密封类时,框架无法正确将所有Owned Entity的主键与宿主实体(BankLayoutSettings)的主键(BankId)关联。第一个Owned Entity能正常继承宿主的BankId,但后续的Owned Entity会被错误生成随机Guid作为主键,导致保存时EF Core找不到对应主键的Added状态实体,从而抛出你看到的异常。
你的一对一关联配置本身没问题,但多Owned Entity的场景触发了这个版本的bug。
解决方案
按推荐优先级排序,有以下几个可行办法:
1. 升级到EF Core 2.1或更高版本
这个bug在EF Core 2.1版本中已经被官方修复,升级是最彻底的解决方案。升级后,多个OwnsOne配置的Owned Entity会正确继承宿主的主键,你的现有配置可以直接正常工作。
2. 手动为每个Owned Entity显式关联宿主主键
如果暂时无法升级EF Core版本,可以在DbContext的配置中,为每个Color类型的OwnsOne显式指定外键关联到宿主的BankId,确保所有Owned Entity都使用同一个主键值。示例代码如下:
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 配置BankLayoutSettings和Bank的一对一关联 modelBuilder.Entity<BankLayoutSettings>() .HasOne<Bank>() .WithOne() .HasForeignKey<BankLayoutSettings>(x => x.BankId); // 为第一个Color属性配置Owned Entity modelBuilder.Entity<BankLayoutSettings>() .OwnsOne(x => x.HeaderAndFooterBackgroundColor, colorBuilder => { // 显式指定该Owned Entity的外键为宿主的BankId colorBuilder.WithOwner().HasForeignKey("BankId"); // 映射Color的实际值到对应列(根据你的TypeConverter逻辑调整) colorBuilder.Property(c => c.Value).HasColumnName("HeaderAndFooterBackgroundColor"); }); // 为第二个Color属性配置Owned Entity,重复同样逻辑 modelBuilder.Entity<BankLayoutSettings>() .OwnsOne(x => x.ButtonColor, colorBuilder => { colorBuilder.WithOwner().HasForeignKey("BankId"); colorBuilder.Property(c => c.Value).HasColumnName("ButtonColor"); }); // 其他Color属性同理配置 }
这种方式强制每个Owned Entity都使用宿主的BankId作为外键/主键,能避免随机Guid的生成问题。
3. 直接使用TypeConverter映射Color属性,放弃OwnsOne
既然你的Color类已经实现了与string互转的TypeConverter,其实完全不需要将它作为Owned Entity配置。可以直接把Color当作普通属性,通过HasConversion方法指定转换器,直接映射到数据库列。这种方式更简洁,也能避开Owned Entity的bug:
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<BankLayoutSettings>() .HasOne<Bank>() .WithOne() .HasForeignKey<BankLayoutSettings>(x => x.BankId); // 直接为每个Color属性配置转换器和列名 modelBuilder.Entity<BankLayoutSettings>() .Property(x => x.HeaderAndFooterBackgroundColor) .HasConversion<ColorToStringConverter>() // 替换成你的TypeConverter类型 .HasColumnName("HeaderAndFooterBackgroundColor"); modelBuilder.Entity<BankLayoutSettings>() .Property(x => x.ButtonColor) .HasConversion<ColorToStringConverter>() .HasColumnName("ButtonColor"); // 其他Color属性同理配置 }
这种方式下,Color会被直接序列化为字符串存储到对应列中,不需要额外的Owned Entity配置,也就不会有主键冲突的问题。
内容的提问来源于stack exchange,提问作者Daniel

