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
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
Next you need to configure a connection string to the MongoDB in appsettings.json
:
{
"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:
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:
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:
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:
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
:
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
:
ConventionRegistry.Register("EnumStringConvention", new ConventionPack
{
new EnumRepresentationConvention(BsonType.String)
}, _ => true);
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
}
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");
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);
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");
}
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>();
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;
}
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
orstring
forId
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)