Docs Menu

Docs HomeDevelop ApplicationsAtlas Device SDKs

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().

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 creating or updating custom user data

  • 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.

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.

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");

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" }

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
{
[BsonElement("_id")]
public ObjectId Id { get; set; } = ObjectId.GenerateNewId();
[BsonElement("name")]
public string? Name { get; set; }
[BsonElement("sunlight")]
[BsonRepresentation(BsonType.String)]
public string? Sunlight { get; set; }
[BsonElement("color")]
[BsonRepresentation(BsonType.String)]
public string? Color { get; set; }
[BsonElement("type")]
[BsonRepresentation(BsonType.String)]
public string? Type { get; set; }
[BsonElement("_partition")]
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.

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().

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;

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;

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().

The following example shows how to find a plant where the "name" property is "petunia":

var petunia = await plantsCollection.FindOneAsync(
new { name = "Petunia" },
null);

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.

The following example returns a count of all plants in the collection:

var allPlants = await plantsCollection.CountAsync();

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().

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()))
);

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);

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"
}
*/

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().

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);

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);

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.

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}");
}

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}");
}

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:

  1. Excluding the "Id" property,

  2. Including the "Partition", "Type", and "Name" properties,

  3. 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 ]
}
}");
← Call a Function - .NET SDK