Chatilay.MongoContext 0.1.0-preview.1

This is a prerelease version of Chatilay.MongoContext.
dotnet add package Chatilay.MongoContext --version 0.1.0-preview.1
                    
NuGet\Install-Package Chatilay.MongoContext -Version 0.1.0-preview.1
                    
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="Chatilay.MongoContext" Version="0.1.0-preview.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Chatilay.MongoContext" Version="0.1.0-preview.1" />
                    
Directory.Packages.props
<PackageReference Include="Chatilay.MongoContext" />
                    
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 Chatilay.MongoContext --version 0.1.0-preview.1
                    
#r "nuget: Chatilay.MongoContext, 0.1.0-preview.1"
                    
#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 Chatilay.MongoContext@0.1.0-preview.1
                    
#: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=Chatilay.MongoContext&version=0.1.0-preview.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Chatilay.MongoContext&version=0.1.0-preview.1&prerelease
                    
Install as a Cake Tool

Chatilay.MongoContext

CI NuGet

A typed MongoDB context for services that keep their own database. A derived context declares collections as properties; the package owns the client, the database, collection naming, index setup, the audit and soft-delete conventions applied on every write, and a readiness check.

The client is built once per context type and collections are cached, so reading a collection property on every request does not allocate a new connection pool — the failure mode this package exists to prevent.

Installation

dotnet add package Chatilay.MongoContext

Defining a context

public sealed class MessageDocContext(MongoContextDependencies dependencies)
    : MongoDocContext(dependencies)
{
    public IMongoCollection<MessageDoc> Messages => GetCollection<MessageDoc>();
}

Client and Database are exposed on the context for the cases the properties do not cover (transactions, RunCommand, aggregation over a raw collection).

Registration

builder.Services.AddMongoDocContext<MessageDocContext>(builder.Configuration);

Reads the MongoDbSettings section by default:

{
  "MongoDbSettings": {
    "ConnectionString": "mongodb://localhost:27017",
    "DatabaseName": "chatilay_msg_mongo"
  }
}

A different section, and any client tuning:

builder.Services.AddMongoDocContext<MessageDocContext>(
    builder.Configuration,
    sectionName: "MessageMongo",
    configure: options =>
    {
        options.ApplicationName = "message-api";
        options.ConfigureClient = settings =>
        {
            settings.MaxConnectionPoolSize = 200;
            settings.ServerSelectionTimeout = TimeSpan.FromSeconds(5);
        };
    });

Without configuration binding — useful in tests:

services.AddMongoDocContext<MessageDocContext>(options =>
{
    options.ConnectionString = "mongodb://localhost:27017";
    options.DatabaseName = "messages_test";
});

Registration is idempotent. Each context type gets its own client, so two contexts in one process may point at different clusters; two contexts sharing a connection string still get one client each. The client and database are also resolvable as keyed services:

var client = provider.GetRequiredKeyedService<IMongoClient>(typeof(MessageDocContext));

A missing setting fails at resolution with a message naming the context and the section that was searched, rather than a driver-level error later on.

Collection naming

The default strategy appends s to the type name — MessageDoc lands in MessageDocs. Pin a name explicitly when the type name should not leak into the database:

[MongoCollection("messages")]
public class MessageDoc : MongoDocument;

Or replace the strategy for the whole context:

options.NamingStrategy = new DelegateCollectionNamingStrategy(type => type.Name.ToLowerInvariant());

Documents

public class MessageDoc : MongoDocument            // Id: ObjectId
{
    public string Content { get; set; } = null!;
    public string ChannelId { get; set; } = null!;
}

public class AuditEvent : MongoDocument<Guid> { }  // Id: Guid

IMongoDocument<TKey> requires nothing but Id, and IMongoDocument is the shorthand for an ObjectId key. Audit and soft-delete fields are not part of the contract: declare the ones a document actually needs and let the conventions below maintain them. A document that needs none of them stays a plain class.

Conventions

Conventions name the properties the package maintains on write. All three are off until a name is set; UseDefaults() turns them on with the names CreatedAt, UpdatedAt and IsDeleted:

builder.Services.AddMongoDocContext<MessageDocContext>(builder.Configuration, configure: options =>
{
    options.UseDefaultConventions();                         // same as options.Conventions.UseDefaults()
    // options.Conventions.UpdatedAtProperty = null;          // turn just this one off
    // options.Conventions.SoftDeleteProperty = "Deleted";    // or use your own name
});
Convention Applied on Behaviour
CreatedAtProperty insert Stamped with the current time when the caller left it unset
UpdatedAtProperty update, replace Stamped unless the update already writes that field
SoftDeleteProperty NotDeleted, FindActive, soft-delete helpers The boolean flag those helpers read and write

Stamping happens inside the collection the context hands out, so it covers the driver's own API — InsertOneAsync, UpdateOneAsync, ReplaceOneAsync, BulkWriteAsync and their session overloads. There is no separate write API to remember:

