EF Core 改用复杂类型后,嵌套 JSON 字段名变了
EF Core 10 的复杂属性 JSON 映射可能忽略嵌套对象或集合上的 JsonPropertyName,标量字段却仍正常。显式设置模型中的 JSON 名称,保持既有文档契约。
把 JSON 模型从 OwnsOne、OwnsMany 改为复杂属性后,原来短键名的嵌套对象突然写成 CLR 属性名:例如预期的 a 变成 Author,集合的 tg 变成 Tags。同一文档里,Title 上的 [JsonPropertyName("t")] 却仍然有效。
这种局部失效很容易被误认为序列化器配置没有加载。先区分标量属性、嵌套复杂属性和复杂集合,再检查 EF 模型里最终登记的 JSON 名称;仅核对 Attribute 还不够。
先保住已经存在的 JSON 契约
已确认的组合为 EF Core 10.0.14、Npgsql 10.0.3,触发点是复杂属性或复杂集合自身的 JSON 名称。旧的 owned 映射能够识别的 Attribute,不能直接推定新映射也会按同一条约定处理。
若数据库里已经有 a、tg 等键,先暂停依赖新映射的批量写入。把键改回长名称、重写全部文档,或删除旧字段,都可能让其他消费者读不到原数据。需要修正的是应用的模型命名契约,不能仅为让一次查询通过而把数据结构换掉。
在模型定稿前补齐复杂属性名称
小模型可以逐项显式配置名称。若项目已经广泛使用 JsonPropertyName,可以在 OnModelCreating 中完成原映射后,把 CLR 属性上的名称写入对应的复杂属性元数据。下面的辅助方法递归处理嵌套复杂属性,包括复杂集合:
using System.Reflection;
using System.Text.Json.Serialization;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
static void ApplyComplexJsonNames(IMutableComplexProperty property)
{
var attribute = property.PropertyInfo?
.GetCustomAttribute<JsonPropertyNameAttribute>();
if (attribute is not null)
property.SetJsonPropertyName(attribute.Name);
foreach (var child in property.ComplexType.GetComplexProperties())
ApplyComplexJsonNames(child);
}
foreach (var entity in modelBuilder.Model.GetEntityTypes())
foreach (var property in entity.GetDeclaredComplexProperties())
ApplyComplexJsonNames(property);
这段代码应放在本项目所有相关映射配置之后、模型完成之前。它只读取有 PropertyInfo 的 CLR 属性,不为字段映射、影子成员或没有 Attribute 的属性猜名称。若项目同时通过 Fluent API 设置了不同名称,应先决定哪一种配置优先,不要无意间用这段遍历覆盖已经明确的契约。
EF Core 10 的关系元数据 API 支持 SetJsonPropertyName。采用前仍要按实际安装包编译核对;预发布包或后续版本若调整了复杂类型约定,应重新验证,而不是无限期叠加补丁。
同时验证旧文档和新文档
先读取一份保留旧短键名的文档,核对嵌套对象、嵌套集合及内部标量。随后在可回滚环境中保存同样形状的新对象,直接检查 JSON 键仍为既定名称,再通过查询读取回来。空集合、可空嵌套对象和包含多层复杂属性的记录也要覆盖。
若查询条件会进入 JSON 路径,另外核对生成 SQL 中使用的键。写入正确不代表所有查询投影已经正确;读取到默认值也不等于数据库真的没有值。
这里处理的是复杂属性名称约定缺失。若错误是 CreateJsonShapers 中的 null key,或 SQLite 生成了错误的 JSON 路径,需要依据各自异常和 SQL 另查,不能用同一个补丁解释所有 JSON 故障。


