批量添加Vehicle时Color.Red静态属性为null触发PostgreSQL非空约束异常
批量添加Vehicle时Color静态属性被设为null触发数据库非空约束异常
问题现象
使用Color.Red静态属性创建Vehicle实体,批量添加到PostgreSQL数据库时,color字段被设为null,触发非空约束异常。单个添加Vehicle并保存时一切正常。
异常信息
Npgsql.PostgresException 23502: null value in column "color" of relation "vehicle" violates not-null constraint DETAIL: Failing row contains (25134bbd-2420-4b61-8ec8-e484cddce50b, 6778fe55-7596-4c22-aa29-43247d6d59a9, 1, К333ОТ77, null, SALYA2BN2KA791786, 0, 10, 10).
相关代码
创建Vehicle的代码
var model = Domain.ModelAggregate.Model.Create(Brand.Create("Kia"), CarModel.Create("Rio"), Category.Create('B'), Tariff.Create(1, 2, 3)); await Context.Models.AddAsync(model); await Context.SaveChangesAsync(); var vehicles = Enumerable.Range(0, vehiclesCount) .Select(_ => Domain.VehicleAggregate.Vehicle.Create(model.Id, PlateNumber.Create("К333ОТ77"), Color.Red, Vin.Create("SALYA2BN2KA791786"), Location.Create(10.0, 10.0), FuelLevel.Create())) .ToList(); Context.AttachRange(vehicles.Select(x => x.Status).ToList()); await Context.Vehicles.AddRangeAsync(vehicles); await Context.SaveChangesAsync();
Color类定义
public sealed class Color : ValueObject { public static readonly Color White = new(nameof(White).ToLowerInvariant()); public static readonly Color Red = new(nameof(Red).ToLowerInvariant()); ... }
DbContext及配置
public sealed class DataContext(DbContextOptions<DataContext> options) : DbContext(options) { public DbSet<Vehicle> Vehicles { get; set; } public DbSet<Model> Models { get; set; } public DbSet<OutboxEvent> Outbox { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfiguration(new ModelEntityTypeConfiguration()); modelBuilder.ApplyConfiguration(new VehicleEntityTypeConfiguration()); modelBuilder.ApplyConfiguration(new StatusEntityTypeConfiguration()); modelBuilder.ApplyConfiguration(new OutboxEventTypeConfiguration()); } } internal sealed class VehicleEntityTypeConfiguration : IEntityTypeConfiguration<Vehicle> { public void Configure(EntityTypeBuilder<Vehicle> builder) { builder.ToTable("vehicle"); builder.HasKey(x => x.Id); builder.Property(x => x.Id).ValueGeneratedNever().HasColumnName("id").IsRequired(); builder.Property(x => x.ModelId).HasColumnName("model_id").IsRequired(); builder.HasOne<Model>().WithMany().HasForeignKey(x => x.ModelId).HasConstraintName("FK_model_id").IsRequired(); builder.HasOne(x => x.Status) .WithMany() .HasForeignKey("status_id") .HasConstraintName("FK_status_id") .IsRequired(); builder.OwnsOne(x => x.PlateNumber, cfg => { cfg.Property(x => x.Value).HasColumnName("plate_number").IsRequired(); }); builder.OwnsOne(x => x.Color, cfg => { cfg.Property(x => x.Name).HasColumnName("color").IsRequired(); }); builder.OwnsOne(x => x.Vin, cfg => { cfg.Property(x => x.Number).HasColumnName("vin").IsRequired(); }); builder.OwnsOne(x => x.FuelLevel, cfg => { cfg.Property(x => x.Percents).HasColumnName("fuel_level_percents").IsRequired(); }); builder.OwnsOne(x => x.Location, cfg => { cfg.Property(x => x.Latitude).HasColumnName("location_latitude").IsRequired(); cfg.Property(x => x.Longitude).HasColumnName("location_longitude").IsRequired(); }); builder.Ignore(x => x.DomainEvents); } } internal sealed class StatusEntityTypeConfiguration : IEntityTypeConfiguration<Status> { public void Configure(EntityTypeBuilder<Status> builder) { builder.ToTable("vehicle_status"); builder.HasKey(x => x.Id); builder.Property(x => x.Id).ValueGeneratedNever().HasColumnName("id").IsRequired(); builder.Property(x => x.Name).HasColumnName("name").IsRequired(); builder.HasData(Status.All()); } }
解决方案
原因分析
EF Core批量添加实体时,对于复用的同一个值对象静态实例(如Color.Red),可能因追踪上下文的引用识别逻辑,未正确读取其属性值,导致生成SQL时传入null。单个添加时EF能正确处理,但批量场景下引用复用引发追踪异常。
修复方案
方案1:每次创建Vehicle时生成新的Color实例
避免复用静态实例,每次创建Vehicle时生成新的Color对象:
var vehicles = Enumerable.Range(0, vehiclesCount) .Select(_ => Domain.VehicleAggregate.Vehicle.Create(model.Id, PlateNumber.Create("К333ОТ77"), new Color("red"), // 或通过工厂方法创建新实例 Vin.Create("SALYA2BN2KA791786"), Location.Create(10.0, 10.0), FuelLevel.Create())) .ToList();
可给Color添加工厂方法简化创建:
public sealed class Color : ValueObject { public static readonly Color White = new(nameof(White).ToLowerInvariant()); public static readonly Color Red = new(nameof(Red).ToLowerInvariant()); // 添加工厂方法 public static Color Create(string name) => new(name.ToLowerInvariant()); // 确保重写值对象的相等性判断 protected override IEnumerable<object> GetEqualityComponents() { yield return Name; } }
方案2:为Color值对象配置自定义ValueComparer
在EF Core的实体配置中,为Color值对象指定ValueComparer,让EF正确识别值对象的属性:
builder.OwnsOne(x => x.Color, cfg => { cfg.Property(x => x.Name).HasColumnName("color").IsRequired(); // 添加自定义ValueComparer cfg.Comparer(new ValueComparer<Color>( (c1, c2) => c1?.Name == c2?.Name, c => c?.Name.GetHashCode() ?? 0, c => new Color(c.Name))); });
这个配置告诉EF如何比较Color实例的相等性,以及如何复制实例,避免批量添加时的追踪问题。
内容的提问来源于stack exchange,提问作者Nikita Berezhko
相关产品推荐
相关产品推荐

