Как включить свойство таблицы для каждой иерархии в быструю загрузку EF Core

Рассмотрим следующие классы моделей:

public abstract class A
{
    public enum T { B_, C_, D_ }
    public int Id { get; set; }

    public int? ParentId { get; set; }
    public A? Parent { get; set; }

    public virtual ICollection<A> Children { get; set; } = []; // an B has Cs and a C has Ds as children
    public virtual ICollection<E> Es { get; set; } = [];
}

public class B : A
{
}

public class C : A
{
}

public class D : A
{
    public virtual ICollection<F> Fs { get; set; } = [];
}

public class E
{
}

public class F
{
}

Мой вопрос: как мне включить все в один запрос B, используя быструю загрузку и свободный синтаксис, поэтому в основном я хочу сделать что-то вроде следующего (что терпит неудачу из-за выдачи исключения в последнем ThenInclude):

private IQueryable<B> Bs => _dbContext.Bs
  .Include(b => b.Children)
  .Include(b => b.Es)
    .ThenInclude(c => c.Children)
// how to include c.Es ?
      .ThenInclude(d => (d as D).Fs)
// and how to I include d.Es ?

Я пробовал приведение C.Children с помощью linq Cast<T>() или фильтрацию с помощью OfType<T>(), но сообщение об исключении (которое затем выдается) говорит мне, что доступны только Where, OrderBy и ThenBy, и приведение должно выполняться с использованием оператора «as» или явного приведения, а не из них работают в показанном примере.

Редактировать

Допустим, мы расширяем классы модели следующим образом:

public class E
{
    public int Id { get; set; }
    public int GId { get; set; }
    public virtual G G { get; set; }
}
public class G
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

Как мне заказать A.Es от G.Name?

public IQueryable<B> Bs => _dbContext.Bs
    .Include(b => b.Es)
        .ThenInclude(e => e.G)
    .Include(b => b.Es.OrderBy(e => e.G.Name);

Разве A.Es уже не включен (упорядочен, как в таблице БД), как утверждает Иван Стоев в своем ответе? Как мне заказать A.Es от G.Name? Я не могу осмыслить это, потому что для использования G мне нужно включить его, для чего мне нужно перейти к нему, для чего мне нужно включить A.Es, а затем он заказан, верно?

Что такое исключение? Обратите внимание, что навигации от B к F нет, поэтому я не уверен, чего вы пытаетесь достичь.

Gert Arnold 25.05.2024 11:36

@GertArnold В настоящее время у меня нет доступа к исключению и его сообщению, я опубликую его здесь позже в качестве комментария. Вы правы в том, что прямой навигации от B к F нет, но поскольку B и D оба относятся к типу A, я подумал, что должно быть каким-то образом возможно включить D.F в запрос _dbContext.Bs, что я и пытаюсь сделать. достичь.

dude 25.05.2024 13:35

Я не пояснил, что в таблице БД для A дочерние элементы B относятся к типу C, а дочерние элементы C относятся к типу D.

dude 25.05.2024 14:14

Разве в вашем примере не должно быть Bs? например As ?

Ivan Stoev 25.05.2024 16:55

Но в любом случае, даже без TPH, ваша модель является иерархической с неограниченным количеством (с точки зрения реляционной модели) уровней, поэтому нет возможности быстро загрузить все уровни (в LINQ нет поддержки рекурсивных запросов к запросу, следовательно, EF Core).

Ivan Stoev 25.05.2024 17:03

@IvanStoev Я отредактировал операцию, так как тип возвращаемого значения запроса должен быть IQueryable<B> вместо A. Спасибо, что указали на меня.

dude 25.05.2024 17:15

@IvanStoev ок, понял. Верно ли это даже для моего комментария типа A.Children (у D никогда не бывает детей [D.Children всегда пусто.])

dude 25.05.2024 17:20

Хотя с вашими ограничениями, я думаю, что-то можно сделать :) См. ниже.

Ivan Stoev 25.05.2024 19:43
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
8
78
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Включение конкретного члена TPH (или TPT/TPC) должно осуществляться так, как описано в документации — либо с использованием приведения C#, либо с помощью оператора as. А чтобы попасть на определенный уровень, необходимо перезапустить включение из корня и повторить путь до уровня, на котором вы хотите добавить новый ThenInclude. По сути, создание всех путей к конечным узлам. Элементы пути включаются автоматически и только один раз (даже если вы их повторяете).

Самый простой способ визуализировать желаемые включения в образце модели — с помощью перегрузок строк Include:

private IQueryable<B> Bs => _dbContext.Bs
    .Include("Es")
    .Include("Children.Es")
    .Include("Children.Children.Es")
    .Include("Children.Children.Fs");

Но, конечно, это подвержено ошибкам, поэтому эквивалентный «типобезопасный» способ:

private IQueryable<B> Bs => _dbContext.Bs
    .Include(b => b.Es)
    .Include(b => b.Children).ThenInclude(c => c.Es)
    .Include(b => b.Children).ThenInclude(c => c.Children).ThenInclude(d => d.Es)
    .Include(b => b.Children).ThenInclude(c => c.Children).ThenInclude(d => ((D)d).Fs);

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

предположим, что эти расширения классов модели:

dude 25.05.2024 21:02

Другие вопросы по теме