無限層級 net core + ef core實現筆記


在系統設計中,總會碰到上下級的概念,這個時候,怎么設計數據庫是一門學問

場景描述 : M存款10000元 他的上級H抽取a個點, H的上級D抽取b個點, D的上級A抽取c個點 類似傳銷/直銷?

 

麻煩的地方:  當找出一個用戶時,要找到他的所有下級,以及所有的父級

參考文檔  https://ouhaohan8023.github.io/2019/04/12/Artisans/ClosureTable%20%E2%80%94%E2%80%94%20%E6%97%A0%E9%99%90%E6%9E%81%E5%88%86%E7%B1%BB%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/

基於上述文檔方案三的實現

文檔末尾有一個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文件 執行腳本生成數據庫 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM