{"id":18494210,"url":"https://github.com/workleap/wl-extensions-mongo","last_synced_at":"2025-04-08T22:31:05.530Z","repository":{"id":152450439,"uuid":"621872140","full_name":"workleap/wl-extensions-mongo","owner":"workleap","description":"Provides MongoDB access through .NET dependency injection, following Microsoft.Extensions.* library practices.","archived":false,"fork":false,"pushed_at":"2025-04-08T02:43:19.000Z","size":387,"stargazers_count":9,"open_issues_count":4,"forks_count":1,"subscribers_count":29,"default_branch":"main","last_synced_at":"2025-04-08T03:31:04.332Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/workleap.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-03-31T15:09:58.000Z","updated_at":"2025-04-07T14:06:27.000Z","dependencies_parsed_at":"2023-05-16T20:00:22.997Z","dependency_job_id":"f03b532a-077b-4d2d-8efd-02d7ea58c292","html_url":"https://github.com/workleap/wl-extensions-mongo","commit_stats":null,"previous_names":["gsoft-inc/wl-extensions-mongo","workleap/wl-extensions-mongo"],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workleap%2Fwl-extensions-mongo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workleap%2Fwl-extensions-mongo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workleap%2Fwl-extensions-mongo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workleap%2Fwl-extensions-mongo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/workleap","download_url":"https://codeload.github.com/workleap/wl-extensions-mongo/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247939995,"owners_count":21021885,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":[],"created_at":"2024-11-06T13:18:23.275Z","updated_at":"2025-04-08T22:31:00.520Z","avatar_url":"https://github.com/workleap.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Workleap.Extensions.Mongo\n\n[![nuget](https://img.shields.io/nuget/v/Workleap.Extensions.Mongo.svg?logo=nuget)](https://www.nuget.org/packages/Workleap.Extensions.Mongo/)\n[![build](https://img.shields.io/github/actions/workflow/status/gsoft-inc/wl-extensions-mongo/publish.yml?logo=github\u0026branch=main)](https://github.com/gsoft-inc/wl-extensions-mongo/actions/workflows/publish.yml)\n\nWorkleap.Extensions.Mongo is a convenient set of .NET libraries designed to enhance and streamline the [MongoDB C# driver](https://github.com/mongodb/mongo-csharp-driver) integration into your C# projects.\n\n## Value proposition and features overview\n\nIntegrating the MongoDB C# driver into your C# projects can often lead to several questions:\n\n* What's the optimal way to configure a MongoDB client from my app configuration? Should I use `appsettings.json`, environment variables, custom option classes, plain C# code?\n* How can I expose the MongoDB client to the rest of my code? Should I use dependency injection?\n* What are the best practices for configuring the MongoDB client? How should MongoDB C# driver static settings be handled?\n* If I want to support multiple MongoDB clusters and/or databases, won't that require a significant refactor of my existing code?\n* What's the most effective way to manage my indexes? Should they be created manually, or within my C# code? How can I ensure that indexes are synchronized with my code?\n* If I have multiple C# applications, how can I prevent MongoDB setup code duplication?\n* How can I instrument my code, considering the MongoDB C# driver doesn't natively support OpenTelemetry?\n* How can I execute integration tests in an isolated environment? Using a shared database requires cleanup, leading to unreliable test results.\n\n**Workleap.Extensions.Mongo** was developed to address these challenges. We offer a straightforward, flexible, and standard approach to adding and configuring the MongoDB C# driver in your C# projects. Here is an overview of the features of Workleap.Extensions.Mongo:\n\n* **Support for dependency injection**: We use [Microsoft's modern dependency injection](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection) (`IServiceCollection`) to expose MongoDB's classes and interfaces.\n* **Standardized configuration**: We leverage [Microsoft's configuration](https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration), enabling easy configuration of MongoDB settings via diverse configuration providers of your choice. The [options pattern](https://learn.microsoft.com/en-us/dotnet/core/extensions/options) simplifies overriding and extending any setting, including static MongoDB settings (custom serializers and convention packs).\n* **Support for multiple MongoDB clusters and/or databases**: You won't need to refactor your entire codebase to support multiple MongoDB data sources - it's supported by default.\n* **Elimination of boilerplate and duplicated code**: Remove redundant, copy-pasted MongoDB C# code from your codebase, enabling you to focus on actually utilizing the driver.\n* **Built-in instrumentation**: We provide built-in support for OpenTelemetry instrumentation, adhering to  [OpenTelemetry's semantic conventions for MongoDB](https://opentelemetry.io/docs/specs/semconv/database/mongodb/). Additionally, we offer an extra NuGet package for Application Insights .NET SDK support.\n* **Optional index management**: Declare indexes in your C# code, and then use our C# API to automatically create and update indexes based on what's declared in your code. Built-in Roslyn analyzers will assist developers in considering new indexes.\n* **Optional async enumerables support**: You can simplify your code by using our extension methods that employ `IAsyncEnumerable` rather than more verbose MongoDB cursors.\n* **Optional field-level encryption**: Implement your own encrypt and decrypt methods, which can then automatically encrypt annotated MongoDB document fields for at-rest security.\n* **Optional ephemeral database for integration tests**: Each of your integration test methods can have its own new MongoDB database operating locally.\n\n## Getting started\n\nWe offer three main NuGet packages:\n\n**Firstly**, [Workleap.Extensions.Mongo](https://www.nuget.org/packages/Workleap.Extensions.Mongo/), is the package that you'd ideally install in your *startup project*, where your main method resides. This is where you would incorporate our library into your dependency injection services and link your configuration to our option classes:\n\n```csharp\n// Method 1: Directly set the options values with C# code\nservices.AddMongo(options =\u003e\n{\n    options.ConnectionString = \"[...]\";\n    options.DefaultDatabaseName = \"marketing\";\n});\n\n// Method 2: Bind the options values to a configuration section\nservices.AddMongo(configuration.GetRequiredSection(\"Mongo\").Bind);\n\n// Method 3: Lazily bind the options values to a configuration section\nservices.AddMongo();\nservices.AddOptions\u003cMongoClientOptions\u003e().Bind(configuration.GetRequiredSection(\"Mongo\"));\n\n// appsettings.json (or any other configuration source such as environment variables or Azure KeyVault)\n{\n  \"Mongo\": {\n    \"ConnectionString\": \"[...]\",\n    \"DefaultDatabaseName\": \"marketing\"\n  }\n}\n\n// Method 4: Implement IConfigureNamedOptions\u003cMongoClientOptions\u003e:\n// https://learn.microsoft.com/en-us/dotnet/core/extensions/options#use-di-services-to-configure-options\n```\n\n**The second NuGet package**, [Workleap.Extensions.Mongo.Abstractions](https://www.nuget.org/packages/Workleap.Extensions.Mongo.Abstractions/), only provides abstractions and extension methods, aligning with the principles of Microsoft's extension libraries such as [Microsoft.Extensions.Logging.Abstractions](https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions/) and [Microsoft.Extensions.Configuration.Abstractions](https://www.nuget.org/packages/Microsoft.Extensions.Configuration.Abstractions/). You would typically install this package in your domain-specific .NET projects to avoid unnecessary NuGet dependencies. **This package already includes the MongoDB C# driver**, so there's no need to install it separately (unless you need a specific version).\n\n```csharp\n// 1) Directly inject a collection bound to the default database\nvar people = serviceProvider.GetRequiredService\u003cIMongoCollection\u003cPersonDocument\u003e\u003e();\n\n// 2) You can inject the default database\nvar people = serviceProvider.GetRequiredService\u003cIMongoDatabase\u003e().GetCollection\u003cPersonDocument\u003e();\n\n// 3) You can inject the default client\nvar people = serviceProvider.GetRequiredService\u003cIMongoClient\u003e()\n    .GetDatabase(\"marketing\").GetCollection\u003cPersonDocument\u003e();\n\n// 4) Finally, you can inject a specific client for a specific registered MongoDB cluster\n// More on that later in this document\nvar people = serviceProvider.GetRequiredService\u003cIMongoClientProvider\u003e()\n    .GetClient(\"mycluster\").GetDatabase(\"marketing\").GetCollection\u003cPersonDocument\u003e();\n\n[MongoCollection(\"people\")]\npublic class PersonDocument : IMongoDocument // IMongoDocument is an empty marker interface (required)\n{\n    // [...]\n}\n```\n\n**The third NuGet package**, [Workleap.Extensions.Mongo.Ephemeral](https://www.nuget.org/packages/Workleap.Extensions.Mongo.Ephemeral/), is designed for your integration tests. It can be utilized whenever you require a real yet ephemeral MongoDB cluster with a single node replica set. Through dependency injection, each integration test method can have access to a unique and isolated database.\n\n## Adding and configuring MongoDB clients\n\nWhen registering your dependency injection services, you can invoke `services.AddMongo(...)` as demonstrated in the previous section. This action registers MongoDB dependencies and the main MongoDB cluster by providing the connection string and default (primary) database name.\n\nIt is also possible to register multiple additional MongoDB clusters:\n\n```csharp\nservices.AddMongo(options =\u003e { /* [...] */ })\n    .AddNamedClient(\"anotherCluster\", options =\u003e { /* [...] */ })\n    .AddNamedClient(\"andAnotherOne\", options =\u003e { /* [...] */ });\n\n// There are many ways to configure these named options\nservices.AddOptions\u003cMongoClientOptions\u003e(\"anotherCluster\").Bind(configuration.GetRequiredSection(\"Mongo:AnotherCluster\"));\nservices.AddOptions\u003cMongoClientOptions\u003e(\"andAnotherOne\").Bind(configuration.GetRequiredSection(\"Mongo:AndAnotherOne\"));\n```\n\nThe `MongoClientOptions` option class further permits you to configure the `MongoClientSettings` for a cluster:\n\n```csharp\nservices.AddMongo(options =\u003e\n{\n    options.MongoClientSettingsConfigurator = settings =\u003e\n    {\n        settings.ApplicationName = \"myapp\";\n\n        settings.ClusterConfigurator = cluster =\u003e\n        {\n            // [...]\n        };\n    };\n});\n```\n\nWhile `MongoClientOptions` is the option class for configuring a specific MongoDB cluster connection, `MongoStaticOptions` is available to customize MongoDB static options such as BSON serializers and convention packs for the entire application:\n\n```csharp\nservices.AddMongo().ConfigureStaticOptions(options =\u003e\n{\n    // There are built-in serializers and conventions registered, but you can remove or override them\n    // ⚠ Caution, these are objects that will live for the entire lifetime of the application (singleton) as the MongoDB C# driver\n    // uses static properties to configure its behavior and serialization\n    options.GuidRepresentationMode = GuidRepresentationMode.V2; // V3 is the default\n    options.BsonSerializers[typeof(Guid)] = new GuidSerializer(GuidRepresentation.Standard);\n    options.ConventionPacks.Add(new MyConventionPack());\n});\n```\n\nYou can also use different options patterns to configure static options:\n\n```csharp\nservices.AddOptions\u003cMongoStaticOptions\u003e().Configure(options =\u003e { /* [...] */ })\n```\n\nWhen using the `AddMongo()` method, multiple conventions are added automatically:\n- `IgnoreExtraElementsConvention(ignoreExtraElements: true)`\n- `EnumRepresentationConvention(BsonType.String)`, so changing an enum member name is a breaking change\n- `DateTime` and `DateTimeOffset` are serialized as `DateTime` instead of the default Ticks or (Ticks, Offset). In MongoDB, DateTime only supports precision up to the milliseconds. If you need more precision, you need to set the serializer at property level.\n\n## Declaring Mongo Documents\n### With Attributes Decoration\n\nThe process doesn't deviate much from the standard way of declaring and using MongoDB collections in C#. However, there are two additional steps:\n\n* You must decorate the document class with the `MongoCollectionAttribute` to specify the collection name,\n* The document class must implement the empty marker interface `IMongoDocument` for generic constraints purposes.\n\n```csharp\n[MongoCollection(\"people\")]\npublic class PersonDocument : IMongoDocument\n{\n    // [...]\n}\n```\n\nFor multiple databases setup, you can specify the database name directly on the attribute. This will have the following effect:\n* The index creation from `UpdateIndexesAsync` can be invoked on the assembly and the indexer will use the proper database\n* The injection of `IMongoCollection\u003cTDocument\u003e` will work, regardless of which database the collection is bound to\n\n```csharp\n[MongoCollection(\"people\", DatabaseName = \"foo\")]\npublic class PersonDocument : IMongoDocument\n{\n    // [...]\n}\n```\n\n### With Configuration\n\nIn certain scenarios, like in Domain Driven Design (DDD), one would like to persist their Domain Aggregates as is in the Document Database. These Domain objects are not aware of how they are persisted. They cannot be decorated with Persistence level attributes (ie `[MongoCollection()]`), nor can they implement `IMongoDocument`.\n\nYou can configure your Object to Database mapping throught `IMongoCollectionConfiguration\u003cTDocument\u003e` instead.\n\n```csharp\npublic sealed class Person\n{\n    // [...]\n}\n```\n\n```csharp\ninternal sealed class PersonConfiguration: IMongoCollectionConfiguration\u003cPerson\u003e\n{\n    public void Configure(IMongoCollectionBuilder\u003cPerson\u003e builder) \n    {\n        builder.CollectionName(\"people\");\n        builder.DatabaseName(\"foo\"); // optional, not calling this will use the default database\n    }\n}\n```\n\n#### Bootstrapping Configurations\n\nSince the Configuration approach uses reflection to find the implementations of `IMongoCollectionConfiguration\u003cT\u003e` during the startup, we have to tell the library that we opt-in the Configuration mode by calling AddCollectionConfigurations and pass it the Assemblies where you can locate the Configurations.\n\n```csharp\nservices.AddMongo().AddCollectionConfigurations(InfrastructureAssemblyHandle.Assembly);\n```\n\n## Usage\nRefer back to the [getting started section](#getting-started) to learn how to resolve `IMongoCollection\u003cTDocument\u003e` from the dependency injection services.\n\n## Extensions\nWe also provide `IAsyncEnumerable\u003cTDocument\u003e` extensions on `IAsyncCursor\u003cTDocument\u003e` and `IAsyncCursorSource\u003cTDocument\u003e`, eliminating the need to deal with cursors. For example:\n\n```csharp\nvar people = await collection.Find(FilterDefinition\u003cPersonDocument\u003e.Empty).ToAsyncEnumerable();\n```\n\n## Property Mapping\n\nYou can use Mongo Attributes for Property Mapping, or BsonClassMaps. However, if you are using Configuration, you probably do not want to use Attributes on your Models.\n\n### With Attributes\n\n```csharp\n[MongoCollection(\"people\")]\npublic class PersonDocument : IMongoDocument\n{\n    [BsonId]\n    [BsonRepresentation(BsonType.ObjectId)]\n    public string Id { get; set; }\n\n    [BsonElement(\"n\")]\n    public string Name { get; set; } = string.Empty;\n}\n```\n\n[Mapping Models with Attributes](https://www.mongodb.com/docs/drivers/csharp/v2.19/fundamentals/serialization/poco/)\n\n### With Configuration\n\n```csharp\ninternal sealed class PersonConfiguration: IMongoCollectionConfiguration\u003cPerson\u003e\n{\n    public void Configure(IMongoCollectionBuilder\u003cPerson\u003e builder) \n    {\n        builder.CollectionName(\"people\")\n            .BsonClassMap(map =\u003e \n            {\n                map.MapIdProperty(x =\u003e x.Id)\n                    .SetSerializer(new StringSerializer(BsonType.ObjectId));\n                \n                map.MapProperty(x =\u003e x.Name).SetElementName(\"n\");\n            });\n    }\n}\n```\n\n[Mapping Models with ClassMaps](https://www.mongodb.com/docs/drivers/csharp/v2.19/fundamentals/serialization/class-mapping/)\n\n## Logging and distributed tracing\n\n**Workleap.Extensions.Mongo** supports modern logging with `ILogger` and log level filtering. MongoDB commands can be logged at the `Debug` level and optionally with their BSON content only if you set `MongoClientOptions.Telemetry.CaptureCommandText` to `true`.\n\n**Distributed tracing** with OpenTelemetry is also integrated. We follow the [semantic conventions for MongoDB](https://opentelemetry.io/docs/specs/semconv/database/mongodb/). You can simply observe activities (traces) originating from our `Workleap.Extensions.Mongo` assembly.\n\nWe also support distributed tracing with the [Application Insights .NET SDK](https://github.com/microsoft/ApplicationInsights-dotnet). To enable this feature, you need to install the additional [Workleap.Extensions.Mongo.ApplicationInsights NuGet package](https://www.nuget.org/packages/Workleap.Extensions.Mongo.ApplicationInsights/). Simply use the `.AddApplicationInsights()` on the builder object returned by `services.AddMongo()`:\n\n```csharp\nservices.AddMongo().AddApplicationInsights();\n```\n\nBy default, some commands such as `isMaster`, `buildInfo`, `saslStart`, etc., are ignored by our instrumentation. You can either ignore additional commands or undo the ignoring of commands by modifying the `MongoClientOptions.Telemetry.DefaultIgnoredCommandNames` collection.\nAdditionally, subscribing to more granular driver diagnostic events can be done by setting `MongoClientOptions.Telemetry.CaptureDiagnosticEvents` to `true`.\n\n## Index management\n\nWe provide a mechanism for you to declare your collection indexes and ensure they are applied to your database. To do this, declare your indexes by implementing a custom `MongoIndexProvider\u003cTDocument\u003e`:\n\n```csharp\npublic class PersonDocumentIndexes : MongoIndexProvider\u003cPersonDocument\u003e\n{\n    public override IEnumerable\u003cCreateIndexModel\u003cPersonDocument\u003e\u003e CreateIndexModels()\n    {\n        // Index name is mandatory\n        yield return new CreateIndexModel\u003cPersonDocument\u003e(\n            Builders\u003cPersonDocument\u003e.IndexKeys.Combine().Ascending(x =\u003e x.Name),\n            new CreateIndexOptions { Name = \"name\" });\n    }\n}\n```\n\n### With Attributes Decoration\n\n```csharp\n[MongoCollection(\"people\", IndexProviderType = typeof(PersonDocumentIndexes))]\npublic class PersonDocument : IMongoDocument\n{\n    [BsonId]\n    [BsonRepresentation(BsonType.ObjectId)]\n    public string Id { get; set; }\n\n    public string Name { get; set; } = string.Empty;\n}\n```\n\n### With Configuration\n\n```csharp\ninternal sealed class PersonConfiguration: IMongoCollectionConfiguration\u003cPerson\u003e\n{\n    public void Configure(IMongoCollectionBuilder\u003cPerson\u003e builder) \n    {\n        builder.IndexProvider\u003cPersonDocumentIndexes\u003e();\n    }\n}\n```\n\n### Updating Indexes\n\nAt this stage, nothing will happen. To actually create or update the index, you need to inject our `IMongoIndexer` service and then call one of its `UpdateIndexesAsync()` method overloads, for example:\n\n```csharp\nvar indexer = this.Services.GetRequiredService\u003cIMongoIndexer\u003e();\nawait indexer.UpdateIndexesAsync(new[] { typeof(PersonDocument) });\n```\n\n**It is up to you to decide when and where to run the process of creating and updating the indexes**. You could do it at the start of your application, in a separate application that runs in a continuous delivery pipeline, etc.\n\nOur indexation engine handles:\n\n1. Discovering the `CreateIndexModel\u003cTDocument\u003e` declared in your code using reflection.\n2. Computing a **unique index name** based on the model (we append a hash to the provided index name, for example, `name_512cbbb935626e2b4b7c44972597c4a8`).\n3. Discovering existing indexes in the MongoDB collection.\n4. Comparing the names and hashes of both sides to determine if:\n   1. We need to create a missing index.\n   2. We need to drop and recreate an updated index (the hashes don't match).\n   3. We need to drop an index that is no longer declared in your code.\n5. Leaving any other index intact. We only manage the indexes that have a name ending with a generated hash.\n6. Handling distributed race conditions. If many instances of an application call `indexer.UpdateIndexesAsync()` at the same time, only one will actually succeed (we use a distributed lock).\n\n**Note:**\nWe do not recommend you try and run multiple `UpdateIndexesAsync` tasks at the same time given that only one process can update indexes at a time through the use of a distributed lock. \nFor example in the code below, once a task has acquired the distributed lock, the other will wait until the lock is released before acquiring it and running the index update process.\nIn the end you're not saving any time by having multiple tasks run at the same time.\n\n```csharp\n// ⚠️ Updating indexes in parallel is not recommended\nvar indexer = this.Services.GetRequiredService\u003cIMongoIndexer\u003e();\nawait Task.WhenAll(\n    indexer.UpdateIndexesAsync(AssemblyHandle.Assembly),\n    indexer.UpdateIndexesAsync(new[] { typeof(PersonDocument) })\n);\n```\n\nIdeally if all your indexes are in the same assembly then you only have to call `UpdateIndexesAsync` once.\nBut if you really do need to call it multiple times, then the code above should be re-written to the following:\n```csharp\nvar indexer = this.Services.GetRequiredService\u003cIMongoIndexer\u003e();\nawait indexer.UpdateIndexesAsync(AssemblyHandle.Assembly);\nawait indexer.UpdateIndexesAsync(new[] { typeof(PersonDocument) })\n```\n\n\u003e We include a Roslyn analyzer, detailed in a section below, that encourages developers to adorn classes that consume MongoDB collections with attributes (`IndexByAttribute` or `NoIndexNeededAttribute`). The aim is to increase awareness about which indexes should be used (or created) when querying MongoDB collections.\n\n### Support for inheritance\n\nThe indexer mechanism support document inheritance and different indexer for a same collection. For example:\n\n```csharp \n\n[BsonKnownTypes(typeof(DogPersonDocument), typeof(DogPersonDocument))]\n[MongoCollection(\"people\", IndexProviderType = typeof(PersonDocumentIndexes))]\npublic class PersonDocument : IMongoDocument\n{\n    [BsonId]\n    [BsonRepresentation(BsonType.ObjectId)]\n    public string Id { get; set; }\n\n    public string Name { get; set; } = string.Empty;\n}\n\npublic class PersonDocumentIndexes : MongoIndexProvider\u003cPersonDocument\u003e\n{\n    public override IEnumerable\u003cCreateIndexModel\u003cPersonDocument\u003e\u003e CreateIndexModels()\n    {\n        yield return new CreateIndexModel\u003cPersonDocument\u003e(\n            Builders\u003cPersonDocument\u003e.IndexKeys.Combine().Ascending(x =\u003e x.Name),\n            new CreateIndexOptions { Name = \"name\" });\n    }\n}\n\n// No special indexer for this class\n[BsonDiscriminator(\"Dog\")]\npublic class DogPersonDocument : PersonDocument\n{\n    public int DogCount { get; set; } = string.Empty;\n}\n\n// Need to redefine MongoCollectionAttribute to use a different indexer\n[BsonDiscriminator(\"Cat\")]\n[MongoCollection(\"people\", IndexProviderType = typeof(CatDocumentIndexes))]\npublic class CatPersonDocument : PersonDocument\n{\n    public int CatCount { get; set; }\n}\n\npublic class CatDocumentIndexes : MongoIndexProvider\u003cPersonDocument\u003e\n{\n    public override IEnumerable\u003cCreateIndexModel\u003cPersonDocument\u003e\u003e CreateIndexModels()\n    {\n        yield return new CreateIndexModel\u003cPersonDocument\u003e(\n            Builders\u003cPersonDocument\u003e.IndexKeys.Combine().Ascending(x =\u003e x.CatCount),\n            new CreateIndexOptions { Name = \"cat_count\" });\n    }\n}\n```\n\n## Field encryption\n\nThe [Workleap.Extensions.Mongo](https://www.nuget.org/packages/Workleap.Extensions.Mongo/) library supports field-level encryption at rest, which means you can specify in your C# code which document fields should be encrypted in your MongoDB database. Any C# property can be encrypted, as long as you provide how data gets encrypted and decrypted. These properties then become binary data in your documents.\n\nTo enable field-level encryption, simply decorate the sensitive properties with the `[SensitiveInformationAttribute]`:\n\n```csharp\n[MongoCollection(\"people\")]\npublic class PersonDocument : IMongoDocument\n{\n    // [...]\n\n    [SensitiveInformation(SensitivityScope.User)] // Other values are \"Tenant\" and \"Application\"\n    public string Address { get; set; } = string.Empty;\n}\n```\n\nNext, create a class that implements `IMongoValueEncryptor`:\n\n```csharp\npublic class MyMongoalueEncryptor : IMongoValueEncryptor\n{\n    public byte[] Encrypt(byte[] bytes, SensitivityScope sensitivityScope)\n    {\n        // return protected bytes using the method of your choice\n    }\n\n    public byte[] Decrypt(byte[] bytes, SensitivityScope sensitivityScope)\n    {\n        // return unprotected bytes using the method of your choice\n    }\n}\n```\n\nFinally, register this class in the dependency injection services:\n\n```csharp\n// This ends up registered using the singleton service lifetime\nservices.AddMongo().AddEncryptor\u003cMyMongoalueEncryptor\u003e();\n```\n\nKeep in mind that encrypted values become binary data, which can make querying them more difficult. You'll need to take this into account when designing your database schema and queries.\n\n## Ephemeral MongoDB databases for integration tests\n\nWhen creating integration tests, instead of using a shared MongoDB database for all your tests, you could assign a brand new ephemeral database for each individual test method. This approach reduces test flakiness, prevents the state of one test from impacting others and remove the need for manual or automatic cleanup.\n\nThis is what our NuGet package [Workleap.Extensions.Mongo.Ephemeral](https://www.nuget.org/packages/Workleap.Extensions.Mongo.Ephemeral/) does when you invoke its `UseEphemeralRealServer()` method:\n\n```csharp\nservices.AddMongo().UseEphemeralRealServer();\n```\n\nWhen this method is called, each time a database or collection is requested within the scope of an individual `IServiceProvider`:\n* A MongoDB server starts (we use [EphemeralMongo](https://github.com/asimmon/ephemeral-mongo)),\n* A randomly named database is provided to your code.\n\nWhen you dispose of the `IServiceProvider`, the related resources are destroyed. We leverage internal caching to avoid running multiple instances of MongoDB servers concurrently, opting instead to reuse a single instance. This method allows you to run multiple concurrent tests, each with their own MongoDB database. If your test runner crashes, the MongoDB process will be terminated, preventing orphaned processes from consuming unnecessary resources.\n\nAvailable environment variables:\n- `WORKLEAP_EXTENSIONS_MONGO_EPHEMERAL_BINARYDIRECTORY`: Specify the path of the MongoDB binaries\n- `WORKLEAP_EXTENSIONS_MONGO_EPHEMERAL_DATADIRECTORY`: Specify the path to store data\n- `WORKLEAP_EXTENSIONS_MONGO_EPHEMERAL_CONNECTIONTIMEOUT`: Specify the timeout to connect to the database\n- `WORKLEAP_EXTENSIONS_MONGO_EPHEMERAL_USESINGLENODEREPLICASET`: Configure the replicaset\n\n## Included Roslyn analyzers\n\n| Rule ID | Category | Severity | Description                                                        |\n|---------|----------|----------|--------------------------------------------------------------------|\n| GMNG01  | Design   | Warning  | Add 'IndexBy' or 'NoIndexNeeded' attributes on the containing type |\n\nTo modify the severity of one of these diagnostic rules, you can use a `.editorconfig` file. For example:\n\n```ini\n## Disable analyzer for test files\n[**Tests*/**.cs]\ndotnet_diagnostic.GMNG01.severity = none\n```\n\nTo learn more about configuring or suppressing code analysis warnings, refer to [this documentation](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/suppress-warnings).\n\n## License\n\nCopyright © 2023, Workleap. This code is licensed under the Apache License, Version 2.0. You may obtain a copy of this license at https://github.com/gsoft-inc/gsoft-license/blob/master/LICENSE.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fworkleap%2Fwl-extensions-mongo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fworkleap%2Fwl-extensions-mongo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fworkleap%2Fwl-extensions-mongo/lists"}