MongoDB in .NET part 5: WriteConcerns and replacing documents

Introduction

In the previous part of this series we successfully inserted a new Car object in our database. We also managed to read all items from the database and show them in a table. We’ll continue looking at MongoDb operations.

We’ll keep working on the same demo as before so have it ready in Visual Studio.

WriteConcern

Recall that we inserted a new Car object via the MongoDb Insert method of the MongoCollection object. It returns a WriteConcernResult object that we haven’t bothered with. In the Create action method of the CarsController controller modify…

CarRentalContext.Cars.Insert(car);

…to…

WriteConcernResult writeResult = CarRentalContext.Cars.Insert(car);
bool ok = writeResult.Ok;

Insert a breakpoint within the action method, start the app, navigate to /cars/create and insert a new Car object. When the code execution stops inspect the “ok” boolean value in Visual Studio. It should be true, meaning that the write operation has succeeded. We could read this result because by default the driver will wait for an acknowledgement if some CRUD operation is carried out through the MongoClient object. There’s one more level to enhance durability – you can wait for a journal commit acknowledgement from the database. You can achieve this by either extending the connection string to…

mongodb://localhost/?journal=true

…or in code as follows:

String mongoHost = ConfigurationManager.ConnectionStrings["CarRentalConnectionString"].ConnectionString;
MongoClientSettings settings =
	MongoClientSettings.FromUrl(new MongoUrl(mongoHost));	
settings.WriteConcern.Journal = true;
_mongoClient = new MongoClient(settings);

The WriteConcern property has at least one more important property you should know about: W. That’s right, a property called W. It is of type WValue and is represented by an integer. Say you have a cluster of MongoDb databases – a replica set – with one primary server for writes and 1 or more secondary servers for reads. The write operation will be propagated to all secondary servers in a matter of milliseconds. This is what you often see in real life database environments to enhance durability and data availability – if one server dies then you still have at least one more for reads and writes. The property W indicates the number of nodes in the cluster that must acknowledge the write – or update – operation. A similar mechanism from SQL Server is AlwaysOn. WriteConcern currently has 4 predefined values:

settings.WriteConcern = WriteConcern.W1;
settings.WriteConcern = WriteConcern.W2;
settings.WriteConcern = WriteConcern.W3;
settings.WriteConcern = WriteConcern.W4;

W1 means at least 1 node has to acknowledge the modification. You understand the rest. There is a special property where the majority of the nodes must acknowledge the operation:

settings.WriteConcern = WriteConcern.WMajority;

There’s another special value if you want to skip write acknowledgements altogether:

settings.WriteConcern = WriteConcern.Unacknowledged;

Update the CarRentalContext constructor to the following:

String mongoHost = ConfigurationManager.ConnectionStrings["CarRentalConnectionString"].ConnectionString;
MongoClientSettings settings =
MongoClientSettings.FromUrl(new MongoUrl(mongoHost));
settings.WriteConcern = WriteConcern.Unacknowledged;
_mongoClient = new MongoClient(settings);			
_mongoServer = _mongoClient.GetServer();			
_mongoDatabase = _mongoServer.GetDatabase(ConfigurationManager.AppSettings["CarRentalDatabaseName"]);

Run the app and try to enter a new car. Press Create and then you should get a null pointer exception here:

bool ok = writeResult.Ok;

The writeResult object will be null as we didn’t want any acknowledgement. This is called fire-and-forget.

Why would you want to relax the acknowledgment mechanism? I can give you an example from my work. In one of our projects we monitor some real-time performance statistics during load testing of a web site and show it in various graphs. The process is very “insert” intensive with lots of inserts during the test. We don’t really care if a couple of data points are lost here and there as there are always new ones coming in. So to save time and get better performance we relax all types of write acknowledgment. Also, the stats are only interesting during the live testing so we’re not much concerned with durability either. You can even set the acknowledgement policy on the level of the operation, e.g.:

CarRentalContext.Cars.Insert(car, WriteConcern.Unacknowledged);

…or on the collection level:

public MongoCollection<Car> Cars
{
	get
	{
		return CarRentalDatabase.GetCollection<Car>("cars", WriteConcern.W1);
	}
}

However, for most types of data in a database you’ll want to turn on write acknowledgments. Change the CarRentalContext to the default write concern:

public CarRentalContext()
{
	String mongoHost = ConfigurationManager.ConnectionStrings["CarRentalConnectionString"].ConnectionString;
	MongoClientSettings settings =
		MongoClientSettings.FromUrl(new MongoUrl(mongoHost));			
	_mongoClient = new MongoClient(settings);			
	_mongoServer = _mongoClient.GetServer();			
	_mongoDatabase = _mongoServer.GetDatabase(ConfigurationManager.AppSettings["CarRentalDatabaseName"]);
}

Updates

Updates can be performed in 2 ways:

  • Replace an existing document with a new one
  • Modify an existing document without removing it

The Save() method will replace an existing document. Save() can also act as an Insert method: if there’s no document with the specified ID or no ID is provided at all then Save will create a new document. This is known as an upsert, i.e. update or insert. Save() returns a WriteConcernResult like Insert. There’s also another method called Update which Save calls upon internally. However, Save is more concise and saves you some typing.

The View for /cars created a table for us to show the cars. It has also prepared some links to update a record – check the Edit link. Don’t click on it yet as have no action method or controller for it.

In CarsController add the following Edit stub:

[HttpGet]
public ActionResult Edit(string id)
{

}

We’ll need to find the car with the selected ID and retrieve it from the database. To find an object by id you can use the FindOneById method on the collection like this:

[HttpGet]
public ActionResult Edit(string id)
{
      Car car = CarRentalContext.Cars.FindOneById(new ObjectId(id));
}

You can build very sophisticated search criteria using the IMongoQuery builder. We can write the above query as follows:

IMongoQuery findByIdQuery = Query.EQ("_id", new ObjectId(id));
Car car = CarRentalContext.Cars.FindOne(findByIdQuery);

Query is used to build IMongoQuery objects. Just type “Query.” in VS and you’ll see lots of familiar properties such as “EQ” for equal, GT – greater than, LT – less than – and lots more. Whenever you need build a query to the Find or FindOne methods you’ll use the Query object. In case you need to specify a range of criteria you can combine them with And or Or:

Query.And(Query.EQ("someProperty", propertyValue), Query.EQ("otherProperty", otherValue));

Let’s continue. We’ll need a View for the Edit action and a corresponding view-model. Add a class called UpdateCarViewModel to the ViewModels folder:

public class UpdateCarViewModel
{
	[Editable(false)]
	public string Id { get; set; }
	[Required]
	[Display(Name = "Type of car")]
	public string Make { get; set; }
	[Required]
	[Display(Name = "Number of doors")]
	[Range(2, 6)]
	public int NumberOfDoors { get; set; }
	[Required]
	[Display(Name = "Daily rental fee")]
	public decimal DailyRentalFee { get; set; }
	[Required]
	[Display(Name = "List of allowed countries delimited with ';'")]
	public string DelimitedListOfCountries { get; set; }
}

It’s almost the same as the InsertCarViewModel, we could probably do with some subclassing, but it’s fine as it is for the demo. We’ll need the ID so that the Save method can locate the correct document. Next we’ll need to convert a Car object into an UpdateCarViewModel object. Insert the following extension method to DomainExtensions.cs:

public static UpdateCarViewModel ConvertToUpdateViewModel(this Car carDomain)
{
	UpdateCarViewModel updateVm = new UpdateCarViewModel()
	{
		Id = carDomain.Id
		, DailyRentalFee = carDomain.DailyRentalFee
		, Make = carDomain.Make
		, NumberOfDoors = carDomain.NumberOfDoors
	};

	if (carDomain.CountriesAllowedIn != null && carDomain.CountriesAllowedIn.Count() > 0)
	{
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < carDomain.CountriesAllowedIn.Count(); i++)
		{
			sb.Append(carDomain.CountriesAllowedIn.ElementAt(i));
			if (i < carDomain.CountriesAllowedIn.Count() - 1)
			{
				sb.Append(";");
			}
		}
		updateVm.DelimitedListOfCountries = sb.ToString();
	}

	return updateVm;
}

Back in CarsController modify the Edit GET method as follows:

[HttpGet]
public ActionResult Edit(string id)
{
	Car car = CarRentalContext.Cars.FindOneById(new ObjectId(id));
	return View(car.ConvertToUpdateViewModel());
}

Right-click “Edit”, select Add View:

Adding Edit template to Car object

Again, VS will create a basic Edit template for the selected item. Let’s check if it works so far. Run the application, navigate to /cars and click on the Edit link for one of the Car objects. This should open the following Edit form:

Edit Car template populated

Now we need to write the POST Edit action to complete the loop. Add the following stub to the CarsController:

[HttpPost]
public ActionResult Edit(UpdateCarViewModel updateCarViewModel)
{

}

We’ll need another extension method to convert an UpdateCarViewModel back to a Car. Add the following to DomainExtensions:

public static Car ConvertToDomain(this UpdateCarViewModel updateCarViewModel)
{
	Car car = new Car()
	{
		Id = updateCarViewModel.Id
		, DailyRentalFee = updateCarViewModel.DailyRentalFee
		, Make = updateCarViewModel.Make
		, NumberOfDoors = updateCarViewModel.NumberOfDoors
	};
	string[] countries = updateCarViewModel.DelimitedListOfCountries.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
	car.CountriesAllowedIn = countries.ToList();
	return car;
}

Again, we’ll need the Id so that the Save function can find the document to be modified. Here’s the final version of the POST Edit method:

[HttpPost]
public ActionResult Edit(UpdateCarViewModel updateCarViewModel)
{
	if (ModelState.IsValid)
	{
		Car modifiedCar = updateCarViewModel.ConvertToDomain();
		CarRentalContext.Cars.Save(modifiedCar);
		return RedirectToAction("Index");
	}
	return View(updateCarViewModel);
}

We didn’t have to look up the Car object in the database – the Save method will check the ID for us. Run the application, update an existing Car and save it. If all works fine then you’ll be redirected to the Index page with the updated value in the list of Cars.

In the next post we’ll look at updates a bit closer.

View the posts related to data storage here.

Advertisements

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

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

Elliot Balynn's Blog

A directory of wonderful thoughts

Robin Sedlaczek's Blog

Developer on Microsoft Technologies

HarsH ReaLiTy

My goal with this blog is to offend everyone in the world at least once with my words… so no one has a reason to have a heightened sense of themselves. We are all ignorant, we are all found wanting, we are all bad people sometimes.

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: