Introduction to MongoDb with .NET part 14: object serialisation in the .NET driver

Introduction

In the previous post we started working with the .NET driver for MongoDb. We started building a simple context class that acts as a handle on our MongoDb databases and collections. It will be a gateway to the documents in the collections as well. We also tested how to connect to the MongoDb server in code.

In this post we’ll continue to explore the .NET driver. In particular we’ll see how we can represent MongoDb documents in C# code. We’ll continue working on the demo .NET project we started in the previous post.

MongoDb serialisation

This topic is about building C# objects that can be serialised into BSON documents and back. I.e. when we build a C# object we can determine how it is serialised into a MongoDb document. Also, the same serialisation rules specify how a BSON document is mapped to a C# object. Keeping it simple we can say that if a Person object has a Telephone property it can be represented by a “phone” property in the corresponding BSON document.

The C# driver will translate to and from BSON and C# objects automatically through data (de)serialisation. The default serialisation is very simple. Say we have the following POCO:

public class Customer
{
    public string Name { get; set; }
    public string Address { get; set; }
}

The default JSON representation of this POCO will be:

{
    "Name" : "Elvis"
    , "Address" : "Graceland"
}

The POCO will be saved in the BSON format but the above JSON helps visualise the binary source. If the POCO has some sequence of objects, e.g.:

public class Customer
{
    public string Name { get; set; }
    public string Address { get; set; }
    IEnumerable<string> Telephones { get; set; }
}

The it’s translated into a JSON array:

{
    "Name" : "Elvis"
    , "Address" : "Neverland"
    , "Telephones" : ["123", "456"]
}

Nested objects such as…

public class Customer
{
    public string Name { get; set; }
    public string Address { get; set; }
    IEnumerable<string> Telephones { get; set; }
    public WebPage PublicPage { get; set; }
}
 
public class WebPage
{
    public bool IsSsl { get; set; }
    public string Domain { get; set; }
}

…are represented as nested documents like here:

{
    "Name" : "Elvis"
    , "Address" : "Neverland"
    , "Telephones" : ["123", "456"]
    , "PublicPage" : { "IsSsl": true, "Domain" : "company.com" }
}

You probably see the pattern here. The properties of a C# object are translated into their nearest JSON representation.

Changing the default serialisation

Default serialisation is not always what we need. Take e.g. the documents in the demo Restaurants collection. The properties like “borough” and “cuisine” should be more like Borough and Cuisine to follow the C# property naming convention. Also, it is possible that a document property will get an entirely different name. Perhaps we don’t like the “restaurant_id” property and would like to call it simply “Id” in our Restaurant C# object. There are many other considerations, such as differing data types, null values, missing properties. E.g. an integer may be saved as string in a document but we want to have it as an Int32 in C#.

Serialisation mapping rules are achieved through various MongoDb attributes in the C# code. Let’s get a taste of these attributes now and see how a restaurant document can be mapped into a C# object. Add a new class called RestaurantDb to the Repository folder:

using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Newtonsoft.Json;
using System.Collections.Generic;

namespace MongoDbDotNet.Repository
{
	[BsonIgnoreExtraElements]
	public class RestaurantDb
	{
		[BsonId]
		[BsonElement(elementName:"_id")]
		public ObjectId MongoDbId { get; set; }
		[BsonElement(elementName:"address")]
		public RestaurantAddressDb Address { get; set; }
		[BsonElement(elementName:"borough")]
		public string Borough { get; set; }
		[BsonElement(elementName: "cuisine")]
		public string Cuisine { get; set; }
		[BsonElement(elementName:"grades")]
		public IEnumerable<RestaurantGradeDb> Grades { get; set; }
		[BsonElement(elementName: "name")]
		public string Name { get; set; }
		[BsonElement(elementName:"restaurant_id")]
		[BsonRepresentation(BsonType.String)]
		public int Id { get; set; }

		public override string ToString()
		{
			return JsonConvert.SerializeObject(this, Formatting.Indented);
		}
	}
}

The class won’t compile just yet, we’ll add the missing objects in a bit.

You’ll need to add a reference to the Json.NET library through NuGet to compile this code:

MongoDb net driver from NuGet

Let’s see what each attribute does:

  • [BsonIgnoreExtraElements]: we tell the serializer to ignore elements that figure in a JSON document but is not mapped in the C# object. This is useful if you add a new property to the documents directly in the DB
  • [BsonId]: we declare that this property is the ID
  • [BsonElement(elementName:”_id”)]: the BsonElement attribute with a string input is the way to declare what a C# property is called in the corresponding JSON document. In other words this attribute enables us to have different property names in the JSON document and the corresponding POCO
  • [BsonRepresentation(BsonType.String)]: with this attribute we declare that a C# property type is different from its BSON/JSON counterpart. The restaurant IDs in the restaurant collection are stored as strings. However, we want to convert them to integers instead. Be careful with this attribute and make sure you test it on a large document sample. If a single document has a restaurant_id with a different type then data deserialisation will fail and a format exception will be thrown.

We can now add the missing ingredients to the Repository folder. First off we have the RestaurantAddressDb class:

using MongoDB.Bson.Serialization.Attributes;

namespace MongoDbDotNet.Repository
{
	[BsonIgnoreExtraElements]
	public class RestaurantAddressDb
	{	
		[BsonElement(elementName: "building")]
		public string BuildingNr { get; set; }
		[BsonElement(elementName: "coord")]
		public double[] Coordinates { get; set; }
		[BsonElement(elementName: "street")]
		public string Street { get; set; }
		[BsonElement(elementName:"zipcode")]
		[BsonRepresentation(MongoDB.Bson.BsonType.String)]
		public int ZipCode { get; set; }
	}
}

Also we have a RestaurantGradeDb class:

using MongoDB.Bson.Serialization.Attributes;
using System;

namespace MongoDbDotNet.Repository
{
	[BsonIgnoreExtraElements]
	public class RestaurantGradeDb
	{
		[BsonElement(elementName: "date")]
		public DateTime InsertedUtc { get; set; }
		[BsonElement(elementName: "grade")]
		public string Grade { get; set; }
		[BsonElement(elementName: "score")]
		public object Score { get; set; }
	}
}

The Score property is an odd one in that it is of type object. The problem is that not all scores are stored as integers in the database. Some of them are of BSON type null. Here’s an example:

{
        "_id" : ObjectId("56edc2ff03a1cd840734f966"),
        "address" : {
                "building" : "725",
                "coord" : [
                        -74.01381169999999,
                        40.6336821
                ],
                "street" : "65 Street",
                "zipcode" : "11220"
        },
        "borough" : "Brooklyn",
        "cuisine" : "Other",
        "grades" : [
                {
                        "date" : ISODate("2015-01-20T00:00:00Z"),
                        "grade" : "Not Yet Graded",
                        "score" : null
                }
        ],
        "name" : "Swedish Football Club",
        "restaurant_id" : "41278206"
}

The null type cannot be converted into a C# integer so I declared score as an object instead.

The next step is to add the appropriate handle around the restaurants collection in our ModelContext class. Collections are represented by the generic IMongoCollection interface in C#. Here comes the updated ModelContext class with a Restaurants property that acts as a handle on the restaurants collection. There are some other minor additions as well like the null checking logic in the Create method:

using MongoDB.Driver;
using MongoDbDotNet.Infrastructure;
using System;

namespace MongoDbDotNet.Repository
{
	public class ModelContext
	{
		private IMongoClient Client { get; set; }
		private IMongoDatabase Database { get; set; }
		private IConfigurationRepository ConfigurationRepository { get; set; }
		private static ModelContext _modelContext;

		private ModelContext() { }

