Linq中的分层数据 - 选项和性能
-
03-07-2019 - |
题
我有一些分层数据 - 每个条目都有一个id和一个(可空)父条目id。 我想检索给定条目下树中的所有条目。这是在SQL Server 2005数据库中。我在C#3.5中使用LINQ to SQL查询它。
LINQ to SQL不直接支持 Common Table Expressions 。我的选择是使用几个LINQ查询在代码中汇编数据,或者在面向CTE的数据库上进行查看。
当数据量变大时,您认为哪个选项(或其他选项)会表现更好? Linq to SQL中是否支持SQL Server 2008的 HierarchyId类型?
解决方案
我会根据CTE设置一个视图和一个相关的基于表的函数。我的理由是,虽然您可以在应用程序端实现逻辑,但这将涉及通过线路发送中间数据以便在应用程序中进行计算。使用DBML设计器,视图转换为Table实体。然后,您可以将该函数与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)
);
我有两种方式:
- 根据用户输入驱动树的每一层的检索。想象一下,树视图控件填充了根节点,根节点的子节点和根节点的孙子节点。只扩展了根和子项(孙子被崩溃隐藏)。当用户扩展子节点时,显示根的孙子(先前已检索并隐藏),并且启动对所有曾孙的检索。重复N层深度的模式。这种模式适用于大树(深度或宽度),因为它只检索所需树的部分。
- 对LINQ使用存储过程。在服务器上使用类似公用表表达式的东西来在平面表中构建结果,或者在T-SQL中构建XML树。 Scott Guthrie有一个很棒的文章。如果以平面格式返回,则从结果中构建树,或者使用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的博客中获得了这种方法(请查看第6页的Pt代码,也在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
}
)
};
从客户端获取数据的麻烦在于,您无法确定需要走多远。此方法将在每个深度进行一次往返,并且可以在一次往返中从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;
}
然而,它不能做任意深度。如果你确实需要任意深度,你需要在数据库中这样做 - 这样你就可以做出正确的决定来停止。