entity-framework-core - 如何在 Entity Framework Core 中构建递归表达式树?
问题描述
我们使用EFCore.SqlServer.HierarchyId来表示数据中的层次结构。
我的目标是返回具有不确定长度的特定路径的对象的后代,例如,给定一棵具有层次结构 1->2->3->4 的树,路径 1/2/3 将返回 4
知道路径的长度后,我可以进行如下查询:
var collections = await context.Collections.Where(c => c.CollectionHierarchyid.IsDescendantOf(
context.Collections.FirstOrDefault(c1 => c1.FriendlyId == "three" &&
context.Collections.Any(c2 => c2.CollectionHierarchyid == c1.CollectionHierarchyid.GetAncestor(1) && c2.FriendlyId == "two" &&
context.Collections.Any(c3 => c3.CollectionHierarchyid == c2.CollectionHierarchyid.GetAncestor(1) && c3.FriendlyId == "one")
)
).CollectionHierarchyid
)).ToListAsync();
但是如果路径的长度未知,你会怎么做呢?我不能从表达式中调用递归函数,因为它不会从 Linq 编译到 Entity Sql。
我知道答案在于使用 System.Linq.Expressions 构建表达式,但我不确定从哪里开始。
解决方案
该问题可以在不生成动态表达式树的情况下解决,至少不是直接生成,而是使用标准 LINQ 查询运算符。
假设您有一个像这样的分层实体
public class Entity
{
public HierarchyId Id { get; set; }
// other properties...
}
给定一个返回完整集的子查询
IQueryable<Entity> fullSet = context.Set<Entity>();
和子查询定义一些包含所需祖先的过滤子集
IQueryable<Entity> ancestors = ...;
现在可以很容易地获得所有直接和间接后代
IQueryable<Entity> descendants = fullSet
.Where(d => ancestors.Any(a => d.Id.IsDescendantOf(a.Id));
所以问题是如何ancestors
动态构建子查询。
可以通过使用简单的连接运算符来对整个集合应用一些过滤器并检索由另一个条件过滤的直接祖先
from p in fullSet.Where(condition1)
join c in fullSet.Where(condition2)
on p.Id equals c.Id.GetAncestor(1)
select c
因此,您只需要递归地应用它,例如
IEnumerable<TArg> args = ...;
表示按级别排序的过滤条件参数,则可以按如下方式构建查询
var ancestors = args
.Select(arg => fullSet.Where(e => Predicate(e, arg)))
.Aggregate((prevSet, nextSet) =>
from p in prevSet join c in nextSet on p.Id equals c.Id.GetAncestor(1) select c);
话虽如此,将其应用于您的示例:
IEnumerable<string> friendlyIds = new [] { "one", "two", "three" };
var fullSet = context.Collections.AsQueryable();
var ancestors = friendlyIds
.Select(friendlyId => fullSet.Where(e => e.FriendlyId == friendlyId))
.Aggregate((prevSet, nextSet) =>
from p in prevSet join c in nextSet on p.CollectionHierarchyid equals c.CollectionHierarchyid.GetAncestor(1) select c);
var descendants = fullSet
.Where(d => ancestors.Any(a => d.CollectionHierarchyid.IsDescendantOf(a.CollectionHierarchyid));
推荐阅读
- python - 在 windows10 上安装 pytorch/tensorflow 时访问被拒绝
- google-apps-script - 如何在 google appscripts 中自动更新代码更改
- reactjs - 基于 Context 属性的有状态值未更新
- modelica - 什么会影响 Modelica 中的仿真运行时
- r - 如何组合由自动绘图创建的两个图形?
- java - 从 args (java) 读取文件时捕获错误
- java - 使用JAVA在arraylist的for循环中每x秒执行一条指令
- vue.js - vuejs Google 地图信息窗口 - 隐藏关闭图标
- python - 使用 f.read() 时,每个字母的迭代循环
- scikit-learn - 从分类数据集中删除冗余特征( make_classification )