导航属性顺序导致序列化失败

问题描述 投票:0回答:1

任何人都可以解释这种行为,以便我将来可以管理它吗?

我有一个带有许多包含语句的查询(定义为 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>();

结果:序列化成功。

但是,如果我交换上述导航的顺序(通过剪切粘贴),序列化将因循环错误而失败。

***注意:上面我的实体的编号方案仅用于本文目的,没有任何意义。

由于我不相信顺序很重要,因此我反复从头开始执行此操作,并且始终获得相同的结果 - 我相信没有引入其他差异。

寻求帮助理解这一点:

  1. 这是预期的行为吗?
  2. 如果是,在定义实体中的导航属性的顺序时是否应该遵循一些规则/指南?

这是错误:

System.Text.Json.JsonException:'检测到可能的对象循环。这可能是由于循环造成的,或者对象深度大于允许的最大深度 64。请考虑在 JsonSerializerOptions 上使用 ReferenceHandler.Preserve 来支持循环。路径:

而且,是的,这是一个

MaxDepth
问题。

c# json entity-framework system.text.json
1个回答
0
投票

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 这里.

© www.soinside.com 2019 - 2024. All rights reserved.