MongoDB
C# Driver
Dynamic Filters
Query Building
Nested Filters

Mongo C driver - Building filter dynamically with nesting

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When a MongoDB query depends on optional user input, the clean approach is to build small filter fragments and combine them at the end. With the MongoDB C# driver, that usually means collecting FilterDefinition objects and nesting them with And and Or only where the logic actually requires it.

Start With the Filter Builder

The C# driver exposes a fluent API through Builders<T>.Filter. That builder creates strongly typed query fragments without forcing you to concatenate raw BSON strings.

Suppose you have this model:

csharp
1using System;
2using System.Collections.Generic;
3
4public class Order
5{
6    public string Status { get; set; } = "";
7    public decimal Total { get; set; }
8    public DateTime CreatedAt { get; set; }
9    public bool Deleted { get; set; }
10    public string CustomerName { get; set; } = "";
11}

Now imagine a search screen where status, minimum total, and a free-text customer filter are all optional.

Build Filters Incrementally

A good pattern is to add only the filters that are actually requested:

csharp
1using MongoDB.Driver;
2using System;
3using System.Collections.Generic;
4
5public static class OrderFilters
6{
7    public static FilterDefinition<Order> Build(
8        string? status,
9        decimal? minTotal,
10        string? customer,
11        bool includeDeleted)
12    {
13        var f = Builders<Order>.Filter;
14        var filters = new List<FilterDefinition<Order>>();
15
16        if (!string.IsNullOrWhiteSpace(status))
17            filters.Add(f.Eq(x => x.Status, status));
18
19        if (minTotal.HasValue)
20            filters.Add(f.Gte(x => x.Total, minTotal.Value));
21
22        if (!string.IsNullOrWhiteSpace(customer))
23            filters.Add(f.Regex(x => x.CustomerName, new MongoDB.Bson.BsonRegularExpression(customer, "i")));
24
25        if (!includeDeleted)
26            filters.Add(f.Eq(x => x.Deleted, false));
27
28        return filters.Count == 0 ? f.Empty : f.And(filters);
29    }
30}

This is dynamic, type-safe, and easy to extend.

Add Nesting Only for Real Logic Groups

Nested filters become necessary when the logic is not just a flat AND. For example, “active or pending orders, and not deleted” should be grouped explicitly.

csharp
1var f = Builders<Order>.Filter;
2
3var statusGroup = f.Or(
4    f.Eq(x => x.Status, "active"),
5    f.Eq(x => x.Status, "pending")
6);
7
8var finalFilter = f.And(
9    statusGroup,
10    f.Eq(x => x.Deleted, false)
11);

The important point is structure. Create subgroups as variables when the logic has real parentheses. That makes the final query readable and avoids mistakes when conditions grow.

Why This Pattern Scales Better Than Raw BSON

You can build equivalent queries with BsonDocument, but strongly typed filters catch property-name mistakes at compile time and refactor more safely. If CustomerName is renamed, the lambda-based filter breaks at compile time instead of silently producing a bad query.

That matters once dynamic filtering starts to grow across multiple screens or API endpoints.

Debugging the Generated Query

When filters become complicated, it helps to render the generated BSON during debugging so you can verify the logical shape.

csharp
1var serializerRegistry = MongoDB.Bson.Serialization.BsonSerializer.SerializerRegistry;
2var documentSerializer = serializerRegistry.GetSerializer<Order>();
3var rendered = finalFilter.Render(documentSerializer, serializerRegistry);
4Console.WriteLine(rendered);

This is especially useful when a dynamic Or group is being combined with several And conditions and you want to confirm the nesting is correct.

Common Pitfalls

A common mistake is calling And or Or too early and then trying to mutate the combined filter later. It is usually easier to collect small filters first and combine them once.

Another mistake is building regex filters directly from raw user input without considering escaping and performance. Dynamic does not mean unbounded.

A third mistake is using string field names everywhere when a typed model is available. Strongly typed expressions are more maintainable.

Summary

  • Build dynamic MongoDB filters by collecting small FilterDefinition fragments.
  • Use Builders<T>.Filter and strongly typed expressions whenever possible.
  • Introduce nested And and Or groups only when the query logic truly needs grouping.
  • Render the final BSON while debugging to confirm the generated structure.
  • Keep regex and user-driven filter logic controlled so the query stays correct and maintainable.

Course illustration
Course illustration

All Rights Reserved.