簡體   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