EF Core 7.0 (PostgresSQL) build IQueryable with RowNumber concept

173 views Asked by At

I have a scenario where one Student (identified by StudentId) has many Courses. Each Course has StudentId(FK) and a Grade (simple text type). I want to build an IQueryable to pull 10 StudentIds (skipping first 5) by Course first Grade when sorted in descending order.

Tried below query but getting System.InvalidOperationException : The LINQ expression cannot be translated

    var studentQuery= context.Set<Student>().AsNoTracking().Where(x=>x.IsActive);
    var courseQuery= context.Set<Course>().AsNoTracking()
        .OrderByDescending(x => x.Grade)
        .GroupBy(x => x.StudentId)
        .SelectMany(x => x.Select((b, i) => new { b, rn = i + 1 }))
        .Where(x => x.rn == 1).Select(x => x.b);
    return studentQuery.Join
    (
        courseQuery,
        x => x.StudentId,
        y => y.StudentId,
        (a, b) => b
    )
    .Skip(5)
    .Take(10)
    .Select(x => x.StudentId);
1

There are 1 answers

0
sarvpk On BEST ANSWER

I was able to resolve the issue using the query below. Used GroupJoin (instead Join) to handle "students without any courses" use case. When a course has no Grade, used null coalescing operator to fix that. Such students will be last in the pagination. I was surprised that Max worked on text/string data type. Although this is working am sure there is a better way to fix this.

var studentQuery= context.Set<Student>().AsNoTracking().Where(x=>x.IsActive);   
var courseQuery= context.Set<Course>().AsNoTracking();

return studentQuery.GroupJoin
(
    courseQuery,
    x => x.StudentId,
    y => y.StudentId,
    (a, courses) => new { a.StudentId, courses }
)
.SelectMany(x => x.courses.DefaultIfEmpty(), (a, b) => new { a, b })
.GroupBy(x => x.a.StudentId)
.OrderByDescending(x => x.Max(y => y.b.Grade ?? string.Empty))
.Skip(5)
.Take(10)
.Select(x => x.Key);

Generated postgres SQL looks like

SELECT r.student_id
FROM student AS r
LEFT JOIN (
    SELECT r0.grade, r0.student_id
    FROM course AS r0
) AS t ON r.student_id = t.student_id
WHERE r.is_active
GROUP BY r.student_id
ORDER BY max(COALESCE(t.grade,'')) DESC
LIMIT 10 OFFSET 5