MongoDB indexes in C# are easier to reason about when the conversation starts with a query rather than a field list. An application needs particular documents, in a particular order, with a particular result shape. An index is a candidate way to support that work, not a general promise that every database operation becomes faster.
This guide explains the relationship between typed index definitions and query-plan evidence. It is for C# application developers learning how to ask useful performance questions without inventing timings or treating one index scan as a universal success condition.
Current-information check: October 10, 2026. The official driver documentation links to API 3.12.0, the baseline used here. Server-manual pages are current documentation, not a verified deployed-server version. No index, command, build, or benchmark was executed; check current documentation and your actual server version before running examples.
Start MongoDB indexes with a query shape
A query shape includes the selection predicates, sort order, projection, and result bounds. For example, an application might read orders for one customer, sort by descending sequence and ID, and return only ID, sequence, and total. That is more informative than saying “orders need an index.”
The existing MongoDB filtering tutorial covers predicate construction. Index reasoning builds on the predicate rather than replacing it. A correctly indexed query can still express the wrong application condition.
Write the query's meaning in prose first. Which values are equality restrictions? Which fields determine order? Which comparisons are ranges? Are some predicates optional? A candidate that supports one shape does not automatically support every variant an endpoint can generate.
The result shape also matters. Returning fewer fields can be useful even when the server still fetches documents. A covered query is a more specific execution property than “this endpoint projects a summary.”
Understand equality, sort, and range for MongoDB indexes
The equality, sort, range guideline describes a common compound-key ordering approach. Equality fields come first. Then the relative placement of sort and range fields depends on the needs of the query.
The same document explicitly describes both ESR and ERS reasoning. Putting sort fields before a range can help avoid an in-memory sort. A highly selective range before sort fields can reduce the amount of data to sort. That is why a guideline should not be rewritten as “this key order is always fastest.”
For an equality on CustomerId followed by a descending sort on Sequence and Id, a candidate key definition is customer first, then sequence, then ID. The following program shows the driver's typed index API. If run, it creates an index; it is not part of this article's execution.
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
BsonClassMap.RegisterClassMap<Order>(map =>
{
map.AutoMap();
map.MapIdMember(order => order.Id);
map.MapMember(order => order.CustomerId).SetElementName("customerId");
map.MapMember(order => order.Sequence).SetElementName("sequence");
map.MapMember(order => order.TotalCents).SetElementName("totalCents");
map.MapCreator(order => new Order(
order.Id, order.CustomerId, order.Sequence, order.TotalCents));
});
var uri = Environment.GetEnvironmentVariable("MONGODB_URI")
?? throw new InvalidOperationException("MONGODB_URI is required.");
using var client = new MongoClient(uri);
var orders = client.GetDatabase("application")
.GetCollection<Order>("orders");
var keys = Builders<Order>.IndexKeys
.Ascending(order => order.CustomerId)
.Descending(order => order.Sequence)
.Descending(order => order.Id);
var model = new CreateIndexModel<Order>(
keys,
new CreateIndexOptions { Name = "customer_sequence_id" });
var indexName = orders.Indexes.CreateOne(model);
Console.WriteLine(indexName);
public sealed record Order(
ObjectId Id, string CustomerId, long Sequence, long TotalCents);
The driver index guide documents Builders<TDocument>.IndexKeys, CreateIndexModel<TDocument>, and Indexes.CreateOne. The program uses that documented path rather than inventing an index API.
The name identifies this candidate, but the name is not evidence that the server selected it. Nor does creating the index prove the collection's query distribution justifies maintaining it. Those are later questions.
Keep ordering correctness separate from MongoDB indexes
The server sort documentation explains that tied sort values have no guaranteed relative order unless a unique field resolves them. In these examples, the ID supplies that tie-breaker for an ordinary unsharded collection.
That correctness decision belongs to the query. An index may support the order, but the presence of an index does not repair a query that never requested its intended order. Always preserve the complete sort when explaining the command.
A continuation query must also reflect the complete boundary. For descending sequence and ID, later results have a lower sequence or the same sequence and a lower ID. That predicate is a different shape from the initial page and deserves separate plan evidence.
The published sorting and pagination discussion provides related application-level reasoning. MongoDB's index and plan behavior still needs MongoDB-specific evidence.
Render typed definitions with the selected API
Typed definitions can hide persisted field-name details. Rendering is useful because it shows the BSON produced using the selected serializers. It is not a database execution, but it can investigate whether the filter and sort refer to the intended mapped fields.
Older snippets may use a different Render signature. The 3.12.0 filter source declares Render(RenderArgs<TDocument> args). The RenderArgs source defines a constructor taking the document serializer and serializer registry.
This complete program constructs and renders the query without creating a client:
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
BsonClassMap.RegisterClassMap<Order>(map =>
{
map.AutoMap();
map.MapIdMember(order => order.Id);
map.MapMember(order => order.CustomerId).SetElementName("customerId");
map.MapMember(order => order.Sequence).SetElementName("sequence");
map.MapMember(order => order.TotalCents).SetElementName("totalCents");
map.MapCreator(order => new Order(
order.Id, order.CustomerId, order.Sequence, order.TotalCents));
});
var registry = BsonSerializer.SerializerRegistry;
var serializer = registry.GetSerializer<Order>();
var renderArgs = new RenderArgs<Order>(serializer, registry);
var filter = Builders<Order>.Filter.Eq(order => order.CustomerId, "customer-17");
var sort = Builders<Order>.Sort.Descending(order => order.Sequence)
.Descending(order => order.Id);
Console.WriteLine(filter.Render(renderArgs).ToJson());
Console.WriteLine(sort.Render(renderArgs).ToJson());
public sealed record Order(
ObjectId Id, string CustomerId, long Sequence, long TotalCents);
The class map is part of this program's setup. Its explicit element names are why a typed property expression can become the intended stored field. A raw command using string field names does not automatically inherit those names from the C# record.
No rendered output is asserted here because the program was not run. The source establishes the API shape; exercising the program would provide concrete rendering evidence for this definition.
Explain the exact command
The database-command guide documents passing a BsonDocument command to RunCommand or RunCommandAsync. The server explain reference describes explaining commands and the available execution information.
For a find query, the explain command wraps the find command. The wrapper's verbosity describes the evidence requested. executionStats includes execution of the winning plan; it is not merely a free metadata lookup.
The following complete program sends an explain command when run in an authorized environment. It uses raw BSON names intentionally, matching the mapped collection fields shown above.
using System;
using MongoDB.Bson;
using MongoDB.Driver;
var uri = Environment.GetEnvironmentVariable("MONGODB_URI")
?? throw new InvalidOperationException("MONGODB_URI is required.");
using var client = new MongoClient(uri);
var database = client.GetDatabase("application");
var find = new BsonDocument
{
{ "find", "orders" },
{ "filter", new BsonDocument("customerId", "customer-17") },
{ "sort", new BsonDocument
{
{ "sequence", -1 },
{ "_id", -1 }
}
},
{ "projection", new BsonDocument
{
{ "_id", 1 },
{ "sequence", 1 },
{ "totalCents", 1 }
}
},
{ "limit", 20 }
};
var command = new BsonDocument
{
{ "explain", find },
{ "verbosity", "executionStats" }
};
var result = await database.RunCommandAsync<BsonDocument>(command);
Console.WriteLine(result.ToJson());
This article has not sent the command or observed its response. The program does not invent an IXSCAN, a returned count, or a runtime. Retaining the complete response is preferable to extracting a few expected fields and discarding the rest of the evidence.
The command guide also says RunCommand does not inherit a database instance's read preference and uses primary by default. Do not assume a raw diagnostic command targets the same read destination as a differently configured fluent read without checking its execution settings.
Read explain output as a tree
The explain-results documentation describes plans as trees of stages. COLLSCAN represents a collection scan, IXSCAN an index-key scan, and FETCH document retrieval. The output structure differs between classic and slot-based engines and can change between server versions.
That means a parser expecting one universal winningPlan.inputStage.stage path can miss meaningful evidence. First preserve the response. Then inspect the actual structure for the server and engine that produced it.
An index scan is useful evidence about the chosen approach, but not proof that little work occurred. A query may scan many index keys. A fetch stage may still read many documents. Evaluate the work in relation to the query's result rather than declaring success from one stage name.
The documented execution statistics include nReturned, totalKeysExamined, and totalDocsExamined. Their actual values must come from the actual response. This article deliberately supplies no illustrative numeric “improvement” that could be mistaken for measurement.
Do not use explain as an endpoint stopwatch
Explain evidence is not identical to application latency. The explain-results guide says executionTimeMillis includes plan selection and execution but excludes network time to transmit results to the client. It also warns that the reported time is not necessarily representative of actual query time.
The same guide says explain ignores the plan cache and does not cache the winning plan. A repeated application request and an explain invocation therefore do not necessarily follow the same planning path.
This is an important limitation, not a reason to discard explain. It answers questions about execution strategy and examined work. Endpoint measurements answer additional questions about networking, serialization, response construction, and the deployed application.
Keep those evidence categories separate. “This explain used a particular index” is a narrower statement than “this endpoint became faster.” The latter requires a defined experiment and observed application measurements.
The existing insertion benchmark article is a historical workload-specific resource. Its numbers are not evidence for a present-day read query or a universal claim about synchronous and asynchronous operations.
Account for write maintenance on MongoDB indexes
The write-performance guide states that each collection index adds write overhead. Insertions and deletions add or remove corresponding index keys; updates may change affected indexes.
That makes “add an index to speed up inserts” the wrong generic explanation. An index can support a read or selection workload while imposing maintenance costs on writes. The useful question is whether the workload benefits justify that cost.
More fields in a candidate index are not automatically better. A wider candidate may support an additional query feature, but it also changes what the database maintains. Evaluate a concrete reader need instead of indexing every model property by default.
The existing insertion tutorial and deletion tutorial remain the operation-level resources. Index maintenance is a cross-cutting cost, not another CRUD method walkthrough.
What evidence belongs with decisions about MongoDB indexes
A useful future investigation preserves the query, the deployed server version, driver version, index definitions, and representative data characteristics. It also identifies whether the request is an initial page, a continuation, or a different filter variant.
Then preserve the actual explain response and describe what it shows. Do not change the query, projection, page depth, and indexes simultaneously and attribute the result to only one change. An explanation is strongest when the evidence can be traced to the exact request.
For eventual verification setup, reuse the published MongoDB Testcontainers companion. This article does not reproduce its setup or imply that a temporary deployment was started.
An investigation should also state what it cannot establish. A small fixture can verify that a command is accepted and produces an explain response, but it cannot represent every production distribution. A source review can verify API names, but it cannot identify a winning plan. Those limitations belong beside the result, not hidden behind a broad statement that an index “works.”
The useful learning outcome is a chain of evidence: intended query, expressed command, observed plan, and appropriately scoped interpretation. Each link answers a different question.
MongoDB indexes also need a statement of the query variants the evidence covers. An initial customer page and a continuation page share several fields, but the continuation adds a range boundary. A search with an optional predicate can change again. One saved response should not be presented as proof for every variant.
For MongoDB indexes, that does not require an invented universal checklist or a new abstraction. It requires naming the request that was examined. If a later explanation discusses a different request, keep the evidence separate so a reviewer can see which claim belongs to which command.
MongoDB indexes are therefore a useful introduction to evidence-based reasoning for application developers. Express a candidate clearly, identify what observation could support it, and retain the limits of that observation. The learning outcome is an accurate explanation of the query's work, not a promise of a percentage improvement without an experiment.
Frequently asked questions
These answers separate candidate definitions, command execution, and the interpretation of actual plan evidence.
What should a MongoDB index be based on?
A concrete query shape: predicates, ordering, projection, and result bounds. A property list alone does not explain the workload the index is intended to support.
Is equality, sort, range always the fastest order?
No. The official guideline also discusses placing a selective range before sort fields. It is a framework for forming candidates, not a substitute for workload-specific evidence.
Which Render signature does this example use?
The pinned 3.12.0 API uses Render(RenderArgs<TDocument>). The example supplies a document serializer and serializer registry through the documented constructor shape.
Does executionStats only inspect metadata?
No. It includes execution of the winning plan. Treat an explain invocation as database work requiring an appropriate environment and permissions, not as a cost-free local inspection.
Does IXSCAN prove the application is fast?
No. It identifies an index-scan stage. The amount of examined work, subsequent stages, and application latency remain separate evidence.
Were any index improvements measured here?
No. The examples and rules were reviewed against official documentation and versioned source. No index was created, no explain command was sent, and no performance result is claimed.
Keep reasoning about MongoDB indexes grounded
MongoDB indexes in C# connect an application query to a candidate server execution strategy. Typed definitions make the requested keys convenient to express. Rendering can expose mapped BSON. Explain can provide actual plan evidence when it is separately executed.
The important habit is to keep those layers distinct. A candidate is not a measured winner, a stage name is not endpoint latency, and read benefits do not erase write maintenance. That understanding makes the next technical review more precise without inventing results.




