MVFC.MongoDbFlow
3.3.0
dotnet add package MVFC.MongoDbFlow --version 3.3.0
NuGet\Install-Package MVFC.MongoDbFlow -Version 3.3.0
<PackageReference Include="MVFC.MongoDbFlow" Version="3.3.0" />
<PackageVersion Include="MVFC.MongoDbFlow" Version="3.3.0" />
<PackageReference Include="MVFC.MongoDbFlow" />
paket add MVFC.MongoDbFlow --version 3.3.0
#r "nuget: MVFC.MongoDbFlow, 3.3.0"
#:package MVFC.MongoDbFlow@3.3.0
#addin nuget:?package=MVFC.MongoDbFlow&version=3.3.0
#tool nuget:?package=MVFC.MongoDbFlow&version=3.3.0
MVFC.MongoDbFlow
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.MongoTransactionScopegives 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
| Product | Versions 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. |
-
net10.0
- Microsoft.Extensions.DependencyInjection (>= 10.0.3)
- MongoDB.Driver (>= 3.7.0)
-
net9.0
- Microsoft.Extensions.DependencyInjection (>= 9.0.13)
- MongoDB.Driver (>= 3.7.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.