EF Core односторонняя привязка сущности со ссылками на себя

Я создал самореферентную сущность с использованием EF Core, которая выглядит следующим образом:

Сущность

public class DetailType
{
    public int DetailTypeId { get; set; }
    public string Name { get; set; }

    public int? ParentTypeId { get; set; }
    public DetailType ParentType { get; set; }
    public IEnumerable<DetailType> ChildTypes { get; set; }
}

Привязка

modelBuilder.Entity<DetailType>()
    .ToTable("detailtype")
    .HasOne(x => x.ParentType)
    .WithMany(x => x.ChildTypes)
    .HasForeignKey(x => x.ParentTypeId);

Я получаю эти объекты через API, и текущий результат выглядит примерно так:

[
    {
        "detailTypeId": 20,
        "name": "Money",
        "parentTypeId": null,
        "parentType": null,
        "childTypes": null
    },
    {
        "detailTypeId": 22,
        "name": "Optional Extra",
        "parentTypeId": null,
        "parentType": null,
        "childTypes": [
            {
                "detailTypeId": 42,
                "name": "Extra Nights",
                "parentTypeId": 22,
                "childTypes": null
            }
        ]
    },
    {
        "detailTypeId": 42,
        "name": "Extra Nights",
        "parentTypeId": 22,
        "parentType": {
            "detailTypeId": 22,
            "name": "Optional Extra",
            "parentTypeId": null,
            "parentType": null,
            "childTypes": []
        },
        "childTypes": null
    }
]

Проблема, с которой я столкнулся, заключается в том, что третий элемент в массиве - это как раз обратная сторона второго. Есть ли способ избежать этого, чтобы у меня были только отношения родитель -> ребенок, а не оба родитель -> ребенок, а также ребенок -> родитель. Приведенный выше пример представляет собой сильно урезанную версию того, что на самом деле возвращает мой API, поэтому я хочу максимально уменьшить ненужное раздувание, потому что будет много отношений.

В идеале я хочу просто избавиться от свойства ParentType, но по-прежнему иметь коллекцию ChildTypes, но я не уверен, как определить это в построителе моделей.

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

Я удалил беглые отношения, так как они не нужны. Я также пробовал следующее:

var roots = this.Items.Where(x => x.ParentTypeId == null);
foreach (var root in roots)
{
    root.ChildTypes = this.Items.Where(x => x.ParentTypeId == root.DetailTypeId);
}

return roots.ToList();

(this.Items - это, кстати, DbSet)

Однако для этого нужно изменить ChildTypes на IQueryable, и когда я это сделаю, я получаю следующее исключение:

Тип свойства навигации «ChildTypes» для типа сущности «DetailType» - «EntityQueryable», который не реализует ICollection. Свойства навигации коллекции должны реализовывать ICollection ‹> целевого типа.


person Andy Furniss    schedule 25.09.2018    source источник
comment
Как насчет использования отдельной модели для вашего api, не содержащей свойства ParentType. Затем просто сопоставьте внутреннюю (db) модель с внешней (api) моделью в вашем контроллере, например, Automapper automapper.org   -  person Christoph Lütjen    schedule 25.09.2018
comment
Как уже упоминалось, есть несколько подходящих способов сделать это на уровне дизайна. Но как обходной путь - вы можете применить .Where(x => x.parentTypeId == null), чтобы избавиться от всех элементов, кроме элементов верхнего уровня. Это должно обеспечить желаемый результат.   -  person Yeldar Kurmangaliyev    schedule 25.09.2018
comment
Да, вам нужно будет справиться с этим с помощью моделей представления / DTO, вместо того, чтобы напрямую использовать свой класс сущности.   -  person Chris Pratt    schedule 25.09.2018
comment
@YeldarKurmangaliyev Это действительно возвращает только корневые элементы. Однако он также удаляет все элементы в ChildTypes, поэтому все, что я получаю, - это корневые элементы, которые мне не нужны.   -  person Andy Furniss    schedule 25.09.2018
comment
@AndyFurniss Он действительно возвращает корневые элементы со всеми некорневыми элементами, перечисленными в ChildItems, не так ли? Зачем ему удалять все элементы в ChildTypes?   -  person Yeldar Kurmangaliyev    schedule 26.09.2018


