[英]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.