Introduction to MongoDb with .NET part 19: update operations in the MongoDb driver

Introduction

In the previous post we discussed how to insert new documents via the MongoDb .NET driver. We saw that it wasn’t particularly complicated to use the various Insert methods. We just need to provide the object to be inserted and that’s about it.

In this post we’ll look at how to update documents. We have already seen how to do updates via the Mongo shell and now it’s enough to find how they are represented in C#.

General

Recall that we divided up updates into two categories before: replacements where a matching document is replaced with a brand new one and “true” updates where the matching document is modified without being replaced. Keep in mind though that the _id field cannot be modified even if the document is being replaced.

Updates come in many forms in the driver but they can also be divided into the same two categories. Let’s look at them in turn.

Replacements

Replacements are represented by the following functions:

  • ReplaceOne: to replace a document with the FilterDefinition search syntax
  • ReplaceOne extension method: to replace a document with the Linq expression search syntax
  • ReplaceOneAsync: the awaitable version of ReplaceOne
  • ReplaceOneAsync extension method: the awaitable version of the ReplaceOne extension method
  • FindOneAndReplace: this operation finds a document and replaces atomically
  • FindOneAndReplaceAsync: the awaitable version of FindOneAndReplace

Let’s see an example for the ReplaceOne function with a FilterDefinition. We’ll also see at the same time how to use Upserts via the options that we can pass into the function. Recall that we inserted a couple of fake restaurants in the previous post. One was a Mexican restaurant with the name MexicanKing. Let’s replace it with something new:

ModelContext modelContext = ModelContext.Create(new ConfigFileConfigurationRepository(), new AppConfigConnectionStringRepository());
RestaurantDb mexicanReplacement = new RestaurantDb();
mexicanReplacement.Address = new RestaurantAddressDb()
{
	BuildingNr = "3/D",
	Coordinates = new double[] { 24.68, -100.9 },
	Street = "Mexico Street",
	ZipCode = 768324865
};
mexicanReplacement.Borough = "Somewhere in Mexico";
mexicanReplacement.Cuisine = "Mexican";
mexicanReplacement.Grades = new List<RestaurantGradeDb>()
{
	new RestaurantGradeDb() {Grade = "B", InsertedUtc = DateTime.UtcNow, Score = "10" },
	new RestaurantGradeDb() {Grade = "B", InsertedUtc = DateTime.UtcNow, Score = "4" }
};
mexicanReplacement.Id = 457656745;
mexicanReplacement.Name = "NewMexicanKing";
mexicanReplacement.MongoDbId = ObjectId.Parse("571fc8d290f5282a50778960");
ReplaceOneResult replaceOneResult = modelContext.Restaurants.ReplaceOne(Builders<RestaurantDb>.Filter.Eq(r => r.Name, "MexicanKing"), mexicanReplacement, new UpdateOptions() { IsUpsert = true });
Console.WriteLine(replaceOneResult.IsAcknowledged);
Console.WriteLine(replaceOneResult.MatchedCount);
Console.WriteLine(replaceOneResult.ModifiedCount);
Console.WriteLine(replaceOneResult.UpsertedId);

There are a couple of things to note here:

  • I knew of course beforehand that there will be a matching document which will be replaced. Therefore I had to provide the same object ID as the MexicanKing restraurant otherwise the driver will throw an exception saying the _id cannot be overwritten. Why is that? If we don’t provide an ID field in the replacement document then it will be created for us and the ReplaceOne function will “think” that we really want to change the object ID
  • Therefore the usage of ReplaceOne in this way is not too easy. In practice we would first search for an existing document, map its properties to the replacement document, modify the replacement document in code and then pass it to the ReplaceOne function. This way we don’t lose any object property and don’t make the driver angry either.
  • We get a ReplaceOneResult object back from the function which helps us check whether the replacement operation has succeeded: whether the operation has been acknowledged, how many documents were matched and modified – these can be 0 or 1 in this case – and if the upsert resulted in an insert we can read the ID of the inserted document. In this case this latter field will be empty obviously

The ReplaceOne extension method works in much the same way except that it accepts a LINQ expression for the search function. We’ve seen enough of those already so I won’t show an example here.

Let’s move on to the more exciting FindOneAndReplace method. Here comes an example with using the generic FindAndReplaceOptions object:

ModelContext modelContext = ModelContext.Create(new ConfigFileConfigurationRepository(), new AppConfigConnectionStringRepository());
RestaurantDb mexicanReplacement = new RestaurantDb();
mexicanReplacement.Address = new RestaurantAddressDb()
{
	BuildingNr = "4/D",
	Coordinates = new double[] { 24.68, -100.9 },
	Street = "New Mexico Street",
	ZipCode = 768324865
};
mexicanReplacement.Borough = "In the middle of Mexico";
mexicanReplacement.Cuisine = "Mexican";
mexicanReplacement.Grades = new List<RestaurantGradeDb>()
{
	new RestaurantGradeDb() {Grade = "B", InsertedUtc = DateTime.UtcNow, Score = "10" },
	new RestaurantGradeDb() {Grade = "B", InsertedUtc = DateTime.UtcNow, Score = "4" }
};
mexicanReplacement.Id = 457656745;
mexicanReplacement.Name = "BrandNewMexicanKing";
mexicanReplacement.MongoDbId = ObjectId.Parse("571fc8d290f5282a50778960");
		
