DEV Community

Cover image for Best Practices When Working With MongoDb in .NET
Anton Martyniuk
Anton Martyniuk

Posted on • Originally published at antondevtips.com on

Best Practices When Working With MongoDb in .NET

MongoDB is one of the most popular NoSQL databases, it allows building modern and scalable applications.

In this blog post, I will show you what are the best practices when working with MongoDB in .NET and C#.

On my website: antondevtips.com I share .NET and Architecture best practices.
Subscribe to become a better developer.
Download the source code for this blog post for free.

Get Started with MongoDB in ASP.NET Core

You need to follow these steps to Add MongoDB to your project.

Step 1: Set Up MongoDB

We will set up MongoDB in a docker container using docker-compose-yml:

services:
  mongodb:
    image: mongo:latest
    container_name: mongodb
    environment:
      - MONGO_INITDB_ROOT_USERNAME=admin
      - MONGO_INITDB_ROOT_PASSWORD=admin
    volumes:
      - ./docker_data/mongodb:/data/db
    ports:
      - "27017:27017"
    restart: always
    networks:
      - docker-web

networks:
  docker-web:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

Step 2: Add MongoDB Provider and Connect to Database

To connect to MongoDB, you need to add the official MongoDB package to your project:

dotnet add package MongoDB.Driver
Enter fullscreen mode Exit fullscreen mode

Next you need to configure a connection string to the MongoDB in appsettings.json:

