12

いくつかの階層データがあります。各エントリには ID と (null 可能) 親エントリ ID があります。特定のエントリの下にあるツリー内のすべてのエントリを取得したいと考えています。これは SQL Server 2005 データベースにあります。C# 3.5 で LINQ to SQL を使用してクエリを実行しています。

LINQ to SQL は、共通テーブル式を直接サポートしていません。私の選択は、いくつかの LINQ クエリを使用してコードでデータを組み立てるか、CTE を表示するデータベースのビューを作成することです。

データ ボリュームが大きくなった場合に、どのオプション (または別のオプション) のパフォーマンスが向上すると思いますか? SQL Server 2008 のHierarchyId 型は Linq to SQL でサポートされていますか?

4

9 に答える 9

16

このオプションも役立つ場合があります。

LINQ AsHierarchy() 拡張メソッド
http://www.scip.be/index.php?Page=ArticlesNET18

于 2009-07-10T16:49:26.283 に答える
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

この拡張メソッドは、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 に答える
3

私はこれを2つの方法で行いました:

  1. ユーザー入力に基づいて、ツリーの各レイヤーの取得を促進します。ルートノード、ルートの子、およびルートの孫が配置されたツリービューコントロールを想像してみてください。ルートと子のみが拡張されます(孫は崩壊して隠されます)。ユーザーが子ノードを展開すると、ルートの孫が表示され(以前に取得および非表示にされていた)、すべての曽孫の取得が開始されます。深さN層のパターンを繰り返します。このパターンは、必要なツリーの部分のみを取得するため、大きなツリー(深さまたは幅)に非常に適しています。
  2. LINQでストアドプロシージャを使用します。サーバーで一般的なテーブル式のようなものを使用して、フラットテーブルで結果を構築するか、T-SQLでXMLツリーを構築します。Scott Guthrieが、LINQでのストアドプロシージャの使用に関するすばらしい記事を掲載しています。フラット形式の場合は結果が戻ってきたときに結果からツリーを構築します。それが返される場合はXMLツリーを使用します。
于 2008-10-14T22:30:42.233 に答える
2

MS SQL 2008ではHierarchyIDを直接使用できますが、sql2005では手動で実装する必要がある場合があります。ParentIDは、大規模なデータセットではそれほどパフォーマンスが高くありません。このトピックの詳細については、この記事も確認してください。

于 2008-10-14T21:30:18.503 に答える
1

私はRobConeryのブログからこのアプローチを入手しました(このコードについては、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

クライアント側からデータをフェッチする際の問題は、どれだけ深く進む必要があるかがわからないことです。このメソッドは、深度ごとに1回のラウンドトリップを実行し、1回のラウンドトリップで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 に答える