Самостоятельная ссылка при сериализации вложенного объекта - PullRequest
1 голос
/ 08 октября 2019

Я хочу сохранить снимок вложенной модели в моей базе данных как историю изменений. Поэтому я сделал модель, которая сериализует весь объект в строку JSON для упрощения хранения.

Упрощенный Data класс, который я хочу сохранить:

public class Data
{
    public int Id { get; set; }
    public string SomeInfo { get; set; }
    public virtual ICollection<DataObject> DataObject { get; set; }
}

DataObject для коллекциивнутри Data:

public class DataObject
{
    public int Id { get; set; }
    public string SomeMoreInfo { get; set; }
    public int DataId { get; set; }
    public virtual Data Data { get; set; }
}

Мой класс снимков выглядит примерно так:

public class DataHistory
{
    public int Id { get; set; }
    private string _Data;

    [NotMapped]
    public Data Data
    {
        get { return _Data == null ? null : JsonConvert.DeserializeObject<Data>(_Data); }
        set { _Data = JsonConvert.SerializeObject(value , Formatting.Indented, 
                new JsonSerializerSettings { 
                        ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
                        PreserveReferencesHandling = PreserveReferencesHandling.None
                });
        }
    }
}

Внутри моего контроллера я делаю:

var data = await _repo.GetData(id);
var historyEntry = new DataHistory();
historyEntry.Data= data;
_repo.Add(historyEntry); 

GetData() методвнутри репозитория:

public async Task<Data> GetData(int id)
{
   return await _context.Data
   .Include(d => d.DataObject)
   .FirstOrDefaultAsync(d => d.Id == id);
}

Проблема в том, что когда я пытаюсь сериализовать одну запись Data, я получаю собственную ссылку внутри DataObject, поэтому она снова включает в себя объект Data, а также DataObjects. Даже с ReferenceLoopHandling.Ignore полученный JSON выглядит примерно так:

{
  "Id": 1051,
  "SomeInfo": "asdasd",
  "DataObject": [
    {
      "Id": 121,
      "SomeMoreInfo": "asdasd",
      "Data": {
        "Id": 1051,
        "SomeInfo": "asdasd",
        "DataObject": [
          {
            "Id": 122,
            "SomeMoreInfo": "asdasd",
            "DataId": 1051
          }
        ]
      }
    },
    {
      "Id": 122,
      "SomeMoreInfo": "asdasd",
      "Data": {
        "Id": 1051,
        "SomeInfo": "asdasd",
        "DataObject": [
          {
            "Id": 121,
            "SomeMoreInfo": "asdasd",
            "DataId": 1051
          }
        ]
      }
    }
  ]
}

РЕДАКТИРОВАТЬ: Ожидаемый результат будет примерно таким:

{
    "Id": 1051,
    "SomeInfo": "Data",
    "DataObject": [
        {
            "Id": 121,
            "SomeMoreInfo": "DataObject1"
            "DataId": 1051
        },
        {
            "Id": 122,
            "SomeMoreInfo": "DataObject2"
            "DataId": 1051
        }
    ]
}

Как я могуостановить его от включения Data во второй раз без использования DTO?

РЕДАКТИРОВАТЬ:

Если я попробую это без Entity Framework, ReferenceLoopHandling.None будет работать как положено. См. Dotnet Fiddle https://dotnetfiddle.net/bmAoAW. Так что, похоже, проблема с моей конфигурацией EF Core или чем-то подобным.

1 Ответ

1 голос
/ 08 октября 2019

Вы сказали в комментариях, что фактически хотите, чтобы свойство DataObject.Data игнорировалось при сериализации Data из DataHistory. Вы можете сделать это с помощью пользовательского ContractResolver для программного игнорирования свойства.

Вот код, который вам понадобится для распознавателя:

public class CustomResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty prop = base.CreateProperty(member, memberSerialization);
        if (prop.DeclaringType == typeof(DataObject) && prop.PropertyName == nameof(DataObject.Data))
        {
            prop.Ignored = true;
        }
        return prop;
    }
}

Затем примените его в JsonSerializerSettings в DataHistory.Data:

    set { _Data = JsonConvert.SerializeObject(value , Formatting.Indented, 
            new JsonSerializerSettings { 
                    ContractResolver = new CustomResolver(),
                    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
                    PreserveReferencesHandling = PreserveReferencesHandling.None
            });
    }
...