简体   繁体   中英

Linq group by parent property order by child

Basicly what i am trying to do is getting the parent grouped by Parent.type where their Child.type has status 'y' ordered by child.Date

As an example i created these classes:

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
}

What i want is a list of Parents of each type where the Child's Date is highest in value.

To get this example going i added some test data:

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)
                        }
                    }
                },
            }
        };

The result of the select should be a List containing 2 Parents. One for each type ( a and b ). Both of these still containing all of their Child objects.

Is there any simple solution for this ?

I tried something like:

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();

But that didn't end well.

=====UPDATE=====

Thanks to the example of Enigmativity this issue is solved. I made a slight modification to the linq query as the original content i am using is provided by Entity Framework (6). This meant for me that the selected Parent object was not containing any Children, so i had to select into a new object type (As we can't instantiate a new object of the type provided by the Entity Framework model) . Also my Parents were in another IEnumerable so I had to use a SelectMany on the Parents as well.

This resulted in something like this: (This won't work with the test classes though)

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();

If I've understood your requirement correctly you want to group all of the parents by type and then chose only one parent from each group based on that parent having a child with the highest date from all the children in that group.

Here's what I came up with:

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();

That gives me:

结果

Children can't really be filtered, ordered, etc. when the parents are the ones that queries/projections are being applied to.

You'll have to either:

  • Break the relationship and project to a different data structure (like a dictionary or IGrouping where the parent is the key and the sorted children are the value)
  • Imperatively Sort() or reassign Children in a loop without using LINQ
  • Write multiple-line lambdas where the second line is a side effect like sorting or assigning the children (not recommended)

Personally I think it's easier to project only the data that you need to a simplified representation rather than trying to persist the hierarchy all the way down.

My understanding of the question is that you need to create a list of parents grouped by type and aggregate all the children with status y for every parent type.

For that, please note that you cannot collapse the list by Parent type and keep the parent structure (meaning Id has to be collapsed as well, I've just taken the first value, it's up to you to choose different). Otherwise this should be the solution you are looking for:

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();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM