- Reference
- Driver
- Reading and Writing
- Reading
Counting Documents
The CountDocuments
and CountDocumentsAsync
methods can be used to count all the documents matching a particular filter.
var count = collection.CountDocuments(new BsonDocument("x", 10));
// or
var count = collection.CountDocuments(x => x.Age > 20);
var count = await collection.CountDocumentsAsync(new BsonDocument("x", 10));
// or
var count = await collection.CountDocumentsAsync(x => x.Age > 20);
Counting all the documents in a collection requires an empty filter:
var count = collection.CountDocuments(new BsonDocument());
var count = await collection.CountDocumentsAsync(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 = collection.Find(filter).ToCursor())
{
while (cursor.MoveNext())
{
foreach (var doc in cursor.Current)
{
// do something with the documents
}
}
}
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 = collection.Find(filter, options).Skip(10).Limit(20).ToCursor())
{
// etc...
}
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, the ToList
and ToListAsync
methods are available. These methods are 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 = collection.Find(filter)
.Skip(10)
.ToList();
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 with ForEachAsync
:
await collection.Find(filter)
.Skip(10)
.ForEachAsync(async (doc) => await Console.WriteLineAsync(doc));
When using the synchronous API you can use the C# foreach statement to iterate over the documents in a cursor.
var cursor = collection.Find(filter)
.Skip(10)
.ToCursor();
foreach (var doc in cursor.ToEnumerable())
{
Console.WriteLine(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 First
, FirstOrDefault
, Single
, SingleOrDefault
methods, and their asynchronous counterparts FirstAsync
, FirstOrDefaultAsync
, SingleAsync
, and SingleOrDefaultAsync
are available.
var result = collection.Find(filter)
.Skip(10)
.FirstOrDefault();
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 = db.GetCollection<ZipEntry>.Aggregate()
.Group(x => x.State, g => new { State = g.Key, TotalPopulation = g.Sum(x => x.Population) })
.Match(x => x.TotalPopulation > 20000)
.ToList();
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.
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 Out
or OutAsync
methods. It must be the final stage and it triggers execution of the operation.
Out("myNewCollection");
await OutAsync("myNewCollection");
{ $out: 'myNewCollection' }