2008-10-14 47 views
11

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

LINQ to SQL不直接支持Common Table Expressions。我的选择是将代码中的数据用几个LINQ查询进行汇编,或者对表面CTE的数据库进行查看。

当数据量变大时,您认为哪种选择(或另一种选择)会更好? Linq to SQL是否支持SQL Server 2008的HierarchyId type

回答

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... 
    } 
} 
+1

我试过了这样的功能,它似乎是要走的路。它可以从LINQ调用,附加到datacontext。另外,为什么既是视图又是功能? - 它们似乎是重复的 – Anthony 2008-10-15 15:44:10

+1

函数不会映射与表相同的模式。它包括关卡。如果您没有添加列,可以将其直接映射到表格上。我认为层次结构中的层次很重要。 – tvanfosson 2008-10-15 16:13:53

2

在MS SQL 2008中,您可以直接使用HierarchyID,在sql2005中您可能必须手动实现它们。 ParentID在大型数据集上不是那么好用。另请参阅this article以获取有关该主题的更多讨论。

+0

有没有提到如果有是HIERARCHYID在使用LINQ到SQL – Anthony 2008-10-14 22:09:06

+1

哇,这肯定是答案。好贴! – Shawn 2009-02-19 17:00:49

+0

开箱即用的linq2sql无法使用 – 2009-03-31 15:56:47

3

我这样做有两种方式:

  1. 驱动器的每一层的检索基于用户输入的树。想象一下,填充了根节点,根的孩子和根的孙辈的树视图控件。只有根和孩子被扩大(孙子们被隐藏在崩溃中)。当用户扩展一个子节点时,显示根节点的孙子(先前已被检索和隐藏),并启动所有曾孙的检索。重复N层图案的深度。这种模式适用于大型树木(深度或宽度),因为它仅检索所需树的部分。
  2. 在LINQ中使用存储过程。在服务器上使用类似公共表表达式的表达式,以在平坦表中构建结果,或者在T-SQL中构建XML树。 Scott Guthrie有关于在LINQ中使用存储过程的great article。如果以平面格式返回,则从结果中构建树;如果这就是您返回的结果,则使用XML树。
+1

当你的回答打开了我的想法,我不需要拉一棵树,只是在需要的时候拉动孩子这一事实,我完全陷入困境。 – ProfK 2012-07-10 12:18:29

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) 

此代码是代码的改进,错误修正版本中发现here

+0

或者你可以看看他从哪里获取:http://weblogs.asp.net/okloeten/archive/2006/07/09/Hierarchical-Linq-Queries.aspx – TheSoftwareJedi 2008-10-15 00:38:30

1

我从Rob Conery's blog得到了这个方法(关于这个代码,在codeplex上查看第6篇),我喜欢使用它。这可以重新设计以支持多个“子”级别。

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 
             } 
            ) 
          }; 
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; 
} 

但是,它不能做任意深度。如果你真的需要任意深度,你需要在数据库中这样做 - 所以你可以做出正确的决定来停止。

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) 
);