EF Core使用值对象作为主键,保存后实体ID未更新求助
问题:EF Core保存实体后值对象类型主键未更新为数据库自增ID
问题场景
在C# DDD架构中,实体主键Id是封装非负非零整数的值对象EntityId,通过EF Core Fluent API配置了与int类型的转换器,数据库侧主键为自增int。创建新实体保存后,数据库生成了预期的自增ID,但实体对象的Id仍为初始值0,而DbContext中该实体的ID是正确的。改用int类型主键时能正常更新,使用CSharpFunctionalExtensions实现值对象,尝试过ValueGeneratedOnAdd配置无效。
相关代码
实体类
public class Entity { Entity() {} public static Entity CreateNew() { Id = EntityId.CreateNew(); } public EntityId Id { get; private init; } = null; // ...其他属性 }
值对象类
public class EntityId : SimpleValueObject<int> { EntityId(int value) : base(value) {} public static EntityId CreateNew() { return new EntityId(0); } }
SimpleValueObject核心定义
public T Value { get; private set; }
MySQL表结构
CREATE TABLE entity ( EntityId INT NOT NULL AUTO_INCREMENT PRIMARY KEY, Field1 NVARCHAR(3072) NOT NULL, Field2 INT NOT NULL, -- ...其他字段 );
EF Core配置
public class EntityConfiguration : IEntityTypeConfiguration<Entity> { public void Configure(EntityTypeBuilder<Entity> builder) { builder.ToTable("entity"); builder.HasKey(j => j.Id).HasName("PRIMARY"); builder .Property(e => e.Id) .HasConversion(v => v.Value, v => EntityId.Create(v).Value) .HasColumnName("EntityId") .HasColumnType("int(11)"); } }
测试代码
var entity = Entity.CreateNew(); _db.Entities.Add(entity); await _db.SaveChangesAsync(cancellationToken); // 预期Id是数据库生成的自增值,但实际返回Value=0的EntityId var id = entity.Id; // 但DbContext中的实体Id是正确的 var allEntities = _db.Entities;
运行环境:EF Core v8.0.8、.NET v8.0.303
解决方法
1. 修正值对象的实例创建逻辑
给EntityId添加专门从数据库int值生成实例的方法,同时确保EF Core能正确构造对象:
public class EntityId : SimpleValueObject<int> { // 私有构造,仅内部和EF Core可调用 private EntityId(int value) : base(value) {} // 创建新实体时的初始ID(0) public static EntityId CreateNew() { return new EntityId(0); } // 从数据库返回的int值创建合法的EntityId public static EntityId FromInt(int value) { if (value <= 0) throw new ArgumentException("EntityId必须是正整数", nameof(value)); return new EntityId(value); } }
同时调整SimpleValueObject的Value属性为protected set,让EF Core能通过反射更新值:
public T Value { get; protected set; }
2. 修正EF Core配置
明确告知EF Core该主键是添加时自动生成的,同时修正转换器的反向转换逻辑:
public class EntityConfiguration : IEntityTypeConfiguration<Entity> { public void Configure(EntityTypeBuilder<Entity> builder) { builder.ToTable("entity"); builder.HasKey(j => j.Id).HasName("PRIMARY"); builder .Property(e => e.Id) .HasConversion( v => v.Value, // 实体转数据库:EntityId -> int v => EntityId.FromInt(v) // 数据库转实体:int -> EntityId ) .HasColumnName("EntityId") .HasColumnType("int(11)") .ValueGeneratedOnAdd(); // 标记为添加时自动生成值 } }
3. 调整实体类的主键Setter
将Id的private init改为private set,因为init仅能在对象初始化阶段赋值,EF Core保存后无法更新该属性:
public class Entity { // 私有构造供EF Core使用 private Entity() {} public static Entity CreateNew() { return new Entity { Id = EntityId.CreateNew() }; } public EntityId Id { get; private set; } = null!; // ...其他属性 }
4. 验证结果
保存实体后,entity.Id.Value将返回数据库生成的自增ID,与DbContext中的值一致。
内容的提问来源于stack exchange,提问作者Keyan
相关产品推荐
相关产品推荐

