【问题标题】:System.Text.Json Deserialize object with multiple child objects into the same instanceSystem.Text.Json 将具有多个子对象的对象反序列化到同一个实例中
【发布时间】:2021-08-19 14:11:50
【问题描述】:

也许有点奇怪的用例,但我需要将大量 json 放入数据库,但问题是我需要的 json 对象中多次出现 author 对象引用同一个作者对象。

例如:

public class Book
{
  public string Title { get; set; }
  public string Genre { get; set; }
  public Author Author { get; set; }
  public List<Author> Reviewers { get; set; }
}

public class Author
{
  public string Id { get; set; }
  public string Name{ get; set; }
}
// data.json
{
  "title": "...",
  "genre": "...",
  "author": { // Deserializes into Author class
    "id": "1"
    "name": "..."
  },
  "reviewers": [ // Deserializes into List<Author>
    {
      "id": "1", // Needs to point to same object as "author"
      "name": "..."
    },
    {
       "id": "2",
       "name": "..."
    }
  ]
}

在此示例中,由于 Entity Framework Core 要求不能跟踪具有相同 Id 的两个对象,因此当我需要它们引用同一个对象时,具有相同 Id 的作者被反序列化为两个不同的对象。

System.InvalidOperationException:
  'The instance of entity type 'Author' cannot be tracked because another instance with the key
  value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only
  one entity instance with a given key value is attached.'

【问题讨论】:

  • json 只是一个例子,不是实际数据。不需要Reviewer 类,因为它与Author 格式相同,它们是相同的。我希望它们指向数据库中的同一个表。
  • System.Text.Json 确实支持引用跟踪,请参阅How to preserve references and handle or ignore circular references in System.Text.Json 了解如何做到这一点。但请注意,格式不同,它使用 "$id""$ref" 属性。你能改变你的JSON格式吗?该功能是否满足您的需求?
  • @dbc 我看了看并尝试创建一个处理程序,但我不控制 json 格式。有没有办法将引用解析器设置为使用id
  • 我不相信 System.Text.Json (或 Newtonsoft 就此而言)是可能的。请参阅the docs for ReferenceHandler.Preserve,其中没有提到重命名属性的能力。您可能需要一个自定义转换器,在反序列化时您是否可以控制您的 JsonSerializerOptions
  • 是的。我一直在努力设置一个自定义解析器,但是有很多对象被反序列化,我只关心 Author 引用

标签: c# json .net entity-framework-core system.text.json


【解决方案1】:

您可以在像这样进行反序列化后修复对作者的引用。

Dictionary<string, Author> authors = new();

foreach(var book in books)
{
  book.Author = GetAuthor(book.Author);
  book.Reviewers = book.Reviewers.Select(r => GetAuthor(r)).ToList();
}

Author GetAuthor(Author author)
{
  if(!authors.ContainsKey(author.Id))
  {
    authors[author.Id] = author;
  }
  return authors[author.Id];
}

根据书籍和处理器的数量,并行执行可能是值得的。

ConcurrentDictionary<string, Author> authors = new();

Parallel.ForEach(books, book =>
{
  book.Author = GetAuthor(book.Author);
  book.Reviewers = book.Reviewers.Select(r => GetAuthor(r)).ToList();
});

Author GetAuthor(Author author)
{
  return authors.GetOrAdd(author.Id, _ => author);
}

【讨论】:

  • 这就是我最终要做的。有点痛苦,但我无法让Resolver 工作
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-02-11
  • 2013-02-05
  • 1970-01-01
  • 2016-08-16
  • 2014-05-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多