Рассмотрим следующие классы моделей:
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, а затем он заказан, верно?
@GertArnold В настоящее время у меня нет доступа к исключению и его сообщению, я опубликую его здесь позже в качестве комментария. Вы правы в том, что прямой навигации от B к F нет, но поскольку B и D оба относятся к типу A, я подумал, что должно быть каким-то образом возможно включить D.F в запрос _dbContext.Bs, что я и пытаюсь сделать. достичь.
Я не пояснил, что в таблице БД для A дочерние элементы B относятся к типу C, а дочерние элементы C относятся к типу D.
Разве в вашем примере не должно быть Bs? например As ?
Но в любом случае, даже без TPH, ваша модель является иерархической с неограниченным количеством (с точки зрения реляционной модели) уровней, поэтому нет возможности быстро загрузить все уровни (в LINQ нет поддержки рекурсивных запросов к запросу, следовательно, EF Core).
@IvanStoev Я отредактировал операцию, так как тип возвращаемого значения запроса должен быть IQueryable<B> вместо A. Спасибо, что указали на меня.
@IvanStoev ок, понял. Верно ли это даже для моего комментария типа A.Children (у D никогда не бывает детей [D.Children всегда пусто.])
Хотя с вашими ограничениями, я думаю, что-то можно сделать :) См. ниже.





Вообще такая рекурсивная модель проблематична. Но поскольку у вас есть логические ограничения, которые ограничивают уровни (надеюсь, это обеспечивается вашим кодом, поскольку реляционные базы данных не могут), вопрос, насколько я понимаю, заключается в том, как включить общую коллекцию на каждом уровне и конкретную коллекцию на определенном уровне.
Включение конкретного члена 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отступы на новых строках, чтобы лучше видеть эквивалентность. Конечно, вы можете помещать их с отступом на новой строке, если этого требует ваш стиль кодирования.
предположим, что эти расширения классов модели:
Что такое исключение? Обратите внимание, что навигации от B к F нет, поэтому я не уверен, чего вы пытаетесь достичь.