MongoDB in .NET part 4: the POCO model, insertions, indexes and capped collections

Introduction

We saw the basics of POCO documents in the previous post of this series. In this part we’ll build up our model with POCOs.

Open the demo application CarRentalWeb we started out with and let’s get to work!

Our model

Add a new folder to the solution called Domain. In that folder insert a class called Car:

public class Car
{
        [BsonRepresentation(MongoDB.Bson.BsonType.ObjectId)]
	public string Id { get; set; }
	public string Make { get; set; }
	[BsonRepresentation(MongoDB.Bson.BsonType.Double)]
	public decimal DailyRentalFee { get; set; }
	public int NumberOfDoors { get; set; }
	public List<string> CountriesAllowedIn { get; set; }
}

You’ll recognise the BsonRepresentationAttribute from the previous post. You may be wondering why we have public string Id with the BsonRepresentation attribute. We saw in the previous post that Mongo stores the id field – called “_id” – in its own ObjectId format. We could simply write…

public ObjectId Id { get; set; }

…but ObjectId might cause problems with data binding elsewhere in the application as it is not a native .NET object. So we choose its nearest .NET approximation which is a string.

While we’re on this topic the above object definition may not even be considered strictly POCO anymore. It contains attributes that belong to a technology that a true domain object should not care about. These attributes have to do with data persistence and domain objects should have no knowledge of how they are represented in the data store. If you’re not sure what I mean I recommend that you go through the series on Domain Driven Design to learn how to separate domain and persistence models to keep the domain models strictly POCO. At the same time you may be fine with decorating your domain objects like that but it’s your decision.

However, this series is about MongoDb, not DDD, so we’ll allow for such deviations. You can then organise your objects the way you want in your project.

Inserting cars

As we said before a Mongo collection is a group of related documents. It is similar to a table in a relational database. Mongo collections are represented by the MongoCollection object in the C# driver and can be accessed via the MongoDatabase object. In our CarRentalContext context class we built such an object in the class constructor.

Add a new folder to the solution called ViewModels. ViewModels here are not the same as the behaviour-rich objects in MVVM. Instead they are simple DTOs – data transfer objects – to represent the “View” part of our models in MVC. Essentially they will be bound to the elements of the form where we insert new cars. Add the following class to the ViewModels folder:

public class InsertCarViewModel
{
	[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; }
}

This prepares some basic validation and display values using standard MVC annotations. Let’s prepare the Cars controller. Add a new empty MVC controller like this…:

Add cars controller in Visual Studio

Insert a Create action method like this:

[HttpGet]
public ActionResult Create()
{
	return View();
}

Right-click “Create” and select “Add View”. Fill in the form like this:

Create GET Create template for Car view model

In case the Model class list doesn’t show the InsertCarViewModel then close the window and build the project. Click OK and you’ll be presented an out-of-the box Create view. Start the application and navigate to /cars/create. You should see a simple form:

GET Create form to insert new cars

The VS 2013 equivalent will have a different style but that’s not important. When we press the Edit button the POST Create action will be called. Let’s insert it into the CarsController:

[HttpPost]
public ActionResult Create(InsertCarViewModel insertCarViewModel)
{
	if (ModelState.IsValid)
	{
	}
	return View(insertCarViewModel);
}

It’s not doing anything yet. We want to test if the incoming InsertCarViewModel object is correctly populated. Insert a breakpoint at the if statement. Start the application, navigate to /cars/create, fill in some valid values and press create. Code execution will stop at the breakpoint. Inspect the insertCarViewModel in VS – you’ll see that all properties have been populated correctly. We’ll stay on the same page after the Create method returns. The next step is to actually insert this record into MongoDb.

We’ll need to convert the viewmodel to its proper domain representation. We’ll use an extension method to do that. Insert a class called DomainExtensions in the Domain folder. Change the namespace to CarRentalWeb. Declare the class as static and insert the following static method in it:

namespace CarRentalWeb
{
	public static class DomainExtensions
	{
		public static Car ConvertToDomain(this InsertCarViewModel insertCarViewModel)
		{
			Car car = new Car()
			{
				DailyRentalFee = insertCarViewModel.DailyRentalFee
				, Make = insertCarViewModel.Make
				, NumberOfDoors = insertCarViewModel.NumberOfDoors
			};
			string[] countries = insertCarViewModel.DelimitedListOfCountries.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
			car.CountriesAllowedIn = countries.ToList();
			return car;
		}
	}
}

Back in Create(InsertCarViewModel insertCarViewModel) call this extension method as follows:

if (ModelState.IsValid)
{
	Car car = insertCarViewModel.ConvertToDomain();
}

We’ll need access to the database context in this controller – and most likely in all our controllers. Add a new class called BaseController to the Controllers folder. Select the “class” template, not the New Controller options.

public class BaseController : Controller
{
	private CarRentalContext _carRentalContext;

	public BaseController()
	{
		_carRentalContext = new CarRentalContext();
	}

	public CarRentalContext CarRentalContext
	{
		get
		{
			return _carRentalContext;
		}
	}
}

Change the declaration of CarsController to inherit from the BaseController:

public class CarsController : BaseController

Next we’ll extend the CarRentalContext class to return a mongo collection that holds the Cars objects. Open CarRentalContext.cs and add the following getter:

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

The MongoCollection class has an overloaded version where you can specify the type of objects stored in the collection. The MongoDatabase object is used to get hold of a collection of a specific type. We need to specify the name of the collection – “cars” – which is an arbitrary string that you can specify, it could as well be “MickeyMouse”, but you probably prefer something descriptive. The type specification will help the serialisation mechanism to convert to and from Car and its BSON representation.

Back in CarsController.Create we’ll use the simplest approach to insert a new document. Add the following line after the conversion from the view model:

CarRentalContext.Cars.Insert(car);
return RedirectToAction("Index");

That’s as simple as it gets. We’ll come back to the Insert method in the next post as it returns a WriteConcern object which deserves more attention. We can ignore it for the time being.

After an insertion we’d like to return to the Index page which lists all the Car objects in a table, which is why we have the RedirectToAction call. The CarsController already has an Index method which returns a View(). Let’s tie the View to an IEnumerable of Car view-model objects. We don’t have those yet so add a new class called CarViewModel to the ViewModels folder:

public class CarViewModel
{	
        [Display(Name = "ID")]
	public string Id { get; set; }
	public string Make { get; set; }
	[Display(Name = "Rental fee per day")]
	public decimal DailyRentalFee { get; set; }
	[Display(Name = "Number of doors")]
	public int NumberOfDoors { get; set; }
	[Display(Name = "Allowed countries")]
	public string CountriesAllowedIn { get; set; }
}

We’ll need to convert the Car objects from the database into the CarViewModel representations. Add the following extension methods to DomainExtensions.cs.:

public static IEnumerable<CarViewModel> ConvertAllToViewModels(this IEnumerable<Car> carDomains)
{
	foreach (Car car in carDomains)
	{
		yield return car.ConvertToViewModel();
	}
}

public static CarViewModel ConvertToViewModel(this Car carDomain)
{
	CarViewModel carViewModel = new CarViewModel()
	{
		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(",");
			}
		}
		carViewModel.CountriesAllowedIn = sb.ToString();
	}
        return carViewModel;
}

The CarsController Index action will need to fetch all the car objects, convert them to view models and show them to the viewer. Compile the solution and right-click “Index”. Select Add View…:

Add view to list all car objects

This will give you a basic template to show all items in the sequence in a table.

We can use the FindAll method of MongoCollection to retrieve all items without any search parameters:

public ActionResult Index()
{
	List<Car> carsInDb = CarRentalContext.Cars.FindAll().ToList();
        return View(carsInDb.ConvertAllToViewModels());
}

We should be good to go. Start the application, navigate to /cars/create. Fill in the form with valid data, e.g.:

Create car form values example

If everything’s OK then you’ll see the new Car object listed in its CarViewModel representation on the Index page:

Car created and index page shown

That wasn’t too difficult, right? We didn’t even need to create a database or collection. They were both created for us upon the first insert.

Indexes

You’ll probably know that it’s important to set an index on fields that often figure in searches. Here’s how you can set a composite index on the Car object on the Make and NumberOfDoors properties – which probably doesn’t make sense, this is only example code:

IndexKeysBuilder<Car> carIndexBuilder = IndexKeys<Car>.Ascending(c => c.Make, c => c.NumberOfDoors);
IndexOptionsBuilder<Car> carIndexOptions = IndexOptions<Car>.SetName("Car_CompositeIndex").SetTimeToLive(new TimeSpan(2, 0, 0, 0));
CarRentalContext.Cars.EnsureIndex(carIndexBuilder, carIndexOptions);

With SetTimeToLive you can set an expiry date on a collection.

You can drop an Index by its name as follows:

CarRentalContext.Cars.DropIndexByName("Car_CompositeIndex");

Capped collections

A capped collection is a collection which is not allowed to grow beyond a specific size in bytes. If the maximum size is reached then the elements will be dropped starting from the first element in a FIFO fashion. Here’s how you can set the cap to 50MB:

CollectionOptionsBuilder optionsBuilder = new CollectionOptionsBuilder();
optionsBuilder.SetCapped(true);
optionsBuilder.SetMaxSize(52428800);
CarRentalContext.CarRentalDatabase.CreateCollection("NewCollection", optionsBuilder);

Read the next part of the series here.

View the posts related to data storage here.

Advertisements

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

6 Responses to MongoDB in .NET part 4: the POCO model, insertions, indexes and capped collections

  1. Pingback: Lindermann's Blog | MongoDB Series

  2. Rafa says:

    I have a problem when running the application on the part of creating the first view Create Car in CarController. Does not display the page as you capture samples shows me the blank page with the following line:
    {“Version”:{“Major”:3,”Minor”:0,”Build”:4,”Revision”:-1,”MajorRevision”:-1,”MinorRevision”:-1},”VersionString”:”3.0.4″}

    What am I doing wrong?

    Thank you a lot!

  3. NS says:

    I have some previous experience with pymongo, which has a validate function – is there a .NET equivalent of this?

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: