EF Core 1.0: как запросить все дерево?

Я использую EF 7.0.0.0-rc1-final.

У меня есть древовидная структура с отношениями «один ко многим» от GrandGrandParent к GrandParent к Parent к Child:

public class GrandGrandParent
{
    public int ID { get; set; }
    public string Name { get; set; }

    public virtual List<GrandParent> GrandParents { get; set; }

    public GrandGrandParent()
    {
        this.GrandParents = new List<GrandParent>();
    }
}

public class GrandParent
{
    public int ID { get; set; }
    public string Name { get; set; }

    public virtual GrandGrandParent GrandGrandParent { get; set; }
    public virtual List<Parent> Parents { get; set; }

    public GrandParent()
    {
        this.Parents = new List<Parent>();
    }
}

public class Parent
{
    public int ID { get; set; }
    public string Name { get; set; }

    public virtual GrandParent GrandParent { get; set; }
    public virtual List<Child> Children { get; set; }

    public Parent()
    {
        this.Children = new List<Child>();
    }
}

public class Child
{
    public int ID { get; set; }
    public string Name { get; set; }

    public virtual Parent Parent { get; set; }
}

Как с помощью EF Core 1.0 (EF 7) создать запрос LINQ (или с подзапросами), который даст мне все дерево с учетом определенного идентификатора прапрародителя?

Я могу .Include() на один уровень вверх или вниз, может быть, я слеп для очевидного? Это дает мне GrandGrandParent и список GrandParents:

var ggparent1 = from ggp in myDbContext.GrandGrandParent
                .Include(ggp => ggp.GrandParents)
                where ggp.ID == 2
                select ggp;

Я хочу получить все дерево, вплоть до списка детей. Должен ли я прибегать к коду цикла foreach() и вручную строить дерево?


person Peter Lindgren    schedule 03.03.2016    source источник
comment
Как устроена ваша база данных? У вас 4 стола, 2 или 1? Несмотря на то, что ваша иерархия странная, количество таблиц может помочь нам вам помочь (особенно с включениями и соединениями).   -  person cdie    schedule 04.03.2016
comment
Есть 4 стола, по одному на каждый класс. Иерархия выбрана так, чтобы показать мою проблему в общем виде, поэтому читателям не придется также изучать бизнес-логику. Я пробовал варианты следующего LINQ: var gparents = from gp в myDbContext.GrandParent .Include(gparent => gparent.GrandGrandParent) .Include(gparent => gparent.Parents).ThenInclude(children => children.Select(child = › child.ID)) где gp.GrandGrandParent.ID == 2 select gp; но .ThenInclude всегда выдает исключение.   -  person Peter Lindgren    schedule 04.03.2016
comment
Что является исключением?   -  person cdie    schedule 04.03.2016
comment
System.ArgumentException: выражение свойств «children =› {from Parent child in Children select [child].ID}» недопустимо. Выражение должно представлять доступ к свойству: 't => t.MyProperty'. При указании нескольких свойств используйте анонимный тип: 't =› new { t.MyProperty1, t.MyProperty2 }'.   -  person Peter Lindgren    schedule 04.03.2016
comment
Мое тестовое приложение: http://pastebin.com/b2tWS7LC и классы EF, как указано выше.   -  person Peter Lindgren    schedule 04.03.2016
comment
Я уважаю ваш дизайн таблицы, но если у вас действительно есть иерархическая структура, почему бы не объединить их все в одну таблицу и не иметь неограниченный уровень иерархии. Я успешно реализовал его через sql HierarchyID в EF6 с неограниченным уровнем. Наличие 1 таблицы делает большинство ваших запросов очень простыми. Каждая строка должна иметь ParentID, относящуюся к идентификатору строки в таблице с корневыми узлами, имеющими это как null.   -  person Mohsen Afshin    schedule 01.08.2016
comment
@MohsenAfshin это разные бизнес-объекты с очень разной информацией, так что это не вариант. И проблема со списком‹› детей.   -  person Peter Lindgren    schedule 02.08.2016


Ответы (1)


Я бы просто использовал форму Linq:

private static void Test0(ApplicationDbContext myDbContext)
{
    var ggparent = myDbContext.GrandGrandParents
        .Include(ggp => ggp.GrandParents)
        .ThenInclude(gp => gp.Parents)
        .ThenInclude(p => p.Children)
        .FirstOrDefault(ggp => ggp.ID == 3);

    if (ggparent == null)
    {
        DebugPrint("GrandGrandParent not found");
        return;
    }

    DebugPrint("GrandGrandParent:");
    DebugPrint(ggparent);


    if (ggparent.GrandParents == null)
    {
        DebugPrint("GrandParents null");
        return;
    }

    foreach (var gparent in ggparent.GrandParents)
    {
        DebugPrint(gparent);
        if (gparent.Parents == null) continue;
        foreach (var parent in gparent.Parents)
        {
            DebugPrint(parent);
            if (parent.Children == null) continue;
            foreach (var child in parent.Children)
            {
                DebugPrint(child);
            }
        }
    }

    int changeCount = myDbContext.SaveChanges();
    DebugPrint(string.Format("ChangeCount={0}", changeCount));
}

Тогда вам не нужно делать подзапросы самостоятельно. Но вы можете добавить ведение журнала, чтобы увидеть, какие SQL-запросы создает EF.

Если вам нужно полное дерево, вам не нужно select. Вы можете использовать select для извлечения части результирующего дерева или для создания нового (плоского) объекта из иерархии.

Пример, в котором я сглаживаю иерархию только двумя уровнями (со многими ко многим), чтобы получить элементы для сгруппированного списка выбора:

var result =
    (from c in dbContext.EventTypes
    join j in dbContext.EventType2EventTypes on c.Id equals j.ChildEventTypeId
    join p in dbContext.EventTypes on j.ParentEventTypeId equals p.Id
    where p.EventTypeLevel == EventTypeLevel.First && c.EventTypeLevel == EventTypeLevel.Second
    orderby p.SortOrder, p.Id, c.SortOrder, c.Id
    select new SelectListItem
    {
        Text = c.NameDe,
        Value = c.Id.ToString(),
        Group = GetParentEventTypeSelectListGroup(p.NameDe)
    }).AsNoTracking();
person noox    schedule 07.04.2016