2008-10-14 33 views
11

Tôi có một số dữ liệu phân cấp - mỗi mục nhập có một id và một (nullable) nhập id mẹ. Tôi muốn lấy tất cả các mục nhập trong cây dưới một mục nhập nhất định. Đây là cơ sở dữ liệu SQL Server 2005. Tôi đang truy vấn nó với LINQ to SQL trong C# 3.5.Dữ liệu phân cấp trong LINQ - tùy chọn và hiệu suất

LINQ to SQL không hỗ trợ trực tiếp Common Table Expressions. Các lựa chọn của tôi là tập hợp dữ liệu trong mã với một số truy vấn LINQ, hoặc để tạo một khung nhìn trên cơ sở dữ liệu có bề mặt CTE.

Tùy chọn nào (hoặc tùy chọn khác) bạn cho rằng sẽ hoạt động tốt hơn khi khối lượng dữ liệu lớn hơn? HierarchyId type của SQL Server 2008 có được hỗ trợ trong LINQ to SQL không?

Trả lời

6

Tôi sẽ thiết lập chế độ xem và chức năng dựa trên bảng được liên kết dựa trên CTE. Lý do của tôi cho điều này là, trong khi bạn có thể thực hiện logic ở phía ứng dụng, điều này sẽ liên quan đến việc gửi dữ liệu trung gian qua dây để tính toán trong ứng dụng. Sử dụng trình thiết kế DBML, khung nhìn dịch thành một thực thể Table. Sau đó bạn có thể kết hợp hàm với thực thể Table và gọi phương thức được tạo trên DataContext để lấy ra các đối tượng thuộc kiểu được định nghĩa bởi khung nhìn. Sử dụng hàm dựa trên bảng cho phép công cụ truy vấn đưa các tham số của bạn vào tài khoản trong khi xây dựng bộ kết quả thay vì áp dụng điều kiện trên tập kết quả được xác định bởi khung nhìn sau khi thực tế.

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] 

Để sử dụng nó, bạn sẽ làm điều gì đó giống như - giả định một số đề án đặt tên hợp lý:

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

Tôi đã thử một chức năng như thế này, và nó có vẻ là con đường để đi. Và nó có thể được gọi từ LINQ, gắn liền với datacontext. Ngoài ra, tại sao cả chế độ xem và chức năng? - chúng dường như trùng lặp – Anthony

+1

Hàm không ánh xạ cùng một lược đồ như bảng. Nó bao gồm các cấp độ. Nếu bạn không có cột được thêm vào, bạn có thể ánh xạ nó trực tiếp lên bảng. Tôi cho rằng mức độ trong hệ thống phân cấp là quan trọng. – tvanfosson

2

Trong MS SQL 2008, bạn có thể sử dụng trực tiếp HierarchyID, trong sql2005 bạn có thể phải triển khai chúng theo cách thủ công. ParentID không phải là trình diễn trên các tập dữ liệu lớn. Ngoài ra, hãy kiểm tra this article để biết thêm thảo luận về chủ đề.

+0

Không có đề cập đến đó nếu HierarchyId là có thể sử dụng trong LINQ to SQL – Anthony

+1

wow, điều này chắc chắn là câu trả lời. bài đăng hay! – Shawn

+0

nó không thể sử dụng trong linq2sql ra khỏi hộp –

3

Tôi đã làm điều này theo hai cách:

  1. Lái hồi của mỗi lớp cây dựa trên đầu vào của người dùng. Hãy tưởng tượng một cây kiểm soát xem dân cư với nút gốc, con của gốc, và các cháu của gốc. Chỉ có gốc và trẻ em được mở rộng (cháu được ẩn với sự sụp đổ). Khi người dùng mở rộng một nút con, các cháu của thư mục gốc được hiển thị (trước đây đã được truy xuất và ẩn), và việc truy xuất tất cả các cháu của chúng được khởi chạy. Lặp lại mô hình cho các lớp N sâu. Mô hình này hoạt động rất tốt cho các cây lớn (chiều sâu hoặc chiều rộng) bởi vì nó chỉ lấy được phần của cây cần thiết.
  2. Sử dụng quy trình được lưu với LINQ. Sử dụng một cái gì đó giống như một biểu thức bảng chung trên máy chủ để tạo kết quả của bạn trong một bảng phẳng hoặc xây dựng một cây XML trong T-SQL. Scott Guthrie có một số great article về việc sử dụng các procs được lưu trữ trong LINQ. Xây dựng cây của bạn từ kết quả khi họ quay trở lại nếu ở định dạng phẳng hoặc sử dụng cây XML nếu đó là những gì bạn quay trở lại.
