MongoDB BSON mapping is the contract between the values in a C# object and the fields in a stored document. A typed collection makes that contract convenient to use, but the type declaration alone does not explain field names, ID representation, constructor selection, or compatibility with documents written by older application versions.
This guide is for application developers learning that boundary. It focuses on typed serialization and deserialization, not on choosing an entire database schema or reproducing a CRUD tutorial. The practical question is simple: can the driver represent your object in BSON and reconstruct it from the document shape you actually store?
Current-information check: October 10, 2026. The official driver documentation links to API 3.12.0, the selected baseline here. Examples are source-reviewed, not executed. Check the current serialization documentation before relying on conventions in a different driver version.
What MongoDB BSON mapping does
The class-mapping guide says the driver creates a class map when a class represents collection data. That map connects document fields to members and selects serializers based on member types. If the stored type and expected member type are incompatible, deserialization can throw.
That matters even when the application's current writer is consistent. Historical documents might use different names. Another service might write the same field using a different BSON type. A data import might omit a member that the current constructor expects. A C# compiler cannot inspect those documents when it checks your application.
It helps to separate three shapes: the application model, the persisted BSON document, and the API response. They may share fields, but they serve different purposes. A response model should not accidentally dictate a storage migration just because a client-facing property was renamed.
The existing MongoDB insertion guide explains how documents are written. Before applying those methods to a new model, understand what the serializer will produce. An insertion method cannot repair a contract that was never defined.
Make field names visible
The POCO documentation describes the default behavior: property names and capitalization are preserved. It also documents attributes and conventions for changing that behavior. Explicit class-map element names are another documented mechanism.
That means CustomerId and customerId are not interchangeable merely because they look equivalent to a person. If the collection stores one name and the application expects the other, that difference belongs in the mapping. A rename in C# should be reviewed as a possible persisted-contract change.
This complete console example registers a positional record with explicit names and an explicit creation path:
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
BsonClassMap.RegisterClassMap<Customer>(map =>
{
map.AutoMap();
map.MapIdMember(customer => customer.Id);
map.MapMember(customer => customer.DisplayName)
.SetElementName("displayName");
map.MapMember(customer => customer.LoyaltyPoints)
.SetElementName("loyaltyPoints");
map.MapCreator(customer => new Customer(
customer.Id, customer.DisplayName, customer.LoyaltyPoints));
});
var customer = new Customer(
ObjectId.GenerateNewId(), "Ada", 150);
var document = customer.ToBsonDocument();
var restored = BsonSerializer.Deserialize<Customer>(document);
Console.WriteLine(document.ToJson());
Console.WriteLine(restored == customer);
public sealed record Customer(
ObjectId Id, string DisplayName, int LoyaltyPoints);
The program has no MongoDB connection. Its purpose is to isolate serialization and deserialization. If executed later, it would provide evidence about this local contract, not about network access or the contents of an existing database.
The class-mapping guide documents AutoMap, MapMember, SetElementName, MapIdMember, and MapCreator. Using those mechanisms explicitly avoids assuming that every immutable record shape is automatically supported in the same way.
Give immutable objects a creation path
An immutable model is not a request for the driver to invent a constructor policy. The official mapping guide describes both BsonConstructor and creator mapping for constructors with arguments. The important idea is that deserialization needs a supported way to create the object from mapped values.
A positional record has an obvious application-level constructor, but it is still worth making the serialization creation path explicit. In the example, the map supplies the constructor expression. That is the intended mechanism; it should be checked against actual BSON fixtures rather than treated as a blanket promise about all records.
Read-only members deserve additional care. The POCO guide warns about ordinary getter-only properties and notes that serializing one does not establish a deserialization path. However, the driver's 3.12 immutable-type convention can automatically map qualifying constructor-backed getter-only members. The examples here use explicit creator maps rather than relying on that inference. Mapping a creation path and mapping a member are related but different tasks.
For example, a calculated display label might be useful to application code without belonging in storage. If it is persisted, decide whether it is authoritative data or a derived value. Otherwise a future implementation can change the calculation while old stored values continue to disagree.
The published mapping and serialization examples discuss related code-generation concerns. Generated code can reduce boilerplate, but it does not remove the need to identify the fields and constructor behavior the persisted contract requires.
Choose ID representation deliberately
The class-mapping guide documents that public members named Id, id, or _id map to the BSON _id field by default. It also documents explicit ID mapping. That answers which member is the ID, but not which BSON type it uses.
An ObjectId value, a string value containing hexadecimal characters, and a GUID are different representations. A string that looks like an ObjectId is not automatically stored as BSON ObjectId unless a serializer or representation rule makes that choice.
The following separate program uses a C# string while explicitly storing its ID as BSON ObjectId:
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
BsonClassMap.RegisterClassMap<Account>(map =>
{
map.AutoMap();
map.MapIdMember(account => account.Id)
.SetSerializer(new StringSerializer(BsonType.ObjectId));
map.MapMember(account => account.Name).SetElementName("name");
map.MapCreator(account => new Account(account.Id, account.Name));
});
var account = new Account(
ObjectId.GenerateNewId().ToString(), "Example account");
var document = account.ToBsonDocument();
var restored = BsonSerializer.Deserialize<Account>(document);
Console.WriteLine(document["_id"].BsonType);
Console.WriteLine(restored.Id);
public sealed record Account(string Id, string Name);
The documented string serializer configuration is the basis for this representation choice. The program supplies a generated valid value instead of assuming the driver can mutate an immutable record to assign one during insertion.
An externally supplied arbitrary string is a different case. If the application accepts human-readable IDs, it should not apply ObjectId representation merely because the property type is string. The storage contract should reflect the intended identifier, not a naming coincidence.
The filtering tutorial becomes relevant here because queries must use the same representation. A mismatched ID type can make a correctly formed query fail to match the intended document.
Register mappings before they are needed
The official guide says a class map must be registered before it is needed and recommends registration before initializing a connection. Treat that as application composition, not as work repeated inside every request handler.
In practice, the ordering question includes more than opening a client. A local call to ToBsonDocument, a serializer lookup, or another operation may need the map before a database connection exists. Register the contract at a predictable startup boundary before those paths run.
The examples in this article are independent console programs. Do not paste all registrations into one process and repeatedly register the same type as if they were per-operation settings. Each example owns its one-time setup.
The broader principle resembles dependency registration: composition should happen before consumers depend on the resulting behavior. The driver-specific timing rule comes from the class-mapping documentation, not from DI itself.
Decide how extra fields should behave
When a BSON field has no corresponding mapped member, the class-mapping guide says the default behavior is an exception. It documents ignoring extra elements and capturing them in a BsonDocument member as distinct alternatives.
Ignoring extra fields can make a reader tolerant of documents written by a newer application. The cost is that the ignored values are not part of that reader's model. A read followed by a complete rewrite should not be assumed to preserve values that were never captured.
Strict reading can make unexpected fields visible sooner, but it can also require coordination when writers add fields. Neither policy is a universal architecture prescription. Explain the intended compatibility boundary and then choose the corresponding map behavior.
Here is a complete local illustration of a deliberately tolerant reader:
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
BsonClassMap.RegisterClassMap<CustomerView>(map =>
{
map.AutoMap();
map.MapIdMember(customer => customer.Id);
map.MapMember(customer => customer.DisplayName)
.SetElementName("displayName");
map.SetIgnoreExtraElements(true);
map.MapCreator(customer =>
new CustomerView(customer.Id, customer.DisplayName));
});
var document = new BsonDocument
{
{ "_id", ObjectId.GenerateNewId() },
{ "displayName", "Ada" },
{ "loyaltyPoints", 150 }
};
var view = BsonSerializer.Deserialize<CustomerView>(document);
Console.WriteLine(view.DisplayName);
Console.WriteLine(view.ToBsonDocument().ToJson());
public sealed record CustomerView(ObjectId Id, string DisplayName);
The map intentionally ignores loyaltyPoints because the view has no matching member. This is a read-contract illustration, not a recommendation to replace complete customer documents with that view.
The existing update guide provides the next operation-level discussion. Keep partial read shapes separate from assumptions about preserving complete stored data during writes.
Compatibility means more than field presence
MongoDB BSON mapping has several independent compatibility questions. Is the field name unchanged? Is its BSON type compatible with the selected serializer? Can the object be constructed when a value is absent? What should happen when an unknown field appears? Does the application interpret the value the same way?
A successful round trip of a freshly constructed object answers only part of that list. It demonstrates the current writer and current reader agreeing on that particular value. It does not demonstrate compatibility with older fixtures or documents written by another application.
For a useful future check, use representative BSON fixtures from the versions the application intends to support. Include documents with extra fields and the actual stored identifier representation. If a field is optional, include its absence rather than only testing a newly constructed object that always supplies it.
Avoid quietly changing a serializer to make one failing example pass without considering stored data. A representation change can alter the BSON a future writer produces. The application may then have a mixed collection that needs an explicit compatibility strategy.
This article deliberately does not prescribe embedding, references, or schema migration infrastructure. Those are broader data-modeling topics. The immediate lesson is narrower: the serialized member contract deserves review independently of the domain object's convenience.
It is useful to name the compatibility question before constructing a fixture. “Can the current reader reconstruct a document with an extra field?” is different from “Does the current writer preserve every field?” A single assertion should not be interpreted as answering both. Keeping those questions explicit prevents a tolerant read policy from becoming an accidental data-preservation claim.
Keep the evidence boundary honest
No sample here was compiled or executed. The examples use documented mapping APIs, and the claims ledger identifies the sources reviewed. That is useful preparation for technical review, but it is not a substitute for running local serialization checks in a separately authorized environment.
Database-backed checks add another layer. A local BSON fixture can isolate a serializer issue; a real collection can show whether deployed documents satisfy the intended contract. The existing MongoDB Testcontainers companion remains the separate resource for temporary database setup.
Do not describe a source review as a passing integration test. Keeping those labels precise makes the next verification step easier to choose and makes unresolved compatibility assumptions visible.
Another helpful MongoDB BSON mapping review is to distinguish a value's representation from its meaning. A correctly serialized long can still represent the wrong business quantity. A correctly stored string can still contain an identifier from the wrong domain. Serialization verifies the technical shape, while application validation defines acceptable values.
MongoDB BSON mapping should therefore be reviewed alongside, but not confused with, domain validation. The examples demonstrate explicit names and creation paths. They do not validate every customer name or prescribe a business rule for loyalty points. Keeping the example's purpose narrow makes its remaining assumptions easier to see.
Frequently asked questions
These answers distinguish the mapped storage contract from the application model's other responsibilities.
What is MongoDB BSON mapping?
It is the relationship between C# members, BSON field names, BSON value types, and object creation during deserialization. A typed collection uses that relationship, but the collection type alone does not establish compatibility with stored documents.
Can MongoDB deserialize an immutable record?
Use a supported explicit creation path, such as the documented MapCreator mechanism illustrated here. Review the mapped members and constructor together. Do not generalize one record example into automatic support for every immutable type shape.
Does a property named Id always store an ObjectId?
The name identifies the mapped ID member by convention. The value representation is a separate serializer decision. A string ID can be stored as a string or explicitly represented as BSON ObjectId.
When should class maps be registered?
Before anything needs them. The official guide recommends registration before initializing a connection. A local serializer lookup can also need mapping, so startup order should cover serialization as well as database access.
Should unknown BSON fields be ignored?
That depends on the reader's compatibility contract. Ignoring supports tolerant reads but does not preserve unknown values in the typed model. Strict reading exposes unexpected fields. Capturing extra elements is another documented mechanism.
Does a local round trip prove database compatibility?
No. It checks the supplied object's serialization and deserialization. Historical fixtures and real stored documents can contain different names, types, or missing values. Those require their own evidence.
Understand the persisted contract
MongoDB BSON mapping is more than decorating a model. It defines names, representations, creation paths, and compatibility behavior. Making those decisions explicit helps explain failures that otherwise look like mysterious database errors.
Once that contract is clear, CRUD methods become easier to reason about. You can distinguish an operation problem from a serialization problem, and a current local round trip from evidence about the documents the application already stores.

