EFCore Дубликаты в объекте, ссылающемся на себя

Привет, у меня есть самоссылающийся объект, который фактически является отражением модели задачи › подзадачи в планах проекта, где в плане проекта может быть много слоев задач:

  public class ProjectPlanItem
    {
        [Key]
        public int Id { get; set; }
        public string Name { get; set; }
        public Guid Reference { get; set; } = Guid.NewGuid();
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
        public DateTime BaselineStartDate { get; set; }
        public DateTime BaselineEndDate { get; set; }
        public int Duration { get; set; }
        public string DurationUnit { get; set; }
        public int Progress { get; set; }
        public string Predecessor { get; set; }
        public virtual ICollection<ProjectPlanItem> ProjectPlanSubItems { get; set; } = new  List<ProjectPlanItem>();
    }

DBContext правильно создает таблицу базы данных, имея FK для вложенного ProjectPlanItemID

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

var plans = await _context.ProjectPlans
    .Include(p => p.ProjectPlanEvents)
    .Include(p => p.ProjectPlanHolidays)
    .Include(p => p.ProjectPlanResources)
    .Include(p => p.ProjectPlanItems)
        .ThenInclude(pi => pi.ProjectPlanSubItems)
    .ToListAsync();

Это помогает расширять мой объект по мере необходимости для всех связанных ICollection, кроме вложенных элементов плана проекта, где вложенные элементы появляются (неправильно) в корне и (правильно) во вложенной коллекции. Согласно приведенному ниже:

  • Group 1
    • Group 1 Sub Item 1
    • Группа 1 Подпункт 2
  • Группа 1 Подпункт 1
  • Группа 1 Подпункт 2
  • Group 2
    • Group 2 Sub Item 1
    • Группа 2 Подпункт 2
  • Группа 2 Подпункт 1
  • Группа 2 Подпункт 2

Только что попробовал с еще одним третьим уровнем вложенности, и та же ситуация повторяется, но на этот раз

  • G1
    • G1 I1
    • G2
      • G2 I1
  • G1 I1
  • G2
    • G2 I1

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

Я только что перенес проект на .net 5 rc2 с .net core 3 на случай, если в новой версии будет что-то полезное.

есть идеи?

ОБНОВЛЕНИЕ: Запрос на модель ProjectPlan:

public class ProjectPlan
    {
        [Key]
        public int ID { get; set; }
        public Guid Reference { get; set; } = Guid.NewGuid();
        public string Name { get; set; }
        public string Version { get; set; }
        public DateTime VersionDate { get; set; }
        public ProjectPlanStatus Status { get; set; }
        public string WorkWeek { get; set; }
        public ICollection<ProjectPlanItem> ProjectPlanItems { get; set; } = new List<ProjectPlanItem>();
        public ICollection<ProjectPlanEventMarker> ProjectPlanEvents { get; set; } = new List<ProjectPlanEventMarker>();
        public ICollection<ProjectPlanResource> ProjectPlanResources { get; set; } = new List<ProjectPlanResource>();
        public ICollection<ProjectPlanHoliday> ProjectPlanHolidays { get; set; } = new List<ProjectPlanHoliday>();

    }

ОБНОВЛЕНИЕ 2 - Запрос вывода с тестовыми данными (я не заполнил другие коллекции в родительском элементе ProjectPlan, а только элементы ProjectPlanItems, которые он имеет в коллекции).

{
  "id": 1,
  "reference": "1735cc99-81d4-42aa-b5f5-62c012e1cd6a",
  "name": "My Project Plan",
  "version": "1.0",
  "versionDate": "2020-10-26T00:00:00",
  "status": 1,
  "workWeek": "Monday",
  "projectPlanItems": [
    {
      "id": 1,
      "name": "Group 1",
      "reference": "69e9fe1e-8219-4178-b465-6074cd1e08e4",
      "startDate": "2020-10-01T00:00:00",
      "endDate": "2020-10-30T00:00:00",
      "baselineStartDate": "2020-10-01T00:00:00",
      "baselineEndDate": "2020-10-30T00:00:00",
      "duration": 30,
      "durationUnit": "d",
      "progress": 75,
      "predecessor": null,
      "isSubItem": false,
      "projectPlanSubItems": [
        {
          "id": 2,
          "name": "Group 1 Item 1",
          "reference": "f038d10f-862a-468d-b664-d4e848b73cac",
          "startDate": "2020-10-01T00:00:00",
          "endDate": "2020-10-15T00:00:00",
          "baselineStartDate": "2020-10-01T00:00:00",
          "baselineEndDate": "2020-10-15T00:00:00",
          "duration": 15,
          "durationUnit": "d",
          "progress": 75,
          "predecessor": null,
          "isSubItem": true,
          "projectPlanSubItems": []
        },
        {
          "id": 3,
          "name": "Group 1 Item 2",
          "reference": "3d32845b-3a9e-42bf-8a38-414d8822a8c5",
          "startDate": "2020-10-16T00:00:00",
          "endDate": "2020-10-30T00:00:00",
          "baselineStartDate": "2020-10-16T00:00:00",
          "baselineEndDate": "2020-10-30T00:00:00",
          "duration": 15,
          "durationUnit": "d",
          "progress": 75,
          "predecessor": null,
          "isSubItem": true,
          "projectPlanSubItems": []
        }
      ]
    },
    {
      "id": 2,
      "name": "Group 1 Item 1",
      "reference": "f038d10f-862a-468d-b664-d4e848b73cac",
      "startDate": "2020-10-01T00:00:00",
      "endDate": "2020-10-15T00:00:00",
      "baselineStartDate": "2020-10-01T00:00:00",
      "baselineEndDate": "2020-10-15T00:00:00",
      "duration": 15,
      "durationUnit": "d",
      "progress": 75,
      "predecessor": null,
      "isSubItem": true,
      "projectPlanSubItems": []
    },
    {
      "id": 3,
      "name": "Group 1 Item 2",
      "reference": "3d32845b-3a9e-42bf-8a38-414d8822a8c5",
      "startDate": "2020-10-16T00:00:00",
      "endDate": "2020-10-30T00:00:00",
      "baselineStartDate": "2020-10-16T00:00:00",
      "baselineEndDate": "2020-10-30T00:00:00",
      "duration": 15,
      "durationUnit": "d",
      "progress": 75,
      "predecessor": null,
      "isSubItem": true,
      "projectPlanSubItems": []
    }
  ],
  "projectPlanEvents": [],
  "projectPlanResources": [],
  "projectPlanHolidays": []
}

Вы можете видеть, что в JSON ProjectPlanItem ID=2 и ID=3 правильно вложены в коллекцию в ProjectPlanItem ID=1.

Затем они неправильно повторяются в коллекции родительской корневой коллекции.

ОБНОВЛЕНИЕ 4. Отделите загрузку ProjectPlanItems от .Include в родительском плане проекта:

Я поиграл и решил лениво загрузить все остальные коллекции в родительский объект ProjectPlan, а затем отдельно загрузить ProjectPlanItems в качестве отдельного шага с помощью .Where, выбрав только элементы ProjectPlanItems верхнего уровня.

var projectPlan = await _context.ProjectPlans
    .Where(p=> p.ID==id)
    .Include(p => p.ProjectPlanEvents)
    .Include(p => p.ProjectPlanHolidays)
    .Include(p => p.ProjectPlanResources)
    .FirstOrDefaultAsync();

var projectplanitems = await _context.ProjectPlanItems
    .Where(ppi => ppi.IsSubItem == false)
    .Include(ppi => ppi.ProjectPlanSubItems)
    .ToListAsync();

projectPlan.ProjectPlanItems = projectplanitems;

Это сработало и дало мне только один элемент в ProjectPlan.ProjectPlanItems (с идентификатором = 1), как и ожидалось, а затем 2 элемента в ProjectPlanItem.ProjectPlanSubItems с идентификатором 2 и 3 снова, как и ожидалось.

Итак, учитывая, что я обновил решение до .net5 RC2, я попытался добавить .Where в исходную ленивую загрузку, чтобы (теоретически) дать мне тот же запрос, что и выше, но только в одном туда и обратно:

var plans = await _context.ProjectPlans
    .Include(p => p.ProjectPlanEvents)
    .Include(p => p.ProjectPlanHolidays)
    .Include(p => p.ProjectPlanResources)
    .Include(p => p.ProjectPlanItems.Where(p => p.IsSubItem==false))
        .ThenInclude(pi => pi.ProjectPlanSubItems)
    .ToListAsync();

Но это не имеет никакого эффекта и по-прежнему дает неверный вывод.

Заботится ли EF, если я сделаю два запроса, чтобы заполнить объект в контроллере

Я предполагаю, что если я внесу изменения в клиент, а затем отправлю его обратно, EF не будет знать или не заботиться ни в малейшей степени?


person haPartnerships    schedule 26.10.2020    source источник
comment
Можете ли вы поделиться с нами вашей моделью ProjectPlans?   -  person David Edel    schedule 26.10.2020
comment
@DavidEdel обновил первоначальный вопрос.   -  person haPartnerships    schedule 27.10.2020
comment
Можете ли вы показать нам текущий результат и ожидаемый результат с некоторыми примерами данных?   -  person mj1313    schedule 27.10.2020
comment
@ mj1313 mj1313 Добавил вывод JSON метода Get в API, любые мысли будут очень кстати.   -  person haPartnerships    schedule 27.10.2020
comment
К сведению: у меня есть код для работы, но мне пришлось совершить две поездки: одну для родительского плана проекта и одну для элементов плана проекта. Кажется, ненужные усилия, но мысли о том, должен ли я просто взять на себя удар по этому поводу.   -  person haPartnerships    schedule 27.10.2020


Ответы (1)


Для такого массивного запроса Include лучше использовать представленный в EF Core 5 AsSplitQuery() и молиться, чтобы он работал. В противном случае подготовьте исполняемый образец и опубликуйте сообщение об ошибке на сайте EF Core GitHub.

var plans = await _context.ProjectPlans
    .AsSplitQuery()
    .Include(p => p.ProjectPlanEvents)
    .Include(p => p.ProjectPlanHolidays)
    .Include(p => p.ProjectPlanResources)
    .Include(p => p.ProjectPlanItems)
        .ThenInclude(pi => pi.ProjectPlanSubItems)
    .ToListAsync();
person Svyatoslav Danyliv    schedule 26.10.2020
comment
Спасибо, я попытался добавить это в запрос на включение, и, к сожалению, это то же самое. - person haPartnerships; 27.10.2020