MVFC.MongoDbFlow 3.3.0

dotnet add package MVFC.MongoDbFlow --version 3.3.0
                    
NuGet\Install-Package MVFC.MongoDbFlow -Version 3.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MVFC.MongoDbFlow" Version="3.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MVFC.MongoDbFlow" Version="3.3.0" />
                    
Directory.Packages.props
<PackageReference Include="MVFC.MongoDbFlow" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add MVFC.MongoDbFlow --version 3.3.0
                    
#r "nuget: MVFC.MongoDbFlow, 3.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package MVFC.MongoDbFlow@3.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=MVFC.MongoDbFlow&version=3.3.0
                    
Install as a Cake Addin
#tool nuget:?package=MVFC.MongoDbFlow&version=3.3.0
                    
Install as a Cake Tool

MVFC.MongoDbFlow

🇧🇷 Leia em Português

CI codecov License Platform NuGet

A .NET library for generic MongoDB access, entity mapping, and async CRUD operations — including repository abstraction, custom serializers, transactions, soft-delete, pagination, and Dependency Injection integration.

Motivation

Working with the MongoDB C# driver directly means dealing with:

  • Repetitive boilerplate for every collection (insert, find, update, delete, paging…).
  • Manual BSON class-map registration scattered across startup code.
  • No standard pattern for transactions, soft-delete or bulk operations.
  • Wiring serializers for common types (Guid, DateOnly, enums) by hand.

MVFC.MongoDbFlow solves this by providing a thin, opinionated layer on top of the official driver:

  • A single AddMongoFlow(...) call registers everything — client, database, serializers, maps, context factory and unit-of-work factory.
  • IMongoRepository<T, TId> exposes 25+ async methods covering CRUD, paging, projections, soft-delete/restore, distinct queries and bulk writes.
  • MongoTransactionScope gives you a simple, IAsyncDisposable-based transaction scope.
  • EntityMap<T> keeps BSON mapping next to the entity, making it easy to find and maintain.

The goal is simple: let you focus on domain logic instead of infrastructure plumbing.

Features

Category Capabilities
Repository Insert · InsertMany · GetOne · Find · FindPaged · Exists · Count · Distinct · Projections
Updates Update (by id/filter) · UpdateMany · UpdateFields · Replace · FindOneAndUpdate
Deletes Delete (by id/filter) · DeleteMany · FindOneAndDelete
Soft Delete SoftDelete (by id/filter) · Restore (by id/filter)
Bulk BulkWrite (mixed operations)
Transactions MongoTransactionScope with auto-rollback on dispose
Mapping EntityMap<T> with fluent BSON configuration
Serializers Guid · DateOnly · Enum-as-string · UTC DateTime
DI One-line AddMongoFlow(...) registration
Testing Integration tests with Testcontainers

Installation

dotnet add package MVFC.MongoDbFlow

Usage Examples

1. Define Your Entities

public sealed record User(Guid Id, string Name, DateOnly BirthDate);

public enum OrderStatus { Created, Paid, Cancelled, Shipped }

public sealed record Order(
    Guid Id,
    Guid UserId,
    OrderStatus Status,
    decimal TotalAmount,
    DateTime CreatedAt);

2. Create Entity Maps

Each map defines the collection name and the BSON-level mapping for its entity:

public sealed class UserMap : EntityMap<User>
{
    public override string CollectionName => "users";
    protected override void Configure(BsonClassMap<User> cm)
    {
        cm.AutoMap();
        cm.MapIdMember(x => x.Id);
        cm.MapMember(x => x.Name).SetIsRequired(true);
        cm.MapMember(x => x.BirthDate);
    }
}

public sealed class OrderMap : EntityMap<Order>
{
    public override string CollectionName => "orders";
    protected override void Configure(BsonClassMap<Order> cm)
    {
        cm.AutoMap();
        cm.MapIdMember(x => x.Id);
        cm.MapMember(x => x.Status);
        cm.MapMember(x => x.TotalAmount);
        cm.MapMember(x => x.CreatedAt);
    }
}