await context.Messages.InsertOneAsync(doc, cancellationToken: ct);            // CreatedAt stamped
await context.Messages.UpdateOneAsync(filter, update, cancellationToken: ct); // UpdatedAt added

Date properties may be DateTimeOffset or DateTime, nullable or not; the soft-delete property must be a bool. A property a document does not declare — or declares with a type that cannot be maintained — is skipped with one warning per document type. Reads are skipped the same way rather than filtering on a field that does not exist, which would silently match nothing.

OfType<TDerived>() returns the driver's own filtered collection, so conventions do not follow it; WithReadPreference, WithReadConcern and WithWriteConcern keep them.

Validating the names

A property name is a string, so a typo cannot be caught by the compiler. Check it at startup:

var report = app.Services.ValidateMongoConventions();
if (!report.IsValid)
{
    app.Logger.LogWarning("Mongo conventions: {Issues}", report);
}

It resolves every registered context and lists the conventions that did not apply.

Soft delete

await context.Messages.SoftDeleteOneAsync(id, ct);      // by Id, whatever the key type is
await context.Messages.SoftDeleteManyAsync(filter, ct);

var page = await context.Messages.FindActive(m => m.ChannelId == channelId).ToListAsync(ct);
var filter = context.Messages.NotDeleted() & Builders<MessageDoc>.Filter.Eq(m => m.ChannelId, id);

Soft-deleting a document type that has no soft-delete property throws — doing nothing would look like a successful delete. NotDeleted() on such a type returns an empty filter instead.

Indexes

Declare them next to the documents they belong to:

public sealed class NotificationIndexes : IMongoIndexProvider<NotificationMongoContext, NotificationDoc>
{
    public IMongoCollection<NotificationDoc> GetCollection(NotificationMongoContext context)
        => context.Notifications;

    public IEnumerable<CreateIndexModel<NotificationDoc>> GetIndexes() =>
    [
        new(Builders<NotificationDoc>.IndexKeys
                .Ascending(n => n.TargetUserId)
                .Ascending(n => n.IsRead)
                .Descending(n => n.CreatedAt),
            new CreateIndexOptions { Background = true, Name = "idx_target_user_read_created" })
    ];

    public IEnumerable<string> ObsoleteIndexNames => ["idx_target_user"];
}
builder.Services.AddMongoIndexProviders(typeof(NotificationIndexes).Assembly);
...
await app.Services.EnsureMongoIndexesAsync();

Creating and dropping are both idempotent, so it is safe on every boot; a name in ObsoleteIndexNames that no longer exists is ignored.

Health check

builder.Services.AddHealthChecks()
    .AddMongoContextCheck<MessageDocContext>(tags: ["ready", "db"]);

Runs ping against the context's database.

DateTimeOffset storage

The driver stores DateTimeOffset as a document ({ DateTime, Ticks, Offset }), not as a BSON date. TTL indexes do not work on such a field, and range queries and sorts fall back to document comparison. To store dates as BSON dates instead:

MongoSerializationDefaults.UseUtcDateTimeOffset();   // before the first document is serialized

This changes the on-disk format: documents already written in the document form will fail to deserialize, sub-millisecond precision is lost and the value comes back as UTC. Migrate the collection first — enable it on a new collection, or rewrite the existing one.

MongoSerializationDefaults.UseStandardGuids() is the same kind of opt-in for Guid values. Driver 3.x refuses to serialize a Guid until a representation is chosen, so a document with a Guid key needs this call (or a per-property [BsonGuidRepresentation]).

API surface

Type Purpose
MongoDocContext Base context — GetCollection<TDocument>(), Client, Database
MongoContextDependencies Client, database, naming and conventions handed to a context by the container
IMongoDocument<TKey> / IMongoDocument Id, with any key type or with ObjectId
MongoDocument<TKey> / MongoDocument Base classes for the above
AddMongoDocContext<TContext> Registration, from configuration or explicit options
MongoContextOptions Connection overrides, application name, lifetime, naming, client tuning, conventions
MongoDocumentConventions CreatedAtProperty, UpdatedAtProperty, SoftDeleteProperty, UseDefaults()
ValidateMongoConventions / MongoConventionReport Startup check for convention names that did not resolve
IMongoCollectionNamingStrategy SuffixCollectionNamingStrategy, DelegateCollectionNamingStrategy, [MongoCollection]
IMongoIndexProvider<TContext, TDocument> Index declarations, applied by EnsureMongoIndexesAsync
AddMongoContextCheck<TContext> Readiness check
Query helpers NotDeleted, FindActive, SoftDeleteOneAsync, SoftDeleteManyAsync
MongoSerializationDefaults Opt-in DateTimeOffset and Guid representations

Requirements

.NET 8.0 or later, MongoDB.Driver 3.x.

Building from source

dotnet test -c Release
dotnet pack -c Release

All build output lands under artifacts/ at the repository root.

License

MIT — see LICENSE.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  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 was computed.  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
0.1.0-preview.1 138 9/4/2026