在单个实体框架查询中返回父项和子项,而不返回 IQueryable 或 IEnumerable
本文关键字:返回 IQueryable IEnumerable 查询 框架 单个 实体 | 更新日期: 2023-09-27 18:30:42
我们有一条规则,不公开服务层之外的IQueryable<T>
或IEnumerable<T>
,因此下游代码无法修改进入数据库的查询。这意味着我们返回的类型类似于 IList 或 ICollection。
我想知道如何编写一个 linq 查询来获取父级及其子级,而无需将子集合定义为 IQueryable
或 IEnumerable
。
例如,假设从服务返回的类型是ICollection<Parent>
其中Parent
定义为:
public class Parent
{
public int ParentId { get; set; }
public string ParentName { get; set; }
public ICollection<Child> Children { get; set; }
}
如果我将查询定义为这样...
from p in dbContext.Parents
where p.Name.Contains("test")
select new Parent
{
Children =from c in dbContext.Children
where c.ParentId == p.ParentId
select c;
}
它不编译,因为IQueryable
没有实现ICollection
。如果我将.ToList()
添加到父集合和子集合中,它会编译,但现在它将为每个父集合单独访问数据库以获取其子集合。
当我写这个问题时,我突然想到,也许答案是编写 Linq 查询以选择匿名类型,然后将其映射到要返回的实际类型。我们使用自动映射器,它可以帮助映射。有什么理由这行不通吗?在映射对象之前,我是否必须调用.ToList()
以确保在映射时不会遇到相同的问题?
如果Parent
实体中有如下所示的导航属性:
public class Parent
{
public int ParentId { get; set; }
public string ParentName { get; set; }
public virtual ICollection<Child> Children { get; set; }
//..
}
然后,我建议您在服务层中创建两个新类(可以是ParentViewModel
和ChildViewModel
)来保存查询结果。在这两个类中,仅声明表示层中所需的属性。然后使用自动映射器将实体与 ViewModel 类映射。
之后,您可以执行如下查询:
var query =dbContext.Parents
.Include(p=>p.Children) // load the related entities as part of the query
.Where(p=>p.ParentName.Contains("test"))
.ProjectTo<ParentViewModel>();
使用自动映射程序ProjectTo
扩展方法。
正如您在上面引用的链接中看到的那样,Automapper 支持嵌套映射,因此,如果您在ParentViewModel
具有类型 ICollection<ChildViewModel>
的属性,并且还使用其 ViewModel 映射了Child
实体,那么 Automapper 将帮助自动尝试从一种类型映射到另一种类型。
所有这些都将在一次往返数据库的过程中发生,因为ProjectTo
是一种IQueryable<TEntity>
扩展方法,并且它被转换为Select
。
您可以使用普通的 EF 和不使用内部使用 Anon 查询的自动映射器来执行此操作,但仍会在最终结果中返回强类型对象:
public class Human
{
public int Id { get; set; }
public ICollection<Human> Children { get; set; }
public Human Parent { get; set; }
[ForeignKey("Parent")]
public int ParentId { get; set; }
}
var family = await db.Humans
.Where(h => h.SomeCriteriaForParent == criteria)
.Select(h => new {
H = h,
HH = h.Children
})
.SelectMany(x => x.HH.Concat(new[] { x.H }))
.ToArrayAsync();
例如,如果您的条件是 Id,而您的 ID 是 5,那么您将在单个Human[]
数组中获得 ID 为 5 的父级及其所有子级。
关于SelectMany
的重要说明:您可能希望在此处使用Select
/GroupBy
。
如果使用 SelectMany
,并且条件仅选择一个父项:则将在单个数组中获取父级及其所有子项。(这是我的用例。
如果使用 SelectMany
,并且条件选择多个父项:则将父项及其所有子项放在一个数组中。
如果上述和父母是孩子(祖父母/孙子孙女)符合标准:您将在单个数组中获得完整的家谱,如果您不添加.Distinct()
,您可能会受到欺骗。
如果您使用 Select
: 您将获得由父级分隔的子级集,分成多个数组。这不是我的用例,但我怀疑如果它是你的,你在这里真正想要的是一个 GroupBy。