12

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

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

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

4

9 回答 9

16

选项也可能很有用:

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

于 2009-07-10T16:49:26.283 回答
8

我很惊讶没有人提到替代数据库设计 - 当需要从多个级别展平层次结构并以高性能检索(不考虑存储空间)时,最好使用另一个实体 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)
);
于 2010-03-23T20:43:37.880 回答
6

我会根据 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...
    }
}
于 2008-10-14T22:19:01.103 回答
3

我已经做到了这两种方式:

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

此扩展方法可能会被修改为使用 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)

此代码是此处找到的代码的改进、错误修复版本。

于 2008-10-14T22:59:53.190 回答
2

在 MS SQL 2008 中您可以直接使用HierarchyID,在 sql2005 中您可能必须手动实现它们。ParentID 在大型数据集上表现不佳。另请查看本文以获取有关该主题的更多讨论。

于 2008-10-14T21:30:18.503 回答
1

我从Rob Conery 的博客中获得了这种方法(在 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
                                        }
                                      )
                             };
于 2008-10-14T23:19:02.433 回答
0

从客户端获取数据的问题在于,您永远无法确定需要走多远。此方法将在每个深度执行一次往返,并且可以联合在一次往返中从 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;
}

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

于 2008-10-15T00:15:55.140 回答
0

请阅读以下链接。

http://support.microsoft.com/default.aspx?scid=kb;en-us;q248915

于 2010-03-31T12:28:37.313 回答