		public static ModelContext Create(IConfigurationRepository configurationRepository, 
			IConnectionStringRepository connectionStringRepository)
		{
			if (configurationRepository == null) throw new ArgumentNullException("ConfigurationRepository");
			if (connectionStringRepository == null) throw new ArgumentNullException("ConnectionStringRepository");
			if (_modelContext == null)
			{
				_modelContext = new ModelContext();
				string connectionString = connectionStringRepository.ReadConnectionString("MongoDb");
				_modelContext.Client = new MongoClient(connectionString);
				_modelContext.Database = _modelContext.Client.GetDatabase(configurationRepository.GetConfigurationValue("DemoDatabaseName", "model"));
				_modelContext.ConfigurationRepository = configurationRepository;
			}
			return _modelContext;
		}

		public void TestConnection()
		{
			var dbsCursor = _modelContext.Client.ListDatabases();
			var dbsList = dbsCursor.ToList();
			foreach (var db in dbsList)
			{
				Console.WriteLine(db);
			}
		}

		public IMongoCollection<RestaurantDb> Restaurants
		{
			get { return Database.GetCollection<RestaurantDb>(ConfigurationRepository.GetConfigurationValue("RestaurantsCollectionName", "restaurants")); }
		}
	}
}

Let’s try this in the Main method of Program.cs. The following code sample will show an example of querying. Don’t worry about it too much yet, we’ll look at querying in the .NET driver in more detail later on. We want to filter out all restaurants that are located in the borough called Brooklyn. The “restaurants” variable will contain all the search results whereas “restaurant” will only hold a single value. You’ll see the difference between the ToList and FirstOrDefault operations that do the same thing as in LINQ to collections:

using MongoDB.Driver;
using MongoDbDotNet.Infrastructure;
using MongoDbDotNet.Repository;
using System;

namespace MongoDbDotNet
{
	class Program
	{
		static void Main(string[] args)
		{
			ModelContext modelContext = ModelContext.Create(new ConfigFileConfigurationRepository(), new AppConfigConnectionStringRepository());
			var filter = Builders<RestaurantDb>.Filter.Eq(r => r.Borough, "Brooklyn");
			var restaurants = modelContext.Restaurants.FindSync<RestaurantDb>(filter).ToList();
			var restaurant = modelContext.Restaurants.FindSync<RestaurantDb>(filter).FirstOrDefault();
			Console.WriteLine(restaurant);

			Console.ReadKey();
		}
	}
}

You can add some code that iterates through the restaurants collection if you want. Otherwise a single restaurant will be printed in the console similar to the following:

{
  "MongoDbId": "56edc2ff03a1cd840734dba8",
  "Address": {
    "BuildingNr": "2780",
    "Coordinates": [
      -73.982419999999991,
      40.579505
    ],
    "Street": "Stillwell Avenue",
    "ZipCode": 11224
  },
  "Borough": "Brooklyn",
  "Cuisine": "American ",
  "Grades": [
    {
      "InsertedUtc": "2014-06-10T00:00:00Z",
      "Grade": "A",
      "Score": 5
    },
    {
      "InsertedUtc": "2013-06-05T00:00:00Z",
      "Grade": "A",
      "Score": 7
    },
    {
      "InsertedUtc": "2012-04-13T00:00:00Z",
      "Grade": "A",
      "Score": 12
    },
    {
      "InsertedUtc": "2011-10-12T00:00:00Z",
      "Grade": "A",
      "Score": 12
    }
  ],
  "Name": "Riviera Caterer",
  "Id": 40356018
}

Great, we have successfully implemented some basic serialisation for our restaurant objects in .NET.

We’ll continue with more serialisation examples in the next post.

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.

3 Responses to Introduction to MongoDb with .NET part 14: object serialisation in the .NET driver

  1. Marcos Cruz says:

    Hi Andras,

    Please, using design patterns, how to not smudge the domain layer with drivers MongoDB?

  2. Tim says:

    Hi, great article series thanks. I’ve been trying to use the GeoJson data types in MongoDb.. which are supposedly catered for in the .Net MongoDb.Driver driver – however I’m finding that the GeoJson data types in the driver yield invalid GeoJson (according to the spec) when serialised via a webapi rest service. Any suggestions?
    Thanks.
    Tim

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

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: