You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 11:04:52