Home > Software engineering >  MongoDB Pagination and Sorting With JOIN
MongoDB Pagination and Sorting With JOIN

Time:09-27

Assume I have 2 Type of collections

public class Student
{
  public string Id     {get;set;}
  public int    RollNo {get;set;}
  public string Name   {get;set;}
}
public class TotalMarks
{
  public string Id        {get;set;}
  public string StudentID {get;set;}
  public int    Mark     {get;set;}
}

And a DTO StudentMarksDto

public class StudentMarksDto
{
  public string RollNo {get;set;}
  public string Name {get;set;}
  public int    Mark     {get;set;}
}

The Way i am doing pagination now is as Follows

public async Task<List<StudentMarksDto>> GetPagedAsync(bool isDes, string sortproperty, int pageNo, int itemCount, string searchTearm)
{
    var studentCollection = dbContext.GetMongoDbCollection<Student>();
    var markCollection = dbContext.GetMongoDbCollection<Marks>();

    //Creating and Adding Filter
    var filterBuilder = Builders<Student>.Filter;
    var filter = filterBuilder.Empty;
    if (!string.IsNullOrWhiteSpace(searchTearm))
    {
        var sub = filterBuilder.Where(x => x.Name == searchTearm);
        filter &= sub;
    }

    //Asuming SortProp is not null and matches A FieldName
    var sortBuilder = Builders<Student>.Sort;
    var sort = isDes ? sortBuilder.Descending(sortproperty) : sortBuilder.Ascending(sortproperty);

    pageNo = pageNo < 1 ? 1 : pageNo;
    itemCount = itemCount < 1 ? 1 : itemCount;

    var studentList = await studentCollection.Find(filter).Sort(sort).Skip((pageNo - 1) * itemCount).Limit(itemCount).Project(x => ToDto(x)).ToListAsync();

    // BAD loop
    foreach (var student in studentList)
    {
        student.Mark = await markCollection.Find(x => x.StudentID == student.StudentID).Project(y => y.Mark).FirstOrDefaultAsync();
    }

    return studentList; 
}

Is there any way that I can use Aggregation to avoid the **BAD for loop**, and that enables me to sort based on students marks also?

CodePudding user response:

Yes, you can set up an aggregation pipeline that filters the data, performs the lookup, sorts and applies the paging boundaries.

In order for this to work, you need to define another class that holds the enumeration of marks:

public class StudentWithMarks
{
    public string Id { get; set; }
    public int RollNo { get; set; }
    public string Name { get; set; }

    public IEnumerable<TotalMarks> Marks { get; set; }
}

Then you can define the aggregation pipeline like this:

public async Task<List<StudentMarksDto>> GetPagedAsync(bool isDes, string sortproperty, int pageNo, int itemCount, string searchTearm)
{
    var studentCollection = dbContext.GetMongoDbCollection<Student>();
    var markCollection = dbContext.GetMongoDbCollection<Marks>();

    //Creating and Adding Filter
    var filterBuilder = Builders<Student>.Filter;
    var filter = filterBuilder.Empty;
    if (!string.IsNullOrWhiteSpace(searchTearm))
    {
        var sub = filterBuilder.Where(x => x.Name == searchTearm);
        filter &= sub;
    }

    //Asuming SortProp is not null and matches A FieldName
    var sortBuilder = Builders<StudentMarksDto>.Sort;
    var sort = isDes ? sortBuilder.Descending(sortproperty) : sortBuilder.Ascending(sortproperty);

    pageNo = pageNo < 1 ? 1 : pageNo;
    itemCount = itemCount < 1 ? 1 : itemCount;

    var pipeline = new EmptyPipelineDefinition<Student>()
        .Match(filter)
        .Lookup<Student, Student, TotalMarks, StudentWithMarks>(
            markCollection,
            x => x.Id,
            x => x.StudentID,
            x => x.Marks)
        .Project(x => new StudentMarksDto()
        {
            RollNo = x.RollNo.ToString(), 
            Name = x.Name, 
            Mark = x.Marks.First().Mark,
        })
        .Sort(sort)
        .Skip((pageNo - 1) * itemCount)
        .Limit(itemCount);
    var studentList = await (await studentCollection.AggregateAsync(pipeline)).ToListAsync();
    return studentList; 
}

Please note that in above sample, I have changed the type of the builder for the Sort. Also, you might need to tweak the type names for the lookup.

  • Related