+1

Tôi đã khá mệt mỏi trong việc tìm kiếm một giải pháp cho điều này khi câu trả lời của bạn mở ra tâm trí của tôi với thực tế là tôi không cần phải kéo một cây toàn bộ, chỉ cần kéo trẻ em khi cần thiết. – ProfK

3

Phương pháp mở rộng này có thể được sửa đổi để sử dụng IQueryable. Tôi đã sử dụng nó thành công trong quá khứ trên một bộ sưu tập các đối tượng. Nó có thể làm việc cho kịch bản của bạn.

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

Dưới đây là cách tôi gọi nó là:

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

Mã này là một cải thiện, phiên bản lỗi cố định của mã tìm thấy here.

+0

Hoặc bạn có thể kiểm tra nơi ông snagged rằng từ: http://weblogs.asp.net/okloeten/archive/2006/07/09/Hierarchical-Linq-Queries.aspx – TheSoftwareJedi

+1

Tôi đã thêm thuộc tính cho Jedi. Phiên bản của tôi được đơn giản hóa và cải thiện. – JarrettV

1

Tôi có cách tiếp cận này từ Rob Conery's blog (kiểm tra xung quanh Pt. 6 cho mã này, cũng trên codeplex) và tôi thích sử dụng nó. Điều này có thể được tái thời trang để hỗ trợ nhiều cấp độ "phụ".

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

Nhưng nó có thể được refashioned để hỗ trợ một số lượng không giới hạn của sublevels? – Anthony

+0

Bạn sẽ không thêm một tá danh mục phụ vào truy vấn này - nó không đặc biệt linh hoạt. –

0

Sự cố khi tìm nạp dữ liệu từ phía máy khách là bạn không bao giờ có thể chắc chắn về mức độ cần thiết. Phương pháp này sẽ thực hiện một vòng trên mỗi chiều sâu và nó có thể là union'd để làm từ 0 đến độ sâu xác định trong một vòng.

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

Tuy nhiên, độ sâu tùy ý không thể thực hiện được. Nếu bạn thực sự yêu cầu độ sâu tùy ý, bạn cần phải làm điều đó trong cơ sở dữ liệu - vì vậy bạn có thể đưa ra quyết định đúng để dừng lại.

15

option này cũng có thể chứng minh hữu ích:

LINQ AsHierarchy() phương pháp khuyến nông
http://www.scip.be/index.php?Page=ArticlesNET18

+1

Tôi đang sử dụng tính năng này và hoạt động rất tốt, đặc biệt là phiên bản cập nhật được tham chiếu ở dưới cùng. –

8

Tôi ngạc nhiên không ai có đề cập đến một thiết kế cơ sở dữ liệu thay thế - khi hệ thống phân cấp cần phải được san phẳng từ nhiều cấp độ và được truy xuất với hiệu suất cao (không nên xem xét dung lượng lưu trữ), tốt hơn là sử dụng bảng thực thể-2-thực thể khác để theo dõi cấu trúc phân cấp thay vì cách tiếp cận parent_id.

Nó sẽ cho phép không chỉ quan hệ duy nhất cha mẹ mà còn quan hệ đa phụ huynh, chỉ mức độ và các loại khác nhau của các mối quan hệ:

CREATE TABLE Person (
    Id INTEGER, 
    Name TEXT 
); 

CREATE TABLE PersonInPerson (
    PersonId INTEGER NOT NULL, 
    InPersonId INTEGER NOT NULL, 
    Level INTEGER, 
    RelationKind VARCHAR(1) 
); 
0
+0

Tôi không thích phương pháp đó - "trong khi" vòng lặp không phải là thực hành SQL rất tốt, và nếu có một cách khai báo hơn để làm điều đó, mà nên được ưa thích để thay thế. Và bây giờ có: sử dụng một khung nhìn hoặc hàm dựa trên bảng bằng cách sử dụng biểu thức bảng chung, sử dụng cấu trúc WITH .. UNION ALL như được hiển thị trong các câu trả lời khác ở đây. – Anthony

+0

Vui lòng xem xét chèn một đoạn trích cho giải pháp trên trang bạn đã liên kết. Liên kết có thể đã chết một ngày nào đó. – rcdmk

Các vấn đề liên quan