3. Register with Dependency Injection

A single call registers the MongoDB client, database, serializers, entity maps, context factory and unit-of-work factory:

var services = new ServiceCollection();

services.AddMongoFlow(
    new MongoOptions("mongodb://localhost:27017", "my-database"),
    serializers:
    [
        new GuidSerializerRegistration(),
        new DateOnlySerializerRegistration(),
        new UtcDateTimeSerializerRegistration(),
        new EnumAsStringSerializerRegistration()
    ],
    maps: [new UserMap(), new OrderMap()]);

4. Basic CRUD Operations

// Resolve the context factory (typically injected via constructor)
var contextFactory = provider.GetRequiredService<IMongoContextFactory>();
var context = contextFactory.Create();
var repo = context.GetRepository<User, Guid>();

// Insert
var user = new User(Guid.NewGuid(), "Alice", new DateOnly(1995, 6, 15));
await repo.InsertAsync(user);

// Insert many
await repo.InsertManyAsync([
    new User(Guid.NewGuid(), "Bob",     new DateOnly(1988, 3, 22)),
    new User(Guid.NewGuid(), "Charlie", new DateOnly(2001, 11, 5))
]);

// Get by ID
var loaded = await repo.GetOneAsync(user.Id);

// Get by filter
var alice = await repo.GetOneAsync(
    Builders<User>.Filter.Eq(u => u.Name, "Alice"));

// Find (list) with filter
var allUsers = await repo.FindAsync(Builders<User>.Filter.Empty);

// Check existence
bool exists = await repo.ExistsAsync(
    Builders<User>.Filter.Eq(u => u.Name, "Alice"));

// Count
long total = await repo.CountAsync(Builders<User>.Filter.Empty);

// Update by ID
await repo.UpdateAsync(user.Id,
    Builders<User>.Update.Set(x => x.Name, "Alice Smith"));

// Update many
await repo.UpdateManyAsync(
    Builders<User>.Filter.Gte(u => u.BirthDate, new DateOnly(2000, 1, 1)),
    Builders<User>.Update.Set(x => x.Name, "Young User"));

// Replace entire document
var updated = user with { Name = "Alice Johnson" };
await repo.ReplaceAsync(updated, user.Id);

// Delete by ID
await repo.DeleteAsync(user.Id);

// Delete many
await repo.DeleteManyAsync(
    Builders<User>.Filter.Eq(u => u.Name, "Young User"));

5. Pagination

FindPagedAsync returns a PagedResult<T> with Items, TotalCount, PageIndex, PageSize and a computed PageCount:

var page = await repo.FindPagedAsync(
    filter:    Builders<User>.Filter.Empty,
    pageIndex: 0,
    pageSize:  10,
    sort:      Builders<User>.Sort.Ascending(u => u.Name));

Console.WriteLine($"Page {page.PageIndex + 1} of {page.PageCount}");
Console.WriteLine($"Total items: {page.TotalCount}");

foreach (var item in page.Items)
    Console.WriteLine($"  {item.Name}");

6. Projections

Return only the fields you need by specifying a projection type:

public sealed record UserSummary(Guid Id, string Name);

var summaries = await repo.FindAsync(
    Builders<User>.Filter.Empty,
    Builders<User>.Projection.Expression(u => new UserSummary(u.Id, u.Name)));

7. Distinct Values

Retrieve distinct values for a specific field:

var uniqueNames = await repo.DistinctAsync<string>(
    new StringFieldDefinition<User, string>("Name"),
    Builders<User>.Filter.Empty);

8. Soft Delete & Restore

Mark documents as deleted without physically removing them, then restore when needed:

// Soft-delete by ID (sets an "IsDeleted" field to true)
await repo.SoftDeleteAsync(user.Id);

