【问题标题】:Hierarchical data in Linq - options and performanceLinq 中的分层数据 - 选项和性能
【发布时间】:2010-09-17 04:54:19
【问题描述】:

我有一些分层数据 - 每个条目都有一个 id 和一个(可为空的)父条目 id。 我想检索给定条目下树中的所有条目。这是在 SQL Server 2005 数据库中。我在 C# 3.5 中使用 LINQ to SQL 查询它。

LINQ to SQL 不直接支持Common Table Expressions。我的选择是使用多个 LINQ 查询在代码中组合数据,或者在显示 CTE 的数据库上创建视图。

当数据量变大时,您认为哪个选项(或其他选项)性能更好? Linq to SQL 是否支持 SQL Server 2008 的 HierarchyId type

【问题讨论】:

    标签: sql linq sql-server-2005 c#-3.0 common-table-expression


    【解决方案1】:

    这个option 也可能很有用:

    LINQ AsHierarchy() 扩展方法
    http://www.scip.be/index.php?Page=ArticlesNET18

    【讨论】:

    • 我正在使用它,它工作得非常好,尤其是底部引用的更新版本。
    【解决方案2】:

    我很惊讶没有人提到替代数据库设计 - 当层次结构需要从多个级别展平并以高性能检索时(不考虑存储空间),最好使用另一个实体 2 实体表来跟踪层次结构而不是 parent_id 方法。

    它不仅允许单亲关系,还允许多亲关系、级别指示和不同类型的关系:

    CREATE TABLE Person (
      Id INTEGER,
      Name TEXT
    );
    
    CREATE TABLE PersonInPerson (
      PersonId INTEGER NOT NULL,
      InPersonId INTEGER NOT NULL,
      Level INTEGER,
      RelationKind VARCHAR(1)
    );
    

    【讨论】:

      【解决方案3】:

      我会根据 CTE 设置一个视图和一个关联的基于表的函数。我的理由是,虽然您可以在应用程序端实现逻辑,但这将涉及通过线路发送中间数据以在应用程序中进行计算。使用 DBML 设计器,视图转换为表实体。然后,您可以将该函数与 Table 实体相关联,并调用在 DataContext 上创建的方法来派生视图定义的类型的对象。使用基于表的函数允许查询引擎在构造结果集时考虑您的参数,而不是在事后对视图定义的结果集应用条件。

      CREATE TABLE [dbo].[hierarchical_table](
          [id] [int] IDENTITY(1,1) NOT NULL,
          [parent_id] [int] NULL,
          [data] [varchar](255) NOT NULL,
       CONSTRAINT [PK_hierarchical_table] PRIMARY KEY CLUSTERED 
      (
          [id] ASC
      )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
      ) ON [PRIMARY]
      
      CREATE VIEW [dbo].[vw_recursive_view]
      AS
      WITH hierarchy_cte(id, parent_id, data, lvl) AS
      (SELECT     id, parent_id, data, 0 AS lvl
            FROM         dbo.hierarchical_table
            WHERE     (parent_id IS NULL)
            UNION ALL
            SELECT     t1.id, t1.parent_id, t1.data, h.lvl + 1 AS lvl
            FROM         dbo.hierarchical_table AS t1 INNER JOIN
                                  hierarchy_cte AS h ON t1.parent_id = h.id)
      SELECT     id, parent_id, data, lvl
      FROM         hierarchy_cte AS result
      
      
      CREATE FUNCTION [dbo].[fn_tree_for_parent] 
      (
          @parent int
      )
      RETURNS 
      @result TABLE 
      (
          id int not null,
          parent_id int,
          data varchar(255) not null,
          lvl int not null
      )
      AS
      BEGIN
          WITH hierarchy_cte(id, parent_id, data, lvl) AS
         (SELECT     id, parent_id, data, 0 AS lvl
              FROM         dbo.hierarchical_table
              WHERE     (id = @parent OR (parent_id IS NULL AND @parent IS NULL))
              UNION ALL
              SELECT     t1.id, t1.parent_id, t1.data, h.lvl + 1 AS lvl
              FROM         dbo.hierarchical_table AS t1 INNER JOIN
                  hierarchy_cte AS h ON t1.parent_id = h.id)
          INSERT INTO @result
          SELECT     id, parent_id, data, lvl
          FROM         hierarchy_cte AS result
      RETURN 
      END
      
      ALTER TABLE [dbo].[hierarchical_table]  WITH CHECK ADD  CONSTRAINT [FK_hierarchical_table_hierarchical_table] FOREIGN KEY([parent_id])
      REFERENCES [dbo].[hierarchical_table] ([id])
      
      ALTER TABLE [dbo].[hierarchical_table] CHECK CONSTRAINT [FK_hierarchical_table_hierarchical_table]
      

      要使用它,您可以执行以下操作——假设一些合理的命名方案:

      using (DataContext dc = new HierarchicalDataContext())
      {
          HierarchicalTableEntity h = (from e in dc.HierarchicalTableEntities
                                       select e).First();
          var query = dc.FnTreeForParent( h.ID );
          foreach (HierarchicalTableViewEntity entity in query) {
              ...process the tree node...
          }
      }
      

      【讨论】:

      • 我已经尝试过这样的功能,它似乎是要走的路。它可以从附加到数据上下文的 LINQ 中调用。另外,为什么视图和功能都一样? - 它们似乎是重复的
      • 该函数未映射与表相同的架构。它包括级别。如果您没有添加的列,则可以将其直接映射到表上。我认为层次结构中的级别很重要。
      【解决方案4】:

      我有两种方法:

      1. 根据用户输入驱动对树的每一层的检索。想象一个树视图控件,其中包含根节点、根的子节点和根的孙子节点。只有根和子被展开(孙子被折叠隐藏)。当用户展开子节点时,根的孙子节点被显示(先前被检索和隐藏),并且启动了对所有曾孙子节点的检索。重复 N 层深度的模式。这种模式非常适合大型树(深度或宽度),因为它只检索所需的树部分。
      2. 在 LINQ 中使用存储过程。使用服务器上的公共表表达式之类的东西在平面表中构建结果,或在 T-SQL 中构建 XML 树。 Scott Guthrie 有一个great article 关于在 LINQ 中使用存储过程。如果是平面格式,则在结果返回时根据结果构建树,或者如果返回的是 XML 树,则使用 XML 树。

      【讨论】:

      • 当你的回答让我意识到我不需要拉整棵树,只需要在需要时拉孩子。
      【解决方案5】:

      此扩展方法可能会被修改为使用 IQueryable。我过去曾在一组对象上成功使用过它。它可能适用于您的方案。

      public static IEnumerable<T> ByHierarchy<T>(
       this IEnumerable<T> source, Func<T, bool> startWith, Func<T, T, bool> connectBy)
      {
        if (source == null)
         throw new ArgumentNullException("source");
      
        if (startWith == null)
         throw new ArgumentNullException("startWith");
      
        if (connectBy == null)
         throw new ArgumentNullException("connectBy");
      
        foreach (T root in source.Where(startWith))
        {
         yield return root;
         foreach (T child in source.ByHierarchy(c => connectBy(root, c), connectBy))
         {
          yield return child;
         }
       }
      }
      

      我是这样称呼它的:

      comments.ByHierarchy(comment => comment.ParentNum == parentNum, 
       (parent, child) => child.ParentNum == parent.CommentNum && includeChildren)
      

      此代码是here 代码的改进、错误修复版本。

      【讨论】:

      【解决方案6】:

      在 MS SQL 2008 中您可以直接使用HierarchyID,在 sql2005 中您可能需要手动实现它们。 ParentID 在大型数据集上表现不佳。另请查看this article 以了解有关该主题的更多讨论。

      【讨论】:

      • 这里没有提到 HierarchyID 是否可以在 LINQ to SQL 中使用
      • 开箱即用的 linq2sql 中不可用
      【解决方案7】:

      我从Rob Conery's blog 获得了这种方法(在 Pt. 6 附近查看此代码,也在 codeplex 上),我喜欢使用它。这可以重新设计以支持多个“子”级别。

      var categories = from c in db.Categories
                       select new Category
                       {
                           CategoryID = c.CategoryID,
                           ParentCategoryID = c.ParentCategoryID,
                           SubCategories = new List<Category>(
                                            from sc in db.Categories
                                            where sc.ParentCategoryID == c.CategoryID
                                            select new Category {
                                              CategoryID = sc.CategoryID, 
                                              ParentProductID = sc.ParentProductID
                                              }
                                            )
                                   };
      

      【讨论】:

      • 但是可以重新设计以支持无限数量的子级别吗?
      • 您不会向此查询添加十几个子类别 - 它不是特别灵活。
      【解决方案8】:

      从客户端获取数据的麻烦在于,您永远无法确定需要走多远。此方法将在每个深度进行一次往返,并且可以联合起来在一次往返中从 0 到指定深度。

      public IQueryable<Node> GetChildrenAtDepth(int NodeID, int depth)
      {
        IQueryable<Node> query = db.Nodes.Where(n => n.NodeID == NodeID);
        for(int i = 0; i < depth; i++)
          query = query.SelectMany(n => n.Children);
             //use this if the Children association has not been defined
          //query = query.SelectMany(n => db.Nodes.Where(c => c.ParentID == n.NodeID));
        return query;
      }
      

      但是,它不能做任意深度。如果您确实需要任意深度,则需要在数据库中执行此操作 - 这样您才能做出正确的停止决定。

      【讨论】:

        【解决方案9】:

        【讨论】:

        • 我不喜欢那种方法——“while”循环不是很好的 SQL 实践,如果有更声明性的方式来做,那应该是首选。现在有:使用通用表表达式使用视图或基于表的函数,使用 WITH .. UNION ALL 构造,如此处其他答案所示。
        • 请考虑在您链接的页面上插入解决方案的摘录。链接可能有一天会失效。
        猜你喜欢
        • 2012-05-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-08
        • 1970-01-01
        相关资源
        最近更新 更多