-
zhangwei
2 天以前 7674a4144ba793196607578f8a57771d6beb22c7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
 
namespace Admin.NET.Core;
 
/// <summary>
/// 数据集合拓展类
/// </summary>
public static class EnumerableExtension
{
    private static readonly ConcurrentDictionary<string, PropertyInfo> PropertyCache = new();
 
    /// <summary>
    /// 查询有父子关系的数据集
    /// </summary>
    /// <param name="list">数据集</param>
    /// <param name="idExpression">主键ID字段</param>
    /// <param name="parentIdExpression">父级字段</param>
    /// <param name="topParentIdValue">顶级节点父级字段值</param>
    /// <param name="isContainOneself">是否包含顶级节点本身</param>
    /// <returns></returns>
    public static IEnumerable<T> ToChildList<T, P>(this IEnumerable<T> list,
        Expression<Func<T, P>> idExpression,
        Expression<Func<T, P>> parentIdExpression,
        object topParentIdValue,
        bool isContainOneself = true)
    {
        if (list == null || !list.Any()) return Enumerable.Empty<T>();
 
        var propId = GetPropertyInfo(idExpression);
        var propParentId = GetPropertyInfo(parentIdExpression);
 
        // 查找所有顶级节点
        var topNodes = list.Where(item => Equals(propId.GetValue(item), topParentIdValue)).ToList();
 
        return TraverseHierarchy(list, propId, propParentId, topNodes, isContainOneself);
    }
 
    /// <summary>
    /// 查询有父子关系的数据集
    /// </summary>
    /// <param name="list">数据集</param>
    /// <param name="idExpression">主键ID字段</param>
    /// <param name="parentIdExpression">父级字段</param>
    /// <param name="topLevelPredicate">顶级节点的选择条件</param>
    /// <param name="isContainOneself">是否包含顶级节点本身</param>
    /// <returns></returns>
    public static IEnumerable<T> ToChildList<T, P>(this IEnumerable<T> list,
        Expression<Func<T, P>> idExpression,
        Expression<Func<T, P>> parentIdExpression,
        Expression<Func<T, bool>> topLevelPredicate,
        bool isContainOneself = true)
    {
        if (list == null || !list.Any()) return Enumerable.Empty<T>();
 
        // 获取顶级节点
        var topNodes = list.Where(topLevelPredicate.Compile()).ToList();
 
        if (!topNodes.Any()) return Enumerable.Empty<T>();
 
        var idPropertyInfo = GetPropertyInfo(idExpression);
        var parentPropertyInfo = GetPropertyInfo(parentIdExpression);
 
        return TraverseHierarchy(list, idPropertyInfo, parentPropertyInfo, topNodes, isContainOneself);
    }
 
    /// <summary>
    /// 辅助方法,从表达式中提取属性信息并使用临时缓存
    /// </summary>
    private static PropertyInfo GetPropertyInfo<T, P>(Expression<Func<T, P>> expression)
    {
        // 使用 ConcurrentDictionary 确保线程安全
        return PropertyCache.GetOrAdd(typeof(T).FullName + "." + ((MemberExpression)expression.Body).Member.Name, k =>
        {
            if (expression.Body is UnaryExpression { Operand: MemberExpression member }) return (PropertyInfo)member.Member;
            if (expression.Body is MemberExpression memberExpression) return (PropertyInfo)memberExpression.Member;
            throw Oops.Oh("表达式必须是一个属性访问: " + expression);
        });
    }
 
    /// <summary>
    /// 使用队列遍历层级结构
    /// </summary>
    private static IEnumerable<T> TraverseHierarchy<T>(IEnumerable<T> list,
        PropertyInfo idPropertyInfo,
        PropertyInfo parentPropertyInfo,
        List<T> topNodes,
        bool isContainOneself)
    {
        var queue = new Queue<T>(topNodes);
        var result = new HashSet<T>(topNodes);
 
        while (queue.Count > 0)
        {
            var currentNode = queue.Dequeue();
            var children = list.Where(item => Equals(parentPropertyInfo.GetValue(item), idPropertyInfo.GetValue(currentNode))).ToList();
            children.Where(child => result.Add(child)).ForEach(child => queue.Enqueue(child));
        }
        if (isContainOneself) return result;
 
        // 如果不需要包含顶级节点本身,则移除它们
        topNodes.ForEach(e => result.Remove(e));
 
        return result;
    }
}