Using C# Record types with MongoDB

Published: (February 12, 2026 at 11:42 AM EST)
4 min read
Source: Dev.to

Source: Dev.to

MongoDB Guests

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:

  • record types add an implementation of IEquatable and 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.
  • 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. 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 ToString method 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!

0 views
Back to Blog

Related posts

Read more »

17. C# (Char)

The Real Goal of This Lesson > “When you only need a single character, why should you use char instead of string?” This lesson is not about syntax. It is about...

I Fixed Windows Native Development

Build Requirements: Install Visual Studio > If you’re lucky enough not to know this yet, I envy you. Unfortunately, at this point even Boromir knows… Well put,...