Query MongoDB - .NET SDK
On this page
- Use Cases
- Prerequisites
- Setup
- Example Data
- Mapping Classes
- Create Documents
- Insert a Single Document
- Insert Multiple Documents
- Read Documents
- Find a Single Document
- Find Multiple Documents
- Count Documents in the Collection
- Update Documents
- Update a Single Document
- Update Multiple Documents
- Upsert Documents
- Delete Documents
- Delete a Single Document
- Delete Multiple Documents
- Aggregate Documents
- Group Documents in a Collection
- Filter Documents
- Project Data
You can query data stored in MongoDB Atlas directly from your .NET application code by using the Realm .NET SDK's MongoClient with the Query API. Atlas App Services provides data access rules on collections to securely retrieve results based on the logged-in user or the content of each document.
The following actions enable access to a linked MongoDB Atlas cluster from a .NET application using the Realm .NET SDK.
Note
Each operation described on this page uses a query to
match certain documents in the collection upon which the operation
executes. When a filter matches multiple documents in a collection,
they are returned in an indeterminate order unless you
specify a sorting parameter. This means that if you do not specify
a sort for the findOne()
, updateOne()
, or deleteOne()
functions, your operation could match any document that matches the
query. For more information on sorting, see
cursor.sort().
Use Cases
There are a variety of reasons you might want to query a MongoDB data source. Working with data in your client via Atlas Device Sync is not always practical or possible. You might want to query MongoDB when:
The data set is large or the client device has constraints against loading the entire data set
You are retrieving documents that are not modeled in Realm
Your app needs to access collections that don't have strict schemas
A non-Realm service generates collections that you want to access
While not exhaustive, these are some common use cases for querying MongoDB directly.
Prerequisites
Before you can query MongoDB from your .NET application, you must set up MongoDB Data Access in your App Services App. To learn how to set up your backend App to let the Realm SDK query Atlas, refer to Set Up MongoDB Data Access in the App Services documentation.
Setup
To work directly with the data in your MongoDB Atlas cluster, you first
instantiate a MongoClient
object, passing in the name of the atlas service in your Realm app.
You then instantiate a
MongoClient.Database and a
MongoClient.Collection
for each collection you want to work with.
The following code uses the default "mongodb-atlas" Atlas service name and
creates a MongoClient.Collection
for the "plants" collection in the
"inventory" database:
mongoClient = user.GetMongoClient("mongodb-atlas"); dbPlantInventory = mongoClient.GetDatabase("inventory"); plantsCollection = dbPlantInventory.GetCollection<Plant>("plants");
Example Data
The examples on this page use the following MongoDB collection that describes various plants for sale in a chain of plant stores:
{ _id: ObjectId("5f87976b7b800b285345a8c4"), name: "venus flytrap", sunlight: "full", color: "white", type: "perennial", _partition: "Store 42" }, { _id: ObjectId("5f87976b7b800b285345a8c5"), name: "sweet basil", sunlight: "partial", color: "green", type: "annual", _partition: "Store 42" }, { _id: ObjectId("5f87976b7b800b285345a8c6"), name: "thai basil", sunlight: "partial", color: "green", type: "perennial", _partition: "Store 42" }, { _id: ObjectId("5f87976b7b800b285345a8c7"), name: "helianthus", sunlight: "full", color: "yellow", type: "annual", _partition: "Store 42" }, { _id: ObjectId("5f87976b7b800b285345a8c8"), name: "petunia", sunlight: "full", color: "purple", type: "annual", _partition: "Store 47" }
Mapping Classes
When working with objects in MongoDB, you should create .NET classes (POCOs) that
correspond to the BSON objects. This allows you to serialize and deserialize
the objects directly, rather than working with generic BsonDocument
objects.
In all of the examples on this page, we are using the following Plant
mapping
class for this purpose:
public partial class Plant : IRealmObject { [ ] public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); [ ] public string? Name { get; set; } [ ] [ ] public string? Sunlight { get; set; } [ ] [ ] public string? Color { get; set; } [ ] [ ] public string? Type { get; set; } [ ] public string? Partition { get; set; } } public enum Sunlight { Full, Partial } public enum PlantColor { White, Green, Yellow, Purple } public enum PlantType { Perennial, Annual }
Note
If you choose to provide custom constructors, you must declare a public constructor with no arguments.
For more information on using mapping classes, see Mapping Classes in the MongoDB .NET Driver documentation.
Create Documents
To create a document in the MongoDB datastore, you instantiate the mapping class, and pass the new object to InsertOneAsync(). You can also create multiple documents and insert them in a single call by using InsertManyAsync().
Insert a Single Document
You can insert a single document using InsertOneAsync().
The following snippet inserts a single document describing a "Venus Flytrap" plant into our "plants" collection:
var plant = new Plant { Name = "Venus Flytrap", Sunlight = Sunlight.Full.ToString(), Color = PlantColor.White.ToString(), Type = PlantType.Perennial.ToString(), Partition = "Store 42" }; var insertResult = await plantsCollection.InsertOneAsync(plant); var newId = insertResult.InsertedId;
Insert Multiple Documents
You can insert multiple documents at the same time by using InsertManyAsync().
The following snippet inserts four Plant
objects into the "plants" collection
by instantiating the objects, adding them to a List<Plant>
, and passing that
list to InsertManyAsync()
:
var sweetBasil = new Plant { Name = "Sweet Basil", Sunlight = Sunlight.Partial.ToString(), Color = PlantColor.Green.ToString(), Type = PlantType.Annual.ToString(), Partition = "Store 42" }; var thaiBasil = new Plant { Name = "Thai Basil", Sunlight = Sunlight.Partial.ToString(), Color = PlantColor.Green.ToString(), Type = PlantType.Perennial.ToString(), Partition = "Store 42" }; var helianthus = new Plant { Name = "Helianthus", Sunlight = Sunlight.Full.ToString(), Color = PlantColor.Yellow.ToString(), Type = PlantType.Annual.ToString(), Partition = "Store 42" }; var petunia = new Plant { Name = "Petunia", Sunlight = Sunlight.Full.ToString(), Color = PlantColor.Purple.ToString(), Type = PlantType.Annual.ToString(), Partition = "Store 47" }; var listofPlants = new List<Plant> { sweetBasil, thaiBasil, helianthus, petunia }; var insertResult = await plantsCollection.InsertManyAsync(listofPlants); var newIds = insertResult.InsertedIds;
Read Documents
To retrieve documents from the datastore, you create a BsonDocument
filter
that defines the properties you want to search on, and then pass that filter
to either
FindOneAsync()
or
FindAsync().
You can also get the count of all documents that match the filter by calling
CountAsync().
Find a Single Document
The following example shows how to find a plant where the "name" property is "petunia":
var petunia = await plantsCollection.FindOneAsync( new { name = "Petunia" }, null);
Find Multiple Documents
The following example shows how to find all plants where the "type" property is "perennial":
var allPerennials = await plantsCollection.FindAsync( new { type = PlantType.Perennial.ToString() }, new { name = 1 });
Important
We are using the third parameter of FindAsync()
, which specifies the
sort order. If you are querying for more than one document, you should include
the sort order to ensure consistent results.
Count Documents in the Collection
The following example returns a count of all plants in the collection:
var allPlants = await plantsCollection.CountAsync();
Update Documents
To update an existing document in the MongoDB datastore, you create a
BsonDocument
filter that defines the properties you want to search on, and
then create a second BsonDocument
that defines the properties you want to
change. If you are updating only one document, you pass both objects to
UpdateOneAsync().
If you want to bulk update multiple documents, you call
UpdateManyAsync().
Update a Single Document
The following code finds the plant whose "name" property is "petunia" and changes its "sunlight" property to "partial":
var updateResult = await plantsCollection.UpdateOneAsync( new { name = "Petunia" }, new BsonDocument("$set", new BsonDocument("sunlight", Sunlight.Partial.ToString())) );
Update Multiple Documents
The following code finds all plants with a "_partition" value of "store 47" and changes them all to "area 51":
var filter = new { _partition = "Store 47" }; var updateDoc = new BsonDocument("$set", new BsonDocument("_partition", "Area 51")); var updateResult = await plantsCollection.UpdateManyAsync( filter, updateDoc);
Upsert Documents
Both UpdateOneAsync() and UpdateManyAsync() have an optional Boolean property that specifies whether the update should be an upsert (that is, if the document doesn't exist, it should be created). By default, no upsert is performed.
The following example looks for a plant whose name
property is
"Pothos", type
property is "perennial", and sunlight
property is "full".
If a plant matches these criteria, the method updates the plant's _partition
value to "Store 42". If no plant exists in the collection with that name, the
method will create a new plant with all of the defined properties, including the
update.
var filter = new BsonDocument() .Add("name", "Pothos") .Add("type", PlantType.Perennial.ToString()) .Add("sunlight", Sunlight.Full.ToString()); var updateResult = await plantsCollection.UpdateOneAsync( filter, new BsonDocument("$set", new BsonDocument("_partition", "Store 42")), upsert: true); /* The upsert will create the following object: { "name": "pothos", "sunlight": "full", "type": "perennial", "_partition": "Store 42" } */
Delete Documents
The process for deleting documents is much the same as creating (or updating)
documents: you create a BsonDocument
that defines the properties you want to
match on, and then call either
DeleteOneAsync().
or
DeleteManyAsync().
Delete a Single Document
The following example deletes the first document it finds with a "name" property value of "Thai Basil":
var filter = new BsonDocument("name", "Thai Basil"); var deleteResult = await plantsCollection.DeleteOneAsync(filter);
Delete Multiple Documents
The following example deletes all documents that have a "type" property value of "annual":
var filter = new BsonDocument("type", PlantType.Annual); var deleteResult = await plantsCollection.DeleteManyAsync(filter);
Aggregate Documents
Aggregation operations run all documents in a collection through a series of data aggregation stages called an aggregation pipeline. Aggregation allows you to filter and transform documents, collect summary data about groups of related documents, and other complex data operations.
Aggregation operations accept an array of aggregation stages as input, and return a Task that resolves to a collection of documents processed by the pipeline.
Note
Compass provides a utility for building aggregation pipelines and exporting them to C# and other languages. For more information, see Aggregation Pipeline Builder.
Group Documents in a Collection
The .NET SDK supports aggregation on a collection with the AggregateAsync() method and its generic overload.
The following example groups all documents in the plants collection by
their type
value, aggregates a count of
the number of each type, and then sorts them in ascending order:
var groupStage = new BsonDocument("$group", new BsonDocument { { "_id", "$type" }, { "count", new BsonDocument("$sum", 1) } }); var sortStage = new BsonDocument("$sort", new BsonDocument("_id", 1)); var aggResult = await plantsCollection.AggregateAsync(groupStage, sortStage); foreach (var item in aggResult) { var id = item["_id"]; var count = item["count"]; Console.WriteLine($"Plant type: {id}; count: {count}"); }
The example above builds the pipeline with a series of nested BsonDocuments, which can get complicated to write and debug. If you are already familiar with the Query API, you can pass queries as a string the BsonDocument_Parse() method. The following example performs the same aggregation as the preceding example:
var groupStep = BsonDocument.Parse(@" { $group: { _id: '$type', count: { $sum: 1 } } } "); var sortStep = BsonDocument.Parse("{$sort: { _id: 1}}"); aggResult = await plantsCollection.AggregateAsync(groupStep, sortStep); foreach (var item in aggResult) { var id = item["_id"]; var count = item["count"]; Console.WriteLine($"Id: {id}, Count: {count}"); }
Filter Documents
You can use the $match stage to filter documents using standard MongoDB query syntax.
The following example shows how to filter documents when using Aggregation.
Since we know that this aggregation pipeline returns a collection of Plant
objects, we use the generic override of the
AggregateAsync()
method:
var matchStage = new BsonDocument("$match", new BsonDocument("type", new BsonDocument("$eq", PlantType.Perennial))); // Alternate approach using BsonDocument.Parse(...) matchStage = BsonDocument.Parse(@"{ $match: { type: { $eq: '" + PlantType.Perennial + @"' } }}"); var sortStage = BsonDocument.Parse("{$sort: { _id: 1}}"); var aggResult = await plantsCollection.AggregateAsync<Plant>(matchStage, sortStage); foreach (var plant in aggResult) { Console.WriteLine($"Plant Name: {plant.Name}, Color: {plant.Color}"); }
Project Data
You can use the $project stage to include or omit specific fields from documents or to calculate new fields using aggregation operators. Projections work in two ways:
Explicitly include fields with a value of 1. This has the side-effect of implicitly excluding all unspecified fields.
Implicitly exclude fields with a value of 0. This has the side-effect of implicitly including all unspecified fields.
These two methods of projection are mutually exclusive: if you explicitly include fields, you cannot explicitly exclude fields, and vice versa.
Note
The _id
field is a special case: it is always included in every
query unless explicitly specified otherwise. For this reason, you
can exclude the _id
field with a 0
value while simultaneously
including other fields, like _partition
, with a 1
. Only the
special case of exclusion of the _id
field allows both exclusion
and inclusion in one $project
stage.
The following example shows how to use project when using Aggregation. In this example, we are:
Excluding the "Id" property,
Including the "Partition", "Type", and "Name" properties,
Creating a new property called "storeNumber", which is built by splitting the _partition value on the whitespace and returning only the second part.
var projectStage = new BsonDocument("$project", new BsonDocument { { "_id", 0 }, { "_partition", 1 }, { "type", 1 }, { "name", 1 }, { "storeNumber", new BsonDocument("$arrayElemAt", new BsonArray { new BsonDocument("$split", new BsonArray { "$_partition", " " }), 1 }) } }); var sortStage = BsonDocument.Parse("{$sort: { storeNumber: 1}}"); var aggResult = await plantsCollection.AggregateAsync(projectStage, sortStage); foreach (var item in aggResult) { Console.WriteLine($"{item["name"]} is in store #{item["storeNumber"]}."); }
The following shows how you can also build the projectStage
by using the
BsonDocument.Parse()
method:
projectStage = BsonDocument.Parse(@" { _id:0, _partition: 1, type: 1, name: 1, storeNumber: { $arrayElemAt: [ { $split:[ '$_partition', ' ' ] }, 1 ] } }");