Как загрузить рекурсивную сущность, не загружая потомков на уровне корня? - PullRequest
0 голосов
/ 30 мая 2018

Если вы посмотрите на демонстрационный веб-сайт CoreUI , вы увидите навигационную панель с левой стороны, с множеством складных уровней.Я хочу реализовать что-то подобное, но динамически.Я создал простой класс:

public class NavItem
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int navItemId { get; set; }
    public int sortOrder { get; set; }
    public bool isTitle { get; set; }
    public bool isDivider { get; set; }
    public string cssClass { get; set; }
    public string name { get; set; }
    public string url { get; set; }
    public string icon { get; set; }
    public string variant { get; set; }
    public string badgeText { get; set; }
    public string badgeVariant { get; set; }
    public virtual ICollection<NavItem> children { get; set; }
}

Обратите внимание на свойство ICollection<NavItem> children.

В любом случае, я заполнил его набором образцов данных (пример CoreUI), и он правильно сохраняется вбаза данных, с полем с именем NavItemId1, хранящим идентификатор родителя любого потомка.Пока все хорошо.

Теперь я хотел бы сделать запрос, поэтому я сделал очевидное:

var nI = db.navItems.ToList();

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

Однако оно также включало все дочерние элементы на корневом уровне ... так что вместо 15 элементов корневого уровня у меня есть 40.

Есть ли запрос linq, который я могу выполнить, чтобы предотвратить заполнение корневого уровня списка дочерними элементами (т. Е. Исключая любое, где поле NavItemId1! = Null), но все равно правильно загружает оставшуюся частьструктура?

например вот что я получаю сейчас:

  • root1
  • root2
    • child1-of-root2
    • child2-of-root2
      • child1-of-child2-of-root2
  • root3
    • child1-of-root3<-- I want my list to end here
  • child1-of-root2
  • child2-of-root2
    • child1-of-child2-of-root2
  • child1-of-child2-of-root2
  • child1-of-root3

Я мог бы добавить логическое свойство isRoot, а затем выполнить запрос после чтения, чтобы отбросить любые элементы.на уровне корня, у которого не установлено isRoot, например,

var nI = db.navItems.ToList();
nI = nI.Where(p => p.isRoot).ToList();

, но это кажется слишком громоздким.

Пример кода, показывающий проблему.В частности, посмотрите на пункты 6 и 16 (1-й с 1-м уровнем детей, 2-й с 2-мя уровнями).

https://github.com/adev73/EFCore-LoadParentWithChildren-Example

Ответы [ 3 ]

0 голосов
/ 31 мая 2018

Я могу ошибаться, но если у вас есть дети, вам не нужны родители?

public class NavItem
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int navItemId { get; set; }
    public int? parentNavItemId { get; set; }
    public int sortOrder { get; set; }
    public bool isTitle { get; set; }
    public bool isDivider { get; set; }
    public string cssClass { get; set; }
    public string name { get; set; }
    public string url { get; set; }
    public string icon { get; set; }
    public string variant { get; set; }
    public string badgeText { get; set; }
    public string badgeVariant { get; set; }

    public NavItem parentNavItem { get; set; }
    public virtual ICollection<NavItem> children { get; set; }
}

Тогда вы бы отфильтровали по тем, у кого нет родителей:

var items = db.NavItems
    .ToList() // get every item so that relations load correctly
    .Where(x => x.parentNavItemId == null) // then filter by top-level
    .ToList();
0 голосов
/ 31 мая 2018

Полученный результат верен.Вы запрашиваете все navItems как список, и вы получаете список со всеми navItems.Исправление дочерних узлов является хорошим «бонусом», который EF сделал для вас.

Вы также правильно применяете фильтр к созданному списку, а не к набору данных напрямую.Если бы вы применили его к набору данных, ваши дочерние узлы не были бы получены!Затем, чтобы заполнить дочерние узлы, вы должны будете использовать Include, но это даст вам только 1 уровень, которого может быть недостаточно ... конечно, вы можете добавить несколько включений, но это не будет хорошо масштабироваться ...

Ваш "телесный" способ сделать это, кажется, не такой уж и плохой ... Я нахожу это довольно элегантным.

0 голосов
/ 31 мая 2018

Свойства навигации всегда имеют вспомогательные внешние ключи.Вы можете объявить внешние ключи явно и использовать их в запросах.Если вы добавите родительский внешний ключ в класс модели, вы можете подать на ParentKey == null, чтобы получить только корневые элементы.Вы не добавляете новый столбец, так как этот столбец уже существует в некоторой форме, вы просто включаете его явно в свою модель, чтобы его можно было использовать в запросах.Как будет называться ваш родительский ключ и как он будет называться при добавлении его в модель, зависит от вашего сопоставления.Существует множество вопросов по сопоставлению этих типов ключей.

Вот аналогичный пример, обратите внимание на ParentId, объявленный в модели: Упорядочение иерархии с самоссылкой в ​​LINQ

Я всегда включаю все внешние ключи в модель, потому что это становится удобным для определенных типов запросов или операций обновления, где установка FK более эффективна, чем запрос связанного объекта для создания отношения.Это также гарантирует, что ваши столбцы FK не имеют странного имени в EF.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...