使用实体框架流利语法或内联语法编写递归 CTE

Posted

技术标签:

【中文标题】使用实体框架流利语法或内联语法编写递归 CTE【英文标题】:Writing Recursive CTE using Entity Framework Fluent syntax or Inline syntax 【发布时间】:2012-08-09 09:37:16 【问题描述】:

我不熟悉 SQL 和实体框架(ADO.NET 实体映射)中的这种递归。我正在进行评论管理,其中有一个Comments 表,该表包含NewsID, CommentID, ParentCommentID, IndentLevel, CreatedTime 列。

我正在尝试获取特定新闻项目的 cmets 列表,其中所有 cmets 根据 parent 下的 child 和创建时间排列,如下所示:

CommentID | time | ParentCommentID
Guid1     |  t1  | null
Guid4     |  t4  | Guid1
Guid2     |  t2  | null
Guid3     |  t3  | Guid2

必须优先考虑子父关系,然后是创建时间。

到目前为止我所了解的是(来自互联网资源和之前的 *** Q/A)

如图所示,这些递归查询很慢。并且使用实体框架执行此操作甚至更慢。但这是可以实现的。 因此,可以通过在 SQL Server 中创建存储过程并使用函数导入调用它来完成。另一件事是在实体框架中使用 Linq。 在 SQL Server 中以这种格式使用

SQL:

WITH cte_name ( column_name [,...n] ) 
AS 
( 
CTE_query_definition –- Anchor member is defined. 
UNION ALL 
CTE_query_definition –- Recursive member is defined referencing cte_name. 
) 
-- Statement using the CTE 
SELECT * 
FROM cte_name 
但在尝试之前,我想先尝试一下 Linq。

为此,我参考了这个我有想法的链接: https://***.com/a/6225373/892788

但我试图理解代码但徒劳无功。有人可以给我一个关于在实体框架中编写递归 CTE 的更好更详细的解释吗?

private IEnumerable<NewsComment> ArrangeComments(IEnumerable<NewsComment> commentsList, string parentNewsComntID, int level) 

        Guid parentNewsCommentID;
        if (parentNewsComntID != null)
        
            parentNewsCommentID = new Guid(parentNewsComntID);
        
        else
            parentNewsCommentID = Guid.Empty;

        return commentsList.Where(x => x.ParentCommentID == parentNewsCommentID).SelectMany(x => new[]  x .Concat(ArrangeComments(commentsList, x.NewsCommentID.ToString(), level + 1)); 


我在方法中使用如下:

return ArrangeComments(commentList,null , 0);

我已经尝试过了,但似乎我无处可去。尽管有关于 SQL 递归的解释,但 Linq 的示例较少,而且由于不太熟悉,对我来说也很模糊。有人可以帮我理解这个很棒的 Linq 中的 CTE 递归

提前致谢

【问题讨论】:

CTE 代表“通用表表达式”,与 LINQ 无关。 好的,Serg Rogovtsev。我将编辑我的问题。 【参考方案1】:

AFAIK 在 LINQ 和 EF 中都不支持递归 CTE。解决方案是将 CTE 公开为视图。 Recursive or hierarchical queries using EF Code First and Migrations 上的文章展示了如何使用 EF 代码优先迁移来部署这样的视图。

尝试通过执行递归客户端迭代来模拟 CTE 不会扩展到大型数据集,并会导致与服务器的闲聊交换。请注意,您的 EF 代码如何返回 IEnumerable 而不是 IQueryable,这意味着它会具体化每个级别,然后将每个条目的下一个级别连接起来作为单独的请求。基于 LINQ 的解决方案适用于条目数有限的浅层次结构(请注意,许多项目可以具有这样的数据布局,用户帖子/答案是典型示例),但会在深层次结构下崩溃很多元素。

【讨论】:

非常感谢。我在我的项目中选择了这个。我想出了另一个解决方案,我引入了一个索引,即:AA AB AC 用于父母评论和 AAAB AAAC 用于 AA cmets 的孩子,同样明智。这可用于每个评论 26*26 个子 cmets。因此,当我们获取列表时,我们只需使其升序并获取列表。 @Diode 非常简洁的解决方案 您也可以在该层次结构路径字段中使用分隔符 Name1\Name2\Name3\Name4\。【参考方案2】:

将 CTE 查询放入 StoredProcedure,然后从 Code 中调用它。 EF 提供了执行此操作的所有方法(调用 SP 并检索结果)。我为自己做了同样的事情,效果很好。

无法使用 Linq 写入 CTE 查询 Common Table Expression (CTE) in linq-to-sql?

示例 ArrangeComments 是一个调用自身的递归过程,但我敢质疑它的性能。它从数据库中提取记录,然后在内存中应用操作。

【讨论】:

我试过了,效果很好。但是当我们考虑性能时,为排列好的评论创建视图更方便。因为将元素添加到已经排序的列表中比一遍又一遍地进行递归的开销更少。非常感谢您的回答。【参考方案3】:

在花了几个小时阅读这个问题后,我决定用 C# 来完成,而不必创建数据库视图。

注意:仅用于非性能关键操作。来自http://nosalan.blogspot.se/2012/09/hierarchical-data-and-entity-framework-4.html 的 1000 个节点性能示例。

Loading 1000 cat. with navigation properties took 15259 ms 
Loading 1000 cat. with stored procedure took 169 ms

代码:

public class Category 

    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id  get; set; 

    public string Name  get; set; 

    public int? ParentId  get; set; 

    public virtual Category Parent  get; set; 

    public virtual ICollection<Category> Children  get; set; 

    private IList<Category> allParentsList = new List<Category>();

    public IEnumerable<Category> AllParents()
    
        var parent = Parent;
        while (!(parent is null))
        
            allParentsList.Add(parent);
            parent = parent.Parent;
        
        return allParentsList;
    

    public IEnumerable<Category> AllChildren()
    
        yield return this;
        foreach (var child in Children)
        foreach (var granChild in child.AllChildren())
        
            yield return granChild;
        
       

【讨论】:

您在博客文章中对使用 CTE 和手动进行比较非常有趣 - CTE 快 2 个数量级。

以上是关于使用实体框架流利语法或内联语法编写递归 CTE的主要内容,如果未能解决你的问题,请参考以下文章

SQL With (递归CTE查询)

使用 fluent 为实体框架 4.1 设置递归映射

MariaDB表表达式:CTE

sqlserver实现树形结构递归查询(无限极分类)

SQL Server CTE 递归查询全解

存储过程——公用表表达式(CTE)