首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >如何使用实体框架进行递归加载?

如何使用实体框架进行递归加载?
EN

Stack Overflow用户
提问于 2010-02-15 22:17:40
回答 3查看 24.4K关注 0票数 26

我在DB with TreeNodes表中有一个树形结构。该表包含nodeId、parentId和parameterId。在EF中,结构类似于TreeNode.Children,其中每个子对象都是一个TreeNode...我还有一个包含id、name和rootNodeId的树表。

在一天结束的时候,我想把树加载到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;

这会让我得到前两代,但不会更多。如何加载包含所有代和附加数据的整个树?

EN

回答 3

Stack Overflow用户

发布于 2015-07-30 04:08:48

我想把我的答案贴出来,因为其他人都不帮我。

我的数据库有点不同,基本上我的表有一个ID和一个ParentID。该表是递归的。下面的代码获取所有子代,并将它们嵌套到最终列表中。

public IEnumerable<Models.MCMessageCenterThread> GetAllMessageCenterThreads(int msgCtrId)
{
    var z = Db.MCMessageThreads.Where(t => t.ID == msgCtrId)
        .Select(t => new MCMessageCenterThread
        {
            Id = t.ID,
            ParentId = t.ParentID ?? 0,
            Title = t.Title,
            Body = t.Body
        }).ToList();

    foreach (var t in z)
    {
        t.Children = GetChildrenByParentId(t.Id);
    }

    return z;
}

private IEnumerable<MCMessageCenterThread> GetChildrenByParentId(int parentId)
{
    var children = new List<MCMessageCenterThread>();

    var threads = Db.MCMessageThreads.Where(x => x.ParentID == parentId);

    foreach (var t in threads)
    {
        var thread = new MCMessageCenterThread
        {
            Id = t.ID,
            ParentId = t.ParentID ?? 0,
            Title = t.Title,
            Body = t.Body,
            Children = GetChildrenByParentId(t.ID)
        };

        children.Add(thread);
    }

    return children;
}

为了完整起见,下面是我的模型:

public class MCMessageCenterThread
{
    public int Id { get; set; }
    public int ParentId { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }

    public IEnumerable<MCMessageCenterThread> Children { get; set; }
}
票数 3
EN

Stack Overflow用户

发布于 2017-09-13 23:52:17

作为加载子对象的示例,我将给出包含注释的comment对象的示例。每个注释都可能有一个子注释。

private static void LoadComments(<yourObject> q, Context yourContext)
{
    if(null == q | null == yourContext)
    {
        return;
    }
    yourContext.Entry(q).Reference(x=> x.Comment).Load();
    Comment curComment = q.Comment;
    while(null != curComment)
    {
        curComment = LoadChildComment(curComment, yourContext);
    }
}

private static Comment LoadChildComment(Comment c, Context yourContext)
{
    if(null == c | null == yourContext)
    {
        return null;
    }
    yourContext.Entry(c).Reference(x=>x.ChildComment).Load();
    return c.ChildComment;
}

现在,如果你有一个拥有自己集合的东西,你将需要使用Collection而不是Reference,并做同样的深入研究。至少这是我在处理Entity和SQLite时在这个场景中采用的方法。

票数 0
EN

Stack Overflow用户

发布于 2018-08-01 07:42:26

这是一个古老的问题,但其他答案要么有n+1数据库的命中率,要么他们的模型有利于自下而上(树干到树叶)方法。在这种情况下,标签列表以树的形式加载,一个标签可以有多个父级。我使用的方法只有两次数据库命中:第一次是获取所选文章的标签,另一次是急切地加载连接表。因此,这使用了一种自顶向下(树叶到主干)的方法;如果您的连接表很大,或者如果结果不能真正缓存以供重用,那么急切地加载整个事情就会开始显示这种方法的权衡。

首先,我初始化了两个HashSet:一个用来保存根节点(结果集),另一个用来保存对每个被“命中”的节点的引用。

var roots = new HashSet<AncestralTagDto>(); //no parents
var allTags = new HashSet<AncestralTagDto>();

接下来,我获取客户机请求的所有叶子,将它们放入一个对象中,该对象包含一个子集合(但在此步骤之后,该集合将保持为空)。

var startingTags = await _dataContext.ArticlesTags
        .Include(p => p.Tag.Parents)
        .Where(t => t.Article.CategoryId == categoryId)
        .GroupBy(t => t.Tag)
        .ToListAsync()
        .ContinueWith(resultTask => 
             resultTask.Result.Select(
                  grouping => new AncestralTagDto(
                        grouping.Key.Id, 
                        grouping.Key.Name)));

现在,让我们获取标记自连接表,并将其全部加载到内存中:

var tagRelations = await _dataContext.TagsTags.Include(p => p.ParentTag).ToListAsync();

现在,对于startingTags中的每个标记,将该标记添加到allTags集合,然后向下遍历树以递归获取祖先:

foreach (var tag in startingTags)
{
    allTags.Add(tag);
    GetParents(tag);
}
return roots;

最后,下面是构建树的嵌套递归方法:

void GetParents(AncestralTagDto tag)
{
    var parents = tagRelations.Where(c => c.ChildTagId == tag.Id).Select(p => p.ParentTag);
    if (parents.Any()) //then it's not a root tag; keep climbing down
    {
        foreach (var parent in parents)
        {
            //have we already seen this parent tag before? If not, instantiate the dto.
            var parentDto = allTags.SingleOrDefault(i => i.Id == parent.Id);
            if (parentDto is null)
            {
                parentDto = new AncestralTagDto(parent.Id, parent.Name);
                allTags.Add(parentDto);
            }

            parentDto.Children.Add(tag);
            GetParents(parentDto);
        }
    }
    else //the tag is a root tag, and should be in the root collection. If it's not in there, add it.
    {
        //this block could be simplified to just roots.Add(tag), but it's left this way for other logic.
        var existingRoot = roots.SingleOrDefault(i => i.Equals(tag));
        if (existingRoot is null)
            roots.Add(tag);
    }
}

在幕后,我依靠HashSet的属性来防止重复。为此,重要的是您使用的中间对象(我在这里使用的是AncestralTagDto,它的子代集合也是一个HashSet)覆盖适合您的用例的Equals和GetHashCode方法。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/2266473

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档