0

Maybe somewhat of an odd use case, but I have a large amount of json that I need to put into a database, but the issue is that there are multiple occurrences of the author object within the json object that I need to refer to the same author object.

For example:

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": "..."
    }
  ]
}

In this example, the authors with the same Id are deserialized into two different objects when I need them to reference the same object due to Entity Framework Core demanding that no two objects with the same Id can be tracked.

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.'
BlueFrog
  • 1,147
  • 1
  • 7
  • 15
  • The json is just an example, not the actual data. Don't need a `Reviewer` class because it is the same format as `Author` they are the same thing. I want them to point to the same table in the database. – BlueFrog Aug 19 '21 at 14:23
  • System.Text.Json does support tracking of references, see [How to preserve references and handle or ignore circular references in System.Text.Json](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-preserve-references) for how to do it. Note however that the format is different, it uses `"$id"` and `"$ref"` properties. Can you change your JSON format? Does that functionality meet your needs? – dbc Aug 19 '21 at 14:27
  • @dbc I took a look and tried creating a handler, but I do not control the json format. Would there be some way to set the reference resolver to use `id`? – BlueFrog Aug 19 '21 at 14:39
  • I don't believe that is possible with System.Text.Json (or Newtonsoft for that matter). See [the docs for `ReferenceHandler.Preserve`](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.referencehandler.preserve?view=net-5.0) which do not mention the ability to rename the properties. Likely you will need a custom converter, do you have control over your `JsonSerializerOptions` when deserializing? – dbc Aug 19 '21 at 14:53
  • Yeah. I have been working on setting up a custom resolver, but there are many objects being deserialized and I only care about the `Author` references – BlueFrog Aug 19 '21 at 14:54
  • Where is the `System.InvalidOperationException` thrown? Can you share the full `ToString()` output of the exception including the exception type, message, **traceback** and inner exception(s) if any? Is it in deserialization when constructing and populating an `Author` or is it much later, when submitting changes to the database? If later, then fixing references after serialization makes sense. – dbc Aug 22 '21 at 17:10

1 Answers1

1

You can fix the references to the authors after doing the deserialization like this.

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];
}

Depending on the amount of books and processor it might be worth it to do it in parallel.

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);
}
gjhommersom
  • 159
  • 1
  • 7