Counting Documents

The CountAsync method can be used to count all the documents matching a particular filter.

var count = await collection.CountAsync(new BsonDocument("x", 10));

// or

var count = await collection.CountAsync(x => x.Age > 20);

Counting all the documents in a collection requires an empty filter:

var count = await collection.CountAsync(new BsonDocument());

Finding Documents

Finding all the documents in a collection is done with an empty filter and the method Find. Once we have a cursor (of type IAsyncCursor<TDocument>), we can iterate it like we manually iterate an IEnumerable<TDocument>.

var filter = new BsonDocument();
using (var cursor = await collection.Find(filter).ToCursorAsync())
{
    while (await cursor.MoveNextAsync())
    {
        foreach (var doc in cursor.Current)
        {
            // do something with the documents
        }
    }
}

Note
It is imperative that the cursor get disposed once you are finished with it to ensure that resources on the server are cleaned up.

Some options are available in the optional FindOptions parameter such as setting maxTimeMS, a batch size, or a comment. Others are available as part of the fluent interface such as skip, limit, and sort.

var filter = new BsonDocument();
var options = new FindOptions
{
    MaxTime = TimeSpan.FromMilliseconds(20)
};
using (var cursor = await collection.Find(filter, options).Skip(10).Limit(20).ToCursorAsync())
{
    // etc...
}

Note
The order of the chained methods is unimportant. Limit followed by Skip is the same as Skip followed by Limit. In addition, specifying a method multiple times will result in the last one winning.

Iteration

Other methods of iteration besides using a cursor directly are available.

First, ToListAsync is available. This is useful when the list will be small or you simply need them all as a list. If you are returning a large number of documents, then memory should be considered a factor.

var list = await collection.Find(filter)
    .Skip(10)
    .ToListAsync();

Second, ForEachAsync is available. This method is useful when you just need to process each document and don’t need to keep them around.

await collection.Find(filter)
    .Skip(10)
    .ForEachAsync(doc => Console.WriteLine(doc));

To avoid blocking while processing each document you can use an async lambda:

await collection.Find(filter)
    .Skip(10)
    .ForEachAsync(async (doc) => await Console.WriteLineAsync(doc));

Note
These iteration methods don’t require you to dispose of the cursor. That will be handled for you automatically.

Single Results

When you only want to find one document, the FirstAsync, FirstOrDefaultAsync, SingleAsync, and SingleOrDefaultAsync methods are available.

var result = await collection.Find(filter)
    .Skip(10)
    .FirstOrDefaultAsync();

Aggregation

MongoDB offers the aggregation framework which can be accessed via the Aggregate method. The result type is IAggregateFluent and provides access to a fluent API to build up an aggregation pipeline.

The first example from MongoDB’s documentation is done in a type-safe manner below:

[BsonIgnoreExtraElements]
class ZipEntry
{
    [BsonId]
    public string Zip { get; set; }

    [BsonElement("city")]
    public string City { get; set; }

    [BsonElement("state")]
    public string State { get; set; }

    [BsonElement("pop")]
    public int Population { get; set; }
}

var results = await db.GetCollection<ZipEntry>.Aggregate()
    .Group(x => x.State, g => new { State = g.Key, TotalPopulation = g.Sum(x => x.Population) })
    .Match(x => x.TotalPopulation > 20000)
    .ToListAsync();

This will result in the following aggregation pipeline getting sent to the server:

[{ group: { _id: '$state', TotalPopulation: { $sum : '$pop' } } },
{ $match: { TotalPopulation: { $gt: 20000 } } }]

Note
You can call ToString on the pipeline to see what would be sent to the server.

More samples are located in the [source](https://github.com/mongodb/mongo-csharp-driver/blob/master/src/MongoDB.Driver.Tests/Samples/AggregationSample.cs .

Stage Operators

All the stage operators are supported, however some of them must use the AppendStage method due to lack of support for certain projections in the language.

important
Unlike Find, the order that stages are defined in matters. Skip(10).Limit(20) is not the same as Limit(20).Skip(10).

$project

A $project is defined using the Project method and its overloads. Unlike in Find, an aggregate projection is not executed client-side and must be fully translatable to the server’s supported expressions. See expressions for more detail about the expressions available inside a $project.

Project(x => new { Name = x.FirstName + " " + x.LastName });
{ $project: { Name: { $concat: ['$FirstName', ' ', '$LastName'] } } }

Note
In an aggregation framework projection, a new type, either anonymous or named, must be used.

$match

A $match stage is defined using the Match method and its overloads. It follows the same requirements as that of Find.

Match(x => x.Age > 21);
{ $match: { Age: { $gt: 21 } } }

$redact

There is no method defined for a $redact stage. However, it can be added using AppendStage.

$limit

A $limit stage is defined using the Limit method.

Limit(20);
{ $limit: 20 }

$skip

A $skip stage is defined using the Skip method.

Skip(20);
{ $skip: 20 }

$unwind

An $unwind stage is defined using the Unwind method and its overloads. Because $unwind is a type of projection, you must provide a return type, although not specifying one will use the overload that projects into a BsonDocument.

Unwind(x => x.ArrayFieldToUnwind);
{ $unwind: 'ArrayFieldToUnwind' }

$group

A $group stage is defined using the Group method and its overloads. Because $unwind is a type of projection, you must provide a return type. The most useful of the overloads is where two lambda expressions are expressed, the first for the key and the second for the grouping. See expressions for more detail about the expressions available inside a $group.

Group(x => x.Name, g => new { Name = g.Key, AverageAge = g.Average(x = x.Age) });
{ $group: { _id: '$Name', AverageAge: { $avg: '$Age'} } }

As in project, it is required that the result of the grouping be a new type, either anonymous or named. If the Key of the grouping is not used, an _id will still be inserted as this is required by the $group operator.

$sort

A $sort stage is defined using the Sort method. However, SortBy, SortByDescending, ThenBy, and ThenByDescending are also available.

SortBy(x => x.LastName).ThenByDescending(x => x.Age);
{ $sort: { LastName: 1, Age: -1 } }

$geoNear

There is no method defined for a $geoNear stage. However, it can be added using AppendStage.

$out

A $out stage is defined using the OutAsync method. It must be the final stage and it triggers execution of the operation.

OutAsync("myNewCollection");
{ $out: 'myNewCollection' }