繁体   English   中英

如何使用实体框架检索父子关系数据并对其进行分页/过滤/排序

[英]How to retrieve parent child relationship data using entity framework and do pagination/filtering/sorting on it

我有一个自引用以形成父子关系的数据库表。

ID 姓名 父 ID
1 一个 null
2 1
3 C 2
4 D 2
5 1

我希望通过 EF 核心查询此表并以以下格式检索数据

{
  Id: 1,
  Name: A,
  Children: {
    {
      Id: 2,
      Name : B,
      Children :{
        {
           Id: 3,
           Name : C,
           Children :null
        },
        {
           Id: 4,
           Name : D,
           Children :null
        },
      } 
    },
    {
      Id: 5,
      Name: C,
      Children :null 
    },
  },
}

我的 Model class 是

class Temp
{
   public int Id {get; set }
   public string Name { get; set; }
   public Temp Parent { get; set; }
   public ICollection<Temp> children { get; set; } // Navigation property
}

如果我做这样的查询

dbContext.Temp.Include(x => x.Children);

然后这会带来所有带有子项的数据,子项也列在主列表下,尽管如果任何项目有父项,那么它不应该出现在主列表中,而应该出现在其父项的子项数组下。

结果就像

  • 一个
      • C
      • D
  • C
  • D

非常感谢

这是一个常见问题 - 而不是 EFCore 原生处理的问题。

注意点- 您几乎肯定想要添加检查以确保所有新的/编辑的记录不会无意中创建循环引用,例如: Temp1 = new Temp {Id = 1, ParentId = 2}; Temp2 = new Temp {Id = 2, ParentId = 1}; Temp1 = new Temp {Id = 1, ParentId = 2}; Temp2 = new Temp {Id = 2, ParentId = 1}; - 这会在加载树时导致坏事发生......

首先,不那么明显的解决方案,仅在检索根节点后使用延迟加载:

// Get a list of root nodes.
// Optionally, add pagination, filtering and sorting to the query - .Where(x => x.Name.Contains("blah")).Skip(10).Take(5).OrderBy(x => x.Sequence) etc.
var rootNodes = dbContext.Temp.Where(x => x.parentId == null).ToList();

// Child nodes will be loaded as you access them.
foreach (var node in rootNodes)
{
    foreach (var child in node.Children) {
        // Do something with children that were lazy-loaded.
    }
}

有关配置延迟加载的更多信息: https://csharp.christiannagel.com/2019/01/30/lazyloading/

注意:延迟加载会导致低效行为/意外的数据库查询。 这很方便,但方便是有代价的……

如果您的业务规则规定了最大深度,您可以使用如下包含语句,请注意过滤器仅检索根节点:

// For a maximum depth of two (or root, leaf, leaf)
// Optionally, add pagination, filtering and sorting to the query - .Where(x => x.Name.Contains("blah")).Skip(10).Take(5).OrderBy(x => x.Sequence) etc.
var query = dbContext.Temp
    .Where(x => x.ParentId == null)
    .Include(x => x.children)
        .ThenInclude(x => x.children);

对于未知深度的树,最好通过获取整个平面列表并自己构建嵌套(客户端而不是 EF 查询)来为您服务,请注意,分页必须在结果上:

// My recursive function to load child nodes.
Action<List<Temp>, <Temp>> LoadChildren = delegate(sourceList, loadChildrenFor) {
    // Get the children for the specified node.
    loadChildrenFor.Children = sourceList.Where(x => x.ParentId == loadChildrenFor.Id).ToList();
    // Remove the children from the source list AND load its children.
    foreach(var node in loadChildrenFor.Children)
    {
        sourceList.Remove(node); // Don't need in source list any more, it's a child.
        LoadChildren(sourceList, node);
    }
}

var allNodes = dbContext.Temp.ToList();
foreach (var node in allNodes.Where(x => x.parentId == null))
    LoadChildren(allNodes, node);

// Now allNodes ONLY contains root nodes with all their children populated, // Optionally, add pagination, filtering and sorting to the query - .Where(x => x.Name.Contains("blah")).Skip(10).Take(5).OrderBy(x => x.Sequence) etc.

对于列表展平的通用实现: https://habr.com/en/post/516596/

如果树非常大并且深度未知,并且您只需要一个子集(或一个分支)递归 function 内的多个查询将是最合适的:

// My recursive function to load child nodes.
Action<Temp> LoadChildren = delegate(ofNode) {
    ofNode.Children = dbContext.Temp.Where(x => x.ParentId == ofNode.Id).ToList();
    foreach(var node in ofNode.Children)
        LoadChildren(node);
}

// A list of root nodes I'm interested in - could be any depth in tree.
// Optionally, add pagination and sorting to the query - .Skip(10).Take(5).OrderBy(x => x.Sequence) etc.
var rootNodesImInterestedIn = dbContext.Temp.Where(x => x.Id == 5).ToList();
// Load the children for each root node I'm interested in.
foreach(var node in rootNodesImInterestedIn)
    LoadChildren(node);
private List<Category> toHierarchical(List<Category> categories)
{
    foreach (var category in categories.ToList())
    {
        var childs = categories.Where(s => s.ParentId == category.Id).ToList();
        category.Childs = childs;
        categories.RemoveAll(childs.Contains);
    }
    return categories;
}

享受reference type的好处。 这么简单对吧? :)

更新:您也可以使用它;

var result = await _categoryReadRepository.GetIncluded(
        d => d.Include(r => r.Childs)
    ).ToListAsync(); // <-- actually I get all data with childs here, you can use your own method
var response = result.Where(r=>r.ParentId == null);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM