Search code examples
c#linqgroup-bysubqueryef-core-5.0

GroupBy in subquery with MAX() in C#


I Have this code. It works fine but when I have two same maximal values it appear 2 times. So I need to use OrderBy. But I dont know how. Thanks for any help.

IQueryable<PerformanceRealization> pr = _context.PerformanceRealization
    .Where(u => u.Deadline == _context.PerformanceRealization
        .Where(x => x.GroupRealizationId == u.GroupRealizationId)
        .Max(x => x.Deadline)
    )
    .Select(u => u);

Here is the SQL code with GROUP BY

SELECT PR.GroupRealizationId
FROM Stores.PerformanceRealization PR
LEFT JOIN Stores.GroupRealization ON Stores.GroupRealization.Id = PR.GroupRealizationId
WHERE PR.Deadline = (SELECT MAX(Deadline) 
                     FROM Stores.PerformanceRealization PR2 
                     WHERE PR.GroupRealizationId = PR2.GroupRealizationId)
GROUP BY PR.GroupRealizationId

Solution

  • You can select the first object from the group

    IQueryable<PerformanceRealization> pr2 = pr
        .GroupBy(x => x.GroupRealizationId)
        .Select(g => g.First());
    

    If you need a specific object from the group, then you can order by another column

    IQueryable<PerformanceRealization> pr2 = pr
        .GroupBy(x => x.GroupRealizationId)
        .Select(g => g.OrderBy(x => x.SomeColumn).First());
    

    for SomeColumn having the smallest value. For the greatest value, use OderByDescending instead.

    Of course, you can integrate this approach into the first query:

    IQueryable<PerformanceRealization> pr = _context.PerformanceRealization
        .Where(u => u.Deadline == _context.PerformanceRealization
            .Where(x => x.GroupRealizationId == u.GroupRealizationId)
            .Max(x => x.Deadline)
        )
        .GroupBy(x => x.GroupRealizationId)
        .Select(g => g.OrderBy(x => x.SomeColumn).First());
    

    Note, you don't need to have a Select at the end like .Select(u => u). Since it has no effect, you can just drop it.


    If your EF Core version cannot handle it (as revealed in a comment), then transition to LINQ-to-Objects with AsEnumerable(), but do the filtering in EF Core to minimize the number of records sent to the front-end:

    IQueryable<PerformanceRealization> pr = _context.PerformanceRealization
        .Where(u => u.Deadline == _context.PerformanceRealization
            .Where(x => x.GroupRealizationId == u.GroupRealizationId)
            .Max(x => x.Deadline)
        )
        .AsEnumerable() // <===== transition from LINQ-to-EF-Core to LINQ-to-Objects
        .GroupBy(x => x.GroupRealizationId)
        .Select(g => g.OrderBy(x => x.SomeColumn).First());