【问题标题】:Is there a way, using LINQ/EF, to get the top most item in a parent/child hierarchy?有没有办法使用 LINQ/EF 来获取父/子层次结构中最顶层的项目?
【发布时间】:2010-10-06 15:16:56
【问题描述】:

我有一个班级叫Structure

public class Structure
{
    public int StructureId { get; set; }
    public Structure Parent { get; set; }
}

如您所见,Structure 有一个父 Structure。在这个层次结构中可以有不定数量的结构。

有什么方法可以使用 LINQ(使用实体框架)来获得此层次结构中的 top-most 结构?

目前,我必须多次访问数据库才能找到最顶层的父级。最顶层的父级是 StructureParent 属性为空:

Structure structure = structureRepository.Get(id);
while (structure.Parent != null)
{
    structure = structureRepository.Get(structure.Parent.StructureId);
}

// When we're here; `structure` is now the top most parent.

那么,有没有什么优雅的方法可以使用 LINQ/Lambdas 做到这一点?理想情况下,从以下代码开始:

var structureQuery = from item in context.Structures
                     where item.StructureId == structureId
                     select item;

我只是希望能够编写如下内容,以便我只触发一次数据库命中:

structureQuery = Magic(structureQuery);
Structure topMostParent = structureQuery.Single();

【问题讨论】:

  • 我不认为这是可能的,但我很好奇其他人可能提出的解决方案......
  • @Nick - MS SQL 2005。@jeroenh - 是的,我也不这么认为。我编写了当前完成这项工作的存储过程,它本身多次访问数据库。

标签: linq c#-4.0 entity-framework-4 lambda


【解决方案1】:

这不是一个直接的答案,但您遇到的问题与您存储树的方式有关。有几种方法可以通过不同的数据结构来简化此查询。

一种是使用Nested Set Hierarchy,它可以简化跨树的多种查询。

另一个是存储祖先/后代/深度元组的去规范化表。然后,此查询变为查找具有当前结构的元组作为具有最大深度的后代。

【讨论】:

  • 我使用的是 MS SQL 2005。以前从未听说过 NSH。
  • @GenericTypeTea 这不是数据库引擎功能,只是一种结构化数据的方法,以便可以使用标准 SQL 轻松查询。下面是关于 SQL Server 的讨论:blogs.msdn.com/b/anthonybloesch/archive/2006/02/15/…
【解决方案2】:

我认为我能得到的最好的结果是从我想要其最高父级的结构中一次性加载整个层次结构:

var structureQuery = from item in context.Structures
                         .Include(x => x.Parent)
                     where item.StructureId == structureId
                     select item;

那就直接用代码吧:

while (structure.Parent != null)
{
    structure = structure.Parent;
}

【讨论】:

    【解决方案3】:

    我也有类似的情况。我没有设法直接用 LINQ/EF 解决它。相反,我通过使用递归公用表表达式创建数据库视图来解决问题,如here 所述。我创建了一个用户定义的函数,将所有父级交叉应用到一个子级(反之亦然),然后是一个使用我导入到我的 EF 对象上下文中的用户定义函数的视图。

    (免责声明:简化代码,我没有实际测试过)

    我有两个表,比如 MyTable(包含所有项目)和 MyParentChildTable,其中包含 ChildId、ParentId 关系

    然后我定义了以下udf:

    CREATE FUNCTION dbo.fn_getsupertree(@childid AS INT) 
        RETURNS @TREE TABLE
    (
         ChildId INT NOT NULL
        ,ParentId  INT NULL
        ,Level   INT NOT NULL
    )
    AS
    BEGIN
      WITH Parent_Tree(ChildId, ParentId)
      AS
      ( 
        -- Anchor Member (AM)
        SELECT ChildId, ParentId, 0
        FROM MyParentChildTable
        WHERE ChildId = @childid
    
        UNION all
    
        -- Recursive Member (RM)
        SELECT info.ChildId, info.ParentId, tree.[Level]+1
        FROM MyParentChildTable AS info
          JOIN Parent_Tree AS tree
            ON info.ChildId = tree.ParentId
      )
      INSERT INTO @TREE
        SELECT * FROM Parent_Tree;
    
      RETURN
    END
    

    还有以下观点:

    CREATE VIEW VwSuperTree AS (
    SELECT tree.*
    FROM MyTable
    CROSS APPLY fn_getsupertree(MyTable.Id) as tree
    )
    GO
    

    这给了我每个孩子,所有父母都有他们的“树级别”(直接父母有1级,父母的父母有2级,等等)。从这个角度来看,很容易查询到最高级别的项目。我刚刚在我的 EF 上下文中导入了视图,以便能够使用 LINQ 查询它。

    【讨论】:

    • 是的,我已经有类似的东西了。我目前正在尝试将尽可能多的存储过程转移到 EF。这可能是必须保留为存储过程的其中之一。
    【解决方案4】:

    我喜欢这个问题,想不出一个 linq-y 的方式来做这件事。但是你可以在你的存储库类上实现它吗?毕竟,顶部应该只有一个,如果需要它,那么它可能值得一个structureRepository.GetRoot() 或其他东西。

    【讨论】:

    • 感谢您的输入,但它在我的存储库中(我给出的代码是一个示例,不是我的存储库类),这不是答案;也许您应该删除它并将其表达为评论:)。
    【解决方案5】:

    例如,您可以使用 linq take 构造

                var first3Customers = (
                    from c in customers
                    select new {c.CustomerID, c.CustomerName} )
                .Take(2);
    

    【讨论】:

      猜你喜欢
      • 2023-03-31
      • 2020-05-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多