【问题标题】:How do you build a recursive Expression tree in Entity Framework Core?如何在 Entity Framework Core 中构建递归表达式树?
【发布时间】:2021-06-21 12:49:28
【问题描述】:

我们使用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 构建表达式,但我不确定从哪里开始。

【问题讨论】:

    标签: entity-framework-core linq-expressions hierarchyid


    【解决方案1】:

    无需动态表达式树生成即可解决该问题,至少不能直接解决,而是使用标准 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));
    
    

    【讨论】:

    • 谢谢你;我做了一个动态表达式树解决方案,但这要好得多。如果可以,请添加一个缺少的括号以关闭您应用示例中的 Select 方法:.Select(friendlyId =&gt; fullSet.Where(e =&gt; e.FriendlyId == friendlyId) 我相信您可以进行单个字符编辑,而我需要更改至少六个。
    • 当然,给你 :-)
    猜你喜欢
    • 2021-04-28
    • 1970-01-01
    • 2020-03-23
    • 2021-11-28
    • 2018-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多