Using C# Record types with MongoDB
Source: Dev.to
This guest author article was written by Markus Wildgruber – Managing Director/Cloud Architect, sevantage software GmbH
Introduction
When C# 9.0 was introduced a while back, the main focus was better support for immutable types. This programming pattern strives to enhance support for concurrent execution and make code easier to understand due to a reduced chance of side‑effects. Although you could define immutable types manually before, C# 9.0 introduced the record keyword that allows for easy definition of immutable types. This piece of “syntactic sugar” also adds the following capabilities to a type:
recordtypes add an implementation ofIEquatableand various other methods that handle value equality – no more manual implementation of these methods is necessary. Even if you seldom compare MongoDB documents by all of their properties in application code, unit tests are much easier to read if you can compare objects as a whole instead of checking each property value individually.recordtypes provide an implementation ofGetHashCodebased on the property values. As mostrecordtypes use only positional parameters that are immutable, the hash code also cannot change after initialization of the object. This is especially important if you use the object as a key in a dictionary or add it to a hash set. It rules out a hard‑to‑track bug where items disappear from a collection because the hash code no longer matches the object’s values.- The
ToStringmethod of a record returns a meaningful display of the object’s properties – a small change that helps a lot when debugging!
For a full overview of the characteristics of a record type, please read the documentation on the Microsoft website.
Defining a record POCO
One additional upside of the definition of a record over a traditional class type is that it is much shorter:
using MongoDB.Bson;
public record Movie(
ObjectId Id,
string Title,
string Plot
);
As shown in the sample above, most record types use a primary constructor that lists the properties directly in the parentheses. The compiler will transform this definition into a class similar to the following:
using MongoDB.Bson;
public class Movie : IEquatable
{
public Movie(ObjectId id, string title, string plot)
{
Id = id;
Title = title;
Plot = plot;
}
public ObjectId Id { get; init; }
public string Title { get; init; }
public string Plot { get; init; }
// …
}
Declarative Mapping
When using this approach, you must adapt the declarative mapping of MongoDB attributes so that they are applied to the generated properties. For instance, if you want to store the Id as an ObjectId in MongoDB but expose it as a string in your model, you need to apply the property attribute target specifier:
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
[BsonIgnoreExtraElements]
public record Movie(
[property: BsonRepresentation(BsonType.ObjectId)] string Id,
[property: BsonElement("title")] string Title,
[property: BsonElement("plot")] string Plot
);
Manual Mapping
If you prefer to create the class map manually (see the MongoDB C# Driver documentation for details), this is also supported:
BsonClassMap.RegisterClassMap(classMap =>
{
classMap.SetIgnoreExtraElements(true);
classMap.MapMember(p => p.Id)
.SetSerializer(new StringSerializer(MongoDB.Bson.BsonType.ObjectId));
classMap.MapMember(p => p.Title).SetElementName("title");
classMap.MapMember(p => p.Plot).SetElementName("plot");
});
Putting it all together
The following file‑based C# app shows that no changes are needed when accessing data from MongoDB that is based on a record type:
#:package MongoDB.Driver@*
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
string connStr = "mongodb://localhost:27017";
string databaseName = "sample_mflix";
int limit = 5;
IMongoClient client = new MongoClient(connStr);
IMongoDatabase database = client.GetDatabase(databaseName);
IMongoCollection moviesCollection = database.GetCollection("movies");
foreach (Movie movie in moviesCollection
.Find(FilterDefinition.Empty)
.Limit(limit)
.ToEnumerable())
{
Console.WriteLine(movie);
}
[BsonIgnoreExtraElements]
public record Movie(
[property: BsonRepresentation(BsonType.ObjectId)] string Id,
[property: BsonElement("title")] string Title,
[property: BsonElement("plot")] string Plot
);
Bottom line & next steps
Records make the model layer of a MongoDB‑based C# application both leaner and safer. They give you immutable value semantics for free, remove boiler‑plate code, and provide reliable equality, hashing, and debugging support. Consider adopting record types for your domain models and explore further MongoDB driver features such as custom serializers or conventions to fully leverage the benefits of immutable data structures.
Boilerplate code for equality checking and add a ToString implementation. Be sure to use the property attribute target to apply MongoDB serialization attributes to properties.
Give it a try – replace a few of your class‑based POCOs with records. We love it when you share your experience – if you hit a pitfall or discover a pattern that worked well, drop a comment so we can all learn.
Happy coding!