{
  "ConnectionStrings": {
    "MongoDb": "mongodb://admin:admin@mongodb:27017"
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Register MongoDB Dependencies in DI Container

You need to register a IMongoClient as a single instance in the DI container:

var mongoConnectionString = configuration.GetConnectionString("MongoDb");
var mongoClientSettings = MongoClientSettings.FromConnectionString(mongoConnectionString);

services.AddSingleton<IMongoClient>(new MongoClient(mongoClientSettings));
Enter fullscreen mode Exit fullscreen mode

This class is used to create a connection with a MongoDB database and allow performing database commands.

Now, as we're ready to go, let's explore a real-world application that uses MongoDB.

An Example Application

Let's explore a Shipping Application that is responsible for creating and updating customers, orders and shipments for ordered products.

This application has the following entities:

  • Customers
  • Orders, OrderItems
  • Shipments, ShipmentItems

Let's explore a Shipment and ShipmentItem entities:

public class Shipment
{
    public Guid Id { get; set; }
    public required string Number { get; set; }
    public required string OrderId { get; set; }
    public required Address Address { get; set; }
    public required ShipmentStatus Status { get; set; }
    public required List<ShipmentItem> Items { get; set; } = [];
}

public class ShipmentItem
{
    public required string Product { get; set; }
    public required int Quantity { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

At the end of the blog post, you can download a source code of Shippping Application.

How To Work With IDs in MongoDB

There are multiple options to create entity IDs in MongoDB:

  • using ObjectId MongoDB class
  • using string type with attributes
  • using Guid type without attributes

Let's explore all the options in code:

public class Shipment
{
    public ObjectId Id { get; set; }
}

public class Shipment
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; set; }
}

public class Shipment
{
    public Guid Id { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

The first option has the following disadvantages:

  • your entity is now aware of MongoDB
  • you need to manually create an ObjectId when inserting an entity

The second option allows MongoDB to automatically create IDs when inserting a record in the database.
But it also makes your entity aware of MongoDB.

The third option is the most common option for C# developers, which is also widely used when working with SQL databases.
This approach makes your entity separated from MongoDB.

Among these options, I prefer using the 3rd option with Guid.
In some cases, I can use the 2nd option with string and attributes as well.

To be able to work with Guid you need to turn this feature on:

BsonDefaults.GuidRepresentationMode = GuidRepresentationMode.V3;
BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
Enter fullscreen mode Exit fullscreen mode

Never mind that BsonDefaults.GuidRepresentationMode is obsolete.
In a future version it will be removed from the API and GuidRepresentation.Standard will turn on the GuidRepresentationMode.V3 by default.

How To Configure Properties Serialization in MongoDB

MongoDB stores all the data in the database in the BSON format (binary JSON).
The default casing in JSON is camelCasing, which I recommend to turn on for serialization of C# classes to MongoDB collections.

You need to register CamelCaseElementNameConvention in the MongoDB ConventionRegistry:

ConventionRegistry.Register("camelCase", new ConventionPack {
    new CamelCaseElementNameConvention()
}, _ => true);
Enter fullscreen mode Exit fullscreen mode

When working with Enum values, I recommend serializing it as string in the database.
It makes your collection data much more expressive and readable rather than having numbers, which is the default way of Enum serialization.

This is how you can register it in the ConventionRegistry:

ConventionRegistry.Register("EnumStringConvention", new ConventionPack
{
    new EnumRepresentationConvention(BsonType.String)
}, _ => true);
Enter fullscreen mode Exit fullscreen mode

Screenshot_1

In our application, a Shipment has an enum value ShipmentStatus:

public class Shipment
{
    public Guid Id { get; set; }
    public required string Number { get; set; }
    public required string OrderId { get; set; }
    public required Address Address { get; set; }
    public required ShipmentStatus Status { get; set; }
    public required List<ShipmentItem> Items { get; set; } = [];
}

public enum ShipmentStatus
{
    Created,
    Processing,
    Dispatched,
    InTransit,
    Delivered,
    Received,
    Cancelled
}
Enter fullscreen mode Exit fullscreen mode

The Best Way To Work With Collections in MongoDB

And here is the most interesting part: I will show you what I think is the best way to work with MongoDB collections in C# code.

Every time you need to perform a database command you need to extract a database from IMongoClient.
Then you need to extract a collection from the database.

var database = mongoClient.GetDatabase("shipping-api");
var collection = database.GetCollection<Shipment>("shipments");
Enter fullscreen mode Exit fullscreen mode

Every time you need to path a database and a collection name.
This is tedious and can be error-prone.

One way to solve this problem is by introduction of constants:

public static class MongoDbConsts
{
    public const string DatabaseName = "shipping-api";

    public const string ShipmentCollection = "shipments";
}

var database = mongoClient.GetDatabase(DatabaseName);
var collection = database.GetCollection<Shipment>(ShipmentCollection);
Enter fullscreen mode Exit fullscreen mode

This approach is also error-prone - you can pass a wrong collection name, for example.

Here is my favourite approach for organizing code when working with MongoDB collections:

public class MongoDbContext(IMongoClient mongoClient)
{
    private readonly IMongoDatabase _database = mongoClient.GetDatabase("shipping-api");

    public IMongoCollection<Shipment> Shipments => _database.GetCollection<Shipment>("shipments");

    public IMongoCollection<Customer> Customers => _database.GetCollection<Customer>("customers");

    public IMongoCollection<Order> Orders => _database.GetCollection<Order>("orders");
}
Enter fullscreen mode Exit fullscreen mode

I like creating a MongoDbContext class that encapsulates all IMongoCollections.
I find this approach useful as I can keep all the database and collection names in one place.
With this approach, I can't mess up with a wrong collection name.

To be able to inject MongoDbContext into your classes, simply register it as Singleton in DI:

services.AddSingleton<MongoDbContext>();
Enter fullscreen mode Exit fullscreen mode

Here is how you can use MongoDbContext:

public async Task<ErrorOr<ShipmentResponse>> Handle(
    CreateShipmentCommand request,
    CancellationToken cancellationToken)
{
    var shipmentAlreadyExists = await mongoDbContext.Shipments
        .Find(x => x.OrderId == request.OrderId)
        .AnyAsync(cancellationToken);

    if (shipmentAlreadyExists)
    {
        logger.LogInformation("Shipment for order '{OrderId}' is already created", request.OrderId);
        return Error.Conflict($"Shipment for order '{request.OrderId}' is already created");
    }

    var shipmentNumber = new Faker().Commerce.Ean8();
    var shipment = request.MapToShipment(shipmentNumber);

    await mongoDbContext.Shipments.InsertOneAsync(shipment, cancellationToken: cancellationToken);

    logger.LogInformation("Created shipment: {@Shipment}", shipment);

    var response = shipment.MapToResponse();
    return response;
}
Enter fullscreen mode Exit fullscreen mode

If you have experience with EF Core, it really looks familiar.

At the end of the blog post, you can download a source code of Shippping Application.

Summary

When working with MongoDB in .NET and C#, I recommend using the following best practices:

  • Use Guid or string for Id field
  • Use camelCase when serializing entities into a database
  • Serialize enums as strings
  • Create MongoDbContext to manage database collections in one place

On my website: antondevtips.com I share .NET and Architecture best practices.
Subscribe to become a better developer.
Download the source code for this blog post for free.

Top comments (0)