按子属性排序的父属性分组Linq

8
基本上我想要做的是按Parent.type对父级进行分组,其中他们的Child.type具有状态“y”,并按child.Date排序。
作为示例,我创建了这些类:
public class Collection
{
    public IEnumerable<Parent> Parents { get; set; }
    public int Id { get; set; }
}

public class Parent
{
    public int Id { get; set; }
    public Type type { get; set; }       
    public IEnumerable<Child> Children { get; set; }
}

public class Child
{
    public int Id { get; set; }
    public Status Status { get; set; }
    public DateTime Date { get; set; }
}

public enum Type
{
    a,
    b
}

public enum Status
{
    x,
    y
}

我希望得到每种类型的父项列表,其中子项的日期值最高。
为了让这个例子运行起来,我添加了一些测试数据:
Collection collection= new Collection
        {
            Id = 1,
            Parents = new List<Parent>
            {
                new Parent
                {
                    Id= 1,
                    type = Type.a,
                    Children = new List<Child>
                    {
                        new Child
                        {
                            Id = 1,
                            Status = Status.y,
                            Date = DateTime.Now.AddDays(-1)
                        },
                        new Child
                        {
                            Id = 2,
                            Status = Status.x,
                            Date = DateTime.Now.AddDays(-1)
                        }
                    }
                },
                new Parent
                {
                    Id= 2,
                    type = Type.b,
                    Children = new List<Child>
                    {
                        new Child
                        {
                            Id = 3,
                            Status = Status.y,
                            Date = DateTime.Now.AddDays(-2)
                        },
                        new Child
                        {
                            Id = 4,
                            Status = Status.x,
                            Date = DateTime.Now.AddDays(-2)
                        }
                    }
                },
                new Parent
                {
                    Id= 3,
                    type = Type.a,
                    Children = new List<Child>
                    {
                        new Child
                        {
                            Id = 5,
                            Status = Status.y,
                            Date = DateTime.Now.AddDays(-3)
                        }
                    }
                },
                new Parent
                {
                    Id= 4,
                    type = Type.b,
                    Children = new List<Child>
                    {
                        new Child
                        {
                            Id = 6,
                            Status = Status.y,
                            Date = DateTime.Now.AddDays(-3)
                        },
                        new Child
                        {
                            Id = 7,
                            Status = Status.y,
                            Date = DateTime.Now.AddDays(-4)
                        },
                        new Child
                        {
                            Id = 8,
                            Status = Status.x,
                            Date = DateTime.Now.AddDays(-4)
                        }
                    }
                },
            }
        };

选择的结果应该是一个列表,其中包含2个父对象。每种类型(a和b)各一个。这两个父对象仍然包含它们所有的子对象。

有没有简单的解决方案?

我尝试了以下代码:

List<Parent> test = collection.Parents
                .GroupBy(m => m.type)
                .Select(
                m => m.OrderByDescending(
                    mm => mm.Children.Select(
                        r => r).OrderByDescending(
                            r => r.Date)).FirstOrDefault()).ToList();

但结果并不理想。

=====更新=====

感谢Enigmativity的示例,这个问题得到了解决。我对linq查询进行了轻微修改,因为我使用的原始内容是由Entity Framework(6)提供的。这意味着对于我选择的父对象没有包含任何子对象,所以我必须选择一个新的对象类型(因为我们不能实例化Entity Framework模型提供的类型的新对象)。此外,我的父元素在另一个IEnumerable中,所以我还必须对父元素使用SelectMany。

这导致了类似于这样的结果:(虽然这无法与测试类一起使用)

var result =
    collection
        .SelectMany(c => c.Parents)
        .GroupBy(p => p.type)
        .SelectMany(gps =>
            gps
                .SelectMany(
                    gp => gp.Children.Where(c => c.Status == Status.y),
                    (gp, c) => new { gp, c })
                .OrderByDescending(gpc => gpc.c.Date)
                .Take(1)
                .Select(gpc => new ParentData {Id = gpc.gp.Id, Children= gpc.gp.Children}))
        .ToList();

