Table of Contents

BytLabs.DataAccess.MongoDB

MongoDB implementation of the data-access abstractions: a generic repository, per-tenant database resolution, BSON class-map/serializer setup, dynamic-data query helpers, and health checks.

Install

<PackageReference Include="BytLabs.DataAccess.MongoDB" />
<PackageReference Include="HotChocolate.Data.MongoDb" />   <!-- if using GraphQL Mongo querying -->

Requires a MongoDB server ≥ 4.2 (MongoDB.Driver 3.x). Use mongo:7.0 for local/dev.

What's inside

Type / method Purpose
AddMongoDatabase(config) Registers conventions, base class maps, JsonElement serializer, IMongoClient, per-tenant IMongoDatabase, IUnitOfWork, health checks
AddMongoRepository<TEntity,TId>(collectionName?, autoMap?, configureEntity?) Registers IRepository<TEntity,TId> (MongoRepository) + collection + domain-event decorator
MongoDatabaseConfiguration DatabaseName, ConnectionString (+ inherited UseTransactions, IgnoreDatabaseNamingConvention)
IMongoDatabase.GetCollection<T>() (extension) Gets the collection using the conventional name for T
MongoDatabaseFactory / MongoDatabaseHelper Resolve "{baseName}-{tenantId}" database per tenant; collection naming
IAggregateFluentExtensions ExcludeSoftDeletedEntites(), ApplyDynamicDataFilteration(filter), AppySortingWithDynamicData(order)
FilterDefinitionBuilderExtensions FilterData(...) / FilterDataField(...) translate dynamic-data filters to Mongo filters
JsonElementSerializer, MongoDbConventions Store JsonElement natively; camelCase + naming conventions

Registration

var mongoConfig = configuration.GetConfiguration<MongoDatabaseConfiguration>();

services.AddMongoDatabase(mongoConfig)
    .AddMongoRepository<Order, Guid>()
    .AddMongoRepository<Product, Guid>();

appsettings.json:

{
  "MongoDatabaseConfiguration": {
    "DatabaseName": "microserviceTemplate",
    "ConnectionString": "mongodb://localhost:27017?retryWrites=false",
    "UseTransactions": false
  }
}

AddMongoDatabase automatically:

  • registers MongoDB conventions (unless IgnoreDatabaseNamingConvention),
  • maps Entity<string>/Entity<Guid> ids and unmaps AggregateRootBase.DomainEvents,
  • registers a JsonElement serializer (for IHaveDynamicData.Data),
  • registers a per-request, per-tenant IMongoDatabase (resolved via ITenantIdProviderMongoDatabaseFactory.GetDatabaseForTenant, database name "{DatabaseName}-{tenantId}"),
  • wires IUnitOfWork and health checks.

AddMongoRepository<TEntity,TId> registers the repository and the domain-event dispatch decorator, and lets you customize the collection name / class map:

services.AddMongoRepository<Product, Guid>(
    collectionName: "products",
    configureEntity: cm => cm.MapMember(p => p.Name).SetIsRequired(true));

Usage

Repository (in command handlers)

Inject IRepository<TEntity,TId> (from BytLabs.Application):

var product = await _repository.GetByIdAsync(id, ct);   // throws EntityNotFoundException if missing
product.Rename("New name");
await _repository.UpdateAsync(product, ct);             // domain events dispatch after this succeeds

Custom queries (GraphQL resolvers)

Use the IMongoDatabase + GetCollection<T>() extension and project to a DTO:

public IExecutable<ProductDto> GetProducts([Service] IMongoDatabase db)
    => db.GetCollection<Product>()
         .Aggregate()
         .Project(Builders<Product>.Projection.As<ProductDto>())
         .AsExecutable();

Soft-delete + dynamic-data filtering/sorting

On a Mongo aggregate pipeline (for IHaveDynamicData + ISoftDeletable aggregates):

db.GetCollection<Product>()
  .Aggregate()
  .ExcludeSoftDeletedEntites()                  // filters IsDeleted == true
  .ApplyDynamicDataFilteration(dynamicFilter)   // filters over the JSON `data` field
  .AppySortingWithDynamicData(order)            // sort, incl. dynamic-data paths
  .Project(Builders<Product>.Projection.As<ProductDto>())
  .AsExecutable();

FilterData/FilterDataField map a InputFilteringDynamicData (And/Or/operation tree) onto data.<path> with operators Eq/Ne/Gt/Lt/Lte/Gte/Contains, typed via ValueKind (Number/String/Boolean/DateTime).

Multitenancy

Tenant isolation is automatic and physical: each tenant gets database "{DatabaseName}-{tenantId}", resolved from the request via BytLabs.Multitenancy. No tenant field or per-query filter is needed in your code.

BSON class maps for value objects / immutables

Register class maps in your infrastructure for value objects or constructor-based types whose parameters don't match members (see BytLabs.Domain value objects). Constructor parameter names and types should match members so the creator auto-configures.