在系統設計中,總會碰到上下級的概念,這個時候,怎么設計數據庫是一門學問
場景描述 : M存款10000元 他的上級H抽取a個點, H的上級D抽取b個點, D的上級A抽取c個點 類似傳銷/直銷?
麻煩的地方: 當找出一個用戶時,要找到他的所有下級,以及所有的父級
基於上述文檔方案三的實現
文檔末尾有一個github地址 https://github.com/jiaxincui/closure-table 是php的實現
自己寫了一個c#的實現 orm用的efcore 簡單寫了下單元測試
記錄一下 方便以后用到
把主要代碼貼一下
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace treedemo { public class AgentRelationshipService : IAgentRelationshipService { private readonly IRepository<AgentTree> _agentTreeRepository; private readonly IUnitOfWork<AgentContext> _agentUnitOfWork; public AgentRelationshipService( IRepository<AgentTree> agentTreeRepository, IUnitOfWork<AgentContext> agentUnitOfWork ) { _agentTreeRepository = agentTreeRepository; _agentUnitOfWork = agentUnitOfWork; } #region 查 /// <summary> /// 獲取所有后代 /// </summary> /// <param name="includeSelf"></param> public List<string> GetDescendants(string user, bool includeSelf) { var list = _agentTreeRepository.TableNoTracking.Where(x => x.Ancestor == user); if (includeSelf) { list = list.Where(x => x.Distance >= 0); } else { list = list.Where(x => x.Distance > 0); } var results = list.Select(x => x.Descendant).Distinct(); return results.ToList(); } /// <summary> /// 獲取所有祖先 /// </summary> public List<string> GetAncestors(string user, bool includeSelf) { var list = _agentTreeRepository.TableNoTracking.Where(x => x.Descendant == user); if (includeSelf) { list = list.Where(x => x.Distance >= 0); } else { list = list.Where(x => x.Distance > 0); } var results = list.Select(x => x.Ancestor).Distinct(); return results.ToList(); } /// <summary> /// 獲取直接下級 /// </summary> public List<string> GetChildren(string user) { var list = _agentTreeRepository.TableNoTracking .Where(x => x.Ancestor == user && x.Distance == 1); var results = list.Select(x => x.Descendant).Distinct(); return results.ToList(); } /// <summary> /// 獲取直接上級 /// </summary> public string GetParent(string user) { var p = _agentTreeRepository.TableNoTracking.FirstOrDefault(x => x.Descendant == user && x.Distance == 1); return p?.Ancestor ?? string.Empty; } /// <summary> /// 獲取祖先(根) /// </summary> public string GetRoot(string user) { var m = _agentTreeRepository.TableNoTracking .Where(x => x.Descendant == user).OrderByDescending(x => x.Distance).FirstOrDefault(); return m?.Ancestor; } /// <summary> /// 獲取所有兄弟姐妹(同一個parent) /// </summary> public List<string> GetSiblings(string user, bool includeSelf) { var shangji = _agentTreeRepository.TableNoTracking .FirstOrDefault(x => x.Descendant == user && x.Distance == 1)?.Ancestor; //無上級,即頂級 if (string.IsNullOrEmpty(shangji)) { return includeSelf ? new List<string>() { user } : new List<string>(); } var children = _agentTreeRepository.TableNoTracking .Where(x => x.Ancestor == shangji && x.Distance == 1); var results = children.Select(x => x.Descendant).Distinct().ToList(); if (!includeSelf) { results.Remove(user); } return results; } /// <summary> /// 獲取所有根 /// </summary> public List<string> GetRoots() { var roots = from t in _agentTreeRepository.TableNoTracking group t by t.Descendant into g where g.Count() == 1 select g.Key; var results = roots.Distinct().ToList(); return results; } #endregion #region 增改 /// <summary> /// 將此節點作為根 /// </summary> public void MakeRoot(string user) { MoveTo(user); } /// <summary> /// 創建一個新的節點 /// </summary> /// <param name="user"></param> /// <param name="parent">默認空,表示創建根節點,否則 創建到parent節點下</param> public void Create(string user, string parent = "") { var existed = _agentTreeRepository.TableNoTracking.Any(x => x.Descendant == user); if (existed) { throw new Exception($"{user}節點已存在"); } if (!string.IsNullOrEmpty(parent)) { var parentexisted = _agentTreeRepository.TableNoTracking.Any(x => x.Descendant == parent); if (!parentexisted) { throw new Exception($"{parent}父節點不存在"); } } var self = new AgentTree { Ancestor = user, Descendant = user, Distance = 0, }; if (string.IsNullOrEmpty(parent)) { _agentTreeRepository.Insert(self); } else { //復制父節點的所有記錄,並把這些記錄的distance加一 descendant也要改成自己的 var pnodes = _agentTreeRepository.TableNoTracking.Where(x => x.Descendant == parent).Select(x => new AgentTree { Ancestor = x.Ancestor, Descendant = user, Distance = x.Distance + 1, }); _agentTreeRepository.InsertMany(pnodes); _agentTreeRepository.Insert(self); } _agentUnitOfWork.Save(); } /// <summary> /// 移動到parent的下級,后代也將隨之移動 /// </summary> /// <param name="user"></param> /// <param name="parent">為空表示user作為根節點存在</param> public void MoveTo(string user, string parent = "") { var existed = _agentTreeRepository.TableNoTracking.Any(x => x.Descendant == user); if (!existed) { throw new Exception($"{user}節點已存在"); } if (!string.IsNullOrEmpty(parent)) { var parentexisted = _agentTreeRepository.TableNoTracking.Any(x => x.Descendant == parent); if (!parentexisted) { throw new Exception($"{parent}父節點不存在"); } } var children = _agentTreeRepository.TableNoTracking.Where(x => x.Ancestor == user) .Select(x => new { x.Ancestor, x.Descendant, x.Distance }).ToList(); var pnodes = _agentTreeRepository.TableNoTracking.Where(x => x.Descendant == parent && x.Distance > 0); foreach (var child in children) { //user下所有子節點遍歷=>刪除user以上的父節點 var safeDistance = children.FirstOrDefault(x => x.Ancestor == user && x.Descendant == child.Descendant && x.Distance > 0)?.Distance ?? 0; _agentTreeRepository.DeleteWhere(x => x.Descendant == child.Descendant && x.Distance > safeDistance); //所有user子節點遍歷 =>復制parent節點的所有記錄,並把這些記錄的distance加一,escendant也要改成自己的 _agentTreeRepository.InsertMany(pnodes.Select(x => new AgentTree { Ancestor = x.Ancestor, Descendant = child.Descendant, Distance = x.Distance + safeDistance + 1 })); if (!string.IsNullOrEmpty(parent)) { _agentTreeRepository.Insert(new AgentTree { Ancestor = parent, Descendant = child.Descendant, Distance = safeDistance + 1 }); } } _agentUnitOfWork.Save(); } #endregion #region 生成樹形結構 /// <summary> /// 從當前節點生成樹 /// </summary> public AgentTreeModel GetTree(string user, List<string> leafNodes) { AgentTreeModel root = new AgentTreeModel(user); //所有的葉子節點 if (leafNodes == null || leafNodes.Count == 0) { leafNodes = _agentTreeRepository.TableNoTracking .GroupBy(g => g.Ancestor).Where(x => x.Count() == 1) .Select(x => x.Key).Distinct().ToList(); } var tree = GetChildren(root, leafNodes); return tree; } /// <summary> /// 從根節點生成樹,可能有多個 /// </summary> public List<AgentTreeModel> GetRootTree() { var roots = GetRoots(); var trees = new List<AgentTreeModel>(); var leafNodes = _agentTreeRepository.TableNoTracking .GroupBy(g => g.Ancestor).Where(x => x.Count() == 1) .Select(x => x.Key).Distinct().ToList(); foreach (var root in roots) { var tree = GetTree(root, leafNodes); trees.Add(tree); } return trees; } /// <summary> /// 遞歸向下遍歷 /// </summary> /// <param name="user"></param> /// <param name="tree"></param> private AgentTreeModel GetChildren(AgentTreeModel root, List<string> leafNodes) { //查找直屬下級 var children = _agentTreeRepository.TableNoTracking .Where(x => x.Ancestor == root.UserName && x.Distance == 1) .OrderBy(x => x).Distinct() .Select(x => new AgentTreeModel(x.Descendant)); root.Children = children.ToList(); foreach (var child in root.Children) { //葉子節點跳過 if (leafNodes.Contains(child.UserName)) continue; GetChildren(child, leafNodes); } return root; } #endregion } }
完整的地址 https://github.com/bbenph/treedemo
記得看AgentContext文件 執行腳本生成數據庫