如何使用Entity Framework实现递归加载?

6 浏览
0 Comments

如何使用Entity Framework实现递归加载?

我在数据库中有一个带有TreeNodes表的树结构。该表具有nodeId、parentId和parameterId。在EF中,结构类似于TreeNode.Children,其中每个子节点都是一个TreeNode...

我还有一个包含id、name和rootNodeId的Tree表。

最终,我想将树加载到TreeView中,但我不知道如何一次性加载所有内容。

我尝试了以下代码:

var trees = from t in context.TreeSet.Include("Root").Include("Root.Children").Include("Root.Children.Parameter")
        .Include("Root.Children.Children")
                        where t.ID == id
                        select t;

这将获取前两代,但不会再多了。

如何加载整个树以及所有的代和其他数据?

0
0 Comments

在这个问题中,问题的原因是作者想要实现递归加载(recursive load)的功能,但是其他人提供的解决方法并没有帮助到作者。作者的数据库表是一个递归表,其中包含一个ID和一个ParentID字段。作者提供了一个方法GetAllMessageCenterThreads来获取所有子项并将其嵌套到最终列表中。还有一个私有方法GetChildrenByParentId来根据父ID获取子项。作者的模型是MCMessageCenterThread,其中包含了ID、ParentID、Title、Body和Children等属性。

解决方法是使用递归的方式从数据库中获取所有子项。首先,根据给定的ID查询数据库,然后将结果转化为MCMessageCenterThread对象,并将其添加到最终列表中。接着,对于列表中的每个对象,调用GetChildrenByParentId方法来获取其子项,并将子项赋值给Children属性。GetChildrenByParentId方法同样是使用递归的方式来获取子项。最后,返回最终列表。

然而,代码中存在一些问题。首先,GetAllMessageCenterThreads方法返回的是一个列表,但实际上只有一个元素,这个元素是根据给定的ID查询得到的。其次,ParentID属性应该是可空的,但是代码中没有将其声明为可空类型。最后,模型缺少一个MessageCenterId属性,所有项都应该包含这个字段,以便根据该字段获取所有项。如果目标是获取特定线程的所有回复,可以考虑使用CTEs(Common Table Expressions)来实现递归加载的功能。

作者想要实现递归加载的功能,通过查询数据库并使用递归的方式获取所有子项,并将其嵌套到最终列表中。代码中存在一些问题,但作者提供的解决方案对于作者来说是有效的。如果有更好的解决方案,欢迎分享。

0
0 Comments

使用Include()时,您正在要求Entity Framework将您的查询转换为SQL语句。那么请想一想:你如何编写一个返回任意深度树的SQL语句呢?

答案是:除非您使用数据库服务器的特定层次结构功能(这些功能不是SQL标准,但某些服务器支持,如SQL Server 2008,但不支持其Entity Framework提供程序),否则您不会这样做。在SQL中处理任意深度的树的常用方法是使用嵌套集模型而不是父ID模型。

因此,有三种方法可以解决这个问题:

  1. 使用嵌套集模型。这需要更改您的元数据。
  2. 使用SQL Server的层次结构功能,并将Entity Framework修改为理解它们(这很棘手,但这种技术可能有效)。同样,您需要更改您的元数据。
  3. 使用显式加载或EF 4的延迟加载,而不是急切加载。这将导致多个数据库查询而不是一个。

最后,我添加了一个递归调用,调用Children和其他引用对象的Load方法。谢谢

顺便说一句... '嵌套集模型'的链接无效。InformationWeek说'URL不可用'。

0
0 Comments

如何使用Entity Framework进行递归加载?

近期我遇到了这个问题,后来我找到了一个简单的方法来解决。我对Craig的答案进行了编辑,提供了第四种方法,但最后决定将其作为另一个答案。这对我来说没问题 🙂

我的原始问题/答案可以在这里找到。

只要你的表中的项都知道它们属于哪棵树(在你的情况下看起来是这样的:t.ID),这个方法就适用。不过,不清楚你实际上使用了哪些实体,但是即使你有多个实体,如果Children不是TreeSet,那么它在实体中必须有一个外键。

基本上,不要使用Include()

var query = from t in context.TreeSet
            where t.ID == id
            select t;
//如果TreeSet.Children是一个不同的实体:
var query = from c in context.TreeSetChildren
            //猜测TreeSetID是外键属性
            where c.TreeSetID == id
            select c;

这将返回树中的所有项,并将它们都放在集合的根目录下。此时,你的结果集将如下所示:

-- Item1

-- Item2

-- Item3

-- Item4

-- Item5

-- Item2

-- Item3

-- Item5

由于你可能希望从EF中返回的实体按层次结构排列,这不是你想要的,对吧?

..然后,排除根级别存在的子项:

幸运的是,由于你在模型中有导航属性,子实体集合仍然会被填充,就像你在上面的结果集示例中看到的那样。通过使用foreach()循环手动迭代结果集,并将这些根项添加到new List<TreeSet>()中,你现在将拥有一个包含根元素和所有正确嵌套的后代的列表。

如果你的树很大,性能是一个问题,你可以按ParentID升序对返回集进行排序(它是Nullable的,对吧?),这样所有的根项都会排在前面。像之前一样迭代并添加,但是一旦到达一个不为空的位置,就跳出循环。

var subset = query
     //对数据库执行查询
     .ToList()
     //过滤掉非根项
     .Where(x => !x.ParentId.HasValue);

现在,subset看起来是这样的:

-- Item1

-- Item2

-- Item3

-- Item4

-- Item5

关于Craig的解决方案:

  1. 你真的不想使用延迟加载来解决这个问题!基于需要进行n+1查询的设计将严重影响性能。
  2. ********* (嗯,公平地说,如果你允许用户选择性地深入挖掘树,那么这可能是合适的。只是不要在一开始就使用延迟加载!!)

  3. 我从未尝试过嵌套设置的东西,我也不建议通过修改EF配置来使其工作,因为有一种更简单的解决方案。
  4. 另一个合理的建议是创建一个提供自链接的数据库视图,然后将该视图映射到一个中间的连接/链接/m2m表。个人认为这个解决方案比必要复杂,但它可能有其用途。

JoeBrockhaus,我还没有测试你的代码,但有几个问题:1)该代码只是迭代并将数据填充为单个节点,而不是树节点。2)根节点永远不会被添加。也许你应该尝试将子对象分配给一个ICollectionable。这样你就会创建一个树。我不会对你的答案投反对票,但请改进你的答案。

ésBrancolini原始问题缺乏对模型的细节描述,但我的答案涵盖了我提到的两种可能的情况。至于子项集合,由于实体中定义了导航属性,它们将自动填充。其中之一将成为根节点。最终的变化在于逻辑:不要考虑查询父项并获取其递归子项,而是仅仅查询所有子项,并在需要时独立获取父项。

此外,关于反对票的评论是因为在任何其他投票或评论之前,原始答案就已经被投反对票了,而至少有一些人认为这个答案更好地回答了问题。

这个答案对我很有用,尽管在使用延迟加载时需要进行一步额外的操作。我正在使用Entity Framework Core 3.1和延迟加载代理。因此,即使树被填充,当访问Children时代理仍然会触发额外的加载。所以,解决方案是为每个返回的节点添加dbContext.Entry(treeNode).Collection(n => n.Children).IsLoaded = true;

0