Storing Dynamic Objects in MongoDB with C#

Software Development
Published May 29, 2022 ยท 2 min read
The main purpose of using a NoSQL DB vendor such as MongoDB is to take advantage from the flexibility it provides at the level of the structure of the stored data. However, when used with C#, storing a field whose structure is dynamic and differs from object to another is a bit difficult.

Motivation

For the sake of this article, let's assume we have a class Vehicle than can be car, motorcycle, etc. We want to store our data in the database in a structure similar to this:

{
  "vehicles": [
    { 
      "type": "car", 
      "model": "Tesla", 
      "color":  "white"
    },
    { 
      "type": "motorcycle", 
      "brand": "Yamaha"
    },
    { "type": "plane", 
      "numberOfSeats": 100,
      "previousFlights": [
        { "date": "03-11-2021", "from": "Beirut", "to": "Istanbul" },
        { "date": "21-12-2021", "from": "Paris", "to": "Beirut" }
      ]
    },
    { 
      "type": "car", 
      "model": "Mercedes", 
      "model":  "2022"
    },
  ]
}

We can, of course, create a class for each vehicle covering all the possible fields. However, this approach is not recommended when the data is not limited to a specific structure, because any new model would require new development to be done. Instead, we can treat a vehicle as a JSON object and store it as is in the database taking advantage of the power NoSQL databases provide us with.

Prerequisite

In this example, I will be using the official MongoDB Driver for C#.

Implementation

To achieve our goal we will create two representations for the vehicle class, one to serve as a database entity, and another one as an API data transfer object.

VehicleEntity

In the database entity, we will represent the content of the vehicle as a BsonArray:

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

namespace Mohammed.Ezzedine.MongoExample.Entities;

public class VehicleEntity
{
    [BsonId]
    [BsonRepresentation(BsonType.Int32)]
    public int Id { get; set; }

    public BsonArray Content { get; set; }
}

VehicleDto

For the API data transfer object, and since BsonArray is a representation scoped for MongoDB, we will use JsonNode. This way, we are not coupling our API behavior to the DB vendor we are using.

using System.Text.Json.Nodes;

namespace Mohammed.Ezzedine.MongoExample.Dtos;

public class VehicleDto
{
    public int Id { get; set; }

    public JsonNode Content { get; set; }
}

Mapping

When our API receives a request to store a new vehicle in the database, the content will be in the form of a JsonNode, however, we want it to be a BsonArray, and this conversion is not done implicitly. To solve this issue, we can convert back and forth between the twp types as follows:

using System.Text.Json.Nodes;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using Mohammed.Ezzedine.MongoExample.Dtos;
using Mohammed.Ezzedine.MongoExample.Entities;

public class VehicleDataMapping
{
    public static VehicleEntity map(VehicleDto dto)
    {
        return new VehicleEntity
        {
            Id = dto.Id,
            Content = BsonSerializer.Deserialize<BsonArray>(dto.Content.ToString(), null)
        };
    }

    public static VehicleDto map(VehicleEntity entity)
    {
        return new VehicleDto
        {
            Id = entity.Id,
            Content = JsonArray.Parse(entity.Content.ToString(), null, default)
        };
    }
}