Linqの階層データ-オプションとパフォーマンス
-
03-07-2019 - |
質問
いくつかの階層データがあります-各エントリにはIDと(null可能な)親エントリIDがあります。 特定のエントリの下にあるツリーのすべてのエントリを取得したい。これは、SQL Server 2005データベースにあります。 C#3.5のLINQ to SQLでクエリを実行しています。
LINQ to SQLは、一般的なテーブル式を直接サポートしていません。私の選択は、いくつかのLINQクエリを使用してデータをコードにアセンブルするか、CTEを表示するデータベースのビューを作成することです。
データボリュームが大きくなると、どのオプション(または別のオプション)のパフォーマンスが向上すると思いますか? SQL Server 2008の HierarchyIdタイプは、Linq to SQLでサポートされていますか?
解決
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...
}
}
他のヒント
このオプションも有用である可能性があります。
LINQ AsHierarchy()拡張メソッド
http://www.scip.be/index.php?Page=ArticlesNET18
別のデータベース設計について言及している人がいないことに驚いています-階層を複数のレベルからフラット化し、高性能で取得する必要がある場合(ストレージスペースを考慮しない)、別のエンティティ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)
);
この2つの方法を実行しました:
- ユーザーの入力に基づいてツリーの各レイヤーの取得を促進します。ルートノード、ルートの子、およびルートの孫が入力されたツリービューコントロールを想像してください。ルートと子のみが展開されます(孫は折りたたまれて非表示になります)。ユーザーが子ノードを展開すると、ルートの孫が表示され(以前に取得および非表示にされていた)、すべてのrand孫の取得が開始されます。 N層の深さに対してパターンを繰り返します。このパターンは、必要なツリーの部分のみを取得するため、大きなツリー(深さまたは幅)で非常にうまく機能します。
- LINQでストアドプロシージャを使用します。サーバー上の共通テーブル式のようなものを使用して、結果をフラットテーブルに構築するか、T-SQLでXMLツリーを構築します。 Scott Guthrieには LINQでのストアドプロシージャの使用に関するすばらしい記事。結果がフラット形式の場合は結果からツリーを構築し、それが返される場合はXMLツリーを使用します。
この拡張メソッドは、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)
このコードは、こちら。
MS SQL 2008では、 HierarchyID を直接使用できます。 sql2005では、手動で実装する必要があります。 ParentIDは、大規模なデータセットではそれほどパフォーマンスが高くありません。トピックの詳細については、この記事も参照してください。
>Rob Coneryのブログからこのアプローチを得ました(これについてはPt。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
}
)
};
クライアント側からデータを取得する際の問題は、どこまで行かなければならないかわからないことです。このメソッドは、深さごとに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;
}
ただし、任意の深さを行うことはできません。本当に任意の深さが必要な場合は、データベースでそれを行う必要があります。そのため、停止するための正しい決定を下すことができます。