- Reference
- Driver
- Reading and Writing
- Reading
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 callToString
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
UnlikeFind
, 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' }