Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Paging MongoDB query with C# drivers

I am using version 2.2 of MongoDB drivers for C#. I want to paginate a query : the response to the query must contain the items of the current page and the total count of items matching the query.

I want to do one query. With the mongo shell I can realize that as it :

var c = db.mycol.find({....}).skip(0).limit(10)
var total = c.count();
while (c.hasNext()) {
   print(tojson(c.next()));
}

But with the C# driver, I don't know how to do it with only one query.

var find = collection
  .Find(x => x.Valid == true)
  .Skip(0)
  .Limit(10);

var cursor = await find.ToCursorAsync(cancellationToken);
// How to get the count? There is no method in the IAsyncCursor interface.

Is it possible ? Some ideas ?

like image 746
gentiane Avatar asked Dec 29 '15 22:12

gentiane


People also ask

What is the use of the limit () and Skip () method?

The limit() function in MongoDB is used to specify the maximum number of results to be returned. Only one parameter is required for this function.to return the number of the desired result. Sometimes it is required to return a certain number of results after a certain number of documents. The skip() can do this job.

What is skip in MongoDB?

MongoDB's cursor object has a method called skip , which as per documentation and definition, controls where MongoDB begins returning results. Thus in combination with function limit, one can easily have paginated results.

How do I count documents in MongoDB?

In MongoDB, the countDocuments() method counts the number of documents that matches to the selection criteria. It returns a numeric value that represents the total number of documents that match the selection criteria. It takes two arguments first one is the selection criteria and other is optional.


2 Answers

You can't accomplish your task by sending to DB only one query. The common practice is following

var query = GetCollection().Find(x => x.Valid == true);
var totalTask = query.CountAsync();
var itemsTask = query.Skip(0).Limit(10).ToListAsync();
await Task.WhenAll(totalTask, itemsTask);
return new Page{ Total = totalTask.Result, Items = itemsTask.Result};
like image 128
rnofenko Avatar answered Oct 03 '22 06:10

rnofenko


Within MongoDB we have the ability to create a data processing pipeline that will get executed against our data once.

public class Person
{
    [BsonId]
    [BsonRepresentation(BsonType.String)]
    public string Id { get; set; }

    public string FirstName { get; set; }

    public string Surname { get; set; }
}

public class Peger
{
    public int Count { get; set; }

    public int Page { get; set; }

    public int Size { get; set; }

    public IEnumerable<Person> Items { get; set; }
}

class Program
{
    static async Task Main(string[] args)
    {
        var client = new MongoClient();
        var database = client.GetDatabase("pager_test");
        var collection = database.GetCollection<Person>(nameof(Person));

        int page = 1;
        int pageSize = 5;
        var results = await GetPagerResultAsync(page, pageSize, collection);
    }

    private static async Task<Peger> GetPagerResultAsync(int page, int pageSize, IMongoCollection<Person> collection)
    {
        // count facet, aggregation stage of count
        var countFacet = AggregateFacet.Create("countFacet",
            PipelineDefinition<Person, AggregateCountResult>.Create(new[]
            {
                PipelineStageDefinitionBuilder.Count<Person>()
            }));

        // data facet, we’ll use this to sort the data and do the skip and limiting of the results for the paging.
        var dataFacet = AggregateFacet.Create("dataFacet",
            PipelineDefinition<Person, Person>.Create(new[]
            {
                PipelineStageDefinitionBuilder.Sort(Builders<Person>.Sort.Ascending(x => x.Surname)),
                PipelineStageDefinitionBuilder.Skip<Person>((page - 1) * pageSize),
                PipelineStageDefinitionBuilder.Limit<Person>(pageSize),
            }));

        var filter = Builders<Person>.Filter.Empty;
        var aggregation = await collection.Aggregate()
            .Match(filter)
            .Facet(countFacet, dataFacet)
            .ToListAsync();

        var count = aggregation.First()
            .Facets.First(x => x.Name == "countFacet")
            .Output<AggregateCountResult>()
            ?.FirstOrDefault()
            ?.Count ?? 0;

        var data = aggregation.First()
            .Facets.First(x => x.Name == "dataFacet")
            .Output<Person>();

        return new Pager
        {
            Count = (int)count / pageSize,
            Size = pageSize,
            Page = page,
            Items = data
        };
    }
}
like image 35
Роман Тимохов Avatar answered Oct 03 '22 07:10

Роман Тимохов