MongoDB in .NET part 6: updating documents

Introduction

In the previous part of this series we looked at write concerns, write acknowledgements and replacing documents with the Save method. We said that Save() works as an Insert if the document ID is not found. If the ID exists then the whole document is replaced with a new one.

The Update method

It is possible to update an existing document without replacing it using the Update method of MongoCollection. The Update method can be finetuned using its overloaded versions. The Save() method sends a completely new document to the Mongo server for replacement. The Update method instead sends an update document to the server containing one or more modification instructions. Update is used by the Save method behind the scenes. Our Save method in CarsController…

CarRentalContext.Cars.Save(modifiedCar);	

…can be mimicked by Update as follows:

CarRentalContext.Cars.Update(Query.EQ("_id", ObjectId.Parse(updateCarViewModel.Id)), Update.Replace(modifiedCar), UpdateFlags.Upsert);	

“Update” is a builder which helps build Update instructions – update documents – to the Mongo server. Notice the “Replace” update instruction which – as you may have guessed – will replace any existing document with a matching ID with the new one. Then we have the UpdateFlag where we specify an Upsert operation which we are familiar with by now. Skipping this flag would mean that a document is only updated if it exists by the given ID.

The Update builder contains a lot of operators. The operators return an IMongoUpdate object which represents a modification document and can be used in the Update method as a parameter. Just type “Update.” in Visual Studio and IntelliSense will show you about 20 different ones. Examples:

  • Inc: increments a numeric field – int, double, long – by a specified value
  • Rename: change the name of a field, e.g. from “Make” to “Type” in our Car domain object
  • Set: change the value of a field or insert it if it doesn’t exist, e.g. change 12 to 15 of the field DailyRentalFee
  • Unset: remove a field
  • Push: an array operation which allows us to append a value to an array
  • PopFirst and PopLast: removes the first or the last element of an array
  • Pull: remove specific elements from an array
  • AddToSet: add item to an array if it doesn’t exist

Some array operators have versions with “Each” in the method name. They allow to pass in several values e.g. in the AddToSetEach method. These operators are all fluent ones, i.e. you can chain them together to create a composite IMongoUpdate document.

“Update” is not the only way to build update documents. The following statements are all equivalent:

Update.Set("price", 1);
new UpdateBuilder().Set("price", 1);
Update<Car>.Set(c => c.DailyRentalFee, 2);
new UpdateBuilder<Car>().Set(c => c.DailyRentalFee, 3);

You will probably want to use the strongly typed versions to avoid hard coded string values.

So if you want to specifically update the rental fee of a Car object you can write as follows:

CarRentalContext.Cars.Update(Query.EQ("_id", ObjectId.Parse(updateCarViewModel.Id)), Update<Car>.Set(c => c.DailyRentalFee, 12));

By default the Update method will only update the first document that matches the query. If you want all documents to be updated that match the query then the Multi flag must be given. To change the fee of all Ford cars you can write as follows:

CarRentalContext.Cars.Update(Query.EQ("Make", "Ford"), Update<Car>.Set(c => c.DailyRentalFee, 2), UpdateFlags.Multi);

How to update then?

We’ve now seen two ways of updating a document: Save and Update. Here are some considerations:

  • Save is much more compact: you type less, it’s easier to test, is more robust than the Update equivalent
  • Update results in a more procedural style of code whereas Save is more OOP
  • Replacing a document has more overhead than modifying it, so Update performs better. The Save method fetches a document, modifies it and puts it in place of the original
  • While the Save method performs these steps we might run into concurrency issues. To exclude the possibility of data corruption during an update you may want to go for atomic updates, e.g. the FindAndModify method
  • Update allows for some very fine-grained modifications. They can be very useful in a data migration scenario, especially the Multi updates

We mentioned the FindAndModify atomic method above. It returns a FindAndModifyResult and accepts a FindAndModifyArgs object. Example:

FindAndModifyArgs args = new FindAndModifyArgs()
{
	Query = Query.EQ("_id", ObjectId.Parse(updateCarViewModel.Id))
	,Update = Update<Car>.Set(c => c.DailyRentalFee, 3)
	,Upsert = false
	,SortBy = SortBy<Car>.Ascending(c => c.Id)
	,VersionReturned = FindAndModifyDocumentVersion.Original
};
FindAndModifyResult res = CarRentalContext.Cars.FindAndModify(args);

You’ll recognise the Query, Upsert and Update properties. FindAndModify always modifies a single document. If there are more matching documents then the SortBy property can be used to determine which document will be updated: first or last. In the above example we’re querying on the ID field so this shouldn’t be an issue. The FindAndModifyResult return object includes a ModifiedDocument property of type BsonDocument. It represents the modified document but the exact representation depends on the VersionReturned argument. If it’s set to “Original” then ModifiedDocument will show the document before the modification. If it’s set to “Modified” then the modified document will be returned. The point is that if you run the Update method then and then query for the same document then someone else might have modified the same document. With FindAndModify you can avoid this as it can return the document in the way that you have modified it. The BsonDocument can be deserialised with the GetModifiedDocumentAs method of the FindAndModifyResult object.

Miscellaneous

  • A document is limited to 16MB – this can hold a very large domain. However, always consider how large your domain can grow especially with nested arrays
  • Atomic updates to documents are atomic per document. If you batch update several documents then each document will be updated atomically one by one but the whole operation is not atomic, i.e. we have no transactions
  • There’s no built-in mechanism to check for concurrent access to a document. The EntityFramework object context ensures that each operation is carried out in a thread-safe manner. There’s no equivalent of this context object in MongoDb so it’s possible that while you’re updating a document someone else deletes it. Or you change the rental fee to 2 and someone else updates it to 3 at the same time. The update to be processed last will win. However, this is nothing new – we could easily fill a whole textbook with concurrency issues in databases

In the next post we’ll look at how to delete documents.

View the posts related to data storage here.

Advertisements

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

One Response to MongoDB in .NET part 6: updating documents

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: