LINQ의 계층 적 데이터 - 옵션 및 성능
-
03-07-2019 - |
문제
계층 적 데이터가 있습니다. 각 항목에는 ID와 (무효) 상위 항목 ID가 있습니다. 주어진 항목 아래 트리의 모든 항목을 검색하고 싶습니다. 이것은 SQL Server 2005 데이터베이스에 있습니다. C# 3.5에서 LINQ에서 SQL로 쿼리하고 있습니다.
LINQ에서 SQL은 지원하지 않습니다 일반적인 테이블 표현 곧장. 내 선택은 여러 LINQ 쿼리로 코드로 데이터를 조립하거나 CTE를 표면으로하는 데이터베이스를보기위한 것입니다.
데이터 볼륨이 커지면 어떤 옵션 (또는 다른 옵션)이 더 잘 수행 될 것이라고 생각하십니까? SQL Server 2008입니다 HierarchyId 유형 LINQ에서 SQL에서 지원 되나요?
해결책
CTE를 기반으로 뷰와 관련 테이블 기반 기능을 설정합니다. 이에 대한 나의 추론은 응용 프로그램 측에서 논리를 구현할 수 있지만 응용 프로그램의 계산을 위해 중간 데이터를 와이어 위로 전송하는 것이 포함된다는 것입니다. DBML 디자이너를 사용하여보기는 테이블 엔티티로 변환됩니다. 그런 다음 함수를 테이블 엔티티와 연결하고 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)
);
이 두 가지 방법을 수행했습니다.
- 사용자 입력에 따라 트리의 각 레이어를 검색합니다. 트리 뷰 컨트롤이 루트 노드, 뿌리의 어린이 및 뿌리의 손자로 채워진 것을 상상해보십시오. 뿌리와 아이들 만 확장됩니다 (손자는 붕괴로 숨겨져 있습니다). 사용자가 어린이 노드를 확장함에 따라 뿌리의 손자는 디스플레이 (이전에 검색되고 숨겨져 있음)이며 모든 증손자의 검색이 시작됩니다. N- 레이어 깊이의 패턴을 반복하십시오. 이 패턴은 큰 나무 (깊이 또는 너비)에 매우 잘 작동합니다.
- LINQ와 함께 저장된 절차를 사용하십시오. 서버의 공통 테이블 표현식과 같은 것을 사용하여 평평한 테이블에 결과를 구축하거나 T-SQL에 XML 트리를 작성하십시오. Scott Guthrie는 a 훌륭한 기사 LINQ에서 저장된 Procs 사용에 대해 평평한 형식으로 돌아올 때 결과에서 트리를 만들거나 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)
이 코드는 개선 된 버그 고정 버전의 코드입니다. 여기.
나는이 접근법을 얻었다 Rob Conery의 블로그 (CodePlex 에서도이 코드에 대한 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
}
)
};
클라이언트 측에서 데이터를 가져 오는 데 어려움을 겪는 것은 얼마나 깊이 가야하는지 확신 할 수 없다는 것입니다. 이 방법은 깊이 당 하나의 라운드 트립을 수행하며 하나의 왕복에서 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;
}
그러나 임의의 깊이는 할 수 없습니다. 실제로 임의의 깊이가 필요하다면 데이터베이스에서이를 수행해야하므로 중지하기로 한 올바른 결정을 내릴 수 있습니다.