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

EF Core 8 + PostgreSQL JSON列变更跟踪失效问题排查

EF Core 8 + PostgreSQL JSON列变更跟踪问题:标记Modified后无法保存变更

模型定义

public class DocumentReadModelEntity<T> where T : IDocumentReadModel
{
    public Guid Id { get; set; }
    public T Data { get; set; }

    public DocumentReadModelEntity()
    {
    }

    public DocumentReadModelEntity(T data)
    {
        Data = data;
        Id = data.Id;
    }
}
public sealed class SupplierReadModel : IDocumentReadModel
{
    public string? Description { get; set; }
    public string? Website { get; set; }
    public Guid? LogoFileId { get; set; }
    public bool IsActive { get; set; }
    public bool ShouldSendRemittanceEmail { get; set; }

    public List<SupplierBlockModel> Blocks { get; set; }
    public List<EstateBlockModel> Estates { get; set; }

    public Guid CompanyId { get; set; }
    public CompanyShortData? CompanyData { get; set; }

    public BankAccountModel? BankAccount { get; set; }
}

SupplierReadModel中的复杂属性均继承自标记了[Owned]的抽象类ReadModelDataModel,示例:

public class SupplierBlockModel : ReadModelDataModel
{
    public Guid BlockId { get; set; }
    public BlockShortData? BlockData { get; set; }
}

实体配置

public static void ApplyDefaultConfiguration<T>(this EntityTypeBuilder<DocumentReadModelEntity<T>> builder) where T : class, IDocumentReadModel
{
    builder.ToTable(typeof(T).Name);

    builder.HasKey(x => x.Id);

    builder.OwnsOne(x => x.Data, x =>
    {
        x.ToJson();
    });
}

查询与更新逻辑

查询时使用AsNoTracking直接获取Data属性:

return await _entities.AsNoTracking()
                      .Where(x => ids.Contains(x.Id))
                      .Select(x => x.Data)
                      .ToListAsync();

修改SupplierReadModel后,通过创建新实体并标记状态来更新:

public void Update(T updated)
{
    var entity = new DocumentReadModelEntity<T>(updated);
    _entities.Entry(entity).State = EntityState.Modified;
}

问题现象

调用SaveChangesAsync()后无任何数据写入数据库,查看DbContext变更跟踪器调试视图显示:

DocumentReadModelEntity<SupplierReadModel> {Id: 018d834f-75d9-48eb-8af1-543579976bc5} Modified
    Id: '018d834f-75d9-48eb-8af1-543579976bc5' PK
  Data: <not found>

问题原因

  1. 新创建的DocumentReadModelEntity实例中,Data作为Owned JSON属性未被EF Core变更跟踪器正确识别为已变更属性。仅标记整个实体为Modified时,EF无法感知Data的具体变化,调试视图的<not found>说明跟踪器未加载该属性的当前值,因此不会生成JSON列的更新SQL。
  2. 之前用AsNoTracking仅查询Data属性而非完整实体,导致变更跟踪器没有原始实体上下文可用于对比变更。

解决方案

方案1:显式标记Data属性为已修改

创建实体后先Attach到上下文,再单独标记Data属性为已修改:

public void Update(T updated)
{
    var entity = new DocumentReadModelEntity<T>(updated);
    _entities.Attach(entity);
    // 显式标记Data属性为已变更
    _entities.Entry(entity).Reference(e => e.Data).IsModified = true;
}

方案2:查询完整实体并更新(推荐)

避免直接查询Data,而是查询完整的DocumentReadModelEntity(不使用AsNoTracking),更新Data属性后让EF自动跟踪变更:

// 查询完整实体(不带AsNoTracking)
var entity = await _entities.FirstAsync(x => x.Id == updated.Id);
// 更新Data属性
entity.Data = updated;
// 无需手动设置状态,EF会自动标记Data为已修改

方案3:使用CurrentValues.SetValues设置属性

通过CurrentValues.SetValues直接设置Data属性的值,让EF识别变更:

public void Update(T updated)
{
    // 先创建仅包含Id的实体并Attach
    var entity = new DocumentReadModelEntity<T> { Id = updated.Id };
    _entities.Attach(entity);
    // 设置Data属性的当前值
    _entities.Entry(entity).CurrentValues.SetValues(new { Data = updated });
}

内容的提问来源于stack exchange,提问作者bartosz.grzeda

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 07:43:12