Ответы (1)


Перво-наперво - вам не нужно указывать это отношение (отношения) в построителе модели. Он разбирается в этом сам.

Что касается вашего вопроса - первое, что приходит мне в голову (я не знаю всего вашего набора данных), - это получить все объекты DetailType, которые имеют ParentTypeId == null.

Таким образом вы получите корни, а затем рекурсивно создадите tree дочерних элементов.

Это очистит ваш результат, и вы увидите структуру такой, какой хотите ее видеть.

person m3n7alsnak3    schedule 25.09.2018
comment
Разве для этого не потребуется несколько вызовов БД? - person Andy Furniss; 25.09.2018
comment
Что ж, зависит от того, включена ли у вас ленивая загрузка или вы можете явно включить .ChildTypes в вызове БД. Кроме того, вы можете сначала запросить все элементы из базы данных, а затем из полученного списка построить свою структуру, получив только корневые элементы, и продолжить рекурсию. Вы уже запросили все элементы, и все операции будут в памяти без дальнейших обращений к базе данных. - person m3n7alsnak3; 25.09.2018
comment
Более того, если вы работаете с коллекцией IQuerable DetailType и строите свою структуру, а в конце вы вызываете .ToList() коллекции, вы выполните только один запрос (когда вызывается .ToList()). Вы всегда можете использовать SQL Server Profiler, чтобы проверить свои запросы и убедиться, что выполняется именно то, что вы хотите выполнить. - person m3n7alsnak3; 25.09.2018
comment
Хм, я попробовал, но не добился большого прогресса. См. Подробности в редактировании моего вопроса. Дай мне знать, если я делаю что-то не так. - person Andy Furniss; 25.09.2018
comment
что такое this.Items? Разве вы не запрашиваете dbcontext? - person m3n7alsnak3; 25.09.2018
comment
Это DbSet. Я добавил комментарий под кодом, чтобы объяснить. - person Andy Furniss; 25.09.2018
comment
У меня похожий случай, и я действительно использую ICollection ‹› для дочерней коллекции. Просто взглянул на это. Давай с этим - person m3n7alsnak3; 25.09.2018
comment
Но чтобы использовать ICollection, мне нужно вызывать ToList() на каждой итерации цикла (после Where), что в конечном итоге приведет к вызову базы данных X раз, где X - количество корневых элементов. Как ты с этим справляешься? - person Andy Furniss; 25.09.2018
comment
Тебе это не нужно. Вот почему я спросил - включена ли у вас ленивая загрузка или вы явно включаете коллекцию Child. Вот как я с этим справляюсь, и у меня только один запрос - person m3n7alsnak3; 25.09.2018
comment
У меня не включена отложенная загрузка, но, похоже, это сработало - this.Items.Where(x => x.ParentTypeId == null).Include(x => x.ChildTypes). - person Andy Furniss; 26.09.2018
comment
Мне кажется, что для этого по-прежнему требуется свойство ParentType, хотя оно всегда будет иметь значение NULL. Я получаю ошибку Неизвестный столбец «x.DetailTypeId1» в «списке полей», если удаляю его из модели. Есть ли способ настроить отношение так, чтобы это свойство не требовалось, чтобы у него были только свойства ParentTypeId и ChildTypes? - person Andy Furniss; 26.09.2018
comment
Ни в коем случае - колонку нужно сохранить. Моя точка зрения на отношения была для modelbuilder материала. Конкретнее - .HasOne(x => x.ParentType) .WithMany(x => x.ChildTypes) эта часть. Вам это не нужно, потому что оно решает само, НО вам по-прежнему нужен столбец внешнего ключа (ParentId). Я рад видеть, что явное включение сработало для вас. Ты на правильном пути - person m3n7alsnak3; 26.09.2018