2
做得好,作为一个新手在这里提出如此详细的问题并提供可编译的代码。只需要两个快速的复制粘贴,我就能运行你的代码了。我希望更多的新用户也能和你一样出色。 - Enigmativity
3个回答

2
如果我正确理解您的要求,您想按类型对所有父级进行分组,然后从每个组中选择一个父级,该父级具有该组中所有子级中最高日期的子级。
这是我想到的解决方案:
var result =
    collection
        .Parents
        .GroupBy(p => p.type)
        .SelectMany(gps =>
            gps
                .SelectMany(
                    gp => gp.Children.Where(c => c.Status == Status.y),
                    (gp, c) => new { gp, c })
                .OrderByDescending(gpc => gpc.c.Date)
                .Take(1)
                .Select(gpc => gpc.gp))
        .ToList();

这给了我:

结果


我想做的是按Parent.type分组获取其父级,其中Child.type具有状态“y”,并按child.Date排序。我在您的结果中没有看到这个。 - mariovalens
@mariovalens - 已修复。 - Enigmativity
仍然不正确。他想要所有状态为y的孩子。您只选择具有所选父级的状态y的孩子。他真正想要的是我在我的答案中发布的按类型折叠父项并聚合该类型状态y的所有子项。 - mariovalens
@mariovalens - 不是的,我对这个问题的理解不是这样的。 “这两个对象仍然包含它们所有的子对象”这一行意味着我们只是选择现有的父级。OP只想按Status.y过滤而不是进行选择。 - Enigmativity
现在这个示例的状态正是我想要的。非常感谢您。我已经更新了我的问题,并附上了我最终使用的linq查询。 - Kevin
显示剩余3条评论

0

当父级对象是查询/投影的目标时,子对象无法进行过滤、排序等操作。

你需要:

  • 断开关系并投射到不同的数据结构(如字典或 IGrouping,其中父项是键,排序后的子项是值)
  • 使用循环以编程方式 Sort() 或重新分配 Children,而不使用 LINQ
  • 编写多行 lambda 表达式,其中第二行是排序或分配子项的副作用(不推荐)

个人认为,将只需要的数据投影到简化的表示形式比试图一路保留层次结构更容易。


0

我理解这个问题是需要创建一个按类型分组的父级列表,并为每个父级类型聚合所有状态为y的子项。

为此,请注意您不能折叠父级类型列表并保留父级结构(意味着Id也必须被折叠,我只取了第一个值,您可以选择不同的值)。否则,这应该是您要寻找的解决方案:

var result = collection.Parents.GroupBy(c => c.type)
    .Select(g => new Parent
                 {
                     type = g.Key,      
                     Id = g.Select(p => p.Id).FirstOrDefault(),
                     Children = g.SelectMany(p => p.Children
                              .Where(c => c.Status == Status.y)
                              .OrderByDescending(c => c.Date)).ToList()                           
                 }).ToList();

你的代码创建了新的父对象并填充了错误的子对象,这两个对象仍然包含它们所有的子对象。 - Enigmativity
Enigmativity,你是否尝试运行查询以检查子项是否正确?作者所说的“所有子对象”指的是状态为“Y”的所有子项。是的,该查询会创建新的父级,并且我已经解释了原因,而且并没有说明他需要相同的父级对象。 - mariovalens
我错过了Status.y的要求,但我已经通过代码解决了我的问题。你的还是不正确的。 - Enigmativity
你知道你的代码正在将每个类型的其他父母的子代添加到你创建的那些子代中吗?即使我们在是否包括“x”子代上有分歧,这肯定也不是正确的做法吧? - Enigmativity
是的,正如我所说,我们对这个问题有不同的理解。让作者决定他的意思。 - mariovalens
我会关注这个问题,让我们拭目以待。 - Enigmativity

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