Как заставить EF Core Include () не отслеживать сущности? - PullRequest
0 голосов
/ 19 марта 2019

TLDR : Я хотел бы знать, возможно ли использование разных «включающих логик» для одного типа сущности в одном запросе в ядре EF.

Редактировать : Просто чтобы прояснить, в названии я сказал, что отслеживаю сущности, потому что я думаю, что это то, что делает EF, но .AsNoTracking() здесь ничего не делает, прежде чем кто-либо предложит.

Проблема в относительно небольшом приложении React, поддерживаемом приложением ASP.NET Core web api. Что я хочу сделать, так это, когда я звоню api/parents, я хочу, чтобы приложение дало мне json, который выглядит следующим образом:

[
  {
    "id": "parentid1",
    "extraProperty": "value",
    "children": [
      {
        "id": "childid1",
        "parent": {
          "id": "parentid1",
          "extraProperty": "value"
        }
      }
    ]
  }
]

Моя установка выглядит следующим образом:

запрос EF :

(from p in _context.Parents
 select p)
 .Include(p => p.Children)
     .ThenInclude(c => c.Parent)
 .ToList();

После этого у меня есть AutoMapper, отображающий сущность в dto, там происходит не так много. Я также использую по умолчанию Json Serializer (Newtonsoft) для приложения. Это настроено с SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore.

При такой настройке вызов API возвращает этот ответ:

[
  {
    "id": "parentid1",
    "extraProperty": "value",
    "children": [
      {
        "id": "childid1"
      }
    ]
  }
]

Который, как вы видите, "игнорирует" собственную ссылку на родителя.

Решение, которое я придумал, состояло в том, что я должен настроить Newtonsoft для «сериализации» эталонных циклов, и я попробовал это. И он выбрасывает, потому что приведенный выше запрос EF возвращает список сущностей, который выглядит следующим образом:

[
  {
    "id": "parentid1",
    "extraProperty": "value",
    "children": [
      {
        "id": "childid1",
        "parent": {
          "id": "parentid1",
          "extraProperty": "value",
          "children": [
            {
              "id": "childid1",
              "parent": {
                "id": "parentid1",
                "extraProperty": "value"
                ...
              }
            }
          ]
        }
      }
    ]
  }
]

Если вы посмотрите на мои вызовы Include, там явно сказано, что для моих дочерних объектов мне нужен только родительский объект, а внутри этого родительского объекта никаких других ссылок нет.

На мой взгляд, EF использует начальную настройку .Include(child).ThenInclude(parent), когда сталкивается с любым родительским объектом для этого запроса. Поэтому, когда он находит Parent объект в Child объекте, вместо использования no include (которого у меня нет после ThenInclude), он использует .Include(child).ThenInclude(parent).

Я не хочу решать эту проблему путем хаков с помощью картографа или сериализатора, если мне это не нужно. Хотелось бы узнать, возможно ли то, что я ищу: использовать разные «логики включения» для одного типа сущности в одном запросе.

Ответы [ 2 ]

1 голос
/ 19 марта 2019

Вызов ThenInclude(parent) является избыточным, поскольку вы начинаете с родителя - материализованным дочерним элементам уже будет заполнено свойство Parent. Вам нужно настроить сериализацию, как @IvanStoev указано в первом комментарии.

Альтернатива - запросить детей по родителю, а затем спроецировать желаемый результат:

var parents = _context.Children
    .Include( c => c.Parent )
    .GroupBy( c => c.Parent )
    .ToArray()
    .Select( g => 
        {
            // assign children to g.Key (the parent object)
            g.Key.Children = g.ToArray(); // I don't know type of `Children` property
            // select parent
            return g.Key;
        } );
0 голосов
/ 19 марта 2019

Я решил решить эту проблему в начале, потому что другие решения просто не работали для меня.Для будущих ссылок я буду публиковать свое путешествие:

EF

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

После этого я подумал, что, возможно, смогу решить эту проблему на этапе отображения.Я пытался сделать разные профили / конфигурации для разных сценариев, и это стало очень уродливо.Тогда я подумал, что, возможно, с помощью одного профиля и логики AfterMap() сработает (сначала загрузите все данные, а затем удалите ненужные данные).Но тогда, если вы не делаете некрасивые вещи, применяется тот же принцип.AutoMapper также сохраняет ссылки, поэтому при изменении объекта child.Parent исходный родительский объект также изменяется.Я мог бы, возможно, использовать клонирование и, возможно, сделать некоторые другие трюки, но, как я уже сказал, на мой взгляд, это уродливо.

Пока я делаю эту задачу вручную в приложении React.Когда я получаю данные с сервера, я просто делаю

parent.children.forEach(c => c.parent = parent);

Имейте в виду, что обычно parent.children.parent is null.Это не относится к объектным объектам или DTO, это просто случай после сериализации.Потому что я настроил ReferenceLoopHandling.Ignore для сериализатора.

Это исправляет все мои проблемы с приложением, за исключением того, что мой API кажется неполным.Возвращенный JSON выглядит так, как будто чего-то не хватает (может быть, это только я, IDK).

В конце дня я рассматриваю возможность иметь несколько DTO одного и того же типа объекта для разных сценариев или получить возвращенные поля idв API для полноты в долгосрочной перспективе.

Спасибо всем за комментарии и ответы.

...