LINQ获取所有层级子元素

4

我已经研究了相当长一段时间。

public class Person
{
   public string Name { get; set; }    
   public string Age { get; set; }    
   public List<Person> Children { get; set; }    
}

我希望能够使用一条LINQ查询语句找出“在此集合中所有年龄>4的人”。请注意:您需要遍历Person和Children的集合,因此每个Children对象将具有一个包含Person的集合,直到Children变为null。

1
请你务必将“Childrens”替换为“Children”,拜托了。 - Christofer Ohlsson
你可以用 public class Person : List<Person> { public string Name { get; set; } public string Age { get; set; } } 替换这个类,从而避免每次创建 Children 属性的需要。这样还能让你使用集合初始化器。 - Enigmativity
3个回答

3

首先,我不明白为什么你的所有属性都是 private,而 Age 不是 int 类型。所以我的类看起来像这样:

public partial class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public List<Person> Childrens { get; set; }
}

注意 partial 关键字。这个关键字可以让你把类的逻辑分离到不同的文件中,当你需要在类中创建一些自定义逻辑时非常有用。
然后我只需在另一个文件中创建这个方法:
public partial class Person
{
    public Person GetPersonWithChindren(int maxAge)
    {
        return new Person
        {
            Age = this.Age,
            Name = this.Name,
            Childrens = this.Childrens != null 
            ? this.Childrens
                .Where(x => x.Age < maxAge)
                .Select(x => x.GetPersonWithChindren(maxAge)) //this line do recursive magic
                .ToList() 
            : null
        };
    }
}

正如您所看到的,该方法检查每个孩子的 Age,如果 Age 正确,则检查下一层级的结构,直到 Childrensnull

因此,您可以像这样使用它:

var person = new Person()
{
  //initialisation of your collection here
}

//result will contains only nodes where Person have age < 4 and Childs that have age < 4
var result = person.GetPersonWithChindren(4);

请注意,这个解决方案在使用 linqToEntities 时可以正常工作。但是如果你使用 LinqToSQL,这个表达式会对每个 Person 实体产生数据库查询。因此,如果你有很多人和深层次的层次结构,它将花费你很多机器时间。在这种情况下,你应该使用 CTE 编写存储过程,而不是 LinQ 查询。 更新: 你甚至可以通过使用 Func<T> 类编写更通用的解决方案,如下所示:
public partial class Person
{
    public Person GetPersonWithChindren(Func<Person, bool> func)
    {
        return new Person
        {
            Age = this.Age,
            Name = this.Name,
            Childrens = this.Childrens != null
            ? this.Childrens
                .Where(x => func(x))
                .Select(x => x.GetPersonWithChindren(func))
                .ToList()
            : null
        };
    }
}

然后你可以像这样使用它:
var result = person.GetPersonWithChindren(x => x.Age < 4);

您现在可以随时更改要在何处使用您的函数的标准。


我想只使用LINQ来实现这个...我的意思是...我们可以仅使用LINQ来完成这个吗...一个Person集合上的LINQ查询? - anand mishra
@anandmishra 所以...我不使用其他任何东西。 - teo van kot

3
创建一个访客。在这个例子中,通过实现一个帮助类来实现:
public static class Helpers
public static IEnumerable<Person> GetDescendants(this Person person)
{
    foreach (var child in person.Children)
    {
        yield return child;
        foreach (var descendant in child.GetDescendants())
        {
           yield return descendant;
        }
    }
}

这是其中一种情况,使用 "yield return many" 会很有用。


1
如果您确保自动创建 .Children,那么这个方法是可行的:
Func<Person, Func<Person, bool>, Person> clone = null;
clone = (p, f) => f(p) ? new Person()
{
    Name = p.Name,
    Age = p.Age,
    Children = p.Children.Select(c => clone(c, f)).Where(x => x != null).ToList(),
} : null;

var olderThan4 = clone(person, p => p.Age > 4);

是的,就是这样。实际上只有三行。

如果您从这些数据开始:

var person = new Person()
{
    Name = "Fred", Age = 30,
    Children = new List<Person>()
    {
        new Person() { Name = "Bob", Age = 7, },
        new Person() { Name = "Sally", Age = 3, }
    },
};

然后您会得到这个结果:

result

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    private List<Person> _children = null;
    public List<Person> Children
    {
        get
        {
            if (_children == null)
            {
                _children = new List<Person>();
            }
            return _children;
        }
        set
        {
            _children = value;
        }
    }
}

有没有一种方法可以像这样编写,但如果我需要获取层次结构,而不是扁平化集合呢? - teo van kot
@teovankot - 基本上您想克隆树,但不获取任何4岁或以下的子元素... - Enigmativity

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接