blog post

Best Practices When Working With MongoDb in .NET

A Big Thanks To The Sponsors of This Blog Post

Go From Undiscovered to Growing & Monetizing Your LinkedIn Account. Join 60+ LinkedIn Top Voices and 25,000+ students to accelerate the growth of your personal brand on LinkedIn.

Learn more

Build a better, faster content production system. The Content OS is a multi-step system for creating a high-quality newsletter and 6-12 pieces of high-performance social media content each week.

Learn more

Build a lean, profitable internet business in 2024. The Creator MBA delivers a complete blueprint for starting, building, and sustaining a profitable Internet business.

Learn more

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#.

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:

yaml
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

Step 2: Add MongoDB Provider and Connect to Database

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

bash
dotnet add package MongoDB.Driver

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

csharp
{ "ConnectionStrings": { "MongoDb": "mongodb://admin:admin@mongodb:27017" } }

Step 3: Register MongoDB Dependencies in DI Container

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

csharp
var mongoConnectionString = configuration.GetConnectionString("MongoDb"); var mongoClientSettings = MongoClientSettings.FromConnectionString(mongoConnectionString); services.AddSingleton<IMongoClient>(new MongoClient(mongoClientSettings));

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:

csharp
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; } }

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:

csharp
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; } }

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:

csharp
BsonDefaults.GuidRepresentationMode = GuidRepresentationMode.V3; BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));

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:

csharp
ConventionRegistry.Register("camelCase", new ConventionPack { new CamelCaseElementNameConvention() }, _ => true);

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:

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

Screenshot_1

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

csharp
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 }

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.

csharp
var database = mongoClient.GetDatabase("shipping-api"); var collection = database.GetCollection<Shipment>("shipments");

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:

csharp
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);

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:

csharp
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"); }

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:

csharp
services.AddSingleton<MongoDbContext>();

Here is how you can use MongoDbContext:

csharp
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; }

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

Hope you find this blog post useful. Happy coding!

You can download source code for this blog post for free

Improve Your .NET and Architecture Skills

Join my community of 1800+ developers and architects.

Each week you will get 2 practical tips with best practises and architecture advice.