Keyset(Seek) Pagination and MongoDb Driver C#

Viewed 291

I am trying to paginate my MongoDB results by means of keyset/seek pagination using the C# MongoDB Driver. I am NOT trying to use offset pagination.

Essentially, I have a database containing many tags. The structure of a tag is as follows:

{
        "_id" : ObjectId("5fc5cdb5d6ee08681d32d3ba"),
        "UsageCount" : NumberLong(123456),
        "Name" : "technology"
}

I want to fist sort my database in descending order by the UsageCount property. This property is not unique. Many tags can have the same value for UsageCount. Example results would be:

[
        {
                "_id" : ObjectId("5fc5cdb5d6ee08681d32d3ba"),
                "UsageCount" : NumberLong(9000),
                "Name" : "technology"
        },
        {
                "_id" : ObjectId("5fc5cdb5d6ee08681d32d3b1"),
                "UsageCount" : NumberLong(8000),
                "Name" : "technology"
        },
        {
                "_id" : ObjectId("5fc5cdb5d6ee08681d32d3b2"),
                "UsageCount" : NumberLong(7000),
                "Name" : "technology"
        },
        ...
]

Example C# code:

// Get the mongo queryable
IMongoQueryable<Tag> tags = _mongoDbService.GetCollection<Tag>(nameof(Tag));

// Order the database descending by usage count
IOrderedMongoQueryable<Tag> orderedTags = tags.OrderByDescending(tag => tag.UsageCount);

// Take the tag after the tag with ID 5fc5cdb5d6ee08681d32d3b1
List<Tag> results = orderedTags.After("5fc5cdb5d6ee08681d32d3b1").Take(1).ToListAsync().ConfigureAwait(false);

The results of this code should be a list containing only the last tag from the example above:

{
    "_id" : ObjectId("5fc5cdb5d6ee08681d32d3b2"),
    "UsageCount" : NumberLong(7000),
    "Name" : "technology"
}

The problem here is that the After method in the above code doesn't exist. I am looking to accomplish the same result be any means available.

Some possible solutions might be to

  1. First find the index of 5fc5cdb5d6ee08681d32d3b2 in the sorted document list and then call skip(index). This requires two queries, which isn't quite as nice but more importantly, I don't know if I can find the index either?
  2. Find a method equivalent to After?
  3. Others?

I have seen this question asked before with no solutions.

1 Answers

you can get the desired result using an aggregation pipeline like this:

db.Tags.aggregate(
[
    {
        $sort: { UsageCount: -1 }
    },
    {
        $group: {
            _id: null,
            tags: { $push: "$$ROOT" }
        }
    },
    {
        $set: {
            tags: {
                $slice: [
                    "$tags",
                    { $add: [{ $indexOfArray: ["$tags._id", ObjectId("5fc5cdb5d6ee08681d32d3b1")] }, 1] },
                    { $size: "$tags" }]
            }
        }
    },
    {
        $unwind: "$tags"
    },
    {
        $replaceWith: "$tags"
    }
]
)

https://mongoplayground.net/p/uqmxl7cBCMA

however, it will be quite difficult (if not impossible) to translate this into a strongly-typed c# query. have a look at this library for an alternative way to run complex queries like the above.

Related