DEV Community

MongoDB Guests
MongoDB Guests

Posted on

Using C# Record types with MongoDB

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:

  • record types add an implementation of IEquatable<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, record types provide an implementation of GetHashCode based on the property values. As most record types 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 ToString method 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
);
Enter fullscreen mode Exit fullscreen mode

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

    // ... 
}
Enter fullscreen mode Exit fullscreen mode

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
);
Enter fullscreen mode Exit fullscreen mode

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");
});
Enter fullscreen mode Exit fullscreen mode

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
);
Enter fullscreen mode Exit fullscreen mode

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)