// Soft-delete by filter
await repo.SoftDeleteAsync(
    Builders<User>.Filter.Eq(u => u.Name, "Bob"));

// Restore by ID
await repo.RestoreAsync(user.Id);

// Restore by filter
await repo.RestoreAsync(
    Builders<User>.Filter.Eq(u => u.Name, "Bob"));

9. Find-and-Modify (Atomic Operations)

Atomically find, update (or delete) and return the document:

// Find one and update — returns the document AFTER the update
var result = await repo.FindOneAndUpdateAsync(
    user.Id,
    Builders<User>.Update.Set(u => u.Name, "Updated Alice"),
    new FindOneAndUpdateOptions<User> { ReturnDocument = ReturnDocument.After });

// Find one and delete — returns the removed document
var removed = await repo.FindOneAndDeleteAsync(user.Id);

10. Bulk Write

Execute multiple write operations in a single round-trip:

var newId = Guid.NewGuid();

await repo.BulkWriteAsync([
    new InsertOneModel<User>(new User(newId, "Bulk User", new DateOnly(1990, 1, 1))),
    new UpdateOneModel<User>(
        Builders<User>.Filter.Eq(u => u.Id, newId),
        Builders<User>.Update.Set(u => u.Name, "Renamed")),
    new DeleteOneModel<User>(
        Builders<User>.Filter.Eq(u => u.Name, "Charlie"))
]);

11. Transactions

MongoTransactionScope wraps a IMongoUnitOfWork with IAsyncDisposable — if CommitAsync() is not called, the transaction is automatically rolled back on dispose:

var uowFactory = provider.GetRequiredService<IMongoUnitOfWorkFactory>();

await using (var tx = new MongoTransactionScope(uowFactory))
{
    var userRepo  = tx.Uow.GetRepository<User, Guid>();
    var orderRepo = tx.Uow.GetRepository<Order, Guid>();

    var userId = Guid.NewGuid();
    await userRepo.InsertAsync(
        new User(userId, "Transactional User", new DateOnly(2000, 1, 1)));

    await orderRepo.InsertAsync(
        new Order(Guid.NewGuid(), userId, OrderStatus.Created, 99.90m, DateTime.UtcNow));

    // Both inserts are committed atomically
    await tx.CommitAsync();
}
// If an exception occurs before CommitAsync(), both operations are rolled back.

Project Structure

src/
  MVFC.MongoDbFlow/
    Abstractions/       # Interfaces (IMongoRepository, IMongoContext, etc.)
    Bootstrap/          # MongoBootstrap — client/database initialization
    Config/             # MongoOptions
    Context/            # MongoContext, MongoContextFactory
    Extensions/         # AddMongoFlow, GetRepository
    Mapping/            # EntityMap<T>, MongoMappingRegistry
    Models/             # PagedResult<T>
    Repositories/       # MongoRepository<T, TId>
    Resolver/           # CollectionNameResolver
    Serialization/      # Custom serializers (Guid, DateOnly, Enum, UTC)
    UnitOfWork/         # MongoTransactionScope, MongoUnitOfWork
tests/
  MVFC.MongoDbFlow.Tests/

Requirements

  • .NET 9 or .NET 10
  • MongoDB (local, Atlas or container)
  • Docker (for running integration tests with Testcontainers)

Integration Tests

The test project uses Testcontainers to spin up isolated MongoDB instances during test execution, ensuring reliability and reproducibility. Tests cover:

  • Document insertion, retrieval, filtering and deletion
  • Pagination and projections
  • Soft-delete and restore
  • Transaction commit and rollback
  • Multiple repositories within the same transaction
  • Bulk write operations

Contributing

See CONTRIBUTING.md.

License

Apache-2.0

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.3.0 104 4/3/2026
3.2.1 112 3/21/2026
3.2.0 102 3/19/2026
3.1.0 98 3/19/2026
1.0.2 128 1/4/2026
1.0.1 213 12/24/2025