This guest author article was written by Markus Wildgruber - Managing Director/Cloud Architect, sevantage software GmbH
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 reduced chance of side effects. Though you could define immutable types before manually, 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 ofIEquatable<T>and various other methods that handle value equality - so 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. - Also,
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. This rules out a kind of bug that is very hard to track down: not finding items in a collection anymore due to mismatches between hash code and object values. - The
ToStringmethod of a record returns a meaningful display of the object 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 so 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 change this definition to a class similar to the following:
using MongoDB.Bson;
public class Movie : IEquatable<Movie>
{
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 following this approach, be aware that you have to adapt the declarative mapping of MongoDB attributes so that they are applied to the properties after the compiler has changed the structure.
For instance, if you want to use string as the type of the Id property (but have an ObjectId in MongoDB after mapping) and change the properties in MongoDB, 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<Movie>(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 upon 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<Movie> moviesCollection = database.GetCollection<Movie>("movies");
foreach (Movie movie in moviesCollection.Find(FilterDefinition<Movie>.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 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!
Top comments (0)