任何人都可以解释这种行为,以便我将来可以管理它吗?
我有一个带有许多包含语句的查询(定义为 IQueryable)。我正在将实体框架用于 SQL Server 数据库。
主要实体和一些包含的实体有一个代码表的外键,我用它来定义时间段类型(年、月、季度等)以及与这些类型相关的数据。
在“周期”实体中,我具有为其提供值的实体的导航属性。
成功运行查询及其包含内容后,我将使用 system.text.json 序列化结果。
尽管将引用处理程序设置为“保留”,但我开始遇到序列化循环错误。
经过调查,我发现序列化成功或失败取决于我在代码表实体中包含的顺序。
所以,如果我的实体包含这样的导航属性:
public IList<entity1>? entity1Collection { get; set; } = new List<entity1>();
public IList<entity2>? entity2Collection { get; set; } = new List<entity3>();
public IList<entity3>? entity3Collection { get; set; } = new List<entity3>();
public IList<entity4>? entity4Collection { get; set; } = new List<entity4>();
public IList<entity5>? entity5Collection { get; set; } = new List<entity5>();
结果:序列化成功。
但是,如果我交换上述导航的顺序(通过剪切粘贴),序列化将因循环错误而失败。
***注意:上面我的实体的编号方案仅用于本文目的,没有任何意义。
由于我不相信顺序很重要,因此我反复从头开始执行此操作,并且始终获得相同的结果 - 我相信没有引入其他差异。
寻求帮助理解这一点:
这是错误:
System.Text.Json.JsonException:'检测到可能的对象循环。这可能是由于循环造成的,或者对象深度大于允许的最大深度 64。请考虑在 JsonSerializerOptions 上使用 ReferenceHandler.Preserve 来支持循环。路径:
而且,是的,这是一个
MaxDepth
问题。
System.Text.Json 是一个深度优先序列化器。当启用引用保存时,对象将在第一次遇到时被序列化。只有
"$ref": "N"
会在后续遭遇中被序列化。因此,如果您的序列化图足够深,更改属性顺序可能会导致超出MaxDepth
。
例如,考虑以下模型,其中包含父母列表和所有孩子的列表。父母有孩子,孩子有朋友:
public class Model
{
public List<Child> AllChildren { get; set; }= new();
public List<Parent> Parents { get; set; }= new();
}
public class Parent
{
public List<Child> Children { get; set; } = new();
}
public class Child
{
public List<Child> Friends { get; set; } = new ();
}
如果我构造一个有 2 个父母和 2 个孩子的实例,其中每个孩子都是朋友:
var child1 = new Child { };
var child2 = new Child { Friends = { child1 } };
child1.Friends.Add(child2);
var model = new TModel
{
AllChildren = { child1, child2 },
Parents = { new Parent { Children = { child1 } }, new Parent { Children = { child2 } } },
};
然后模型将在
MaxDepth = 10
处序列化成功,并在 MaxDepth = 9
处失败。演示小提琴#1 这里.
现在想象我修改
Model
以首先序列化父母:
public class Model
{
public List<Parent> Parents { get; set; }= new();
public List<Child> AllChildren { get; set; }= new();
}
然后 序列化将在
MaxDepth
10、11 和 12。它只会从 MaxDepth = 13
开始成功序列化。演示小提琴 #2 这里.
造成差异的原因是之前提到的深度优先序列化。在第一个版本中,第二个孩子在
AllChildren
列表中被序列化为第一个孩子的朋友:
{
"$id": "1",
"AllChildren": {
"$id": "2",
"$values": [
{
"$id": "3",
"Friends": {
"$id": "4",
"$values": [
{
"$id": "5",
"Friends": {
"$id": "6", // Here is where the second child is serialized.
"$values": [
{
"$ref": "3"
}
]
}
}
]
}
},
{
"$ref": "5"
}
]
},
"Parents": {
"$id": "7",
"$values": [
{
"$id": "8",
"Children": {
"$id": "9",
"$values": [
{
"$ref": "3"
}
]
}
},
{
"$id": "10",
"Children": {
"$id": "11",
"$values": [
{
"$ref": "5"
}
]
}
}
]
}
}
但是在第二个版本中,第二个子元素在
Parents
列表中序列化,导致最大深度增加:
Testing MaxDepth = 13:
{
"$id": "1",
"Parents": {
"$id": "2",
"$values": [
{
"$id": "3",
"Children": {
"$id": "4",
"$values": [
{
"$id": "5",
"Friends": {
"$id": "6",
"$values": [
{
"$id": "7",
"Friends": {
"$id": "8", // Here is where the second child is serialized. Notice it's deeper in the graph.
"$values": [
{
"$ref": "5"
}
]
}
}
]
}
}
]
}
},
{
"$id": "9",
"Children": {
"$id": "10",
"$values": [
{
"$ref": "7"
}
]
}
}
]
},
"AllChildren": {
"$id": "11",
"$values": [
{
"$ref": "5"
},
{
"$ref": "7"
}
]
}
}
JsonPropertyOrderAttribute
,以强制子级在父级之前被序列化:
public class Model
{
[JsonPropertyOrder(2)]
public List<Parent> Parents { get; set; }= new();
[JsonPropertyOrder(1)]
public List<Child> AllChildren { get; set; }= new();
}
如果这样做,序列化将不再依赖于 C# 属性顺序。演示#3 这里.