{"id":21281763,"url":"https://github.com/johnknoop/mongorepository","last_synced_at":"2025-07-11T10:33:53.366Z","repository":{"id":39636460,"uuid":"97951384","full_name":"johnknoop/MongoRepository","owner":"johnknoop","description":"An easy-to-configure, powerful repository for MongoDB with support for multi-tenancy","archived":false,"fork":false,"pushed_at":"2023-03-03T23:31:01.000Z","size":210,"stargazers_count":48,"open_issues_count":8,"forks_count":19,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-11-17T09:44:57.635Z","etag":null,"topics":["csharp","dotnet","mongodb","mongorepository","multi-tenancy"],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johnknoop.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}},"created_at":"2017-07-21T13:41:30.000Z","updated_at":"2024-10-24T17:21:20.000Z","dependencies_parsed_at":"2023-01-21T23:16:45.451Z","dependency_job_id":null,"html_url":"https://github.com/johnknoop/MongoRepository","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnknoop%2FMongoRepository","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnknoop%2FMongoRepository/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnknoop%2FMongoRepository/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnknoop%2FMongoRepository/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johnknoop","download_url":"https://codeload.github.com/johnknoop/MongoRepository/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225715931,"owners_count":17512909,"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":["csharp","dotnet","mongodb","mongorepository","multi-tenancy"],"created_at":"2024-11-21T10:50:43.900Z","updated_at":"2024-11-21T10:50:44.450Z","avatar_url":"https://github.com/johnknoop.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JohnKnoop.MongoRepository\r\n\r\nAn easy-to-configure extension to the MongoDB driver, adding support for: \\\r\n\\\r\n✔️ Multi-tenancy \\\r\n✔️ Simplified transaction handling, including support for [TransactionScope](#transactions) \\\r\n✔️ [Soft-deletes](#soft-deleting)\r\n\r\n## Install via NuGet\r\n\r\n    Install-Package JohnKnoop.MongoRepository\r\n\r\n### Configure mappings, indices, multitenancy etc with a few lines of code:\r\n\r\n```csharp\r\nMongoRepository.Configure()\r\n    .Database(\"HeadOffice\", db =\u003e db\r\n        .Map\u003cEmployee\u003e()\r\n    )\r\n    .DatabasePerTenant(\"Zoo\", db =\u003e db\r\n        .Map\u003cAnimalKeeper\u003e()\r\n        .Map\u003cEnclosure\u003e(\"Enclosures\")\r\n        .Map\u003cAnimal\u003e(\"Animals\", x =\u003e x\r\n            .WithIdProperty(animal =\u003e animal.NonConventionalId)\r\n            .WithIndex(animal =\u003e animal.Name, unique: true)\r\n        )\r\n    )\r\n    .Build();\r\n```\r\n[See more options](#configuration)\r\n### ...then start hacking away\r\n\r\n```csharp\r\nvar employeeRepository = mongoClient.GetRepository\u003cEmployee\u003e();\r\nvar animalRepository = mongoClient.GetRepository\u003cAnimal\u003e(tenantKey);\r\n```\r\n\r\nIn the real world you'd typically resolve `IRepository\u003cT\u003e` through your dependency resolution system. See the section about [DI frameworks](#di-frameworks) for more info.\r\n\r\n## Getting started\r\n\r\n- [Querying](#querying)\r\n    - [Get by id](#get-by-id)\r\n        - [Projection](#get-by-id)\r\n    - [Find by expression](#find-by-expression)\r\n        - [Count, Sort, Skip, Limit, Project, Cursor](#find-by-expression)\r\n    - [LINQ](#linq)\r\n- [Inserting, updating and deleting](#inserting-updating-and-deleting)\r\n    - [InsertAsync, InsertManyAsync](#insertasync-insertmanyasync)\r\n    - [UpdateOneAsync, UpdateManyAsync](#updateoneasync-Updatemanyasync)\r\n    - [UpdateOneBulkAsync](#updateonebulkasync)\r\n    - [FindOneAndUpdateAsync](#FindOneAndUpdateasync)\r\n    - [FindOneAndReplaceAsync](documentation under construction)\r\n    - [FindOneOrInsertAsync](documentation under construction)\r\n    - [UpdateOrInsertOneAsync](#UpdateOrInsertOneAsync)\r\n    - [Aggregation](#aggregation)\r\n    - [Deleting](#deleting)\r\n        - [Soft-deletes](#soft-deleting)\r\n    - [Transactions](#transactions)\r\n    - [UnionWith](#unionwith)\r\n    - [ArrayFilters helpers](#arrayfilters-helpers)\r\n- [Advanced features](#advanced-features)\r\n    - [Counters](#counters)\r\n    - [Deleting properties](#deleting-properties)\r\n- Configuration\r\n    - [Multi-tenancy](#multi-tenancy)\r\n    - [Polymorphism](#polymorphism)\r\n    - [Indices](#indices)\r\n    - [Capped collections](#capped-collections)\r\n    - [Unconventional id properties](#unconventional-id-properties)\r\n    - [DI frameworks](#di-frameworks)\r\n        - [Ninject](#ninject)\r\n        - [.Net Core](#net-core)\r\n- Contribute\r\n    - [Design philosophy](#design-philosophy)\r\n\r\n\r\n## Querying\r\n\r\n### Get by id\r\n```csharp\r\nawait repository.GetAsync(\"id\");\r\nawait repository.GetAsync\u003cSubType\u003e(\"id\");\r\n\r\n// With projection\r\nawait repository.GetAsync(\"id\", x =\u003e x.TheOnlyPropertyIWant);\r\nawait repository.GetAsync\u003cSubType\u003e(\"id\", x =\u003e new\r\n    {\r\n        x.SomeProperty,\r\n        x.SomeOtherProperty\r\n    }\r\n);\r\n```\r\n\r\n### Find by expression\r\n\r\n```csharp\r\nawait repository.Find(x =\u003e x.SomeProperty == someValue);\r\nawait repository.Find\u003cSubType\u003e(x =\u003e x.SomeProperty == someValue);\r\nawait repository.Find(x =\u003e x.SomeProperty, regexPattern);\r\n```\r\nReturns an [IFindFluent](http://api.mongodb.com/csharp/current/html/T_MongoDB_Driver_IFindFluent_2.htm) which offers methods like `ToListAsync`, `CountAsync`, `Project`, `Skip` and `Limit`\r\n\r\nExamples:\r\n\r\n```csharp\r\nvar dottedAnimals = await repository\r\n    .Find(x =\u003e x.Coat == \"dotted\")\r\n    .Limit(10)\r\n    .Project(x =\u003e x.Species)\r\n    .ToListAsync()\r\n```\r\n### LINQ\r\n```csharp\r\nrepository.Query();\r\nrepository.Query\u003cSubType\u003e();\r\n```\r\nReturns an [IMongoQueryable](http://api.mongodb.com/csharp/current/html/T_MongoDB_Driver_Linq_IMongoQueryable.htm) which offers async versions of all the standard LINQ methods.\r\n\r\nExamples:\r\n\r\n```csharp\r\nvar dottedAnimals = await repository.Query()\r\n    .Where(x =\u003e x.Coat == \"dotted\")\r\n    .Take(10)\r\n    .Select(x =\u003e x.Species)\r\n    .ToListAsync()\r\n```\r\n## Inserting, updating and deleting\r\n### InsertAsync, InsertManyAsync\r\n\r\n```csharp\r\nawait repository.InsertAsync(someObject);\r\nawait repository.InsertManyAsync(someCollectionOfObjects);\r\n```\r\n\r\n### UpdateOneAsync, UpdateManyAsync\r\n```csharp\r\n// Update one document\r\nawait repository.UpdateOneAsync(\"id\", x =\u003e x.Set(y =\u003e y.SomeProperty, someValue), upsert: true);\r\nawait repository.UpdateOneAsync(x =\u003e x.SomeProperty == someValue, x =\u003e x.Push(y =\u003e y.SomeCollection, someValue));\r\nawait repository.UpdateOneAsync\u003cSubType\u003e(x =\u003e x.SomeProperty == someValue, x =\u003e x.Push(y =\u003e y.SomeCollection, someValue));\r\n\r\n// Update all documents matched by filter\r\nawait repository.UpdateManyAsync(x =\u003e x.SomeProperty == someValue, x =\u003e x.Inc(y =\u003e y.SomeProperty, 5));\r\n```\r\n### UpdateOneBulkAsync\r\nPerform multiple update operations with different filters in one db roundtrip.\r\n```csharp\r\nawait repository.UpdateOneBulkAsync(new List\u003cUpdateOneCommand\u003cMyEntity\u003e\u003e {\r\n    new UpdateOneCommand\u003cMyEntity\u003e {\r\n        Filter = x =\u003e x.SomeProperty = \"foo\",\r\n        Update = x =\u003e x.Set(y =\u003e y.SomeOtherProperty, 10)\r\n    },\r\n    new UpdateOneCommand\u003cMyEntity\u003e {\r\n        Filter = x =\u003e x.SomeProperty = \"bar\",\r\n        Update = x =\u003e x.Set(y =\u003e y.SomeOtherProperty, 20)\r\n    }\r\n});\r\n```\r\n### FindOneAndUpdateAsync\r\nThis is a really powerful feature of MongoDB, in that it lets you update and retrieve a document atomically.\r\n```csharp\r\nvar entityAfterUpdate = await repository.FindOneAndUpdateAsync(\r\n    filter: x =\u003e x.SomeProperty.StartsWith(\"Hello\"),\r\n    update: x =\u003e x.AddToSet(y =\u003e y.SomeCollection, someItem)\r\n);\r\n\r\nvar entityAfterUpdate = await repository.FindOneAndUpdateAsync(\r\n    filter: x =\u003e x.SomeProperty.StartsWith(\"Hello\"),\r\n    update: x =\u003e x.PullFilter(y =\u003e y.SomeCollection, y =\u003e y.SomeOtherProperty == 5),\r\n    returnProjection: x =\u003e new {\r\n        x.SomeCollection\r\n    },\r\n    returnedDocumentState: ReturnedDocumentState.AfterUpdate,\r\n    upsert: true\r\n);\r\n```\r\n\r\n### UpdateOrInsertOneAsync\r\nLets you upsert a document of type `T` using an instance of type `T` as default and then apply updates on top of that, in an atomic operation. If the filter is matched, the default instance will not be used, and only the updates will be applied.\r\n\r\nThe same result can be achieved with common UpdateOne/FindOneAndUpdate using `upsert` and a bunch of `SetOnInsert`s, but the advantage of `UpdateOrInsertOneAsync` is you don't have to add a `SetOnInsert` for each property manually.\r\n\r\n\r\n### Aggregation\r\n```csharp\r\nrepository.Aggregate();\r\nrepository.Aggregate(options);\r\n```\r\nReturns an [IAggregateFluent](http://api.mongodb.com/csharp/current/html/T_MongoDB_Driver_IAggregateFluent_1.htm) which offers methods like `AppendStage`, `Group`, `Match`, `Unwind`, `Out`, `Lookup` etc.\r\n\r\n### Deleting\r\n```csharp\r\nawait repository.DeleteByIdAsync(\"id\");\r\n// or\r\nawait repository.DeleteManyAsync(x =\u003e x.SomeProperty === someValue);\r\n// or\r\nvar deleted = await repository.FindOneAndDeleteAsync(\"id\");\r\n// or\r\nvar deleted = await repository.FindOneAndDeleteAsync\u003cDerivedType\u003e(x =\u003e x.SomeProp == someValue);\r\n```\r\n#### Soft-deleting\r\nSoft-deleting an entity will move it to a different collection, preserving type-information.\r\n```csharp\r\nawait repository.DeleteByIdAsync(\"id\", softDelete: true);\r\n// or\r\nvar deleted = await repository.FindOneAndDeleteAsync(\"id\", softDelete: true);\r\n```\r\nListing soft-deleted entities:\r\n```csharp\r\nawait repository.ListTrashAsync();\r\n```\r\nRestoring one (or many) soft-deleten entities\r\n```csharp\r\nawait repository.RestoreSoftDeletedAsync(\"id\");\r\nawait repository.RestoreSoftDeletedAsync(x =\u003e x.TimestampDeletedUtc \u003e DateTime.Today);\r\n```\r\nPermanently delete soft-deleted documents\r\n```cs\r\nawait repository.PermamentlyDeleteSoftDeletedAsync(x =\u003e x.Foo == \"bar\");\r\n```\r\n\r\n### Transactions\r\nMongoDB 4 introduced support for multi-document transactions. We provide a simplified interface: you don't have to pass around the session object. Instead we detect any ambient transaction and uses it for all write/update/delete operations.:\r\n\r\n```csharp\r\nusing (var transaction = repository.StartTransaction()) {\r\n    // ...\r\n    await transaction.CommitAsync();\r\n}\r\n```\r\n\r\nSince version 5 we also support enlisting with a `TransactionScope`. This is useful to be able to put a transactional boundary around MongoDB operations and anything that is compatible with TransactionScopes.\r\n\r\n```csharp\r\nusing (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) {\r\n    repository.EnlistWithCurrentTransactionScope();\r\n    // ...\r\n    transaction.Complete();\r\n}\r\n```\r\nIf you configure the repository with `.AutoEnlistWithTransactionScopes()` then it will automatically enlist to any ambient TransactionScope without the need to do it explicitly like in the example above.\r\n\r\nMongoDB replica sets sometimes encounter transient transaction errors, in which case the recommended course of action from the MongoDB team is to simply retry until it succeeds. We offer a shorthand for this:\r\n\r\n```csharp\r\n// Retry using standard MongoDB transaction\r\nawait repo.WithTransactionAsync(async () =\u003e\r\n{\r\n    // your code here\r\n}, maxRetries: 3);\r\n\r\n// Retry using TransactionScope\r\nawait repo.WithTransactionAsync(async () =\u003e\r\n{\r\n    // your code here\r\n}, TransactionType.TransactionScope, maxRetries: 3);\r\n```\r\n\r\n`RetryAsync` also comes with an overload that takes a number representing the max number of retries.\r\n\r\n### UnionWith\r\n\r\nThis library provides an extension method to `IAggregateFluent\u003cT\u003e` called `UnionWith` that accepts a repository and a projection expression.\r\n\r\n```cs\r\nusing JohnKnoop.MongoRepository.Extensions;\r\n\r\nvar allContacts = await soccerPlayersRepository\r\n    .Aggregate()\r\n    .Project(x =\u003e new\r\n    {\r\n        PlayerName = x.SoccerPlayerName,\r\n        TeamName = x.SoccerTeamName\r\n    })\r\n    .UnionWith(\r\n        rugbyPlayersRepository,\r\n        x =\u003e new\r\n        {\r\n            PlayerName = x.RugbyPlayerName,\r\n            TeamName = x.RugbyTeamName\r\n        }\r\n    )\r\n    .SortBy(x =\u003e x.PlayerName)\r\n    .ToListAsync();\r\n```\r\n\r\n### ArrayFilters helpers\r\n\r\nWorking with [ArrayFilters](https://www.mongodb.com/docs/manual/reference/operator/update/positional-filtered/) using the MongoDB C# driver is an unpleasant experience in that it doesn't provide any compile-time checking. This library contains a few handy helpers that lets you replace this code:\r\n\r\n```cs\r\nawait _repository.UpdateOneAsync(\r\n    filter: x =\u003e x.Title == \"Game of Thrones\",\r\n    update: x =\u003e x.Set(\r\n        \"Seasons.$[a].Episodes.$[b].Title\",\r\n        \"Qarth\"\r\n    ),\r\n    options: new UpdateOptions\r\n    {\r\n        ArrayFilters = new List\u003cArrayFilterDefinition\u003cShow\u003e\u003e\r\n        {\r\n            new BsonDocument(\"a.Year\", new BsonDocument(\"$ne\", \"2013\")),\r\n            new BsonDocument(\"b.Number\", 2),\r\n        }\r\n    }\r\n);\r\n```\r\n...with this:\r\n```cs\r\nawait _repository.UpdateOneAsync(\r\n    filter: x =\u003e x.Title == \"Game of Thrones\",\r\n    update: x =\u003e x.Set(\r\n        ArrayFilters.CreateArrayFilterPath\u003cShow\u003e()\r\n            .SelectEnumerable(x =\u003e x.Seasons, \"a\")\r\n            .SelectEnumerable(x =\u003e x.Episodes, \"b\")\r\n            .SelectProperty(x =\u003e x.Title)\r\n            .Build(),\r\n        \"Qarth\"\r\n    ),\r\n    options: new UpdateOptions\r\n    {\r\n        ArrayFilters = ArrayFilters.DefineFilters\u003cShow\u003e()\r\n            .AddFilter(\"a\", show =\u003e show.Seasons, f =\u003e f.Eq(x =\u003e x.Year, \"2013\"))\r\n            .ThenAddFilter(\"b\", season =\u003e season.Episodes, f =\u003e f.Eq(x =\u003e x.Number, 2))\r\n    }\r\n);\r\n```\r\n\r\nThis ensures already at design-time that you haven't misspelled any properties, or that you're using the wrong data type for any values.\r\n\r\nPlease note that this feature is still experimental.\r\n\r\n## Advanced features\r\n\r\n### Counters\r\n\r\nAuto-incrementing fields is a feature of most relational databases that unfortunately isn't supported by MongoDB. To get around this, _counters_ are a way to solve the problem of incrementing a number with full concurrency support.\r\n```csharp\r\nvar value = await repository.GetCounterValueAsync();\r\nvar value = await repository.GetCounterValueAsync(\"MyNamedCounter\");\r\n```\r\nAtomically increment and read the value of a counter:\r\n```csharp\r\nvar value = await repository.IncrementCounterAsync(); // Increment by 1\r\nvar value = await repository.IncrementCounterAsync(name: \"MyNamedCounter\", incrementBy: 5);\r\n```\r\nReset a counter:\r\n```csharp\r\nawait repository.ResetCounterAsync(); // Reset to 1\r\nawait repository.ResetCounterAsync(name: \"MyNamedCounter\", newValue: 5);\r\n```\r\n\r\n### Deleting properties\r\n\r\nDelete a property from a document:\r\n\r\n```csharp\r\nawait repository.DeletePropertyAsync(x =\u003e x.SomeProperty == someValue, x =\u003e x.PropertyToRemove);\r\n```\r\n\r\n## Configuration\r\n\r\nConfiguration is done once, when the application is started. Use `MongoRepository.Configure()` as shown below.\r\n\r\n### Multi-tenancy\r\n\r\nDatabase-per-tenant style multi-tenancy is supported. When defining a database, just use the `DatabasePerTenant` method:\r\n\r\n```csharp\r\nMongoRepository.Configure()\r\n    // Every tenant should have their own Sales database\r\n    .DatabasePerTenant(\"Sales\", db =\u003e db\r\n        .Map\u003cOrder\u003e()\r\n        .Map\u003cCustomer\u003e(\"Customers\")\r\n    )\r\n    .Build();\r\n```\r\nThe name of the database will be \"{tenant key}_{database name}\".\r\n\r\n### Polymorphism\r\n\r\nMapping a type hierarchy to the same collection is easy. Just map the base type using `MapAlongWithSubclassesInSameAssembly\u003cMyBaseType\u003e()`. It takes all the same arguments as `Map`.\r\n\r\n### Indices\r\n\r\nIndices are defined when mapping a type:\r\n```csharp\r\nMongoRepository.Configure()\r\n    // Every tenant should have their own Sales database\r\n    .Database(\"Zoo\", db =\u003e db\r\n        .Map\u003cAnimal\u003e(\"Animals\", x =\u003e x\r\n            .WithIndex(a =\u003e a.Species)\r\n            .WithIndex(a =\u003e a.EnclosureNumber, unique: true)\r\n            .WithIndex(a =\u003e a.LastVaccinationDate, sparse: true)\r\n        )\r\n        .Map\u003cFeedingRoutine\u003e(\"FeedingRoutines\", x =\u003e x\r\n            // Composite index\r\n            .WithIndex(new { Composite index })\r\n        )\r\n    )\r\n    .Build();\r\n```\r\n\r\n### Capped collections\r\n\r\n_[To be documented]_\r\n\r\n### Unconventional id properties\r\n\r\n_[To be documented]_\r\n\r\n### DI frameworks\r\n#### .NET Core\r\n\r\nThere is an extension package called `JohnKnoop.MongoRepository.DotNetCoreDi` that registers `IRepository\u003cT\u003e` as a dependency with the .NET Core dependency injection framework.\r\n\r\nSee the [repository readme](https://github.com/johnknoop/MongoRepository.DotNetCoreDi) for more information.\r\n\r\n#### Ninject\r\n\r\n```csharp\r\nthis.Bind(typeof(IRepository\u003c\u003e)).ToMethod(context =\u003e\r\n{\r\n    Type entityType = context.GenericArguments[0];\r\n    var mongoClient = context.Kernel.Get\u003cIMongoClient\u003e();\r\n    var tenantKey = /* Pull out your tenent key from auth ticket or Owin context or what suits you best */;\r\n\r\n    var getRepositoryMethod = typeof(MongoConfiguration).GetMethod(nameof(MongoConfiguration.GetRepository));\r\n    var getRepositoryMethodGeneric = getRepositoryMethod.MakeGenericMethod(entityType);\r\n    return getRepositoryMethodGeneric.Invoke(this, new object[] { mongoClient, tenantKey });\r\n});\r\n```\r\n\r\n## Design philosophy\r\n\r\nThis library is an extension to the MongoDB C# driver, and thus I don't mind exposing types from the MongoDB.Driver namespace, like IFindFluent or the result types of the various operations.\r\n\r\nAny contributions to this library should be in line with the philosophy of this primarily being an extension that makes it easy to write multi-tenant applications using the MongoDB driver. I'm not looking to widen the scope of this library.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnknoop%2Fmongorepository","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohnknoop%2Fmongorepository","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnknoop%2Fmongorepository/lists"}