Return 父项和子项在单个 entity framework 查询中不返回 IQueryable 或 IEnumerable?
Return parent and children in single entity framework query WITHOUT returning IQueryable or IEnumerable?
我们的规则是不在服务层之外公开 IQueryable<T>
或 IEnumerable<T>
,因此下游代码无法修改对数据库的查询。这意味着我们 return 的类型类似于 IList 或 ICollection。
我想知道如何在不将子集合定义为 IQueryable
或 IEnumerable
的情况下编写一个 linq 查询来在一次访问数据库的过程中获取父项及其子项。
例如,假设从服务中 returned 的类型是 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 查询写入 select 到匿名类型,然后将其映射到 return 的实际类型。我们使用可以协助映射的AutoMapper。这行不通的任何原因?我是否必须在映射对象之前调用 .ToList()
以确保我在映射时不会 运行 遇到同样的问题?
如果您的 Parent
实体中有这样的导航 属性:
public class Parent
{
public int ParentId { get; set; }
public string ParentName { get; set; }
public virtual ICollection<Child> Children { get; set; }
//..
}
那我建议你在你的服务层新建两个类(可以是ParentViewModel
,和ChildViewModel
)来保存你的查询结果。在这两个 类 中只声明您在表示层中需要的属性。然后使用 Automapper.
将您的实体与您的 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 的扩展方法。
正如您在我上面引用的 link 中看到的那样,Automapper 支持嵌套映射,因此如果您在 ParentViewModel
中有一个 ICollection<ChildViewModel>
类型的 属性 并且您还将 Child
实体与其 ViewModel 映射,然后 Automapper 将自动尝试从一种类型映射到另一种类型。
所有这些都将在您的数据库的一次往返中发生,因为 ProjectTo
是一个 IQueryable<TEntity>
扩展方法,它被转换为 Select
.
您可以在内部使用 Anon 查询使用普通 ol' EF 而不使用 AutoMapper 来执行此操作,但仍然会在最终结果中返回强类型 objects:
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 及其所有 children 的 parent ]数组。
关于 SelectMany
的重要说明:您可能想在这里使用 Select
/GroupBy
。
如果您使用 SelectMany
,而您的条件 select 只有一个 parent:您将获得 parent 及其所有 children单数组。 (这是我的 use-case。)
如果您使用 SelectMany
,并且您的条件 select 多个 parent:您将在一个文件中获得 parent 及其所有 children单个数组。
如果以上和 children (grandparents/grandchildren) 的 parent 满足条件:您将在一个数组中获得完整的家谱,如果你不添加 .Distinct()
.
如果您使用 Select
:您将得到 children 组,由 parent 分隔成多个数组。这不是我的用例,但我怀疑如果它是你的,你在这里真正想要的是一个 GroupBy。
我们的规则是不在服务层之外公开 IQueryable<T>
或 IEnumerable<T>
,因此下游代码无法修改对数据库的查询。这意味着我们 return 的类型类似于 IList 或 ICollection。
我想知道如何在不将子集合定义为 IQueryable
或 IEnumerable
的情况下编写一个 linq 查询来在一次访问数据库的过程中获取父项及其子项。
例如,假设从服务中 returned 的类型是 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 查询写入 select 到匿名类型,然后将其映射到 return 的实际类型。我们使用可以协助映射的AutoMapper。这行不通的任何原因?我是否必须在映射对象之前调用 .ToList()
以确保我在映射时不会 运行 遇到同样的问题?
如果您的 Parent
实体中有这样的导航 属性:
public class Parent
{
public int ParentId { get; set; }
public string ParentName { get; set; }
public virtual ICollection<Child> Children { get; set; }
//..
}
那我建议你在你的服务层新建两个类(可以是ParentViewModel
,和ChildViewModel
)来保存你的查询结果。在这两个 类 中只声明您在表示层中需要的属性。然后使用 Automapper.
之后你可以这样查询:
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 的扩展方法。
正如您在我上面引用的 link 中看到的那样,Automapper 支持嵌套映射,因此如果您在 ParentViewModel
中有一个 ICollection<ChildViewModel>
类型的 属性 并且您还将 Child
实体与其 ViewModel 映射,然后 Automapper 将自动尝试从一种类型映射到另一种类型。
所有这些都将在您的数据库的一次往返中发生,因为 ProjectTo
是一个 IQueryable<TEntity>
扩展方法,它被转换为 Select
.
您可以在内部使用 Anon 查询使用普通 ol' EF 而不使用 AutoMapper 来执行此操作,但仍然会在最终结果中返回强类型 objects:
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 及其所有 children 的 parent ]数组。
关于 SelectMany
的重要说明:您可能想在这里使用 Select
/GroupBy
。
如果您使用 SelectMany
,而您的条件 select 只有一个 parent:您将获得 parent 及其所有 children单数组。 (这是我的 use-case。)
如果您使用 SelectMany
,并且您的条件 select 多个 parent:您将在一个文件中获得 parent 及其所有 children单个数组。
如果以上和 children (grandparents/grandchildren) 的 parent 满足条件:您将在一个数组中获得完整的家谱,如果你不添加 .Distinct()
.
如果您使用 Select
:您将得到 children 组,由 parent 分隔成多个数组。这不是我的用例,但我怀疑如果它是你的,你在这里真正想要的是一个 GroupBy。