2008-10-14 23 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) 
);