[英]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);
然后这会带来所有带有子项的数据,子项也列在主列表下,尽管如果任何项目有父项,那么它不应该出现在主列表中,而应该出现在其父项的子项数组下。
结果就像
非常感谢
这是一个常见问题 - 而不是 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.