考虑以下模型类:
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 ?
我尝试使用 linq 的
C.Children
来转换 Cast<T>()
或使用 OfType<T>()
进行过滤,但异常消息(然后抛出)告诉我只有 Where
、OrderBy
和 ThenBy
可用并进行转换应该使用“as”运算符或显式转换来完成,它们在所示示例中都不起作用。
一般来说,这样的递归模型是有问题的。但是,由于您有限制级别的逻辑约束(希望由您的代码强制执行,因为关系数据库不能),因此我理解的问题是如何在每个级别包含公共集合以及在特定级别包含特定集合。
包括特定的 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
缩进新行,以便更好地看到等效性。当然,如果您的编码风格需要,您可以将它们缩进新行。