{"id":34897063,"url":"https://github.com/marcus-v-freitas/mvfc.mongodbflow","last_synced_at":"2026-04-03T18:01:13.174Z","repository":{"id":330216188,"uuid":"1122013756","full_name":"Marcus-V-Freitas/MVFC.MongoDbFlow","owner":"Marcus-V-Freitas","description":"A purpose-driven .NET library that provides a clean, opinionated surface for managing MongoDB repositories, entity mapping, and transactional data flows.","archived":false,"fork":false,"pushed_at":"2026-03-19T01:48:52.000Z","size":4577,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-19T15:35:54.620Z","etag":null,"topics":["asp-net-core","crud","csharp","di-container","dotnet","flow","integration-testing","mapping","mongo-driver","mongodb","nosql","repository","repository-pattern","testcontainers","transaction","unit-of-work","unit-testing"],"latest_commit_sha":null,"homepage":"https://www.nuget.org/packages/MVFC.MongoDbFlow","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Marcus-V-Freitas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-24T00:41:17.000Z","updated_at":"2026-03-19T01:50:21.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Marcus-V-Freitas/MVFC.MongoDbFlow","commit_stats":null,"previous_names":["marcus-v-freitas/mvfc.mongodbflow"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/Marcus-V-Freitas/MVFC.MongoDbFlow","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Marcus-V-Freitas%2FMVFC.MongoDbFlow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Marcus-V-Freitas%2FMVFC.MongoDbFlow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Marcus-V-Freitas%2FMVFC.MongoDbFlow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Marcus-V-Freitas%2FMVFC.MongoDbFlow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Marcus-V-Freitas","download_url":"https://codeload.github.com/Marcus-V-Freitas/MVFC.MongoDbFlow/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Marcus-V-Freitas%2FMVFC.MongoDbFlow/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31368156,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-03T17:53:18.093Z","status":"ssl_error","status_checked_at":"2026-04-03T17:53:17.617Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asp-net-core","crud","csharp","di-container","dotnet","flow","integration-testing","mapping","mongo-driver","mongodb","nosql","repository","repository-pattern","testcontainers","transaction","unit-of-work","unit-testing"],"created_at":"2025-12-26T07:36:31.650Z","updated_at":"2026-04-03T18:01:13.160Z","avatar_url":"https://github.com/Marcus-V-Freitas.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MVFC.MongoDbFlow\n\n\u003e 🇧🇷 [Leia em Português](README.pt-BR.md)\n\n[![CI](https://github.com/Marcus-V-Freitas/MVFC.MongoDbFlow/actions/workflows/ci.yml/badge.svg)](https://github.com/Marcus-V-Freitas/MVFC.MongoDbFlow/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/Marcus-V-Freitas/MVFC.MongoDbFlow/branch/main/graph/badge.svg)](https://codecov.io/gh/Marcus-V-Freitas/MVFC.MongoDbFlow)\n[![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)\n![Platform](https://img.shields.io/badge/.NET-9%20%7C%2010-blue)\n[![NuGet](https://img.shields.io/nuget/dt/MVFC.MongoDbFlow)](https://www.nuget.org/packages/MVFC.MongoDbFlow)\n\nA .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.\n\n## Motivation\n\nWorking with the MongoDB C# driver directly means dealing with:\n\n- Repetitive boilerplate for every collection (insert, find, update, delete, paging…).\n- Manual BSON class-map registration scattered across startup code.\n- No standard pattern for transactions, soft-delete or bulk operations.\n- Wiring serializers for common types (`Guid`, `DateOnly`, enums) by hand.\n\n**MVFC.MongoDbFlow** solves this by providing a thin, opinionated layer on top of the official driver:\n\n- A single `AddMongoFlow(...)` call registers everything — client, database, serializers, maps, context factory and unit-of-work factory.\n- `IMongoRepository\u003cT, TId\u003e` exposes **25+ async methods** covering CRUD, paging, projections, soft-delete/restore, distinct queries and bulk writes.\n- `MongoTransactionScope` gives you a simple, `IAsyncDisposable`-based transaction scope.\n- `EntityMap\u003cT\u003e` keeps BSON mapping next to the entity, making it easy to find and maintain.\n\nThe goal is simple: let you focus on **domain logic** instead of infrastructure plumbing.\n\n## Features\n\n| Category | Capabilities |\n|---|---|\n| **Repository** | Insert · InsertMany · GetOne · Find · FindPaged · Exists · Count · Distinct · Projections |\n| **Updates** | Update (by id/filter) · UpdateMany · UpdateFields · Replace · FindOneAndUpdate |\n| **Deletes** | Delete (by id/filter) · DeleteMany · FindOneAndDelete |\n| **Soft Delete** | SoftDelete (by id/filter) · Restore (by id/filter) |\n| **Bulk** | BulkWrite (mixed operations) |\n| **Transactions** | MongoTransactionScope with auto-rollback on dispose |\n| **Mapping** | EntityMap\\\u003cT\\\u003e with fluent BSON configuration |\n| **Serializers** | Guid · DateOnly · Enum-as-string · UTC DateTime |\n| **DI** | One-line `AddMongoFlow(...)` registration |\n| **Testing** | Integration tests with Testcontainers |\n\n---\n\n## Installation\n\n```sh\ndotnet add package MVFC.MongoDbFlow\n```\n\n---\n\n## Usage Examples\n\n### 1. Define Your Entities\n\n```csharp\npublic sealed record User(Guid Id, string Name, DateOnly BirthDate);\n\npublic enum OrderStatus { Created, Paid, Cancelled, Shipped }\n\npublic sealed record Order(\n    Guid Id,\n    Guid UserId,\n    OrderStatus Status,\n    decimal TotalAmount,\n    DateTime CreatedAt);\n```\n\n### 2. Create Entity Maps\n\nEach map defines the collection name and the BSON-level mapping for its entity:\n\n```csharp\npublic sealed class UserMap : EntityMap\u003cUser\u003e\n{\n    public override string CollectionName =\u003e \"users\";\n    protected override void Configure(BsonClassMap\u003cUser\u003e cm)\n    {\n        cm.AutoMap();\n        cm.MapIdMember(x =\u003e x.Id);\n        cm.MapMember(x =\u003e x.Name).SetIsRequired(true);\n        cm.MapMember(x =\u003e x.BirthDate);\n    }\n}\n\npublic sealed class OrderMap : EntityMap\u003cOrder\u003e\n{\n    public override string CollectionName =\u003e \"orders\";\n    protected override void Configure(BsonClassMap\u003cOrder\u003e cm)\n    {\n        cm.AutoMap();\n        cm.MapIdMember(x =\u003e x.Id);\n        cm.MapMember(x =\u003e x.Status);\n        cm.MapMember(x =\u003e x.TotalAmount);\n        cm.MapMember(x =\u003e x.CreatedAt);\n    }\n}\n```\n\n### 3. Register with Dependency Injection\n\nA single call registers the MongoDB client, database, serializers, entity maps, context factory and unit-of-work factory:\n\n```csharp\nvar services = new ServiceCollection();\n\nservices.AddMongoFlow(\n    new MongoOptions(\"mongodb://localhost:27017\", \"my-database\"),\n    serializers:\n    [\n        new GuidSerializerRegistration(),\n        new DateOnlySerializerRegistration(),\n        new UtcDateTimeSerializerRegistration(),\n        new EnumAsStringSerializerRegistration()\n    ],\n    maps: [new UserMap(), new OrderMap()]);\n```\n\n### 4. Basic CRUD Operations\n\n```csharp\n// Resolve the context factory (typically injected via constructor)\nvar contextFactory = provider.GetRequiredService\u003cIMongoContextFactory\u003e();\nvar context = contextFactory.Create();\nvar repo = context.GetRepository\u003cUser, Guid\u003e();\n\n// Insert\nvar user = new User(Guid.NewGuid(), \"Alice\", new DateOnly(1995, 6, 15));\nawait repo.InsertAsync(user);\n\n// Insert many\nawait repo.InsertManyAsync([\n    new User(Guid.NewGuid(), \"Bob\",     new DateOnly(1988, 3, 22)),\n    new User(Guid.NewGuid(), \"Charlie\", new DateOnly(2001, 11, 5))\n]);\n\n// Get by ID\nvar loaded = await repo.GetOneAsync(user.Id);\n\n// Get by filter\nvar alice = await repo.GetOneAsync(\n    Builders\u003cUser\u003e.Filter.Eq(u =\u003e u.Name, \"Alice\"));\n\n// Find (list) with filter\nvar allUsers = await repo.FindAsync(Builders\u003cUser\u003e.Filter.Empty);\n\n// Check existence\nbool exists = await repo.ExistsAsync(\n    Builders\u003cUser\u003e.Filter.Eq(u =\u003e u.Name, \"Alice\"));\n\n// Count\nlong total = await repo.CountAsync(Builders\u003cUser\u003e.Filter.Empty);\n\n// Update by ID\nawait repo.UpdateAsync(user.Id,\n    Builders\u003cUser\u003e.Update.Set(x =\u003e x.Name, \"Alice Smith\"));\n\n// Update many\nawait repo.UpdateManyAsync(\n    Builders\u003cUser\u003e.Filter.Gte(u =\u003e u.BirthDate, new DateOnly(2000, 1, 1)),\n    Builders\u003cUser\u003e.Update.Set(x =\u003e x.Name, \"Young User\"));\n\n// Replace entire document\nvar updated = user with { Name = \"Alice Johnson\" };\nawait repo.ReplaceAsync(updated, user.Id);\n\n// Delete by ID\nawait repo.DeleteAsync(user.Id);\n\n// Delete many\nawait repo.DeleteManyAsync(\n    Builders\u003cUser\u003e.Filter.Eq(u =\u003e u.Name, \"Young User\"));\n```\n\n### 5. Pagination\n\n`FindPagedAsync` returns a `PagedResult\u003cT\u003e` with `Items`, `TotalCount`, `PageIndex`, `PageSize` and a computed `PageCount`:\n\n```csharp\nvar page = await repo.FindPagedAsync(\n    filter:    Builders\u003cUser\u003e.Filter.Empty,\n    pageIndex: 0,\n    pageSize:  10,\n    sort:      Builders\u003cUser\u003e.Sort.Ascending(u =\u003e u.Name));\n\nConsole.WriteLine($\"Page {page.PageIndex + 1} of {page.PageCount}\");\nConsole.WriteLine($\"Total items: {page.TotalCount}\");\n\nforeach (var item in page.Items)\n    Console.WriteLine($\"  {item.Name}\");\n```\n\n### 6. Projections\n\nReturn only the fields you need by specifying a projection type:\n\n```csharp\npublic sealed record UserSummary(Guid Id, string Name);\n\nvar summaries = await repo.FindAsync(\n    Builders\u003cUser\u003e.Filter.Empty,\n    Builders\u003cUser\u003e.Projection.Expression(u =\u003e new UserSummary(u.Id, u.Name)));\n```\n\n### 7. Distinct Values\n\nRetrieve distinct values for a specific field:\n\n```csharp\nvar uniqueNames = await repo.DistinctAsync\u003cstring\u003e(\n    new StringFieldDefinition\u003cUser, string\u003e(\"Name\"),\n    Builders\u003cUser\u003e.Filter.Empty);\n```\n\n### 8. Soft Delete \u0026 Restore\n\nMark documents as deleted without physically removing them, then restore when needed:\n\n```csharp\n// Soft-delete by ID (sets an \"IsDeleted\" field to true)\nawait repo.SoftDeleteAsync(user.Id);\n\n// Soft-delete by filter\nawait repo.SoftDeleteAsync(\n    Builders\u003cUser\u003e.Filter.Eq(u =\u003e u.Name, \"Bob\"));\n\n// Restore by ID\nawait repo.RestoreAsync(user.Id);\n\n// Restore by filter\nawait repo.RestoreAsync(\n    Builders\u003cUser\u003e.Filter.Eq(u =\u003e u.Name, \"Bob\"));\n```\n\n### 9. Find-and-Modify (Atomic Operations)\n\nAtomically find, update (or delete) and return the document:\n\n```csharp\n// Find one and update — returns the document AFTER the update\nvar result = await repo.FindOneAndUpdateAsync(\n    user.Id,\n    Builders\u003cUser\u003e.Update.Set(u =\u003e u.Name, \"Updated Alice\"),\n    new FindOneAndUpdateOptions\u003cUser\u003e { ReturnDocument = ReturnDocument.After });\n\n// Find one and delete — returns the removed document\nvar removed = await repo.FindOneAndDeleteAsync(user.Id);\n```\n\n### 10. Bulk Write\n\nExecute multiple write operations in a single round-trip:\n\n```csharp\nvar newId = Guid.NewGuid();\n\nawait repo.BulkWriteAsync([\n    new InsertOneModel\u003cUser\u003e(new User(newId, \"Bulk User\", new DateOnly(1990, 1, 1))),\n    new UpdateOneModel\u003cUser\u003e(\n        Builders\u003cUser\u003e.Filter.Eq(u =\u003e u.Id, newId),\n        Builders\u003cUser\u003e.Update.Set(u =\u003e u.Name, \"Renamed\")),\n    new DeleteOneModel\u003cUser\u003e(\n        Builders\u003cUser\u003e.Filter.Eq(u =\u003e u.Name, \"Charlie\"))\n]);\n```\n\n### 11. Transactions\n\n`MongoTransactionScope` wraps a `IMongoUnitOfWork` with `IAsyncDisposable` — if `CommitAsync()` is not called, the transaction is automatically rolled back on dispose:\n\n```csharp\nvar uowFactory = provider.GetRequiredService\u003cIMongoUnitOfWorkFactory\u003e();\n\nawait using (var tx = new MongoTransactionScope(uowFactory))\n{\n    var userRepo  = tx.Uow.GetRepository\u003cUser, Guid\u003e();\n    var orderRepo = tx.Uow.GetRepository\u003cOrder, Guid\u003e();\n\n    var userId = Guid.NewGuid();\n    await userRepo.InsertAsync(\n        new User(userId, \"Transactional User\", new DateOnly(2000, 1, 1)));\n\n    await orderRepo.InsertAsync(\n        new Order(Guid.NewGuid(), userId, OrderStatus.Created, 99.90m, DateTime.UtcNow));\n\n    // Both inserts are committed atomically\n    await tx.CommitAsync();\n}\n// If an exception occurs before CommitAsync(), both operations are rolled back.\n```\n\n---\n\n## Project Structure\n\n```\nsrc/\n  MVFC.MongoDbFlow/\n    Abstractions/       # Interfaces (IMongoRepository, IMongoContext, etc.)\n    Bootstrap/          # MongoBootstrap — client/database initialization\n    Config/             # MongoOptions\n    Context/            # MongoContext, MongoContextFactory\n    Extensions/         # AddMongoFlow, GetRepository\n    Mapping/            # EntityMap\u003cT\u003e, MongoMappingRegistry\n    Models/             # PagedResult\u003cT\u003e\n    Repositories/       # MongoRepository\u003cT, TId\u003e\n    Resolver/           # CollectionNameResolver\n    Serialization/      # Custom serializers (Guid, DateOnly, Enum, UTC)\n    UnitOfWork/         # MongoTransactionScope, MongoUnitOfWork\ntests/\n  MVFC.MongoDbFlow.Tests/\n```\n\n---\n\n## Requirements\n\n- .NET 9 or .NET 10\n- MongoDB (local, Atlas or container)\n- Docker (for running integration tests with Testcontainers)\n\n---\n\n## Integration Tests\n\nThe test project uses [Testcontainers](https://github.com/testcontainers/testcontainers-dotnet) to spin up isolated MongoDB instances during test execution, ensuring reliability and reproducibility. Tests cover:\n\n- Document insertion, retrieval, filtering and deletion\n- Pagination and projections\n- Soft-delete and restore\n- Transaction commit and rollback\n- Multiple repositories within the same transaction\n- Bulk write operations\n\n---\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\n\n[Apache-2.0](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcus-v-freitas%2Fmvfc.mongodbflow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcus-v-freitas%2Fmvfc.mongodbflow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcus-v-freitas%2Fmvfc.mongodbflow/lists"}