RestaurantDb replaced = modelContext.Restaurants.FindOneAndReplace
	(Builders<RestaurantDb>.Filter.Eq(r => r.Name, "NewMexicanKing"), 
	mexicanReplacement, 
	new FindOneAndReplaceOptions<RestaurantDb, RestaurantDb>()
	{
		IsUpsert = true,
		ReturnDocument = ReturnDocument.After,
		Sort = Builders<RestaurantDb>.Sort.Descending(r => r.Name)
	});

As mentioned above this is an atomic operation which includes a search and a replace. You’ll recognise the first two parameters, i.e. the filter definition and the replacement document. The FindOneAndReplaceOptions object is a lot more interesting. Note that this is an optional input that defaults to null but it offers a couple of interesting options:

  • IsUpsert: we’re familiar with this by now
  • ReturnDocument: can be After or Before. This option specifies which document to return from the replacement operation: the one that was replaced, i.e. Before, or the one that replaces an existing document, i.e. After. This can be useful if we want to make extra checks after the replacement operation has returned
  • Sort: as the function name suggests we’ll find exactly one document even if there are many that match the search criteria. We have not much control over which document will be found by MongoDb but the Sort option offers some control at least. Sort accepts a sort definition we saw earlier and means that the function should replace the first document in a given sort order. In the example the matching documents will be sorted by name in a descending order and the first document in that sort order will be replaced

You’ll notice that FindOneAndReplaceOptions is generic and accepts the document type and the type it will be projected into. Most often the two types will be the same but occasionally you might want to map a DB document type into a different one.

Update

Updates are represented by a list of functions in the driver:

  • UpdateOne: the update “brother” of ReplaceOne. It accepts a filter definition and an update definition
  • UpdateOne extension method: same as UpdateOne but with LINQ expressions to filter the documents
  • UpdateOneAsync: the async version of UpdateOne
  • UpdateMany: same as UpdateOne but it updates all the matching documents
  • UpdateMany extension: same as UpdateMany but with LINQ expressions
  • UpdateManyAsync: the awaitable version of UpdateMany
  • UpdateManyAsync extension: the awaitable version of the UpdateMany extension method
  • FindOneAndUpdate: same as FindOneAndReplace but with an update definition object
  • FindOneAndUpdateAsync: the awaitable version of FindOneAndUpdate

So we have a long list of options here. They work in much the same way as their replacement counterparts. The update definition builder is new and it exposes the update operators we looked at before, such as $set, $inc, $addToSet. It also exposes a Combine method we saw before and accepts a list of update definitions. This helps us build multiple update operations on a single document.

Let’s see an example of using UpdateOne where we set a new value to the Borough property and push a new grade to the grades collection:

ModelContext modelContext = ModelContext.Create(new ConfigFileConfigurationRepository(), new AppConfigConnectionStringRepository());
var updateDefinitionBorough = Builders<RestaurantDb>.Update.Set(r => r.Borough, "New Borough");
var updateDefinitionGrades = Builders<RestaurantDb>.Update.Push(r => r.Grades, new RestaurantGradeDb() { Grade = "A", InsertedUtc = DateTime.UtcNow, Score = "6" });
var combinedUpdateDefinition = Builders<RestaurantDb>.Update.Combine(updateDefinitionBorough, updateDefinitionGrades);
UpdateResult updateResult = modelContext.Restaurants.UpdateOne(r => r.Name == "BrandNewMexicanKing", combinedUpdateDefinition, new UpdateOptions() { IsUpsert = true });		

You’ll recognise the Builder by now. It exposes an Update property to build update definitions. If you type “Update.” in Visual Studio then IntelliSense will show a number of options that are familiar from before like $set and $push, you’ll find all of them there. Next we combine the update definitions so that both updates will be applied to the matched document. Finally we call UpdateOne and this time with a LINQ expression search. The UpdateResult object exposes the exact same properties as ReplaceOneResult above so I won’t go through that again.

The UpdateMany functions are applied in the exact same manner. The difference is of course that the update definitions will be applied to all matching documents.

Also, FindOneAndUpdate is almost identical to FindOneAndReplace except that it accepts a filter definition, an update definition and a FindOneAndUpdateOptions object. This latter has the same properties as FindOneAndReplaceOptions so you can easily figure out how to use it in code.

In the next post we’ll look at deletions.

You can view all posts related to data storage on this blog here.

Advertisements

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

2 Responses to Introduction to MongoDb with .NET part 19: update operations in the MongoDb driver

  1. John Hurrell says:

    I think it’s important to note that in order for the ReplaceOne() example to work, you need to first determine your specific instance of the _id/MongoDbID value and replace the example you provided. Without doing so will result in an exception stating that _id cannot be changed.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

ultimatemindsettoday

A great WordPress.com site

iReadable { }

.NET Tips & Tricks

Robin Sedlaczek's Blog

Developer on Microsoft Technologies

HarsH ReaLiTy

A Good Blog is Hard to Find

Softwarearchitektur in der Praxis

Wissenswertes zu Webentwicklung, Domain-Driven Design und Microservices

the software architecture

thoughts, ideas, diagrams,enterprise code, design pattern , solution designs

Technology Talks

on Microsoft technologies, Web, Android and others

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

Anything around ASP.NET MVC,WEB API, WCF, Entity Framework & AngularJS

Cyber Matters

Bite-size insight on Cyber Security for the not too technical.

Guru N Guns's

OneSolution To dOTnET.

Johnny Zraiby

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

%d bloggers like this: