├── .config └── dotnet-tools.json ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── pull-request.yml │ └── release.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── Directory.Packages.props ├── LICENSE ├── README.md ├── global.json ├── nuget.config ├── samples ├── .dockerignore ├── Context │ ├── DataAccess │ │ ├── BlogRepository.cs │ │ ├── Configuration │ │ │ ├── BlogCollectionConfiguration.cs │ │ │ ├── TagCollectionConfiguration.cs │ │ │ └── UserCollectionConfiguration.cs │ │ ├── DataAccess.csproj │ │ ├── IBlogRepository.cs │ │ ├── ITagRepository.cs │ │ ├── IUserRepository.cs │ │ ├── ServiceCollectionExtensions.cs │ │ ├── SimpleBlogDbContext.cs │ │ ├── TagRepository.cs │ │ ├── UserRepository.cs │ │ └── WellKnown.cs │ ├── Domain │ │ ├── BlogService.cs │ │ ├── Domain.csproj │ │ ├── IBlogService.cs │ │ ├── IUserService.cs │ │ ├── ServiceCollectionExtensions.cs │ │ └── UserService.cs │ ├── Host │ │ ├── Controllers │ │ │ └── SimpleBlogController.cs │ │ ├── Host.csproj │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Startup.cs │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ └── Models │ │ ├── Blog.cs │ │ ├── Models.csproj │ │ ├── Tag.cs │ │ └── User.cs ├── Directory.Packages.props ├── Migration │ ├── Customer.cs │ ├── ExampleMigration.cs │ ├── Migration.csproj │ ├── Program.cs │ ├── Repository.cs │ ├── appsettings.Development.json │ └── appsettings.json └── MongoDB.Extensions.Samples.sln └── src ├── Context.AllowedTypes.Tests ├── Context.AllowedTypes.Tests.csproj ├── Helpers │ ├── Bar.cs │ ├── Foo.cs │ ├── MongoCollectionFixture.cs │ └── TestHelpers.cs ├── MongoDatabaseBuilderTests.cs ├── Properties │ └── AssemblyInfo.cs ├── TypeObjectSerializerTests.cs └── __snapshots__ │ ├── MongoDatabaseBuilderTests.AddAllowedTypes_AddAllowedTypesByNamespaces_Success.snap │ ├── MongoDatabaseBuilderTests.AddAllowedTypes_AddAllowedTypesByTypes_Success.snap │ ├── MongoDatabaseBuilderTests.AddAllowedTypes_AddAllowedTypesOfAllDependencies_Success.snap │ ├── TypeObjectSerializerTests.AddAllowedTypes_AddAllowedTypesByNamespaces_Success.snap │ ├── TypeObjectSerializerTests.AddAllowedTypes_AddAllowedTypesByTypes_Success.snap │ ├── TypeObjectSerializerTests.AddAllowedTypes_AddAllowedTypesOfAllDependencies_Success.snap │ ├── TypeObjectSerializerTests.IsTypeAllowed_InAllowedNamespaces_True.snap │ ├── TypeObjectSerializerTests.IsTypeAllowed_InAllowedTypesInDependencies_True.snap │ ├── TypeObjectSerializerTests.IsTypeAllowed_InAllowedTypes_False.snap │ ├── TypeObjectSerializerTests.IsTypeAllowed_InAllowedTypes_True.snap │ ├── TypeObjectSerializerTests.IsTypeAllowed_PartIsInAllowedNamespacesCaseInsensitive_True.snap │ ├── TypeObjectSerializerTests.IsTypeAllowed_PartIsInAllowedNamespaces_True.snap │ └── TypeObjectSerializerTests.IsTypeAllowed_PartIsNotInAllowedNamespaces_False.snap ├── Context.GuidSerializer.Tests ├── Context.GuidSerializers.Tests.csproj ├── GuidSerializerTests.cs ├── Helpers │ ├── Bar.cs │ ├── Foo.cs │ └── FooBarMongoDbContext.cs └── __snapshots__ │ ├── GuidSerializerTests.Serialize_GuidPropertyGuidSerialized_Successfully.snap │ └── GuidSerializerTests.Serialize_ObjectPropertyGuidSerialized_Successfully.snap ├── Context.InterferingTests ├── Context.InterferingTests.csproj ├── Helpers │ ├── Foo.cs │ └── FooCollectionConfiguration.cs ├── MongoDatabaseBuilderTests.cs └── __snapshots__ │ └── MongoDatabaseBuilderTests.ConfigureCollection_SetDifferentSettingsToCollection_CollectionConfiguredSuccessfully.snap ├── Context.Tests ├── Context.Tests.csproj ├── Helpers │ ├── Bar.cs │ ├── Foo.cs │ └── Order.cs ├── ImmutableConventionTests.cs ├── ImmutableConventionWithRecordsTests.cs ├── Internal │ ├── DependencyTypesResolverTests.cs │ └── __snapshots__ │ │ └── DependencyTypesResolverTests.GetAllowedTypesByDependencies_All_Successful.snap ├── IsExternalInit.cs ├── MongoCollectionBuilderTests.cs ├── MongoCollectionsTests.cs ├── MongoDbContextDataTests.cs ├── MongoDbContextTests.cs ├── MongoTransactionDbContextTests.cs ├── Properties │ └── AssemblyInfo.cs └── __snapshots__ │ ├── AbstractImmutableWithAbstractBasePropertyCase.ApplyConvention_SerializeSuccessful.snap │ ├── AbstractImmutableWithBasePropertyCase.ApplyConvention_SerializeSuccessful.snap │ ├── AbstractImmutableWithNullableVirtualBasePropertyCase.ApplyConvention_SerializeSuccessful.snap │ ├── AbstractImmutableWithVirtualBasePropertyCase.ApplyConvention_SerializeSuccessful.snap │ ├── MongoTransactionDbContextTests.StartNewTransactionAsync_AddFooBarWithCommit_AllSaved.snap │ ├── MongoTransactionDbContextTests.StartNewTransactionAsync_AddFooBarWithRollback_NothingSaved.snap │ ├── MongoTransactionDbContextTests.StartNewTransactionAsync_CreateNewTransactionDbContext_OptionsCorrect.snap │ ├── MongoTransactionDbContextTests.StartNewTransactionAsync_SetTransactionTransactionOptions_OptionsCorrect.snap │ ├── MongoTransactionDbContextTests.StartNewTransactionAsync_TwoTransactionContextWithDifferentObjects_AllSaved.snap │ ├── MongoTransactionDbContextTests.StartNewTransactionAsync_TwoTransactionContextWithSameObjects_ConcurrencyExceptionAndSaved.snap │ ├── NullableReferenceTypeCase.ApplyConvention_CtorWithDefault_SerializeSuccessful.snap │ ├── NullableReferenceTypeCase.ApplyConvention_WithValue_SerializeSuccessful.snap │ ├── NullableReferenceTypeCase.ApplyConvention_WithoutValueInDbWithoutDefault_SerializeSuccessful.snap │ ├── NullableReferenceTypeCase.ApplyConvention_WithoutValueInDb_SerializeSuccessful.snap │ ├── NullableReferenceTypeCase.ApplyConvention_WithoutValue_SerializeSuccessful.snap │ ├── NullableValueTypeCase.ApplyConvention_WithValue_SerializeSuccessful.snap │ ├── NullableValueTypeCase.ApplyConvention_WithoutValue_SerializeSuccessful.snap │ ├── SimpleImmutableCase.ApplyConvention_SerializeSuccessful.snap │ └── SimpleImmutableWithInterfaceCase.ApplyConvention_SerializeSuccessful.snap ├── Context ├── Context.csproj ├── DefaultDefinitions.cs ├── Exceptions │ └── MissingAllowedTypesException.cs ├── Extensions │ └── TypeExtensions.cs ├── IMongoCollectionBuilder.cs ├── IMongoCollectionConfiguration.cs ├── IMongoDatabaseBuilder.cs ├── IMongoDbContext.cs ├── IMongoDbTransaction.cs ├── IMongoTransactionDbContext.cs ├── ImmutableConvention.cs ├── Internal │ ├── DependencyTypesResolver.cs │ ├── IMongoCollections.cs │ ├── MongoCollectionBuilder.cs │ ├── MongoCollections.cs │ ├── MongoDatabaseBuilder.cs │ ├── MongoDbContextData.cs │ └── TypeObjectSerializer.cs ├── InternalsVisibleTo.cs ├── MongoDbContext.cs ├── MongoInstrumentationExtensions.cs ├── MongoOptions.cs ├── MongoOptionsConfiguration.cs ├── MongoOptionsExtensions.cs ├── MongoServerExtensions.cs ├── MongoTransactionDbContext.cs └── Properties │ └── InternalsVisibleTo.cs ├── Dependencies.props ├── Directory.Build.props ├── Directory.Build.targets ├── Migration.Tests ├── Integration │ ├── Scenario1 │ │ ├── MigrateDownTests.cs │ │ ├── MigrateUpTests.cs │ │ ├── TestEntityForDown.cs │ │ └── TestEntityForUp.cs │ ├── Scenario2 │ │ ├── MigrateDownTests.cs │ │ ├── MigrateUpTests.cs │ │ ├── TestEntityForDown.cs │ │ └── TestEntityForUp.cs │ └── SharedMongoDbCollection.cs ├── Migration.Tests.csproj ├── TestMigration1.cs ├── TestMigration2.cs ├── TestMigration3.cs └── Unit │ ├── EntityOptionBuilderTests.cs │ ├── MigrationOptionBuilderTests.cs │ ├── MigrationRunnerTests.cs │ ├── TestEntity.cs │ └── TestMigration.cs ├── Migration ├── Builders │ ├── EntityOptionBuilder.cs │ └── MigrationOptionBuilder.cs ├── Contracts │ ├── IMigration.cs │ └── IVersioned.cs ├── Exceptions │ └── InvalidConfigurationException.cs ├── Migration.csproj ├── MigrationExtensions.cs ├── MigrationRunner.cs ├── MigrationSerializer.cs ├── MigrationSerializerProvider.cs ├── Models │ ├── EntityContext.cs │ ├── EntityOption.cs │ ├── MigrationContext.cs │ └── MigrationOption.cs └── Readme.md ├── MongoDB.Extensions.sln ├── Package.props ├── Prime.Extensions.Tests ├── AsyncCursorSourceExtensionsTests.cs ├── FilterDefinitionExtensionsTests.cs ├── FindFluentExtensionsTests.cs ├── Helpers │ ├── Bar.cs │ └── Foo.cs ├── MongoCollectionExtensionsTests.cs ├── MongoCollectionFindExtensionsTests.cs ├── MongoDatabaseExtensionsTests.cs ├── Prime.Extensions.Tests.csproj ├── UpdateDefinitionExtensionsTests.cs └── __snapshots__ │ ├── AsyncCursorSourceExtensionsTests.ToDictionaryAsync_FindMultipleDocumentsWithIdAsKey_ReturnRightDocuments.snap │ ├── AsyncCursorSourceExtensionsTests.ToDictionaryAsync_FindMultipleDocumentsWithNameAsKey_ReturnRightDocuments.snap │ ├── FilterDefinitionExtensionsTests.ToDefinitionString_DefinitionStringNoIdent_Success.snap │ ├── FilterDefinitionExtensionsTests.ToDefinitionString_DefinitionStringWithIdent_Success.snap │ ├── FilterDefinitionExtensionsTests.ToFilterString_FilterStringNoIdent_Success.snap │ ├── FilterDefinitionExtensionsTests.ToFilterString_FilterStringWithIdent_Success.snap │ ├── FindFluentExtensionsTests.PrintQuery_PrintOneSingleQuery_OriginalMongoDbQueryPrinted.snap │ ├── FindFluentExtensionsTests.ToQueryString_ToOneSingleQueryString_OriginalMongoDbQueryPrinted.snap │ ├── MongoCollectionExtensionsTests.FindIds_FindDuplicatedBarIdsAsynchronously_ReturnsDistinctBars.snap │ ├── MongoCollectionExtensionsTests.FindIds_FindDuplicatedBarIdsSynchronously_ReturnsDistinctBars.snap │ ├── MongoCollectionExtensionsTests.FindIds_FindFourBarIdsAsynchronously_ReturnsRightBars.snap │ ├── MongoCollectionExtensionsTests.FindIds_FindFourBarIdsSynchronously_ReturnsRightBars.snap │ ├── MongoCollectionExtensionsTests.FindIds_FindFourBarNamesAsynchronously_ReturnsRightBars.snap │ ├── MongoCollectionExtensionsTests.FindIds_FindFourBarNamesSynchronously_ReturnsRightBars.snap │ ├── MongoCollectionExtensionsTests.FindIds_FindOneId_ReturnsRightBar.snap │ ├── MongoCollectionFindExtensionsTests.FindIds_FindDuplicatedBarIdsAsynchronously_ReturnsDistinctBars.snap │ ├── MongoCollectionFindExtensionsTests.FindIds_FindDuplicatedBarIdsSynchronously_ReturnsDistinctBars.snap │ ├── MongoCollectionFindExtensionsTests.FindIds_FindFourBarIdsAsynchronously_ReturnsRightBars.snap │ ├── MongoCollectionFindExtensionsTests.FindIds_FindFourBarIdsSynchronously_ReturnsRightBars.snap │ ├── MongoCollectionFindExtensionsTests.FindIds_FindFourBarNamesAsynchronously_ReturnsRightBars.snap │ ├── MongoCollectionFindExtensionsTests.FindIds_FindFourBarNamesSynchronously_ReturnsRightBars.snap │ ├── MongoCollectionFindExtensionsTests.FindIds_FindOneId_ReturnsRightBar.snap │ ├── MongoDatabaseExtensionsTests.GetProfiledOperations_GetAllExecutedOperations_ReturnsAllMongoDBOperations.snap │ ├── MongoDatabaseExtensionsTests.GetProfiledOperations_GetOneExecutedOperations_ReturnsOneMongoDBOperation.snap │ ├── UpdateDefinitionExtensionsTests.ToDefinitionString_DefinitionStringNoIdent_Success.snap │ └── UpdateDefinitionExtensionsTests.ToDefinitionString_DefinitionStringWithIdent_Success.snap ├── Prime.Extensions ├── AsyncCursorSourceExtensions.cs ├── ClientSessionHandleExtensions.cs ├── FilterDefinitionExtensions.cs ├── FindFluentExtensions.cs ├── MongoCollectionExtensions.cs ├── MongoCollectionFindExtensions.cs ├── MongoDatabaseExtensions.cs ├── Prime.Extensions.csproj ├── ProfileLevel.cs ├── ProfilingStatus.cs ├── StringExtensions.cs └── UpdateDefinitionExtensions.cs ├── ResourceProject.props ├── Session.Tests ├── MongoServerExtensionsTests.cs ├── MongoSessionProviderTests.cs ├── Session.Tests.csproj └── __snapshots__ │ └── MongoServerExtensionsTests.GiveSession_WhenRefresh_ThenOkResult.snap ├── Session ├── ClientSessionHandleExtensions.cs ├── ISession.cs ├── ISessionProvider.cs ├── ITransactionSession.cs ├── Internal │ ├── MongoSession.cs │ ├── MongoSessionProvider.cs │ └── MongoTransactionSession.cs ├── Models │ ├── RefreshSession.cs │ └── SessionId.cs ├── MongoServerSessionExtensions.cs ├── ServiceCollectionExtensions.cs ├── Session.csproj └── TransactionSessionExtensions.cs ├── TestProject.props ├── Transactions.Tests ├── TransactionCollectionTests.cs ├── Transactions.Tests.csproj └── User.cs ├── Transactions ├── MongoDbEnlistmentScope.cs ├── MongoTransactionClient.cs ├── MongoTransactionCollection.cs ├── MongoTransactionDatabase.cs ├── MongoTransactionFilteredCollection.cs ├── TransactionClientExtensions.cs ├── TransactionCollectionExtensions.cs ├── TransactionDatabaseExtensions.cs ├── TransactionStore.cs └── Transactions.csproj └── Version.props /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-sonarscanner": { 6 | "version": "5.13.1", 7 | "commands": [ 8 | "dotnet-sonarscanner" 9 | ] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Desktop (please complete the following information):** 21 | - OS: [e.g. iOS] 22 | - Version [e.g. 22] 23 | 24 | **Additional context** 25 | Add any other context about the problem here. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | 5 | --- 6 | 7 | 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Summary of the changes (Less than 80 chars) 2 | 3 | - Detail 1 4 | - Detail 2 5 | 6 | Addresses #bugnumber (in this specific format) 7 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: 4 | pull_request: 5 | branches: ["master"] 6 | 7 | jobs: 8 | tests: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v4 13 | 14 | - name: Setup .NET 15 | uses: actions/setup-dotnet@v4 16 | with: 17 | dotnet-version: | 18 | 6.x.x 19 | 8.x.x 20 | 21 | - name: Dotnet Test 22 | run: dotnet test src 23 | 24 | # - name: Build, Test and Sonar 25 | # uses: swisslife-oss/actions/pull-request@main 26 | # with: 27 | # sonar_token: ${{ secrets.SONAR_TOKEN }} 28 | # sonar_project_key: "SwissLife-OSS_Mongo-Extensions" 29 | # sonar_project_name: "mongo-extensions" 30 | # pr_number: ${{ github.event.pull_request.number }} 31 | # pr_source_branch: ${{ github.head_ref }} 32 | # pr_target_branch: ${{ github.base_ref }} 33 | # github_repository: ${{ github.repository }} 34 | # sonar_exclusions: ${{ vars.SONAR_EXCLUSIONS }} 35 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | - name: Use .NET SDK from global.json 16 | uses: actions/setup-dotnet@v3 17 | - name: Build, Test and Push 18 | uses: swisslife-oss/actions/release-packages@main 19 | with: 20 | tag: ${{ github.ref_name }} 21 | nuget_api_key: ${{ secrets.NUGET_API_KEY }} 22 | enable_push: 'yes' 23 | 24 | # sonar: 25 | # runs-on: ubuntu-latest 26 | # steps: 27 | # - name: Checkout code 28 | # uses: actions/checkout@v2 29 | # - name: Setup .NET 30 | # uses: actions/setup-dotnet@v3 31 | # with: 32 | # dotnet-version: | 33 | # 8 34 | # - name: Restore tools 35 | # run: dotnet tool restore 36 | # - name: Build, Test and Sonar 37 | # uses: swisslife-oss/actions/release-sonar@main 38 | # with: 39 | # tag: ${{ github.ref_name }} 40 | # sonar_token: ${{ secrets.SONAR_TOKEN }} 41 | # sonar_project_key: 'SwissLife-OSS_Mongo-Extensions' 42 | # sonar_project_name: "mongo-extensions" 43 | # sonar_exclusions: ${{ vars.SONAR_EXCLUSIONS }} 44 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at Philippe.Birbaum@swisslife.ch. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Swiss Life OSS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.402", 4 | "rollForward": "latestFeature" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /samples/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /samples/Context/DataAccess/BlogRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Models; 6 | using MongoDB.Driver; 7 | 8 | namespace SimpleBlog.DataAccess 9 | { 10 | public class BlogRepository : IBlogRepository 11 | { 12 | private InsertOneOptions _insertOneOptions; 13 | private IMongoCollection _mongoCollection; 14 | 15 | public BlogRepository(ISimpleBlogDbContext simpleBlogDbContext) 16 | { 17 | if (simpleBlogDbContext == null) 18 | throw new ArgumentNullException(nameof(simpleBlogDbContext)); 19 | 20 | _mongoCollection = simpleBlogDbContext.CreateCollection(); 21 | 22 | _insertOneOptions = new InsertOneOptions() 23 | { 24 | BypassDocumentValidation = false 25 | }; 26 | } 27 | 28 | public async Task AddBlogAsync( 29 | Blog blog, CancellationToken cancellationToken) 30 | { 31 | await _mongoCollection 32 | .InsertOneAsync(blog, _insertOneOptions, cancellationToken); 33 | } 34 | 35 | public async Task> GetBlogsAsync( 36 | CancellationToken cancellationToken = default) 37 | { 38 | var findOptions = new FindOptions(); 39 | 40 | IAsyncCursor result = await _mongoCollection.FindAsync( 41 | Builders.Filter.Empty, findOptions, cancellationToken); 42 | 43 | return await result.ToListAsync(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /samples/Context/DataAccess/Configuration/BlogCollectionConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Models; 3 | using MongoDB.Driver; 4 | using MongoDB.Extensions.Context; 5 | 6 | namespace SimpleBlog.DataAccess 7 | { 8 | internal class BlogCollectionConfiguration : IMongoCollectionConfiguration 9 | { 10 | public void OnConfiguring(IMongoCollectionBuilder mongoCollectionBuilder) 11 | { 12 | mongoCollectionBuilder 13 | .WithCollectionName("blogs") 14 | .AddBsonClassMap(cm => 15 | { 16 | cm.AutoMap(); 17 | cm.MapIdMember(c => c.Id); 18 | }) 19 | .WithCollectionSettings(settings => settings.ReadConcern = ReadConcern.Majority) 20 | .WithCollectionSettings(settings => settings.ReadPreference = ReadPreference.Nearest) 21 | .WithCollectionConfiguration(collection => 22 | { 23 | var timestampIndex = new CreateIndexModel( 24 | Builders.IndexKeys.Ascending(blog => blog.TimeStamp), 25 | new CreateIndexOptions { Unique = false }); 26 | 27 | collection.Indexes.CreateOne(timestampIndex); 28 | }); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/Context/DataAccess/Configuration/TagCollectionConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Models; 2 | using MongoDB.Driver; 3 | using MongoDB.Extensions.Context; 4 | using Tag = Models.Tag; 5 | 6 | namespace SimpleBlog.DataAccess 7 | { 8 | internal class TagCollectionConfiguration : IMongoCollectionConfiguration 9 | { 10 | public void OnConfiguring(IMongoCollectionBuilder mongoCollectionBuilder) 11 | { 12 | mongoCollectionBuilder 13 | .AddBsonClassMap(cm => 14 | { 15 | cm.AutoMap(); 16 | cm.SetIgnoreExtraElements(true); 17 | }) 18 | .WithCollectionSettings(setting => 19 | { 20 | setting.ReadPreference = ReadPreference.Nearest; 21 | setting.ReadConcern = ReadConcern.Available; 22 | setting.WriteConcern = WriteConcern.Acknowledged; 23 | }) 24 | .WithCollectionConfiguration(collection => 25 | { 26 | var timestampIndex = new CreateIndexModel( 27 | Builders.IndexKeys.Ascending(tag => tag.Name), 28 | new CreateIndexOptions { Unique = true }); 29 | 30 | collection.Indexes.CreateOne(timestampIndex); 31 | }); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /samples/Context/DataAccess/Configuration/UserCollectionConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Models; 3 | using MongoDB.Bson.Serialization; 4 | using MongoDB.Driver; 5 | using MongoDB.Extensions.Context; 6 | 7 | namespace SimpleBlog.DataAccess 8 | { 9 | internal class UserCollectionConfiguration : IMongoCollectionConfiguration 10 | { 11 | public void OnConfiguring(IMongoCollectionBuilder mongoCollectionBuilder) 12 | { 13 | mongoCollectionBuilder 14 | .WithCollectionName("users") 15 | .AddBsonClassMap(ConfigureUserClassMap()) 16 | .WithCollectionSettings(ConfigureCollectionSettings()) 17 | .WithCollectionConfiguration(ConfigureIndexes()); 18 | } 19 | 20 | private static Action ConfigureCollectionSettings() 21 | { 22 | return setting => 23 | { 24 | setting.WriteConcern = WriteConcern.WMajority.With(journal: true); 25 | setting.ReadConcern = ReadConcern.Majority; 26 | setting.ReadPreference = ReadPreference.Primary; 27 | }; 28 | } 29 | 30 | private static Action> ConfigureIndexes() 31 | { 32 | return collection => 33 | { 34 | var emailIndex = new CreateIndexModel( 35 | Builders.IndexKeys.Ascending(user => user.Email), 36 | new CreateIndexOptions { Unique = true }); 37 | 38 | var nicknameIndex = new CreateIndexModel( 39 | Builders.IndexKeys.Ascending(user => user.Nickname), 40 | new CreateIndexOptions { Unique = true }); 41 | 42 | var firstname = new CreateIndexModel( 43 | Builders.IndexKeys.Ascending(user => user.Firstname), 44 | new CreateIndexOptions { Unique = false }); 45 | 46 | var secondname = new CreateIndexModel( 47 | Builders.IndexKeys.Ascending(user => user.Lastname), 48 | new CreateIndexOptions { Unique = false }); 49 | 50 | collection.Indexes.CreateMany( 51 | new[] { emailIndex, nicknameIndex, firstname, secondname }); 52 | }; 53 | } 54 | 55 | private static Action> ConfigureUserClassMap() 56 | { 57 | return cm => 58 | { 59 | cm.AutoMap(); 60 | cm.MapIdMember(u => u.UserId); 61 | }; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /samples/Context/DataAccess/DataAccess.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | SimpleBlog.DataAccess 6 | SimpleBlog.DataAccess 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /samples/Context/DataAccess/IBlogRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Models; 5 | 6 | namespace SimpleBlog.DataAccess 7 | { 8 | public interface IBlogRepository 9 | { 10 | Task AddBlogAsync( 11 | Blog blog, CancellationToken cancellationToken = default); 12 | 13 | Task> GetBlogsAsync( 14 | CancellationToken cancellationToken = default); 15 | } 16 | } -------------------------------------------------------------------------------- /samples/Context/DataAccess/ITagRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Models; 5 | 6 | namespace SimpleBlog.DataAccess 7 | { 8 | public interface ITagRepository 9 | { 10 | Task TryAddTagsAsync( 11 | IEnumerable newTags, 12 | CancellationToken cancellationToken = default); 13 | 14 | Task> GetTagsAsync( 15 | CancellationToken cancellationToken = default); 16 | } 17 | } -------------------------------------------------------------------------------- /samples/Context/DataAccess/IUserRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Models; 5 | 6 | namespace SimpleBlog.DataAccess 7 | { 8 | public interface IUserRepository 9 | { 10 | Task GetUserAsync( 11 | string userId, CancellationToken cancellationToken = default); 12 | 13 | Task AddUserAsync( 14 | User user, CancellationToken cancellationToken = default); 15 | 16 | Task AttachBlogToUserAsync( 17 | string userId, Guid blogId, CancellationToken cancellationToken = default); 18 | } 19 | } -------------------------------------------------------------------------------- /samples/Context/DataAccess/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using MongoDB.Extensions.Context; 4 | 5 | namespace SimpleBlog.DataAccess 6 | { 7 | public static class ServiceCollectionExtensions 8 | { 9 | public static IServiceCollection AddBlogDatabase( 10 | this IServiceCollection services, IConfiguration configuration) 11 | { 12 | MongoOptions blogDbOptions = configuration 13 | .GetMongoOptions(WellKnown.Path.SimpleBlogDB); 14 | 15 | services.AddSingleton(blogDbOptions); 16 | services.AddSingleton(); 17 | services.AddSingleton(); 18 | services.AddSingleton(); 19 | services.AddSingleton(); 20 | 21 | return services; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/Context/DataAccess/SimpleBlogDbContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Bson; 3 | using MongoDB.Bson.Serialization.Serializers; 4 | using MongoDB.Driver; 5 | using MongoDB.Driver.Core.Events; 6 | using MongoDB.Extensions.Context; 7 | 8 | namespace SimpleBlog.DataAccess 9 | { 10 | public class SimpleBlogDbContext : MongoDbContext, ISimpleBlogDbContext 11 | { 12 | public SimpleBlogDbContext(MongoOptions mongoOptions) : base(mongoOptions) 13 | { 14 | } 15 | 16 | protected override void OnConfiguring(IMongoDatabaseBuilder mongoDatabaseBuilder) 17 | { 18 | mongoDatabaseBuilder 19 | .RegisterCamelCaseConventionPack() 20 | .RegisterSerializer(new DateTimeOffsetSerializer()) 21 | .ConfigureConnection(con => con.ReadConcern = ReadConcern.Majority) 22 | .ConfigureConnection(con => con.WriteConcern = WriteConcern.WMajority) 23 | .ConfigureConnection(con => con.ReadPreference = ReadPreference.Primary) 24 | .ConfigureCollection(new UserCollectionConfiguration()) 25 | .ConfigureCollection(new BlogCollectionConfiguration()) 26 | .ConfigureCollection(new TagCollectionConfiguration()); 27 | } 28 | } 29 | 30 | public interface ISimpleBlogDbContext : IMongoDbContext 31 | { 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/Context/DataAccess/TagRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using MongoDB.Driver; 7 | using Tag = Models.Tag; 8 | 9 | namespace SimpleBlog.DataAccess 10 | { 11 | public class TagRepository : ITagRepository 12 | { 13 | private IMongoCollection _mongoCollection; 14 | 15 | public TagRepository(ISimpleBlogDbContext simpleBlogDbContext) 16 | { 17 | if (simpleBlogDbContext == null) 18 | throw new ArgumentNullException(nameof(simpleBlogDbContext)); 19 | 20 | _mongoCollection = simpleBlogDbContext.CreateCollection(); 21 | } 22 | 23 | public async Task> GetTagsAsync( 24 | CancellationToken cancellationToken) 25 | { 26 | var findOptions = new FindOptions(); 27 | 28 | IAsyncCursor result = await _mongoCollection.FindAsync( 29 | Builders.Filter.Empty, findOptions, cancellationToken); 30 | 31 | return await result.ToListAsync(); 32 | } 33 | 34 | public async Task TryAddTagsAsync( 35 | IEnumerable tags, CancellationToken cancellationToken) 36 | { 37 | var bulkWriteOptions = new BulkWriteOptions(); 38 | 39 | IEnumerable> bulkWrites = 40 | tags.Select(tag => new UpdateOneModel( 41 | Builders.Filter.Eq(t => t.Name, tag), 42 | Builders.Update.Inc(t => t.Count, 1)) 43 | { IsUpsert = true }); 44 | 45 | await _mongoCollection 46 | .BulkWriteAsync(bulkWrites, bulkWriteOptions, cancellationToken); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /samples/Context/DataAccess/UserRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Models; 5 | using MongoDB.Driver; 6 | 7 | namespace SimpleBlog.DataAccess 8 | { 9 | public class UserRepository : IUserRepository 10 | { 11 | private InsertOneOptions _insertOneOptions; 12 | private IMongoCollection _mongoCollection; 13 | 14 | public UserRepository(ISimpleBlogDbContext simpleBlogDbContext) 15 | { 16 | if (simpleBlogDbContext == null) 17 | throw new ArgumentNullException(nameof(simpleBlogDbContext)); 18 | 19 | _mongoCollection = simpleBlogDbContext.CreateCollection(); 20 | 21 | _insertOneOptions = new InsertOneOptions() 22 | { 23 | BypassDocumentValidation = false 24 | }; 25 | } 26 | 27 | public async Task GetUserAsync( 28 | string userId, CancellationToken cancellationToken= default) 29 | { 30 | FilterDefinition filter = Builders.Filter 31 | .Eq(user => user.UserId, userId); 32 | 33 | return await _mongoCollection 34 | .Find(filter) 35 | .SingleOrDefaultAsync(cancellationToken); 36 | } 37 | 38 | public async Task AddUserAsync( 39 | User user, CancellationToken cancellationToken = default) 40 | { 41 | await _mongoCollection 42 | .InsertOneAsync(user, _insertOneOptions, cancellationToken); 43 | } 44 | 45 | public async Task AttachBlogToUserAsync( 46 | string userId, Guid blogId, CancellationToken cancellationToken = default) 47 | { 48 | UpdateDefinition update = Builders.Update 49 | .AddToSet(u => u.Posts, blogId); 50 | 51 | var updateOptions = new UpdateOptions() 52 | { 53 | IsUpsert = true 54 | }; 55 | 56 | await _mongoCollection.UpdateOneAsync( 57 | user => user.UserId == userId, update, updateOptions, cancellationToken); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /samples/Context/DataAccess/WellKnown.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SimpleBlog.DataAccess 6 | { 7 | public static class WellKnown 8 | { 9 | public static class Path 10 | { 11 | public const string SimpleBlogDB = "SimpleBlog:Database"; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/Context/Domain/BlogService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Models; 6 | using SimpleBlog.DataAccess; 7 | 8 | namespace SimpleBlog.Domain 9 | { 10 | public class BlogService : IBlogService 11 | { 12 | private readonly IBlogRepository _blogRepository; 13 | private readonly IUserRepository _userRepository; 14 | private readonly ITagRepository _tagRepository; 15 | 16 | public BlogService( 17 | IBlogRepository blogRepository, 18 | IUserRepository userRepository, 19 | ITagRepository tagRepository) 20 | { 21 | _blogRepository = blogRepository; 22 | _userRepository = userRepository; 23 | _tagRepository = tagRepository; 24 | } 25 | 26 | public async Task PostBlogAsync(BlogPost blogPost, CancellationToken cancellationToken) 27 | { 28 | var blog = new Blog() 29 | { 30 | Id = Guid.NewGuid(), 31 | TimeStamp = DateTime.UtcNow, 32 | UserId = blogPost.UserId, 33 | Titel = blogPost.Titel, 34 | Text = blogPost.Text, 35 | Tags = blogPost.Tags 36 | }; 37 | 38 | await _blogRepository.AddBlogAsync(blog, cancellationToken); 39 | await _userRepository.AttachBlogToUserAsync(blog.UserId, blog.Id, cancellationToken); 40 | await _tagRepository.TryAddTagsAsync(blogPost.Tags, cancellationToken); 41 | } 42 | 43 | public async Task> GetAllTagsAsync(CancellationToken cancellationToken) 44 | { 45 | return await _tagRepository.GetTagsAsync(cancellationToken); 46 | } 47 | 48 | public async Task> GetAllBlogsAsync(CancellationToken cancellationToken) 49 | { 50 | return await _blogRepository.GetBlogsAsync(cancellationToken); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /samples/Context/Domain/Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | SimpleBlog.Domain 6 | SimpleBlog.Domain 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /samples/Context/Domain/IBlogService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Models; 5 | 6 | namespace SimpleBlog.Domain 7 | { 8 | public interface IBlogService 9 | { 10 | Task PostBlogAsync(BlogPost blogPost, CancellationToken cancellationToken); 11 | Task> GetAllBlogsAsync(CancellationToken cancellationToken); 12 | Task> GetAllTagsAsync(CancellationToken cancellationToken); 13 | } 14 | } -------------------------------------------------------------------------------- /samples/Context/Domain/IUserService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Models; 4 | 5 | namespace SimpleBlog.Domain 6 | { 7 | public interface IUserService 8 | { 9 | Task EnsureUserAsync(string userId, CancellationToken cancellationToken = default); 10 | Task RegisterUserAsync(User newUser, CancellationToken cancellationToken = default); 11 | } 12 | } -------------------------------------------------------------------------------- /samples/Context/Domain/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace SimpleBlog.Domain 4 | { 5 | public static class ServiceCollectionExtensions 6 | { 7 | public static IServiceCollection AddBlogDomain( 8 | this IServiceCollection services) 9 | { 10 | services.AddSingleton(); 11 | services.AddSingleton(); 12 | 13 | return services; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/Context/Domain/UserService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Models; 5 | using SimpleBlog.DataAccess; 6 | 7 | namespace SimpleBlog.Domain 8 | { 9 | public class UserService : IUserService 10 | { 11 | private readonly IUserRepository _userRepository; 12 | 13 | public UserService(IUserRepository userRepository) 14 | { 15 | _userRepository = userRepository; 16 | } 17 | 18 | public async Task EnsureUserAsync( 19 | string userId, CancellationToken cancellationToken = default) 20 | { 21 | User user = await _userRepository.GetUserAsync(userId, cancellationToken); 22 | 23 | if(user == null) 24 | { 25 | await RegisterUserAsync(new User() 26 | { 27 | UserId = userId, 28 | Email = $"{userId}@UnknownEmail.ch", 29 | Nickname = $"UnknownNickName-{userId}" 30 | }); 31 | } 32 | } 33 | 34 | public async Task RegisterUserAsync( 35 | User newUser, CancellationToken cancellationToken = default) 36 | { 37 | await _userRepository.AddUserAsync(newUser, cancellationToken); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/Context/Host/Controllers/SimpleBlogController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Models; 6 | using SimpleBlog.Domain; 7 | 8 | namespace SimpleBlog.Api.Controllers 9 | { 10 | [ApiController] 11 | [Route("api/[controller]")] 12 | public class SimpleBlogController : ControllerBase 13 | { 14 | private readonly IBlogService _blogService; 15 | private readonly IUserService _userService; 16 | 17 | public SimpleBlogController( 18 | IBlogService blogService, IUserService userService) 19 | { 20 | _blogService = blogService; 21 | _userService = userService; 22 | } 23 | 24 | [HttpPost] 25 | [Route("blogs")] 26 | public async Task PostBlog( 27 | BlogPost blogPost, CancellationToken cancellationToken) 28 | { 29 | await _userService.EnsureUserAsync(blogPost.UserId, cancellationToken); 30 | await _blogService.PostBlogAsync(blogPost, cancellationToken); 31 | } 32 | 33 | [HttpGet] 34 | [Route("blogs")] 35 | public async Task>> GetBlogs( 36 | CancellationToken cancellationToken) 37 | { 38 | IEnumerable result = await _blogService.GetAllBlogsAsync(cancellationToken); 39 | 40 | return Ok(result); 41 | } 42 | 43 | [HttpGet] 44 | [Route("tags")] 45 | public async Task>> GetTags( 46 | CancellationToken cancellationToken) 47 | { 48 | IEnumerable result = await _blogService.GetAllTagsAsync(cancellationToken); 49 | 50 | return Ok(result); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /samples/Context/Host/Host.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | fbbac7be-63d2-4849-8dc4-31492a2367a2 6 | Linux 7 | ..\.. 8 | SimpleBlog.Host 9 | SimpleBlog.Host 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /samples/Context/Host/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | using Squadron; 10 | 11 | namespace SimpleBlog.Api 12 | { 13 | public class Program 14 | { 15 | private static MongoReplicaSetResource _mongoResource; 16 | 17 | public static async Task Main(string[] args) 18 | { 19 | _mongoResource = new MongoReplicaSetResource(); 20 | 21 | await _mongoResource.InitializeAsync(); 22 | 23 | CreateHostBuilder(args).Build().Run(); 24 | 25 | await _mongoResource.DisposeAsync(); 26 | } 27 | 28 | public static IHostBuilder CreateHostBuilder(string[] args) 29 | { 30 | string mongoConnectionString = _mongoResource.ConnectionString; 31 | string mongoDatabaseName = _mongoResource 32 | .CreateDatabase() 33 | .DatabaseNamespace 34 | .DatabaseName; 35 | 36 | return Host.CreateDefaultBuilder(args) 37 | .ConfigureLogging(configure => 38 | { 39 | configure.AddConsole(); 40 | }) 41 | .ConfigureAppConfiguration(builder => 42 | { 43 | builder.AddJsonFile("appsettings.json"); 44 | builder.AddEnvironmentVariables(); 45 | builder.AddInMemoryCollection(new Dictionary() 46 | { 47 | { "SimpleBlog:Database:ConnectionString", mongoConnectionString }, 48 | {"SimpleBlog:Database:DatabaseName", mongoDatabaseName } 49 | }); 50 | }) 51 | .ConfigureWebHostDefaults(webBuilder => 52 | { 53 | webBuilder.UseStartup(); 54 | }); 55 | } 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /samples/Context/Host/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:59489/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "Host": { 12 | "commandName": "Project", 13 | "launchBrowser": true, 14 | "launchUrl": "http://localhost:59490/swagger", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | }, 18 | "applicationUrl": "http://localhost:59490/" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /samples/Context/Host/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | using SimpleBlog.DataAccess; 7 | using SimpleBlog.Domain; 8 | 9 | namespace SimpleBlog.Api 10 | { 11 | public class Startup 12 | { 13 | public Startup(IConfiguration configuration) 14 | { 15 | Configuration = configuration; 16 | } 17 | 18 | public IConfiguration Configuration { get; } 19 | 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | services.AddControllers(); 23 | services.AddBlogDomain(); 24 | services.AddBlogDatabase(Configuration); 25 | services.AddSwaggerDocument(); 26 | 27 | } 28 | 29 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 30 | { 31 | if (env.IsDevelopment()) 32 | { 33 | app.UseDeveloperExceptionPage(); 34 | } 35 | 36 | app.UseOpenApi(); 37 | app.UseSwaggerUi(); 38 | 39 | app.UseHttpsRedirection(); 40 | 41 | app.UseRouting(); 42 | 43 | app.UseAuthorization(); 44 | 45 | app.UseEndpoints(endpoints => 46 | { 47 | endpoints.MapControllers(); 48 | }); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /samples/Context/Host/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/Context/Host/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "SimpleBlog": { 11 | "Database": { 12 | "ConnectionString": "mongodb://localhost:27017", 13 | "DatabaseName": "SimpleBlog" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/Context/Models/Blog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Models 5 | { 6 | public class BlogPost 7 | { 8 | public string Titel { get; set; } 9 | public string Text { get; set; } 10 | public IEnumerable Tags { get; set; } 11 | public string UserId { get; set; } 12 | } 13 | 14 | public class Blog 15 | { 16 | public Guid Id { get; set; } 17 | public string Titel { get; set; } 18 | public string Text { get; set; } 19 | public DateTime TimeStamp { get; set; } 20 | public IEnumerable Tags { get; set; } 21 | public string UserId { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/Context/Models/Models.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | SimpleBlog.Models 6 | SimpleBlog.Models 7 | false 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /samples/Context/Models/Tag.cs: -------------------------------------------------------------------------------- 1 | namespace Models 2 | { 3 | public class Tag 4 | { 5 | public string Name { get; set; } 6 | 7 | public int Count { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /samples/Context/Models/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Models 5 | { 6 | public class User 7 | { 8 | public User() 9 | { 10 | Posts = new List(); 11 | } 12 | 13 | public string UserId { get; set; } 14 | 15 | public string Email { get; set; } 16 | 17 | public string Nickname { get; set; } 18 | 19 | public string Firstname { get; set; } 20 | 21 | public string Lastname { get; set; } 22 | 23 | public IEnumerable Posts { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /samples/Migration/Customer.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Extensions.Migration; 2 | 3 | namespace Migration; 4 | 5 | public record Customer(string Id, string Name) : IVersioned 6 | { 7 | public int Version { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /samples/Migration/ExampleMigration.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson; 2 | using MongoDB.Extensions.Migration; 3 | 4 | namespace Migration; 5 | 6 | public class ExampleMigration : IMigration 7 | { 8 | public int Version => 1; 9 | 10 | public void Up(BsonDocument document) 11 | { 12 | document["Name"] += " Migrated up to 1"; 13 | } 14 | 15 | public void Down(BsonDocument document) 16 | { 17 | document["Name"] += " Migrated down to 0"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/Migration/Migration.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/Migration/Program.cs: -------------------------------------------------------------------------------- 1 | using Migration; 2 | using MongoDB.Extensions.Migration; 3 | using MongoDB.Driver; 4 | 5 | var builder = WebApplication.CreateBuilder(args); 6 | 7 | builder.Services 8 | .AddSingleton(_ => new MongoClient("mongodb://localhost:27017")) 9 | .AddTransient(); 10 | 11 | var app = builder.Build(); 12 | 13 | app.UseMongoMigration(m => m 14 | .ForEntity(e => e 15 | .AtVersion(1) 16 | .WithMigration(new ExampleMigration()))); 17 | 18 | app.MapGet("/customer/{id}", (string id, Repository repo) => repo.GetAsync(id)); 19 | app.MapPost("/customer/", (Customer customer, Repository repo) => repo.AddAsync(customer)); 20 | 21 | app.Run(); 22 | -------------------------------------------------------------------------------- /samples/Migration/Repository.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | using MongoDB.Driver.Linq; 3 | 4 | namespace Migration; 5 | 6 | public class Repository 7 | { 8 | private readonly IMongoCollection _collection; 9 | 10 | public Repository(MongoClient client) 11 | { 12 | var database = client.GetDatabase("Example1"); 13 | _collection = database.GetCollection("customer"); 14 | } 15 | 16 | public Task AddAsync(Customer customer) => _collection.InsertOneAsync(customer); 17 | 18 | public Task GetAsync(string id) => _collection.AsQueryable() 19 | .SingleOrDefaultAsync(c => c.Id == id); 20 | } 21 | -------------------------------------------------------------------------------- /samples/Migration/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/Migration/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/Context.AllowedTypes.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MongoDB.Extensions.Context.AllowedTypes.Tests 6 | MongoDB.Extensions.Context.AllowedTypes.Tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/Helpers/Bar.cs: -------------------------------------------------------------------------------- 1 | namespace MongoDB.Extensions.Context.AllowedTypes.Tests; 2 | 3 | public class Bar 4 | { 5 | public int Id { get; set; } 6 | public string? BarName { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/Helpers/Foo.cs: -------------------------------------------------------------------------------- 1 | namespace MongoDB.Extensions.Context.AllowedTypes.Tests; 2 | 3 | public class Foo 4 | { 5 | public int Id { get; set; } 6 | public string? FooName { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/Helpers/MongoCollectionFixture.cs: -------------------------------------------------------------------------------- 1 | using Squadron; 2 | using Xunit; 3 | 4 | namespace MongoDB.Extensions.Context.AllowedTypes.Tests; 5 | 6 | public static class CollectionFixtureNames 7 | { 8 | public const string MongoCollectionFixture = "MongoCollectionFixture"; 9 | } 10 | 11 | [CollectionDefinition(CollectionFixtureNames.MongoCollectionFixture)] 12 | public class MongoCollectionFixture 13 | : ICollectionFixture 14 | { 15 | } 16 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/Helpers/TestHelpers.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace MongoDB.Extensions.Context.AllowedTypes.Tests.Helpers; 5 | 6 | internal static class TestHelpers 7 | { 8 | public static object GetTypeObjectSerializerContent() 9 | { 10 | return new 11 | { 12 | AllowedTypes = TypeObjectSerializer.AllowedTypes 13 | .Select(pair => new KeyValuePair(pair.Key.FullName, pair.Value)) 14 | .OrderBy(pair => pair.Key), 15 | AllowedTypesByNamespaces = TypeObjectSerializer.AllowedTypesByNamespaces 16 | .OrderBy(x => x), 17 | AllowedTypesByDependencies = TypeObjectSerializer.AllowedTypesByDependencies 18 | .Except(new[] { "Coverlet" }) 19 | .OrderBy(x => x) 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/MongoDatabaseBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using MongoDB.Bson.Serialization; 4 | using MongoDB.Driver; 5 | using MongoDB.Extensions.Context.AllowedTypes.Tests.Helpers; 6 | using Snapshooter.Xunit; 7 | using Squadron; 8 | using Xunit; 9 | 10 | namespace MongoDB.Extensions.Context.AllowedTypes.Tests; 11 | 12 | [Collection(CollectionFixtureNames.MongoCollectionFixture)] 13 | public class MongoDatabaseBuilderTests 14 | { 15 | private readonly MongoOptions _mongoOptions; 16 | private readonly IMongoDatabase _mongoDatabase; 17 | 18 | public MongoDatabaseBuilderTests(MongoResource mongoResource) 19 | { 20 | _mongoDatabase = mongoResource.CreateDatabase(); 21 | _mongoOptions = new MongoOptions 22 | { 23 | ConnectionString = mongoResource.ConnectionString, 24 | DatabaseName = _mongoDatabase.DatabaseNamespace.DatabaseName 25 | }; 26 | } 27 | 28 | [Fact] 29 | public void AddAllowedTypes_AllowedTypesRegisteredByDefault_Success() 30 | { 31 | // Arrange 32 | var mongoDatabaseBuilder = new MongoDatabaseBuilder(_mongoOptions); 33 | 34 | mongoDatabaseBuilder.ClearAllowedTypes(); 35 | 36 | // Act 37 | MongoDbContextData context = mongoDatabaseBuilder.Build(); 38 | 39 | // Assert 40 | Assert.NotNull(context); 41 | } 42 | 43 | [Fact] 44 | public void AddAllowedTypes_AddAllowedTypesOfAllDependencies_Success() 45 | { 46 | // Arrange 47 | var mongoDatabaseBuilder = new MongoDatabaseBuilder(_mongoOptions); 48 | 49 | mongoDatabaseBuilder.ClearAllowedTypes(); 50 | 51 | // Act 52 | mongoDatabaseBuilder.AddAllowedTypesOfAllDependencies(); 53 | mongoDatabaseBuilder.Build(); 54 | 55 | // Assert 56 | IBsonSerializer registeredSerializer = 57 | BsonSerializer.LookupSerializer(); 58 | 59 | Assert.True(registeredSerializer is TypeObjectSerializer); 60 | Snapshot.Match(TestHelpers.GetTypeObjectSerializerContent(), 61 | options => options.Assert(fieldOption => 62 | Assert.Contains("MongoDB", fieldOption 63 | .Fields("AllowedTypesByDependencies[*]")))); 64 | } 65 | 66 | [Fact] 67 | public void AddAllowedTypes_AddAllowedTypesByNamespaces_Success() 68 | { 69 | // Arrange 70 | var mongoDatabaseBuilder = new MongoDatabaseBuilder(_mongoOptions); 71 | 72 | mongoDatabaseBuilder.ClearAllowedTypes(); 73 | 74 | // Act 75 | mongoDatabaseBuilder.AddAllowedTypes("Mongo", "SwissLife"); 76 | mongoDatabaseBuilder.Build(); 77 | 78 | // Assert 79 | IBsonSerializer registeredSerializer = 80 | BsonSerializer.LookupSerializer(); 81 | 82 | Assert.True(registeredSerializer is TypeObjectSerializer); 83 | Snapshot.Match(TestHelpers.GetTypeObjectSerializerContent()); 84 | } 85 | 86 | [Fact] 87 | public void AddAllowedTypes_AddAllowedTypesByTypes_Success() 88 | { 89 | // Arrange 90 | var mongoDatabaseBuilder = new MongoDatabaseBuilder(_mongoOptions); 91 | 92 | mongoDatabaseBuilder.ClearAllowedTypes(); 93 | 94 | // Act 95 | mongoDatabaseBuilder.AddAllowedTypes(typeof(Foo), typeof(Bar)); 96 | mongoDatabaseBuilder.Build(); 97 | 98 | // Assert 99 | IBsonSerializer registeredSerializer = 100 | BsonSerializer.LookupSerializer(); 101 | 102 | Assert.True(registeredSerializer is TypeObjectSerializer); 103 | Snapshot.Match(TestHelpers.GetTypeObjectSerializerContent()); 104 | } 105 | 106 | public class Bar 107 | { 108 | public int Id { get; set; } 109 | public string? BarName { get; set; } 110 | } 111 | 112 | public class Foo 113 | { 114 | public int Id { get; set; } 115 | public string? FooName { get; set; } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(DisableTestParallelization = true)] 4 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/__snapshots__/MongoDatabaseBuilderTests.AddAllowedTypes_AddAllowedTypesByNamespaces_Success.snap: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedTypes": [], 3 | "AllowedTypesByNamespaces": [ 4 | "Mongo", 5 | "SwissLife" 6 | ], 7 | "AllowedTypesByDependencies": [] 8 | } 9 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/__snapshots__/MongoDatabaseBuilderTests.AddAllowedTypes_AddAllowedTypesByTypes_Success.snap: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedTypes": [ 3 | { 4 | "Key": "MongoDB.Extensions.Context.AllowedTypes.Tests.MongoDatabaseBuilderTests+Bar", 5 | "Value": true 6 | }, 7 | { 8 | "Key": "MongoDB.Extensions.Context.AllowedTypes.Tests.MongoDatabaseBuilderTests+Foo", 9 | "Value": true 10 | } 11 | ], 12 | "AllowedTypesByNamespaces": [], 13 | "AllowedTypesByDependencies": [] 14 | } 15 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/__snapshots__/MongoDatabaseBuilderTests.AddAllowedTypes_AddAllowedTypesOfAllDependencies_Success.snap: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedTypes": [], 3 | "AllowedTypesByNamespaces": [], 4 | "AllowedTypesByDependencies": [ 5 | "Docker", 6 | "Internal", 7 | "MongoDB", 8 | "Newtonsoft", 9 | "Polly", 10 | "Snapshooter", 11 | "Squadron", 12 | "Xunit" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/__snapshots__/TypeObjectSerializerTests.AddAllowedTypes_AddAllowedTypesByNamespaces_Success.snap: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedTypes": [], 3 | "AllowedTypesByNamespaces": [ 4 | "Mongo", 5 | "SwissLife" 6 | ], 7 | "AllowedTypesByDependencies": [] 8 | } 9 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/__snapshots__/TypeObjectSerializerTests.AddAllowedTypes_AddAllowedTypesByTypes_Success.snap: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedTypes": [ 3 | { 4 | "Key": "MongoDB.Extensions.Context.AllowedTypes.Tests.Bar", 5 | "Value": true 6 | }, 7 | { 8 | "Key": "MongoDB.Extensions.Context.AllowedTypes.Tests.Foo", 9 | "Value": true 10 | } 11 | ], 12 | "AllowedTypesByNamespaces": [], 13 | "AllowedTypesByDependencies": [] 14 | } 15 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/__snapshots__/TypeObjectSerializerTests.AddAllowedTypes_AddAllowedTypesOfAllDependencies_Success.snap: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedTypes": [], 3 | "AllowedTypesByNamespaces": [], 4 | "AllowedTypesByDependencies": [ 5 | "Internal", 6 | "MongoDB", 7 | "Newtonsoft", 8 | "Snapshooter", 9 | "Squadron", 10 | "Xunit" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/__snapshots__/TypeObjectSerializerTests.IsTypeAllowed_InAllowedNamespaces_True.snap: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedTypes": [ 3 | { 4 | "Key": "MongoDB.Extensions.Context.AllowedTypes.Tests.Foo", 5 | "Value": true 6 | } 7 | ], 8 | "AllowedTypesByNamespaces": [ 9 | "MongoDB.Extensions.Context.AllowedTypes.Tests" 10 | ], 11 | "AllowedTypesByDependencies": [] 12 | } 13 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/__snapshots__/TypeObjectSerializerTests.IsTypeAllowed_InAllowedTypesInDependencies_True.snap: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedTypes": [ 3 | { 4 | "Key": "MongoDB.Extensions.Context.AllowedTypes.Tests.Foo", 5 | "Value": true 6 | } 7 | ], 8 | "AllowedTypesByNamespaces": [], 9 | "AllowedTypesByDependencies": [ 10 | "Internal", 11 | "MongoDB", 12 | "Newtonsoft", 13 | "Snapshooter", 14 | "Squadron", 15 | "Xunit" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/__snapshots__/TypeObjectSerializerTests.IsTypeAllowed_InAllowedTypes_False.snap: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedTypes": [ 3 | { 4 | "Key": "MongoDB.Extensions.Context.AllowedTypes.Tests.Bar", 5 | "Value": false 6 | }, 7 | { 8 | "Key": "MongoDB.Extensions.Context.AllowedTypes.Tests.Foo", 9 | "Value": true 10 | } 11 | ], 12 | "AllowedTypesByNamespaces": [], 13 | "AllowedTypesByDependencies": [] 14 | } 15 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/__snapshots__/TypeObjectSerializerTests.IsTypeAllowed_InAllowedTypes_True.snap: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedTypes": [ 3 | { 4 | "Key": "MongoDB.Extensions.Context.AllowedTypes.Tests.Foo", 5 | "Value": true 6 | } 7 | ], 8 | "AllowedTypesByNamespaces": [], 9 | "AllowedTypesByDependencies": [] 10 | } 11 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/__snapshots__/TypeObjectSerializerTests.IsTypeAllowed_PartIsInAllowedNamespacesCaseInsensitive_True.snap: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedTypes": [ 3 | { 4 | "Key": "MongoDB.Extensions.Context.AllowedTypes.Tests.Foo", 5 | "Value": true 6 | } 7 | ], 8 | "AllowedTypesByNamespaces": [ 9 | "MONGODB.EXTENSIONS" 10 | ], 11 | "AllowedTypesByDependencies": [] 12 | } 13 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/__snapshots__/TypeObjectSerializerTests.IsTypeAllowed_PartIsInAllowedNamespaces_True.snap: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedTypes": [ 3 | { 4 | "Key": "MongoDB.Extensions.Context.AllowedTypes.Tests.Foo", 5 | "Value": true 6 | } 7 | ], 8 | "AllowedTypesByNamespaces": [ 9 | "MongoDB.Extensions.Context" 10 | ], 11 | "AllowedTypesByDependencies": [] 12 | } 13 | -------------------------------------------------------------------------------- /src/Context.AllowedTypes.Tests/__snapshots__/TypeObjectSerializerTests.IsTypeAllowed_PartIsNotInAllowedNamespaces_False.snap: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedTypes": [ 3 | { 4 | "Key": "MongoDB.Extensions.Context.AllowedTypes.Tests.Foo", 5 | "Value": false 6 | } 7 | ], 8 | "AllowedTypesByNamespaces": [ 9 | "MongoDBs.Context" 10 | ], 11 | "AllowedTypesByDependencies": [] 12 | } 13 | -------------------------------------------------------------------------------- /src/Context.GuidSerializer.Tests/Context.GuidSerializers.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MongoDB.Extensions.Context.GuidSerializers.Tests 6 | MongoDB.Extensions.Context.GuidSerializers.Tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Context.GuidSerializer.Tests/GuidSerializerTests.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | using Squadron; 3 | using Xunit; 4 | using System; 5 | using System.Threading.Tasks; 6 | using Snapshooter.Xunit; 7 | using MongoDB.Prime.Extensions; 8 | 9 | namespace MongoDB.Extensions.Context.GuidSerializers.Tests; 10 | 11 | public class GuidSerializerTests : IClassFixture 12 | { 13 | private readonly MongoOptions _mongoOptions; 14 | private readonly IMongoDatabase _mongoDatabase; 15 | 16 | public GuidSerializerTests(MongoResource mongoResource) 17 | { 18 | _mongoDatabase = mongoResource.CreateDatabase(); 19 | _mongoOptions = new MongoOptions 20 | { 21 | ConnectionString = mongoResource.ConnectionString, 22 | DatabaseName = _mongoDatabase.DatabaseNamespace.DatabaseName 23 | }; 24 | } 25 | 26 | [Fact] 27 | public async Task Serialize_GuidPropertyGuidSerialized_Successfully() 28 | { 29 | // Arrange 30 | var foobarMongoDbContext = new FooBarMongoDbContext(_mongoOptions); 31 | 32 | IMongoCollection collection = 33 | _mongoDatabase.GetCollection("foos"); 34 | 35 | Foo foo = new Foo 36 | ( 37 | fooId: Guid.Parse("b1eba0d6-a1f9-4e31-bd70-0feed19f4492"), 38 | name: "test", 39 | additionalId: Guid.Parse("b58ec857-c874-457e-8662-133a055282f6") 40 | ); 41 | 42 | // Act 43 | await collection.InsertOneAsync(foo); 44 | 45 | // Assert 46 | Snapshot.Match(collection.Dump()); 47 | } 48 | 49 | [Fact] 50 | public async Task Serialize_ObjectPropertyGuidSerialized_Successfully() 51 | { 52 | // Arrange 53 | var foobarMongoDbContext = new FooBarMongoDbContext(_mongoOptions); 54 | 55 | IMongoCollection collection = 56 | _mongoDatabase.GetCollection("bars"); 57 | 58 | Bar bar = new Bar 59 | ( 60 | fooId: Guid.Parse("b1eba0d6-a1f9-4e31-bd70-0feed19f4492"), 61 | name: "test", 62 | additionalId: Guid.Parse("b58ec857-c874-457e-8662-133a055282f6") 63 | ); 64 | 65 | // Act 66 | await collection.InsertOneAsync(bar); 67 | 68 | // Assert 69 | Snapshot.Match(collection.Dump()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Context.GuidSerializer.Tests/Helpers/Bar.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MongoDB.Extensions.Context.GuidSerializers.Tests; 4 | 5 | public class Bar 6 | { 7 | public Bar(Guid fooId, string name, Guid additionalId) 8 | { 9 | Id = fooId; 10 | Name = name; 11 | AdditionalId = additionalId; 12 | } 13 | 14 | public Guid Id { get; private set; } 15 | 16 | public string Name { get; private set;} 17 | 18 | public object AdditionalId { get; private set;} 19 | } 20 | -------------------------------------------------------------------------------- /src/Context.GuidSerializer.Tests/Helpers/Foo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MongoDB.Extensions.Context.GuidSerializers.Tests; 4 | 5 | public class Foo 6 | { 7 | public Foo(Guid fooId, string name, Guid additionalId) 8 | { 9 | Id = fooId; 10 | Name = name; 11 | AdditionalId = additionalId; 12 | } 13 | 14 | public Guid Id { get; private set; } 15 | 16 | public string Name { get; private set;} 17 | 18 | public Guid AdditionalId { get; private set;} 19 | } 20 | -------------------------------------------------------------------------------- /src/Context.GuidSerializer.Tests/Helpers/FooBarMongoDbContext.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson; 2 | using MongoDB.Bson.Serialization.Serializers; 3 | 4 | namespace MongoDB.Extensions.Context.GuidSerializers.Tests; 5 | 6 | public class FooBarMongoDbContext : MongoDbContext 7 | { 8 | public FooBarMongoDbContext(MongoOptions mongoOptions) 9 | : base(mongoOptions) 10 | { 11 | } 12 | 13 | protected override void OnConfiguring( 14 | IMongoDatabaseBuilder databaseBuilder) 15 | { 16 | databaseBuilder.RegisterSerializer(new GuidSerializer(GuidRepresentation.CSharpLegacy)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Context.GuidSerializer.Tests/__snapshots__/GuidSerializerTests.Serialize_GuidPropertyGuidSerialized_Successfully.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Id": "b1eba0d6-a1f9-4e31-bd70-0feed19f4492", 4 | "Name": "test", 5 | "AdditionalId": "b58ec857-c874-457e-8662-133a055282f6" 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /src/Context.GuidSerializer.Tests/__snapshots__/GuidSerializerTests.Serialize_ObjectPropertyGuidSerialized_Successfully.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Id": "b1eba0d6-a1f9-4e31-bd70-0feed19f4492", 4 | "Name": "test", 5 | "AdditionalId": "b58ec857-c874-457e-8662-133a055282f6" 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /src/Context.InterferingTests/Context.InterferingTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MongoDB.Extensions.Context.InterferingTests 6 | MongoDB.Extensions.Context.InterferingTests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Context.InterferingTests/Helpers/Foo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MongoDB.Extensions.Context.InterferingTests.Helpers 4 | { 5 | public class Foo 6 | { 7 | public Foo(Guid fooId, string name, int number) 8 | { 9 | FooId = fooId; 10 | Name = name; 11 | Number = number; 12 | Timestamp = DateTime.UtcNow; 13 | } 14 | 15 | public Guid FooId { get; } 16 | 17 | public string Name { get; } 18 | 19 | public int Number { get; } 20 | 21 | public DateTime Timestamp { get; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Context.InterferingTests/Helpers/FooCollectionConfiguration.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | 3 | namespace MongoDB.Extensions.Context.InterferingTests.Helpers 4 | { 5 | public class FooCollectionConfiguration : IMongoCollectionConfiguration 6 | { 7 | public void OnConfiguring(IMongoCollectionBuilder mongoCollectionBuilder) 8 | { 9 | mongoCollectionBuilder 10 | .WithCollectionName("TheArtOfFoo") 11 | .AddBsonClassMap(cm => 12 | { 13 | cm.AutoMap(); 14 | cm.MapIdMember(c => c.FooId); 15 | }) 16 | .WithCollectionSettings(ConfigureSettings) 17 | .WithCollectionConfiguration(ConfigureIndexes); 18 | } 19 | 20 | private void ConfigureSettings(MongoCollectionSettings settings) 21 | { 22 | settings.ReadConcern = ReadConcern.Available; 23 | settings.WriteConcern = WriteConcern.WMajority.With(journal: true); 24 | settings.ReadPreference = ReadPreference.Primary; 25 | } 26 | 27 | private void ConfigureIndexes(IMongoCollection auditCollection) 28 | { 29 | var nameIndex = new CreateIndexModel( 30 | Builders.IndexKeys.Ascending(foo => foo.Name), 31 | new CreateIndexOptions { 32 | Name = "Name_case_insensitive", 33 | Unique = false, 34 | Background = true, 35 | Collation = new Collation(locale: "en", 36 | strength: CollationStrength.Secondary) 37 | }); 38 | 39 | var numberIndex = new CreateIndexModel( 40 | Builders.IndexKeys.Ascending(foo => foo.Number), 41 | new CreateIndexOptions { Unique = true }); 42 | 43 | auditCollection.Indexes.CreateMany(new[] { nameIndex, numberIndex }); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Context.Tests/Context.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MongoDB.Extensions.Context.Tests 6 | MongoDB.Extensions.Context.Tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Context.Tests/Helpers/Bar.cs: -------------------------------------------------------------------------------- 1 | namespace MongoDB.Extensions.Context.Tests 2 | { 3 | public class Bar 4 | { 5 | public int Id { get; set; } 6 | public string? BarName { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Context.Tests/Helpers/Foo.cs: -------------------------------------------------------------------------------- 1 | namespace MongoDB.Extensions.Context.Tests 2 | { 3 | public class Foo 4 | { 5 | public int Id { get; set; } 6 | public string? FooName { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Context.Tests/Helpers/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace MongoDB.Extensions.Context.Tests.Helpers 6 | { 7 | public class Order 8 | { 9 | public string Id { get; set; } 10 | 11 | public string Name { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Context.Tests/Internal/DependencyTypesResolverTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Snapshooter.Xunit; 4 | using Xunit; 5 | 6 | namespace MongoDB.Extensions.Context.Tests.Internal; 7 | 8 | public class DependencyTypesResolverTests 9 | { 10 | [Fact] 11 | public void GetAllowedTypesByDependencies_All_Successful() 12 | { 13 | // Arrange 14 | 15 | // Act 16 | IEnumerable knownNamespaces = DependencyTypesResolver 17 | .GetAllowedTypesByDependencies(new[] { "Coverlet", "Castle" }) 18 | .OrderBy(x => x); 19 | 20 | // Assert 21 | Snapshot.Match(knownNamespaces, 22 | options => options.Assert(fieldOption => 23 | Assert.Contains("MongoDB", fieldOption 24 | .Fields("[*]")))); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Context.Tests/Internal/__snapshots__/DependencyTypesResolverTests.GetAllowedTypesByDependencies_All_Successful.snap: -------------------------------------------------------------------------------- 1 | [ 2 | "Internal", 3 | "MongoDB", 4 | "Newtonsoft", 5 | "Snapshooter", 6 | "Squadron", 7 | "Xunit" 8 | ] 9 | -------------------------------------------------------------------------------- /src/Context.Tests/IsExternalInit.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System.ComponentModel; 6 | 7 | namespace System.Runtime.CompilerServices 8 | { 9 | /// 10 | /// Reserved to be used by the compiler for tracking metadata. 11 | /// This class should not be used by developers in source code. 12 | /// 13 | [EditorBrowsable(EditorBrowsableState.Never)] 14 | public static class IsExternalInit 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Context.Tests/MongoDbContextTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Bson; 3 | using MongoDB.Driver; 4 | using Squadron; 5 | using Xunit; 6 | 7 | namespace MongoDB.Extensions.Context.Tests 8 | { 9 | public class MongoDbContextTests : IClassFixture 10 | { 11 | private readonly MongoOptions _mongoOptions; 12 | private readonly IMongoDatabase _mongoDatabase; 13 | 14 | public MongoDbContextTests(MongoResource mongoResource) 15 | { 16 | _mongoDatabase = mongoResource.CreateDatabase(); 17 | _mongoOptions = new MongoOptions 18 | { 19 | ConnectionString = mongoResource.ConnectionString, 20 | DatabaseName = _mongoDatabase.DatabaseNamespace.DatabaseName 21 | }; 22 | } 23 | 24 | #region Constructor Tests 25 | 26 | [Fact] 27 | public void Constructor_AutoInitialize_InitializationExecuted() 28 | { 29 | // Arrange 30 | 31 | // Act 32 | var testMongoDbContext = new TestMongoDbContext(_mongoOptions); 33 | 34 | // Assert 35 | Assert.True(testMongoDbContext.IsInitialized); 36 | } 37 | 38 | [Fact] 39 | public void Constructor_NoInitialize_InitializationNotExecuted() 40 | { 41 | // Arrange 42 | 43 | // Act 44 | var testMongoDbContext = new TestMongoDbContext(_mongoOptions, false); 45 | 46 | // Assert 47 | Assert.False(testMongoDbContext.IsInitialized); 48 | } 49 | 50 | [Fact] 51 | public void Constructor_Database_ThrowsWhenNoInitialize() 52 | { 53 | // Arrange 54 | var testMongoDbContext = new TestMongoDbContext(_mongoOptions, false); 55 | 56 | // Act 57 | // Assert 58 | Assert.Throws( 59 | () => _ = testMongoDbContext.Database); 60 | } 61 | 62 | [Fact] 63 | public void Constructor_CreateCollection_ThrowsWhenNotInitialize() 64 | { 65 | // Arrange 66 | var testMongoDbContext = new TestMongoDbContext(_mongoOptions, false); 67 | 68 | // Act 69 | // Assert 70 | Assert.Throws( 71 | () => _ = testMongoDbContext.CreateCollection()); 72 | } 73 | 74 | [Fact] 75 | public void Constructor_Client_ThrowsWhenNoInitialize() 76 | { 77 | // Arrange 78 | var testMongoDbContext = new TestMongoDbContext(_mongoOptions, false); 79 | 80 | // Act 81 | // Assert 82 | Assert.Throws( 83 | () => _ = testMongoDbContext.Client); 84 | } 85 | 86 | [Fact] 87 | public void Constructor_MongoOptions_CanAccessWhenNotInitialize() 88 | { 89 | // Arrange 90 | var testMongoDbContext = new TestMongoDbContext(_mongoOptions, false); 91 | 92 | // Act 93 | MongoOptions mongoOptions = testMongoDbContext.MongoOptions; 94 | 95 | // Assert 96 | Assert.NotNull(mongoOptions); 97 | } 98 | 99 | #endregion 100 | 101 | #region Private Helpers 102 | 103 | private class TestMongoDbContext : MongoDbContext 104 | { 105 | public TestMongoDbContext(MongoOptions mongoOptions) : base(mongoOptions) 106 | { 107 | } 108 | 109 | public TestMongoDbContext(MongoOptions mongoOptions, bool enableAutoInit) 110 | : base(mongoOptions, enableAutoInit) 111 | { 112 | } 113 | 114 | protected override void OnConfiguring(IMongoDatabaseBuilder mongoDatabaseBuilder) 115 | { 116 | IsInitialized = true; 117 | } 118 | 119 | public bool IsInitialized { get; private set; } 120 | } 121 | 122 | #endregion 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Context.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(DisableTestParallelization = true)] 4 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/AbstractImmutableWithAbstractBasePropertyCase.ApplyConvention_SerializeSuccessful.snap: -------------------------------------------------------------------------------- 1 | { 2 | "_A": "a", 3 | "_B": "b" 4 | } 5 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/AbstractImmutableWithBasePropertyCase.ApplyConvention_SerializeSuccessful.snap: -------------------------------------------------------------------------------- 1 | { 2 | "_B": "b", 3 | "_A": "a" 4 | } 5 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/AbstractImmutableWithNullableVirtualBasePropertyCase.ApplyConvention_SerializeSuccessful.snap: -------------------------------------------------------------------------------- 1 | { 2 | "_B": "b", 3 | "_A": null 4 | } 5 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/AbstractImmutableWithVirtualBasePropertyCase.ApplyConvention_SerializeSuccessful.snap: -------------------------------------------------------------------------------- 1 | { 2 | "_B": "b", 3 | "_A": "a" 4 | } 5 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_AddFooBarWithCommit_AllSaved.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "Bar", 4 | "Value": [ 5 | [ 6 | { 7 | "Name": "_id", 8 | "Value": 1 9 | }, 10 | { 11 | "Name": "BarName", 12 | "Value": "Bar1" 13 | } 14 | ] 15 | ] 16 | }, 17 | { 18 | "Key": "Foo", 19 | "Value": [ 20 | [ 21 | { 22 | "Name": "_id", 23 | "Value": 1 24 | }, 25 | { 26 | "Name": "FooName", 27 | "Value": "Foo1" 28 | } 29 | ] 30 | ] 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_AddFooBarWithRollback_NothingSaved.snap: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_CreateNewTransactionDbContext_OptionsCorrect.snap: -------------------------------------------------------------------------------- 1 | { 2 | "MaxCommitTime": "00:01:00", 3 | "ReadConcern": { 4 | "IsServerDefault": false, 5 | "Level": "Majority" 6 | }, 7 | "ReadPreference": { 8 | "Hedge": null, 9 | "MaxStaleness": null, 10 | "ReadPreferenceMode": "Primary", 11 | "TagSets": [] 12 | }, 13 | "WriteConcern": { 14 | "FSync": null, 15 | "IsAcknowledged": true, 16 | "IsServerDefault": false, 17 | "Journal": true, 18 | "W": { 19 | "Value": "majority" 20 | }, 21 | "WTimeout": null 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_SetTransactionTransactionOptions_OptionsCorrect.snap: -------------------------------------------------------------------------------- 1 | { 2 | "MaxCommitTime": "00:05:00", 3 | "ReadConcern": { 4 | "IsServerDefault": false, 5 | "Level": "Local" 6 | }, 7 | "ReadPreference": { 8 | "Hedge": null, 9 | "MaxStaleness": null, 10 | "ReadPreferenceMode": "Secondary", 11 | "TagSets": [] 12 | }, 13 | "WriteConcern": { 14 | "FSync": null, 15 | "IsAcknowledged": true, 16 | "IsServerDefault": false, 17 | "Journal": false, 18 | "W": { 19 | "Value": 3 20 | }, 21 | "WTimeout": null 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_TwoTransactionContextWithDifferentObjects_AllSaved.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "Foo", 4 | "Value": [ 5 | [ 6 | { 7 | "Name": "_id", 8 | "Value": 1 9 | }, 10 | { 11 | "Name": "FooName", 12 | "Value": "Foo1a" 13 | } 14 | ], 15 | [ 16 | { 17 | "Name": "_id", 18 | "Value": 544656454 19 | }, 20 | { 21 | "Name": "FooName", 22 | "Value": "Foo1b" 23 | } 24 | ] 25 | ] 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/MongoTransactionDbContextTests.StartNewTransactionAsync_TwoTransactionContextWithSameObjects_ConcurrencyExceptionAndSaved.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "Foo", 4 | "Value": [ 5 | [ 6 | { 7 | "Name": "_id", 8 | "Value": 1 9 | }, 10 | { 11 | "Name": "FooName", 12 | "Value": "Foo1a" 13 | } 14 | ] 15 | ] 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/NullableReferenceTypeCase.ApplyConvention_CtorWithDefault_SerializeSuccessful.snap: -------------------------------------------------------------------------------- 1 | { 2 | "_A": "a", 3 | "_B": "b" 4 | } 5 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/NullableReferenceTypeCase.ApplyConvention_WithValue_SerializeSuccessful.snap: -------------------------------------------------------------------------------- 1 | { 2 | "_A": "a", 3 | "_B": "b" 4 | } 5 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/NullableReferenceTypeCase.ApplyConvention_WithoutValueInDbWithoutDefault_SerializeSuccessful.snap: -------------------------------------------------------------------------------- 1 | { 2 | "_A": "a", 3 | "_B": null 4 | } 5 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/NullableReferenceTypeCase.ApplyConvention_WithoutValueInDb_SerializeSuccessful.snap: -------------------------------------------------------------------------------- 1 | { 2 | "_A": "a", 3 | "_B": null 4 | } 5 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/NullableReferenceTypeCase.ApplyConvention_WithoutValue_SerializeSuccessful.snap: -------------------------------------------------------------------------------- 1 | { 2 | "_A": "a", 3 | "_B": null 4 | } 5 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/NullableValueTypeCase.ApplyConvention_WithValue_SerializeSuccessful.snap: -------------------------------------------------------------------------------- 1 | { 2 | "_A": "a", 3 | "_B": 9 4 | } 5 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/NullableValueTypeCase.ApplyConvention_WithoutValue_SerializeSuccessful.snap: -------------------------------------------------------------------------------- 1 | { 2 | "_A": "a", 3 | "_B": null 4 | } 5 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/SimpleImmutableCase.ApplyConvention_SerializeSuccessful.snap: -------------------------------------------------------------------------------- 1 | { 2 | "_A": "a" 3 | } 4 | -------------------------------------------------------------------------------- /src/Context.Tests/__snapshots__/SimpleImmutableWithInterfaceCase.ApplyConvention_SerializeSuccessful.snap: -------------------------------------------------------------------------------- 1 | { 2 | "_A": "a" 3 | } 4 | -------------------------------------------------------------------------------- /src/Context/Context.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MongoDB.Extensions.Context 6 | MongoDB.Extensions.Context 7 | MongoDB.Extensions.Context 8 | 9 | 10 | 11 | portable 12 | true 13 | 14 | 15 | 16 | pdbonly 17 | true 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/Context/DefaultDefinitions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Driver; 3 | 4 | namespace MongoDB.Extensions.Context 5 | { 6 | public static class DefaultDefinitions 7 | { 8 | public static readonly TransactionOptions DefaultTransactionOptions = 9 | new TransactionOptions( 10 | ReadConcern.Majority, 11 | ReadPreference.Primary, 12 | WriteConcern.WMajority.With(journal: true), 13 | TimeSpan.FromSeconds(60)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Context/Exceptions/MissingAllowedTypesException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MongoDB.Extensions.Context.Exceptions; 4 | 5 | public class MissingAllowedTypesException : Exception 6 | { 7 | public MissingAllowedTypesException() { } 8 | public MissingAllowedTypesException(string message) : base(message) { } 9 | public MissingAllowedTypesException(string message, Exception innerException) : 10 | base(message, innerException) { } 11 | } 12 | -------------------------------------------------------------------------------- /src/Context/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MongoDB.Extensions.Context.Extensions; 4 | 5 | internal static class TypeExtensions 6 | { 7 | public static string GetRootNamespace(this Type? type) 8 | { 9 | string? fullNamespace = type?.Namespace; 10 | 11 | if(string.IsNullOrEmpty(fullNamespace)) 12 | { 13 | return string.Empty; 14 | } 15 | 16 | ReadOnlySpan namespaceSpan = fullNamespace.AsSpan(); 17 | 18 | int offset = namespaceSpan.IndexOf('.'); 19 | 20 | if(offset <= 0) 21 | { 22 | return fullNamespace; 23 | } 24 | 25 | return namespaceSpan 26 | .Slice(0, offset) 27 | .ToString(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Context/IMongoCollectionBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Bson.Serialization; 3 | using MongoDB.Driver; 4 | 5 | namespace MongoDB.Extensions.Context 6 | { 7 | public interface IMongoCollectionBuilder 8 | { 9 | IMongoCollectionBuilder WithCollectionName(string collectionName); 10 | 11 | IMongoCollectionBuilder AddBsonClassMap() 12 | where TMapDocument : class; 13 | 14 | IMongoCollectionBuilder AddBsonClassMap( 15 | Action> bsonClassMapAction) 16 | where TMapDocument : class; 17 | 18 | IMongoCollectionBuilder AddBsonClassMap( 19 | Type type, 20 | Action? bsonClassMapAction = default); 21 | 22 | IMongoCollectionBuilder WithCollectionSettings( 23 | Action collectionSettings); 24 | 25 | IMongoCollectionBuilder WithCollectionConfiguration( 26 | Action> collectionConfiguration); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Context/IMongoCollectionConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace MongoDB.Extensions.Context 2 | { 3 | public interface IMongoCollectionConfiguration where TDocument : class 4 | { 5 | void OnConfiguring(IMongoCollectionBuilder mongoCollectionBuilder); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Context/IMongoDatabaseBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Bson.Serialization; 3 | using MongoDB.Bson.Serialization.Conventions; 4 | using MongoDB.Driver; 5 | 6 | namespace MongoDB.Extensions.Context 7 | { 8 | public interface IMongoDatabaseBuilder 9 | { 10 | IMongoDatabaseBuilder RegisterSerializer(IBsonSerializer serializer); 11 | 12 | IMongoDatabaseBuilder RegisterConventionPack( 13 | string name, IConventionPack conventions, Func filter); 14 | 15 | IMongoDatabaseBuilder RegisterImmutableConventionPack(); 16 | 17 | IMongoDatabaseBuilder RegisterDefaultConventionPack(); 18 | 19 | IMongoDatabaseBuilder RegisterCamelCaseConventionPack(); 20 | 21 | IMongoDatabaseBuilder RegisterIgnoreIfNullConventionPack(); 22 | 23 | IMongoDatabaseBuilder ConfigureCollection( 24 | IMongoCollectionConfiguration configuration) where TDocument : class; 25 | 26 | IMongoDatabaseBuilder ConfigureConnection( 27 | Action mongoClientSettingsAction); 28 | 29 | IMongoDatabaseBuilder ConfigureDatabase(Action configureDatabase); 30 | 31 | IMongoDatabaseBuilder AddAllowedType(); 32 | 33 | IMongoDatabaseBuilder AddAllowedTypes(params Type[] allowedTypes); 34 | 35 | IMongoDatabaseBuilder AddAllowedTypes(params string[] allowedNamespaces); 36 | 37 | IMongoDatabaseBuilder AddAllowedTypesOfAllDependencies(params string[] excludeNamespaces); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Context/IMongoDbContext.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | 3 | namespace MongoDB.Extensions.Context 4 | { 5 | public interface IMongoDbContext 6 | { 7 | MongoOptions MongoOptions { get; } 8 | IMongoClient Client { get; } 9 | IMongoDatabase Database { get; } 10 | 11 | IMongoCollection CreateCollection() where TDocument : class; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Context/IMongoDbTransaction.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using System.Threading; 3 | using MongoDB.Driver; 4 | 5 | namespace MongoDB.Extensions.Context 6 | { 7 | public interface IMongoDbTransaction 8 | { 9 | Task StartNewTransactionAsync( 10 | TransactionOptions? transactionOptions = null, 11 | CancellationToken cancellationToken = default); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Context/IMongoTransactionDbContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Threading; 4 | using MongoDB.Driver; 5 | 6 | namespace MongoDB.Extensions.Context; 7 | 8 | public interface IMongoTransactionDbContext : IMongoDbContext, IDisposable 9 | { 10 | TransactionOptions TransactionOptions { get; } 11 | 12 | IMongoCollection GetCollection() where TDocument : class; 13 | 14 | Task CommitAsync(CancellationToken cancellationToken = default); 15 | Task RollbackAsync(CancellationToken cancellationToken = default); 16 | } 17 | -------------------------------------------------------------------------------- /src/Context/Internal/DependencyTypesResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using MongoDB.Extensions.Context.Extensions; 6 | 7 | namespace MongoDB.Extensions.Context; 8 | 9 | internal static class DependencyTypesResolver 10 | { 11 | private static readonly HashSet _notAllowedNames = 12 | new HashSet{ "System", "Microsoft" }; 13 | 14 | internal static HashSet GetAllowedTypesByDependencies(string[] excludeNamespaces) 15 | { 16 | Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); 17 | 18 | IEnumerable allowedAssemblies = assemblies 19 | .Where(assembly => IsNamespaceAllowed(assembly.GetName().Name, excludeNamespaces)); 20 | 21 | IEnumerable namespaces = allowedAssemblies 22 | .SelectMany(a => a.GetTypes()) 23 | .Select(type => type.GetRootNamespace()) 24 | .Where(name => IsNamespaceAllowed(name, excludeNamespaces)); 25 | 26 | return new HashSet(namespaces); 27 | } 28 | 29 | private static bool IsNamespaceAllowed(string name, string [] excludeNamespaces) 30 | { 31 | if(string.IsNullOrEmpty(name)) 32 | { 33 | return false; 34 | } 35 | 36 | return !_notAllowedNames.Concat(excludeNamespaces) 37 | .Any(entry => name.StartsWith(entry)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Context/Internal/IMongoCollections.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | 3 | namespace MongoDB.Extensions.Context.Internal 4 | { 5 | internal interface IMongoCollections 6 | { 7 | bool Exists() 8 | where TDocument : class; 9 | 10 | void Add(IMongoCollection mongoCollection) 11 | where TDocument : class; 12 | 13 | IMongoCollection? TryGetCollection() 14 | where TDocument : class; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Context/Internal/MongoCollections.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MongoDB.Driver; 4 | 5 | namespace MongoDB.Extensions.Context.Internal 6 | { 7 | internal class MongoCollections : IMongoCollections 8 | { 9 | private readonly Dictionary _collections; 10 | 11 | public MongoCollections() 12 | { 13 | _collections = new Dictionary(); 14 | } 15 | 16 | public int Count => _collections.Count; 17 | 18 | public bool Exists() 19 | where TDocument : class 20 | { 21 | return _collections.ContainsKey(typeof(TDocument)); 22 | } 23 | 24 | public void Add(IMongoCollection mongoCollection) 25 | where TDocument : class 26 | { 27 | _collections.Add( 28 | typeof(TDocument), 29 | mongoCollection); 30 | } 31 | 32 | public IMongoCollection? TryGetCollection() 33 | where TDocument : class 34 | { 35 | if (_collections.TryGetValue( 36 | typeof(TDocument), out object? configuredMongoCollection)) 37 | { 38 | return (IMongoCollection)configuredMongoCollection; 39 | } 40 | 41 | return null; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Context/Internal/MongoDbContextData.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | using MongoDB.Extensions.Context.Internal; 3 | 4 | namespace MongoDB.Extensions.Context 5 | { 6 | internal class MongoDbContextData 7 | { 8 | private readonly IMongoCollections _mongoCollections; 9 | private readonly object _lockObject = new object(); 10 | 11 | public MongoDbContextData( 12 | IMongoClient mongoClient, 13 | IMongoDatabase mongoDatabase, 14 | IMongoCollections mongoCollections) 15 | { 16 | Client = mongoClient; 17 | Database = mongoDatabase; 18 | _mongoCollections = mongoCollections; 19 | } 20 | 21 | public IMongoClient Client { get; } 22 | public IMongoDatabase Database { get; } 23 | 24 | public IMongoCollection GetCollection() 25 | where TDocument : class 26 | { 27 | return GetConfiguredCollection(); 28 | } 29 | 30 | private IMongoCollection GetConfiguredCollection() 31 | where TDocument : class 32 | { 33 | IMongoCollection? configuredCollection = 34 | _mongoCollections.TryGetCollection(); 35 | 36 | if (configuredCollection == null) 37 | { 38 | lock (_lockObject) 39 | { 40 | configuredCollection = 41 | _mongoCollections.TryGetCollection(); 42 | 43 | if (configuredCollection == null) 44 | { 45 | configuredCollection = 46 | AddDefaultCollection(); 47 | } 48 | } 49 | } 50 | 51 | return configuredCollection; 52 | } 53 | 54 | private IMongoCollection AddDefaultCollection() 55 | where TDocument : class 56 | { 57 | IMongoCollection configuredCollection = 58 | new MongoCollectionBuilder(Database).Build(); 59 | 60 | _mongoCollections.Add(configuredCollection); 61 | 62 | return configuredCollection; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Context/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("MongoDB.Extensions.Context.Tests")] 4 | -------------------------------------------------------------------------------- /src/Context/MongoDbContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MongoDB.Driver; 5 | 6 | namespace MongoDB.Extensions.Context; 7 | 8 | public abstract class MongoDbContext : IMongoDbContext, IMongoDbTransaction 9 | { 10 | private MongoDbContextData? _mongoDbContextData; 11 | 12 | private readonly object _lockObject = new object(); 13 | 14 | public MongoDbContext(MongoOptions mongoOptions) : this(mongoOptions, true) 15 | { 16 | } 17 | 18 | public MongoDbContext(MongoOptions mongoOptions, bool enableAutoInitialize) 19 | { 20 | MongoOptions = mongoOptions.Validate(); 21 | 22 | if (enableAutoInitialize) 23 | { 24 | Initialize(); 25 | } 26 | } 27 | public MongoOptions MongoOptions { get; } 28 | 29 | public IMongoClient Client 30 | { 31 | get 32 | { 33 | EnsureInitialized(); 34 | return _mongoDbContextData!.Client; 35 | } 36 | } 37 | 38 | public IMongoDatabase Database 39 | { 40 | get 41 | { 42 | EnsureInitialized(); 43 | return _mongoDbContextData!.Database; 44 | } 45 | } 46 | 47 | public IMongoCollection CreateCollection() 48 | where TDocument : class 49 | { 50 | EnsureInitialized(); 51 | return _mongoDbContextData!.GetCollection(); 52 | } 53 | 54 | protected abstract void OnConfiguring(IMongoDatabaseBuilder mongoDatabaseBuilder); 55 | 56 | public virtual void Initialize() 57 | { 58 | if(_mongoDbContextData == null) 59 | { 60 | lock (_lockObject) 61 | { 62 | if (_mongoDbContextData == null) 63 | { 64 | var mongoDatabaseBuilder = new MongoDatabaseBuilder(MongoOptions); 65 | 66 | OnConfiguring(mongoDatabaseBuilder); 67 | 68 | _mongoDbContextData = mongoDatabaseBuilder.Build(); 69 | } 70 | } 71 | } 72 | } 73 | 74 | private void EnsureInitialized() 75 | { 76 | if (_mongoDbContextData == null) 77 | { 78 | lock (_lockObject) 79 | { 80 | if (_mongoDbContextData == null) 81 | { 82 | throw new InvalidOperationException("MongoDbContext not initialized."); 83 | } 84 | } 85 | } 86 | } 87 | 88 | public async Task StartNewTransactionAsync( 89 | TransactionOptions? transactionOptions = null, 90 | CancellationToken cancellationToken = default) 91 | { 92 | transactionOptions ??= DefaultDefinitions.DefaultTransactionOptions; 93 | 94 | IClientSessionHandle clientSession = await Client 95 | .StartSessionAsync(cancellationToken: cancellationToken); 96 | 97 | clientSession.StartTransaction(transactionOptions); 98 | 99 | return new MongoTransactionDbContext( 100 | clientSession, transactionOptions, this); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Context/MongoInstrumentationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Driver; 3 | using MongoDB.Driver.Core.Configuration; 4 | using MongoDB.Driver.Core.Extensions.DiagnosticSources; 5 | 6 | namespace MongoDB.Extensions.Context; 7 | 8 | public static class MongoInstrumentationExtensions 9 | { 10 | public static IMongoDatabaseBuilder AddInstrumentation( 11 | this IMongoDatabaseBuilder mongoDatabaseBuilder, 12 | Action? configureInstrumentation = default, 13 | Action? configureCluster = default) 14 | { 15 | return mongoDatabaseBuilder 16 | .ConfigureConnection(s => s 17 | .AddInstrumentation(configureInstrumentation, configureCluster)); 18 | } 19 | 20 | public static MongoClientSettings AddInstrumentation( 21 | this MongoClientSettings mongoClientSettings, 22 | Action? configureInstrumentation = default, 23 | Action? configureCluster = default) 24 | { 25 | var instrumentationOptions = new InstrumentationOptions { CaptureCommandText = true }; 26 | configureInstrumentation?.Invoke(instrumentationOptions); 27 | 28 | mongoClientSettings.ClusterConfigurator = builder => 29 | { 30 | builder.Subscribe(new DiagnosticsActivityEventSubscriber(instrumentationOptions)); 31 | configureCluster?.Invoke(builder); 32 | }; 33 | 34 | return mongoClientSettings; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Context/MongoOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MongoDB.Extensions.Context 4 | { 5 | public class MongoOptions 6 | : MongoOptions where TMongoDBContext : IMongoDbContext 7 | { 8 | } 9 | 10 | public class MongoOptions 11 | { 12 | public string ConnectionString { get; set; } 13 | public string DatabaseName { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Context/MongoOptionsConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace MongoDB.Extensions.Context 4 | { 5 | public static class MongoOptionsConfiguration 6 | { 7 | public static MongoOptions GetMongoOptions( 8 | this IConfiguration configuration, string mongoDbPath) 9 | { 10 | return configuration 11 | .GetSection(mongoDbPath) 12 | .Get() 13 | .Validate(); 14 | } 15 | 16 | public static MongoOptions GetMongoOptions( 17 | this IConfiguration configuration, string mongoDbPath) where TMongoDBContext : IMongoDbContext 18 | { 19 | return configuration 20 | .GetSection(mongoDbPath) 21 | .GetMongoOptions(); 22 | } 23 | 24 | public static MongoOptions GetMongoOptions( 25 | this IConfigurationSection section) where TMongoDBContext : IMongoDbContext 26 | { 27 | MongoOptions mongoOptions = section 28 | .Get>() 29 | .Validate(); 30 | 31 | return mongoOptions; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Context/MongoOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MongoDB.Extensions.Context; 4 | 5 | public static class MongoOptionsExtensions 6 | { 7 | public static MongoOptions Validate( 8 | this MongoOptions? mongoOptions) 9 | where TMongoDBContext : IMongoDbContext 10 | { 11 | Validate(mongoOptions as MongoOptions); 12 | 13 | return mongoOptions!; 14 | } 15 | 16 | public static MongoOptions Validate(this MongoOptions? mongoOptions) 17 | { 18 | if (mongoOptions == null) 19 | { 20 | throw new Exception( 21 | $"The MongoDB options for could not be found " + 22 | $"within the configuration section or the options are null."); 23 | } 24 | 25 | if (string.IsNullOrEmpty(mongoOptions.ConnectionString)) 26 | { 27 | throw new Exception( 28 | $"The connection string of the MongoDB configuration " + 29 | $"could not be found within the configuration section. " + 30 | $"Please verify that this section contains the " + 31 | $"{nameof(MongoOptions.ConnectionString)} field."); 32 | } 33 | 34 | if (string.IsNullOrEmpty(mongoOptions.DatabaseName)) 35 | { 36 | throw new Exception( 37 | $"The database name of the MongoDB configuration " + 38 | $"could not be found within the section " + 39 | $"Please verify that this section contains the " + 40 | $"{nameof(MongoOptions.DatabaseName)} field."); 41 | } 42 | 43 | return mongoOptions; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Context/MongoServerExtensions.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson; 2 | using MongoDB.Driver; 3 | 4 | namespace MongoDB.Extensions.Context 5 | { 6 | public static class MongoServerExtensions 7 | { 8 | public static void DisableTableScan(this IMongoClient mongoClient) 9 | { 10 | var command = new BsonDocument { { "setParameter", 1 }, { "notablescan", 1 } }; 11 | 12 | mongoClient.GetDatabase("admin").RunCommand(command); 13 | } 14 | 15 | public static bool IsTableScanDisabled(this IMongoClient mongoClient) 16 | { 17 | var getNoTableScanCommand = 18 | new BsonDocument { { "getParameter", 1 }, { "notablescan", 1 } }; 19 | 20 | BsonDocument parameterResult = mongoClient.GetDatabase("admin") 21 | .RunCommand(getNoTableScanCommand); 22 | 23 | BsonElement notablescanElement = parameterResult.GetElement("notablescan"); 24 | 25 | if(bool.TryParse(notablescanElement.Value.ToString(), out bool isDisabled)) 26 | { 27 | return isDisabled; 28 | } 29 | 30 | return false; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Context/MongoTransactionDbContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Threading; 4 | using MongoDB.Driver; 5 | using System.Collections.Concurrent; 6 | using MongoDB.Extensions.Transactions; 7 | using MongoDB.Extensions.Context.Internal; 8 | 9 | namespace MongoDB.Extensions.Context; 10 | 11 | public class MongoTransactionDbContext : IMongoTransactionDbContext 12 | { 13 | private readonly MongoDbContext _mongoDbContext; 14 | private readonly MongoCollections _mongoCollections; 15 | private readonly object _lockObject = new object(); 16 | 17 | public MongoTransactionDbContext( 18 | IClientSessionHandle clientSession, 19 | TransactionOptions transactionOptions, 20 | MongoDbContext mongoDbContext) 21 | { 22 | _mongoDbContext = mongoDbContext; 23 | _mongoCollections = new MongoCollections(); 24 | 25 | ClientSession = clientSession; 26 | TransactionOptions = transactionOptions; 27 | MongoOptions = mongoDbContext.MongoOptions; 28 | Client = mongoDbContext.Client.AsTransactionClient(clientSession); 29 | Database = mongoDbContext.Database.AsTransactionDatabase(clientSession); 30 | } 31 | 32 | public TransactionOptions TransactionOptions { get; } 33 | 34 | public MongoOptions MongoOptions { get; } 35 | 36 | public IMongoClient Client { get; } 37 | 38 | public IMongoDatabase Database { get; } 39 | 40 | public IClientSessionHandle ClientSession { get; } 41 | 42 | public IMongoCollection GetCollection() 43 | where TDocument : class => CreateCollection(); 44 | 45 | public IMongoCollection CreateCollection() 46 | where TDocument : class 47 | { 48 | IMongoCollection? collection = 49 | _mongoCollections.TryGetCollection(); 50 | 51 | if (collection is { }) 52 | { 53 | return collection; 54 | } 55 | 56 | lock(_lockObject) 57 | { 58 | collection = _mongoCollections 59 | .TryGetCollection(); 60 | 61 | if (collection is { }) 62 | { 63 | return collection; 64 | } 65 | 66 | collection = _mongoDbContext 67 | .CreateCollection() 68 | .AsTransactionCollection(ClientSession); 69 | 70 | _mongoCollections.Add(collection); 71 | } 72 | 73 | return collection; 74 | } 75 | 76 | public async Task CommitAsync(CancellationToken cancellationToken = default) 77 | { 78 | await ClientSession 79 | .CommitTransactionAsync(cancellationToken); 80 | } 81 | 82 | public async Task RollbackAsync(CancellationToken cancellationToken = default) 83 | { 84 | await ClientSession 85 | .AbortTransactionAsync(cancellationToken); 86 | } 87 | 88 | #region IDisposable 89 | 90 | private bool _disposed; 91 | 92 | protected virtual void Dispose(bool disposing) 93 | { 94 | if (!_disposed) 95 | { 96 | if (disposing) 97 | { 98 | ClientSession.Dispose(); 99 | } 100 | 101 | _disposed = true; 102 | } 103 | } 104 | 105 | public void Dispose() 106 | { 107 | Dispose(true); 108 | GC.SuppressFinalize(this); 109 | } 110 | 111 | #endregion 112 | } 113 | -------------------------------------------------------------------------------- /src/Context/Properties/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("MongoDB.Extensions.Context.Tests")] 4 | [assembly: InternalsVisibleTo("MongoDB.Extensions.Context.AllowedTypes.Tests")] 5 | [assembly: InternalsVisibleTo("MongoDB.Extensions.Context.InterferingTests")] 6 | -------------------------------------------------------------------------------- /src/Dependencies.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0;net8.0 4 | netstandard2.0;netstandard2.1;net6.0;net8.0 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(MSBuildThisFileDirectory.TrimEnd('\').TrimEnd('/')) 4 | $([System.IO.Path]::Combine($(CCSourceDirectory), 'Settings.props')) 5 | $([System.IO.Path]::Combine($(CCSourceDirectory), 'Version.props')) 6 | $([System.IO.Path]::Combine($(CCSourceDirectory), 'Package.props')) 7 | $([System.IO.Path]::Combine($(CCSourceDirectory), 'TestProject.props')) 8 | $([System.IO.Path]::Combine($(CCSourceDirectory), 'ResourceProject.props')) 9 | $([System.IO.Path]::Combine($(CCSourceDirectory), 'Dependencies.props')) 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Migration.Tests/Integration/Scenario1/MigrateDownTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using MongoDB.Extensions.Migration; 4 | using FluentAssertions; 5 | using Microsoft.Extensions.Logging.Abstractions; 6 | using Migration.Tests; 7 | using MongoDB.Bson; 8 | using MongoDB.Bson.Serialization; 9 | using MongoDB.Driver; 10 | using MongoDB.Driver.Linq; 11 | using Squadron; 12 | using Xunit; 13 | 14 | namespace MongoMigrationTest.Integration.Scenario1; 15 | 16 | [Collection("SharedMongoDbCollection")] 17 | public class MigrateDownTests 18 | { 19 | readonly IMongoCollection _typedCollection; 20 | readonly IMongoCollection _untypedCollection; 21 | 22 | public MigrateDownTests(MongoResource resource) 23 | { 24 | RegisterMongoMigrations(); 25 | IMongoDatabase database = resource.Client.GetDatabase("Scenario1-down"); 26 | _typedCollection = database.GetCollection("TestEntityForDown"); 27 | _untypedCollection = database.GetCollection("TestEntityForDown"); 28 | } 29 | 30 | static void RegisterMongoMigrations() 31 | { 32 | MigrationOption options = new MigrationOptionBuilder() 33 | .ForEntity(o => o.AtVersion(0) 34 | .WithMigration(new TestMigration1()) 35 | .WithMigration(new TestMigration2()) 36 | .WithMigration(new TestMigration3())) 37 | .Build(); 38 | var context = new MigrationContext(options, NullLoggerFactory.Instance); 39 | 40 | BsonSerializer.RegisterSerializationProvider(new MigrationSerializerProvider(context)); 41 | } 42 | 43 | [Fact] 44 | public async Task Scenario1_AddRetrieve_NoMigration() 45 | { 46 | // Arrange 47 | const string input = "Bar"; 48 | await _typedCollection.InsertOneAsync(new TestEntityForDown("1", input)); 49 | 50 | // Act 51 | TestEntityForDown result = await _typedCollection.AsQueryable() 52 | .SingleOrDefaultAsync(c => c.Id == "1"); 53 | 54 | // Assert 55 | result.Foo.Should().Be(input); 56 | } 57 | 58 | [Fact] 59 | public async Task Scenario1_RetrieveAtVersion3_MigratedDownTo0() 60 | { 61 | // Arrange 62 | await _untypedCollection.InsertOneAsync(new BsonDocument(new Dictionary 63 | { ["_id"] = "id0", ["Foo"] = "Bar", ["Version"] = 3 })); 64 | 65 | // Act 66 | TestEntityForDown result = await _typedCollection.AsQueryable() 67 | .SingleOrDefaultAsync(c => c.Id == "id0"); 68 | 69 | // Assert 70 | result.Foo.Should().Be("Bar Migrated Down to 2 Migrated Down to 1 Migrated Down to 0"); 71 | } 72 | 73 | [Fact] 74 | public async Task Scenario1_RetrieveAtVersion2_MigratedToVersion3() 75 | { 76 | // Arrange 77 | await _untypedCollection.InsertOneAsync(new BsonDocument(new Dictionary 78 | { ["_id"] = "id1", ["Foo"] = "Bar", ["Version"] = 1 })); 79 | 80 | // Act 81 | TestEntityForDown result = await _typedCollection.AsQueryable() 82 | .SingleOrDefaultAsync(c => c.Id == "id1"); 83 | 84 | // Assert 85 | result.Foo.Should().Be("Bar Migrated Down to 0"); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/Migration.Tests/Integration/Scenario1/MigrateUpTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using MongoDB.Extensions.Migration; 4 | using FluentAssertions; 5 | using Microsoft.Extensions.Logging.Abstractions; 6 | using Migration.Tests; 7 | using MongoDB.Bson; 8 | using MongoDB.Bson.Serialization; 9 | using MongoDB.Driver; 10 | using Xunit; 11 | using MongoDB.Driver.Linq; 12 | using Squadron; 13 | 14 | namespace MongoMigrationTest.Integration.Scenario1; 15 | 16 | [Collection("SharedMongoDbCollection")] 17 | public class MigrateUpTests 18 | { 19 | readonly IMongoCollection _typedCollection; 20 | readonly IMongoCollection _untypedCollection; 21 | 22 | public MigrateUpTests(MongoResource resource) 23 | { 24 | RegisterMongoMigrations(); 25 | IMongoDatabase database = resource.Client.GetDatabase("Scenario1-up"); 26 | _typedCollection = database.GetCollection("TestEntityForUp"); 27 | _untypedCollection = database.GetCollection("TestEntityForUp"); 28 | } 29 | 30 | static void RegisterMongoMigrations() 31 | { 32 | MigrationOption options = new MigrationOptionBuilder() 33 | .ForEntity(o => o 34 | .WithMigration(new TestMigration1()) 35 | .WithMigration(new TestMigration2()) 36 | .WithMigration(new TestMigration3())) 37 | .Build(); 38 | var context = new MigrationContext(options, NullLoggerFactory.Instance); 39 | 40 | BsonSerializer.RegisterSerializationProvider(new MigrationSerializerProvider(context)); 41 | } 42 | 43 | [Fact] 44 | public async Task Scenario1_AddRetrieve_NoMigration() 45 | { 46 | // Arrange 47 | const string input = "Bar"; 48 | await _typedCollection.InsertOneAsync(new TestEntityForUp("1", input)); 49 | 50 | // Act 51 | var result = await _typedCollection.AsQueryable().SingleOrDefaultAsync(c => c.Id == "1"); 52 | 53 | // Assert 54 | result.Foo.Should().Be(input); 55 | } 56 | 57 | [Fact] 58 | public async Task Scenario1_RetrieveWithoutVersion_MigratedToNewestVersion() 59 | { 60 | // Arrange 61 | await _untypedCollection.InsertOneAsync(new BsonDocument(new Dictionary 62 | { ["_id"] = "2", ["Foo"] = "Bar" })); 63 | 64 | // Act 65 | TestEntityForUp result = await _typedCollection.AsQueryable().SingleOrDefaultAsync(c => c.Id == "2"); 66 | 67 | // Assert 68 | result.Foo.Should().Be("Bar Migrated Up to 1 Migrated Up to 2 Migrated Up to 3"); 69 | } 70 | 71 | [Fact] 72 | public async Task Scenario1_RetrieveAtNewUnknownVersion_NoMigration() 73 | { 74 | // Arrange 75 | await _untypedCollection.InsertOneAsync(new BsonDocument(new Dictionary 76 | { ["_id"] = "3", ["Foo"] = "Bar", ["Version"] = 4 })); 77 | 78 | // Act 79 | TestEntityForUp result = await _typedCollection.AsQueryable().SingleOrDefaultAsync(c => c.Id == "3"); 80 | 81 | // Assert 82 | result.Foo.Should().Be("Bar"); 83 | } 84 | 85 | [Fact] 86 | public async Task Scenario1_RetrieveAtVersion2_MigratedToVersion3() 87 | { 88 | // Arrange 89 | await _untypedCollection.InsertOneAsync(new BsonDocument(new Dictionary 90 | { ["_id"] = "4", ["Foo"] = "Bar", ["Version"] = 2 })); 91 | 92 | // Act 93 | TestEntityForUp result = await _typedCollection.AsQueryable().SingleOrDefaultAsync(c => c.Id == "4"); 94 | 95 | // Assert 96 | result.Foo.Should().Be("Bar Migrated Up to 3"); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Migration.Tests/Integration/Scenario1/TestEntityForDown.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Extensions.Migration; 2 | 3 | namespace MongoMigrationTest.Integration.Scenario1; 4 | 5 | public record TestEntityForDown(string Id, string Foo) : IVersioned 6 | { 7 | public int Version { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/Migration.Tests/Integration/Scenario1/TestEntityForUp.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Extensions.Migration; 2 | 3 | namespace MongoMigrationTest.Integration.Scenario1; 4 | 5 | public record TestEntityForUp(string Id, string Foo) : IVersioned 6 | { 7 | public int Version { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/Migration.Tests/Integration/Scenario2/MigrateDownTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using MongoDB.Extensions.Migration; 4 | using FluentAssertions; 5 | using Microsoft.Extensions.Logging.Abstractions; 6 | using Migration.Tests; 7 | using MongoDB.Bson; 8 | using MongoDB.Bson.Serialization; 9 | using MongoDB.Driver; 10 | using MongoDB.Driver.Linq; 11 | using Squadron; 12 | using Xunit; 13 | 14 | namespace MongoMigrationTest.Integration.Scenario2; 15 | 16 | [Collection("SharedMongoDbCollection")] 17 | public class MigrateDownTests 18 | { 19 | readonly IMongoCollection _typedCollection; 20 | readonly IMongoCollection _untypedCollection; 21 | 22 | public MigrateDownTests(MongoResource resource) 23 | { 24 | RegisterMongoMigrations(); 25 | IMongoDatabase database = resource.Client.GetDatabase("Scenario2-down"); 26 | _typedCollection = database.GetCollection("TestEntityForDown"); 27 | _untypedCollection = database.GetCollection("TestEntityForDown"); 28 | } 29 | 30 | static void RegisterMongoMigrations() 31 | { 32 | MigrationOption options = new MigrationOptionBuilder() 33 | .ForEntity(o => o 34 | .WithMigration(new TestMigration1()) 35 | .WithMigration(new TestMigration2()) 36 | .WithMigration(new TestMigration3()) 37 | .AtVersion(2)) 38 | .Build(); 39 | var context = new MigrationContext(options, NullLoggerFactory.Instance); 40 | 41 | BsonSerializer.RegisterSerializationProvider(new MigrationSerializerProvider(context)); 42 | } 43 | 44 | [Fact] 45 | public async Task Scenario2_RetrieveAtNewUnknownVersion_MigrateDownTo2() 46 | { 47 | // Arrange 48 | await _untypedCollection.InsertOneAsync(new BsonDocument(new Dictionary 49 | { ["_id"] = "3", ["Foo"] = "Bar", ["Version"] = 4 })); 50 | 51 | // Act 52 | TestEntityForDown result = await _typedCollection.AsQueryable().SingleOrDefaultAsync(c => c.Id == "3"); 53 | 54 | // Assert 55 | result.Foo.Should().Be("Bar Migrated Down to 2"); 56 | } 57 | 58 | [Fact] 59 | public async Task Scenario2_RetrieveAtVersion3_MigratedToVersion2() 60 | { 61 | // Arrange 62 | await _untypedCollection.InsertOneAsync(new BsonDocument(new Dictionary 63 | { ["_id"] = "4", ["Foo"] = "Bar", ["Version"] = 3 })); 64 | 65 | // Act 66 | TestEntityForDown result = await _typedCollection.AsQueryable().SingleOrDefaultAsync(c => c.Id == "4"); 67 | 68 | // Assert 69 | result.Foo.Should().Be("Bar Migrated Down to 2"); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Migration.Tests/Integration/Scenario2/MigrateUpTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using MongoDB.Extensions.Migration; 4 | using FluentAssertions; 5 | using Microsoft.Extensions.Logging.Abstractions; 6 | using Migration.Tests; 7 | using MongoDB.Bson; 8 | using MongoDB.Bson.Serialization; 9 | using MongoDB.Driver; 10 | using Xunit; 11 | using MongoDB.Driver.Linq; 12 | using Squadron; 13 | 14 | namespace MongoMigrationTest.Integration.Scenario2; 15 | 16 | [Collection("SharedMongoDbCollection")] 17 | public class MigrateUpTests 18 | { 19 | readonly IMongoCollection _typedCollection; 20 | readonly IMongoCollection _untypedCollection; 21 | 22 | public MigrateUpTests(MongoResource resource) 23 | { 24 | RegisterMongoMigrations(); 25 | IMongoDatabase database = resource.Client.GetDatabase("Scenario2-up"); 26 | _typedCollection = database.GetCollection("TestEntityForUp"); 27 | _untypedCollection = database.GetCollection("TestEntityForUp"); 28 | } 29 | 30 | static void RegisterMongoMigrations() 31 | { 32 | MigrationOption options = new MigrationOptionBuilder() 33 | .ForEntity(o => o 34 | .WithMigration(new TestMigration1()) 35 | .WithMigration(new TestMigration2()) 36 | .WithMigration(new TestMigration3()) 37 | .AtVersion(2)) 38 | .Build(); 39 | var context = new MigrationContext(options, NullLoggerFactory.Instance); 40 | 41 | BsonSerializer.RegisterSerializationProvider(new MigrationSerializerProvider(context)); 42 | } 43 | 44 | [Fact] 45 | public async Task Scenario2_AddRetrieve_NoMigration() 46 | { 47 | // Arrange 48 | const string input = "Bar"; 49 | await _typedCollection.InsertOneAsync(new TestEntityForUp("1", input)); 50 | 51 | // Act 52 | var result = await _typedCollection.AsQueryable().SingleOrDefaultAsync(c => c.Id == "1"); 53 | 54 | // Assert 55 | result.Foo.Should().Be(input); 56 | } 57 | 58 | [Fact] 59 | public async Task Scenario2_RetrieveWithoutVersion_MigratedToCurrentVersion() 60 | { 61 | // Arrange 62 | await _untypedCollection.InsertOneAsync(new BsonDocument(new Dictionary 63 | { ["_id"] = "2", ["Foo"] = "Bar" })); 64 | 65 | // Act 66 | TestEntityForUp result = await _typedCollection.AsQueryable().SingleOrDefaultAsync(c => c.Id == "2"); 67 | 68 | // Assert 69 | result.Foo.Should().Be("Bar Migrated Up to 1 Migrated Up to 2"); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Migration.Tests/Integration/Scenario2/TestEntityForDown.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Extensions.Migration; 2 | 3 | namespace MongoMigrationTest.Integration.Scenario2; 4 | 5 | public record TestEntityForDown(string Id, string Foo) : IVersioned 6 | { 7 | public int Version { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/Migration.Tests/Integration/Scenario2/TestEntityForUp.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Extensions.Migration; 2 | 3 | namespace MongoMigrationTest.Integration.Scenario2; 4 | 5 | public record TestEntityForUp(string Id, string Foo) : IVersioned 6 | { 7 | public int Version { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/Migration.Tests/Integration/SharedMongoDbCollection.cs: -------------------------------------------------------------------------------- 1 | using Squadron; 2 | using Xunit; 3 | 4 | namespace MongoMigrationTest.Integration; 5 | 6 | [CollectionDefinition("SharedMongoDbCollection")] 7 | public class SharedMongoDbCollection : ICollectionFixture 8 | { 9 | } -------------------------------------------------------------------------------- /src/Migration.Tests/Migration.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Migration.Tests 6 | Migration.Tests 7 | net6.0;net8.0 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Migration.Tests/TestMigration1.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson; 2 | using MongoDB.Extensions.Migration; 3 | 4 | namespace Migration.Tests; 5 | 6 | public class TestMigration1 : IMigration 7 | { 8 | public int Version { get; } = 1; 9 | 10 | public void Up(BsonDocument document) 11 | { 12 | document["Foo"] += " Migrated Up to 1"; 13 | } 14 | 15 | public void Down(BsonDocument document) 16 | { 17 | document["Foo"] += " Migrated Down to 0"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Migration.Tests/TestMigration2.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson; 2 | using MongoDB.Extensions.Migration; 3 | 4 | namespace Migration.Tests; 5 | 6 | public class TestMigration2 : IMigration 7 | { 8 | public int Version { get; } = 2; 9 | 10 | public void Up(BsonDocument document) 11 | { 12 | document["Foo"] += " Migrated Up to 2"; 13 | } 14 | 15 | public void Down(BsonDocument document) 16 | { 17 | document["Foo"] += " Migrated Down to 1"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Migration.Tests/TestMigration3.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson; 2 | using MongoDB.Extensions.Migration; 3 | 4 | namespace Migration.Tests; 5 | 6 | public class TestMigration3 : IMigration 7 | { 8 | public int Version { get; } = 3; 9 | 10 | public void Up(BsonDocument document) 11 | { 12 | document["Foo"] += " Migrated Up to 3"; 13 | } 14 | 15 | public void Down(BsonDocument document) 16 | { 17 | document["Foo"] += " Migrated Down to 2"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Migration.Tests/Unit/EntityOptionBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using MongoDB.Extensions.Migration; 4 | using Xunit; 5 | 6 | namespace Migration.Tests.Unit; 7 | 8 | public class EntityOptionBuilderTests 9 | { 10 | [Fact] 11 | public void EntityOptionBuilder_AtVersionNotSet_SetToLatestVersion() 12 | { 13 | // Act 14 | EntityOption entityOption = new EntityOptionBuilder() 15 | .WithMigration(new TestMigration1()) 16 | .WithMigration(new TestMigration2()) 17 | .Build(); 18 | 19 | // Assert 20 | entityOption.CurrentVersion.Should().Be(2); 21 | } 22 | 23 | [Fact] 24 | public void EntityOptionBuilder_NoMigrationRegistered_Throws() 25 | { 26 | // Act 27 | Action action = () => new EntityOptionBuilder().Build(); 28 | 29 | // Assert 30 | action.Should().Throw(); 31 | } 32 | 33 | [Fact] 34 | public void EntityOptionBuilder_GabInMigrationVersions_Throws() 35 | { 36 | // Act 37 | Action action = () => new EntityOptionBuilder() 38 | .WithMigration(new TestMigration3()) 39 | .WithMigration(new TestMigration1()) 40 | .Build(); 41 | 42 | // Assert 43 | action.Should().Throw(); 44 | } 45 | 46 | [Fact] 47 | public void EntityOptionBuilder_AtVersionHasNoMigrationMigrationVersions_Throws() 48 | { 49 | // Act 50 | Action action = () => new EntityOptionBuilder() 51 | .WithMigration(new TestMigration2()) 52 | .WithMigration(new TestMigration1()) 53 | .AtVersion(3) 54 | .Build(); 55 | 56 | // Assert 57 | action.Should().Throw(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Migration.Tests/Unit/MigrationOptionBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using MongoDB.Extensions.Migration; 4 | using Xunit; 5 | 6 | namespace Migration.Tests.Unit; 7 | 8 | public class MigrationOptionBuilderTests 9 | { 10 | [Fact] 11 | public void MigrationOptionBuilder_RegisterEntityTwice_Throws() 12 | { 13 | // Arrange 14 | var builder = new MigrationOptionBuilder(); 15 | builder.ForEntity(b => b.WithMigration(new TestMigration())); 16 | 17 | // Act 18 | Action action = () => builder.ForEntity(b => b.WithMigration(new TestMigration())); 19 | 20 | // Assert 21 | action.Should().Throw(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Migration.Tests/Unit/MigrationRunnerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using FluentAssertions; 4 | using Microsoft.Extensions.Logging.Abstractions; 5 | using MongoDB.Bson; 6 | using MongoDB.Extensions.Migration; 7 | using Xunit; 8 | 9 | namespace Migration.Tests.Unit; 10 | 11 | public class MigrationRunnerTests 12 | { 13 | [Fact] 14 | public void MigrationRunner_MigrationUpThrows_Catches() 15 | { 16 | // Arrange 17 | var runner = new MigrationRunner(new EntityContext( 18 | new EntityOptionBuilder().WithMigration(new TestMigration()) 19 | .Build(), 20 | new NullLoggerFactory())); 21 | var document = new Dictionary { ["Version"] = 1, ["_id"] = 1 }; 22 | 23 | // Act 24 | Action action = () => runner.Run(new BsonDocument(document)); 25 | 26 | // Assert 27 | action.Should().NotThrow(); 28 | } 29 | 30 | [Fact] 31 | public void MigrationRunner_MigrationDownThrows_Catches() 32 | { 33 | // Arrange 34 | var runner = new MigrationRunner(new EntityContext( 35 | new EntityOptionBuilder().WithMigration(new TestMigration()).AtVersion(0) 36 | .Build(), 37 | new NullLoggerFactory())); 38 | var document = new Dictionary { ["Version"] = 1, ["_id"] = 1 }; 39 | 40 | // Act 41 | Action action = () => runner.Run(new BsonDocument(document)); 42 | 43 | 44 | // Assert 45 | action.Should().NotThrow(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/Migration.Tests/Unit/TestEntity.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Extensions.Migration; 2 | 3 | namespace Migration.Tests.Unit; 4 | 5 | record TestEntity(int Id) : IVersioned 6 | { 7 | public int Version { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/Migration.Tests/Unit/TestMigration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Bson; 3 | using MongoDB.Extensions.Migration; 4 | 5 | namespace Migration.Tests.Unit; 6 | 7 | public class TestMigration : IMigration 8 | { 9 | public int Version { get; } = 1; 10 | 11 | public void Up(BsonDocument document) 12 | { 13 | throw new Exception(); 14 | } 15 | 16 | public void Down(BsonDocument document) 17 | { 18 | throw new Exception(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Migration/Builders/EntityOptionBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace MongoDB.Extensions.Migration; 5 | 6 | public class EntityOptionBuilder where T : IVersioned 7 | { 8 | private int? _atVersion; 9 | private readonly List _migrations = new(); 10 | 11 | /// 12 | /// Set the current Version of the data which is suitable for the application. 13 | /// If not set the newest Version is used. 14 | /// 15 | public EntityOptionBuilder AtVersion(int atVersion) 16 | { 17 | _atVersion = atVersion; 18 | return this; 19 | } 20 | 21 | /// 22 | /// Register a migration for an entity. The versions of the migrations must start at 1 and be 23 | /// continuously incremented without a gap 24 | /// 25 | public EntityOptionBuilder WithMigration(IMigration migration) 26 | { 27 | _migrations.Add(migration); 28 | return this; 29 | } 30 | 31 | /// 32 | /// Builds the EntityOption 33 | /// 34 | public EntityOption Build() 35 | { 36 | if (!_migrations.Any()) 37 | { 38 | throw new InvalidConfigurationException( 39 | $"There must be at least one migration registered for entity {typeof(T).Name}"); 40 | } 41 | 42 | _migrations.Sort((x, y) => x.Version.CompareTo(y.Version)); 43 | 44 | _atVersion ??= _migrations.Last().Version; 45 | 46 | if (_atVersion != _migrations.First().Version - 1 && 47 | _migrations.All(m => !Equals(m.Version, _atVersion))) 48 | { 49 | throw new InvalidConfigurationException( 50 | $"There is no migration for version {_atVersion} for entity {typeof(T).Name}"); 51 | } 52 | 53 | for (var i = 1; i < _migrations.Count; i++) 54 | { 55 | if (_migrations[i - 1].Version + 1 != _migrations[i].Version) 56 | { 57 | throw new InvalidConfigurationException( 58 | $"{typeof(T).Name}: Migration Versions must be continuously incremented!"); 59 | } 60 | } 61 | 62 | return new EntityOption( 63 | typeof(T), 64 | _atVersion.Value, 65 | _migrations); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Migration/Builders/MigrationOptionBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace MongoDB.Extensions.Migration; 6 | 7 | public class MigrationOptionBuilder 8 | { 9 | private readonly List _entityOptions = new(); 10 | 11 | /// 12 | /// Builds the MigrationOption 13 | /// 14 | public MigrationOption Build() 15 | { 16 | return new MigrationOption(_entityOptions); 17 | } 18 | 19 | /// 20 | /// Register a migration for a given entity. 21 | /// 22 | public MigrationOptionBuilder ForEntity( 23 | Func, EntityOptionBuilder> builderAction) where T : IVersioned 24 | { 25 | if (_entityOptions.Any(e => e.Type == typeof(T))) 26 | { 27 | throw new InvalidConfigurationException( 28 | $"Migrations for entity of type {typeof(T).FullName} have already been registered"); 29 | } 30 | _entityOptions.Add(builderAction(new EntityOptionBuilder()).Build()); 31 | return this; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Migration/Contracts/IMigration.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson; 2 | 3 | namespace MongoDB.Extensions.Migration; 4 | 5 | public interface IMigration 6 | { 7 | int Version { get; } 8 | void Up(BsonDocument document); 9 | void Down(BsonDocument document); 10 | } 11 | -------------------------------------------------------------------------------- /src/Migration/Contracts/IVersioned.cs: -------------------------------------------------------------------------------- 1 | namespace MongoDB.Extensions.Migration; 2 | 3 | public interface IVersioned 4 | { 5 | int Version { get; set; } 6 | } -------------------------------------------------------------------------------- /src/Migration/Exceptions/InvalidConfigurationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MongoDB.Extensions.Migration; 4 | 5 | public class InvalidConfigurationException : Exception 6 | { 7 | public InvalidConfigurationException() { } 8 | public InvalidConfigurationException(string message) : base(message) { } 9 | public InvalidConfigurationException(string message, Exception innerException) : 10 | base(message, innerException) { } 11 | } 12 | -------------------------------------------------------------------------------- /src/Migration/Migration.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MongoDB.Extensions.Migration 6 | MongoDB.Extensions.Migration 7 | MongoDB.Extensions.Migration 8 | net6.0;net8.0 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Migration/MigrationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using MongoDB.Bson.Serialization; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace MongoDB.Extensions.Migration; 8 | 9 | public static class MigrationExtensions 10 | { 11 | public static IApplicationBuilder UseMongoMigration( 12 | this IApplicationBuilder app, 13 | Func builderAction) 14 | { 15 | var builder = new MigrationOptionBuilder(); 16 | MigrationOption options = builderAction(builder).Build(); 17 | 18 | MigrationContext context = new( 19 | options, 20 | app.ApplicationServices.GetRequiredService()); 21 | 22 | BsonSerializer.RegisterSerializationProvider(new MigrationSerializerProvider(context)); 23 | 24 | return app; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Migration/MigrationSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Bson; 3 | using MongoDB.Bson.IO; 4 | using MongoDB.Bson.Serialization; 5 | using MongoDB.Bson.Serialization.Conventions; 6 | using MongoDB.Bson.Serialization.Serializers; 7 | 8 | namespace MongoDB.Extensions.Migration; 9 | 10 | class MigrationSerializer : SerializerBase, IBsonIdProvider, IBsonDocumentSerializer, IBsonPolymorphicSerializer, 11 | IHasDiscriminatorConvention where T : IVersioned 12 | { 13 | private readonly EntityContext _context; 14 | private readonly MigrationRunner _migrationRunner; 15 | private readonly BsonClassMapSerializer _bsonClassMapSerializer; 16 | 17 | public MigrationSerializer(EntityContext context) 18 | { 19 | _bsonClassMapSerializer = new BsonClassMapSerializer(BsonClassMap.LookupClassMap(typeof(T))); 20 | _context = context; 21 | _migrationRunner = new MigrationRunner(context); 22 | } 23 | 24 | public override void Serialize( 25 | BsonSerializationContext context, 26 | BsonSerializationArgs args, 27 | T value) 28 | { 29 | value.Version = _context.Option.CurrentVersion; 30 | _bsonClassMapSerializer.Serialize(context, args, value); 31 | } 32 | 33 | public override T Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) 34 | { 35 | BsonDocument bsonDocument = BsonDocumentSerializer.Instance.Deserialize(context); 36 | 37 | _migrationRunner.Run(bsonDocument); 38 | 39 | var migratedContext = 40 | BsonDeserializationContext.CreateRoot(new BsonDocumentReader(bsonDocument)); 41 | 42 | T entity = _bsonClassMapSerializer.Deserialize(migratedContext, args); 43 | 44 | entity.Version = _context.Option.CurrentVersion; 45 | 46 | return entity; 47 | } 48 | 49 | public bool TryGetMemberSerializationInfo(string memberName, out BsonSerializationInfo serializationInfo) 50 | { 51 | return _bsonClassMapSerializer.TryGetMemberSerializationInfo(memberName, out serializationInfo); 52 | } 53 | 54 | public bool GetDocumentId(object document, out object id, out Type idNominalType, out IIdGenerator idGenerator) 55 | { 56 | return _bsonClassMapSerializer.GetDocumentId(document, out id, out idNominalType, out idGenerator); 57 | } 58 | 59 | public void SetDocumentId(object document, object id) 60 | { 61 | _bsonClassMapSerializer.SetDocumentId(document, id); 62 | } 63 | 64 | public bool IsDiscriminatorCompatibleWithObjectSerializer => 65 | _bsonClassMapSerializer.IsDiscriminatorCompatibleWithObjectSerializer; 66 | 67 | public IDiscriminatorConvention DiscriminatorConvention => _bsonClassMapSerializer.DiscriminatorConvention; 68 | } 69 | -------------------------------------------------------------------------------- /src/Migration/MigrationSerializerProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using MongoDB.Bson.Serialization; 4 | 5 | namespace MongoDB.Extensions.Migration; 6 | 7 | public class MigrationSerializerProvider : IBsonSerializationProvider 8 | { 9 | private readonly MigrationContext _context; 10 | 11 | public MigrationSerializerProvider(MigrationContext context) 12 | { 13 | _context = context; 14 | } 15 | 16 | public IBsonSerializer? GetSerializer(Type type) 17 | { 18 | EntityOption? option = _context.Option.EntityOptions.SingleOrDefault(e => e.Type == type); 19 | if (option is null) 20 | { 21 | return null; 22 | } 23 | 24 | EntityContext entityContext = new(option, _context.LoggerFactory); 25 | Type migrationSerializerDefinition = typeof(MigrationSerializer<>); 26 | Type migrationSerializerType = migrationSerializerDefinition.MakeGenericType(type); 27 | return (IBsonSerializer?)Activator.CreateInstance(migrationSerializerType, entityContext); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Migration/Models/EntityContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace MongoDB.Extensions.Migration; 4 | 5 | public record EntityContext(EntityOption Option, ILoggerFactory LoggerFactory); 6 | -------------------------------------------------------------------------------- /src/Migration/Models/EntityOption.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MongoDB.Extensions.Migration; 5 | 6 | public record EntityOption( 7 | Type Type, 8 | int CurrentVersion, 9 | List Migrations); 10 | -------------------------------------------------------------------------------- /src/Migration/Models/MigrationContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace MongoDB.Extensions.Migration; 4 | 5 | public record MigrationContext(MigrationOption Option, ILoggerFactory LoggerFactory); 6 | -------------------------------------------------------------------------------- /src/Migration/Models/MigrationOption.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MongoDB.Extensions.Migration; 4 | 5 | public record MigrationOption(List EntityOptions); 6 | -------------------------------------------------------------------------------- /src/Migration/Readme.md: -------------------------------------------------------------------------------- 1 | # MongoDB.Extensions.Migration 2 | 3 | MongoDB.Extensions.Migration is a library which supports writing migrations for MongoDB in c#. 4 | Simple changes in the data model can often be accommodated by using data annotations from the MongoDB c# driver like `[BsonDefaultValue(...)]` but for more complicated changes the ability to write migrations is helpful. 5 | 6 | ## Concept 7 | 8 | Traditionally in relational databases, migration have been applied to all documents at once and caused downtime. 9 | With document databases which have a schema on read not on write we can do better. 10 | Introducing a version field on each document and having migration logic which performs the needed migrations when reading a document allows for a downtime free migrations. 11 | This pattern allows for multiple versions of an application to use the same database, for example in the case of a rolling update. 12 | 13 | ## Getting Started 14 | 15 | 1. Add MongoDB.Extensions.Migration to your project using `dotnet add package MongoDB.Extensions.Migration` 16 | 2. Make sure that your your domain entities for which you want to write a migration implement the interface IVersioned 17 | 3. Write a migration by creating a new class which implements IMigration, see below. 18 | 4. Regsiter your migrations with the `UseMongoMigration` extension method on the `IApplicationBuilder`. Either in Program.cs or in the Configure method of Startup.cs. 19 | 20 | ```csharp 21 | ... 22 | var app = builder.Build(); 23 | 24 | app.UseMongoMigration(m => m 25 | .ForEntity(e => e 26 | .AtVersion(1) 27 | .WithMigration(new ExampleMigration()))); 28 | 29 | 30 | public record Customer : IVersioned { 31 | public int Version { get; set; } 32 | public Guid Id {get; set;} 33 | public string FirstName {get; set;} 34 | public string LastName {get; set;} 35 | } 36 | 37 | public class ExampleMigration : IMigration {...} 38 | ``` 39 | 40 | Entities for which a migration is needed must implement IVersioned and Migrations must implement IMigration. 41 | The optional method AtVersion allows setting the version of the data the application currently supports. 42 | This allows for scenarios where a migration down to a specific version is needed. 43 | 44 | ### Writing Migrations 45 | 46 | Each migration has a version. 47 | The versions of all registered migrations must be continuously incrementing without a gap. 48 | We recommend to never change a already deployed migration. 49 | 50 | The up and down method of a migration act directly on the BsonDocument and allows making the needed changes to get from one version to another. 51 | This example shows how a typo in a field name is fixed. 52 | 53 | ```csharp 54 | public class ExampleMigration : IMigration 55 | { 56 | public int Version => 1; 57 | 58 | public void Up(BsonDocument document) 59 | { 60 | document["Name"] = document["Namee"]; 61 | } 62 | 63 | public void Down(BsonDocument document) 64 | { 65 | document["Namee"] = document["Name"]; 66 | } 67 | } 68 | ``` 69 | -------------------------------------------------------------------------------- /src/Package.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MongoDB Extensions 5 | Swiss Life authors and contributors 6 | Swiss Life 7 | Copyright © $(Company) $([System.DateTime]::Now.Year) 8 | https://github.com/SwissLife-OSS/mongo-extensions/blob/master/LICENSE 9 | https://github.com/SwissLife-OSS/mongo-extensions 10 | Release notes: https://github.com/SwissLife-OSS/mongo-extensions/releases/$(Version) 11 | MongoDB Extensions DbContext Context Boostrap Mongo .Net 12 | true 13 | https://github.com/SwissLife-OSS/mongo-extensions-docs/raw/master/logo.png 14 | false 15 | 10.0 16 | 17 | 18 | 19 | true 20 | true 21 | https://github.com/SwissLife-OSS/mongo-extensions.git 22 | GitHub 23 | true 24 | snupkg 25 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/FilterDefinitionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Driver; 3 | using Snapshooter.Xunit; 4 | using Xunit; 5 | 6 | namespace MongoDB.Prime.Extensions.Tests 7 | { 8 | public class FilterDefinitionExtensionsTests 9 | { 10 | [Fact] 11 | public void ToFilterString_FilterStringNoIdent_Success() 12 | { 13 | // Arrange 14 | FilterDefinition filter = 15 | Builders.Filter.Or( 16 | Builders.Filter.And( 17 | Builders.Filter.Eq(u => u.Id, 18 | Guid.Parse("44752191-E10B-435A-A0E9-62E7F13D41CD")), 19 | Builders.Filter.Eq(b => b.Name, "Walse"), 20 | Builders.Filter.Eq(b => b.Name, "Toronto"))); 21 | 22 | // Act 23 | string filterString = filter.ToDefinitionString(); 24 | 25 | // Assert 26 | Snapshot.Match(filterString); 27 | } 28 | 29 | [Fact] 30 | public void ToFilterString_FilterStringWithIdent_Success() 31 | { 32 | // Arrange 33 | FilterDefinition filter = 34 | Builders.Filter.Or( 35 | Builders.Filter.And( 36 | Builders.Filter.Eq(u => u.Id, 37 | Guid.Parse("44752191-E10B-435A-A0E9-62E7F13D41CD")), 38 | Builders.Filter.Eq(b => b.Name, "Walse"), 39 | Builders.Filter.Eq(b => b.Name, "Toronto"))); 40 | 41 | // Act 42 | string filterString = filter.ToDefinitionString(indent: true); 43 | 44 | // Assert 45 | Snapshot.Match(filterString); 46 | } 47 | 48 | [Fact] 49 | public void ToDefinitionString_DefinitionStringNoIdent_Success() 50 | { 51 | // Arrange 52 | UpdateDefinition filter = 53 | Builders.Update.Set(field => field.Name, "Spain"); 54 | 55 | // Act 56 | string filterString = filter.ToDefinitionString(); 57 | 58 | // Assert 59 | Snapshot.Match(filterString); 60 | } 61 | 62 | [Fact] 63 | public void ToDefinitionString_DefinitionStringWithIdent_Success() 64 | { 65 | // Arrange 66 | UpdateDefinition filter = 67 | Builders.Update.Set(field => field.Id, 68 | Guid.Parse("44752191-E10B-435A-A0E9-62E7F13D41CD")); 69 | 70 | // Act 71 | string filterString = filter.ToDefinitionString(indent: true); 72 | 73 | // Assert 74 | Snapshot.Match(filterString); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/FindFluentExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using MongoDB.Driver; 5 | using Snapshooter.Xunit; 6 | using Squadron; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | 10 | namespace MongoDB.Prime.Extensions.Tests 11 | { 12 | public class FindFluentExtensionsTests : IClassFixture 13 | { 14 | private readonly IMongoDatabase _mongoDatabase; 15 | 16 | public FindFluentExtensionsTests(MongoResource mongoResource) 17 | { 18 | _mongoDatabase = mongoResource.CreateDatabase(); 19 | } 20 | 21 | [Fact] 22 | public void PrintQuery_PrintOneSingleQuery_OriginalMongoDbQueryPrinted() 23 | { 24 | // Arrange 25 | IMongoCollection barCollection = 26 | _mongoDatabase.GetCollection(); 27 | 28 | // Act 29 | string mongodbQuery = barCollection 30 | .Find(bar => bar.Name == "Bar1" || bar.Value == "1234") 31 | .Limit(5) 32 | .PrintQuery(); 33 | 34 | // Assert 35 | Snapshot.Match(mongodbQuery); 36 | } 37 | 38 | [Fact] 39 | public void ToQueryString_ToOneSingleQueryString_OriginalMongoDbQueryPrinted() 40 | { 41 | // Arrange 42 | IMongoCollection barCollection = 43 | _mongoDatabase.GetCollection(); 44 | 45 | // Act 46 | string mongodbQuery = barCollection 47 | .Find(bar => bar.Name == "Bar1" || bar.Value == "1234") 48 | .Limit(5) 49 | .ToQueryString(); 50 | 51 | // Assert 52 | Snapshot.Match(mongodbQuery); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/Helpers/Bar.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Bson; 3 | using MongoDB.Bson.Serialization.Attributes; 4 | 5 | namespace MongoDB.Prime.Extensions.Tests; 6 | 7 | public class Bar 8 | { 9 | public Bar(Guid id, string name, string value) 10 | { 11 | Id = id; 12 | Name = name; 13 | Value = value; 14 | } 15 | 16 | [BsonGuidRepresentation(GuidRepresentation.CSharpLegacy)] 17 | public Guid Id { get; } 18 | 19 | public string Name { get; } 20 | public string Value { get; } 21 | } 22 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/Helpers/Foo.cs: -------------------------------------------------------------------------------- 1 | namespace MongoDB.Prime.Extensions.Tests; 2 | 3 | public class Foo 4 | { 5 | public Foo(string id, string name) 6 | { 7 | Id = id; 8 | Name = name; 9 | } 10 | 11 | public string Id { get; set; } 12 | public string Name { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/Prime.Extensions.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MongoDB.Prime.Extensions.Tests 6 | MongoDB.Prime.Extensions.Tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/UpdateDefinitionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Driver; 3 | using Snapshooter.Xunit; 4 | using Xunit; 5 | 6 | namespace MongoDB.Prime.Extensions.Tests 7 | { 8 | public class UpdateDefinitionExtensionsTests 9 | { 10 | [Fact] 11 | public void ToDefinitionString_DefinitionStringNoIdent_Success() 12 | { 13 | // Arrange 14 | UpdateDefinition filter = 15 | Builders.Update.Set(field => field.Name, "Spain"); 16 | 17 | // Act 18 | string filterString = filter.ToDefinitionString(); 19 | 20 | // Assert 21 | Snapshot.Match(filterString); 22 | } 23 | 24 | [Fact] 25 | public void ToDefinitionString_DefinitionStringWithIdent_Success() 26 | { 27 | // Arrange 28 | UpdateDefinition filter = 29 | Builders.Update.Set(field => field.Id, 30 | Guid.Parse("44752191-E10B-435A-A0E9-62E7F13D41CD")); 31 | 32 | // Act 33 | string filterString = filter.ToDefinitionString(indent: true); 34 | 35 | // Assert 36 | Snapshot.Match(filterString); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/AsyncCursorSourceExtensionsTests.ToDictionaryAsync_FindMultipleDocumentsWithIdAsKey_ReturnRightDocuments.snap: -------------------------------------------------------------------------------- 1 | { 2 | "a1c9e3e8-b448-42da-a684-716932903041": { 3 | "Id": "a1c9e3e8-b448-42da-a684-716932903041", 4 | "Name": "Foo1" 5 | }, 6 | "a1c9e3e8-b448-42da-a684-716932903042": { 7 | "Id": "a1c9e3e8-b448-42da-a684-716932903042", 8 | "Name": "Foo2" 9 | }, 10 | "a1c9e3e8-b448-42da-a684-716932903043": { 11 | "Id": "a1c9e3e8-b448-42da-a684-716932903043", 12 | "Name": "Foo3" 13 | }, 14 | "a1c9e3e8-b448-42da-a684-716932903044": { 15 | "Id": "a1c9e3e8-b448-42da-a684-716932903044", 16 | "Name": "Foo4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/AsyncCursorSourceExtensionsTests.ToDictionaryAsync_FindMultipleDocumentsWithNameAsKey_ReturnRightDocuments.snap: -------------------------------------------------------------------------------- 1 | { 2 | "Foo1": { 3 | "Id": "a1c9e3e8-b448-42da-a684-716932903041", 4 | "Name": "Foo1" 5 | }, 6 | "Foo2": { 7 | "Id": "a1c9e3e8-b448-42da-a684-716932903042", 8 | "Name": "Foo2" 9 | }, 10 | "Foo3": { 11 | "Id": "a1c9e3e8-b448-42da-a684-716932903043", 12 | "Name": "Foo3" 13 | }, 14 | "Foo4": { 15 | "Id": "a1c9e3e8-b448-42da-a684-716932903044", 16 | "Name": "Foo4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/FilterDefinitionExtensionsTests.ToDefinitionString_DefinitionStringNoIdent_Success.snap: -------------------------------------------------------------------------------- 1 | { "$set" : { "Name" : "Spain" } } 2 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/FilterDefinitionExtensionsTests.ToDefinitionString_DefinitionStringWithIdent_Success.snap: -------------------------------------------------------------------------------- 1 | { 2 | "$set" : { 3 | "_id" : HexData(3, "91217544-0be1-5a43-a0e9-62e7f13d41cd") 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/FilterDefinitionExtensionsTests.ToFilterString_FilterStringNoIdent_Success.snap: -------------------------------------------------------------------------------- 1 | { "$or" : [{ "$and" : [{ "_id" : HexData(3, "91217544-0be1-5a43-a0e9-62e7f13d41cd") }, { "Name" : "Walse" }, { "Name" : "Toronto" }] }] } 2 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/FilterDefinitionExtensionsTests.ToFilterString_FilterStringWithIdent_Success.snap: -------------------------------------------------------------------------------- 1 | { 2 | "$or" : [{ 3 | "$and" : [{ 4 | "_id" : HexData(3, "91217544-0be1-5a43-a0e9-62e7f13d41cd") 5 | }, { 6 | "Name" : "Walse" 7 | }, { 8 | "Name" : "Toronto" 9 | }] 10 | }] 11 | } 12 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/FindFluentExtensionsTests.PrintQuery_PrintOneSingleQuery_OriginalMongoDbQueryPrinted.snap: -------------------------------------------------------------------------------- 1 | find({ "$or" : [{ "Name" : "Bar1" }, { "Value" : "1234" }] }).limit(5) 2 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/FindFluentExtensionsTests.ToQueryString_ToOneSingleQueryString_OriginalMongoDbQueryPrinted.snap: -------------------------------------------------------------------------------- 1 | find({ "$or" : [{ "Name" : "Bar1" }, { "Value" : "1234" }] }).limit(5) 2 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/MongoCollectionExtensionsTests.FindIds_FindDuplicatedBarIdsAsynchronously_ReturnsDistinctBars.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "a1c9e3e8-b448-42da-a684-716932903041", 4 | "Value": { 5 | "Id": "a1c9e3e8-b448-42da-a684-716932903041", 6 | "Name": "Bar1", 7 | "Value": "Value1" 8 | } 9 | }, 10 | { 11 | "Key": "a1c9e3e8-b448-42da-a684-716932903044", 12 | "Value": { 13 | "Id": "a1c9e3e8-b448-42da-a684-716932903044", 14 | "Name": "Bar4", 15 | "Value": "Value4" 16 | } 17 | }, 18 | { 19 | "Key": "a1c9e3e8-b448-42da-a684-716932903046", 20 | "Value": { 21 | "Id": "a1c9e3e8-b448-42da-a684-716932903046", 22 | "Name": "Bar6", 23 | "Value": "Value6" 24 | } 25 | }, 26 | { 27 | "Key": "a1c9e3e8-b448-42da-a684-716932903047", 28 | "Value": { 29 | "Id": "a1c9e3e8-b448-42da-a684-716932903047", 30 | "Name": "Bar7", 31 | "Value": "Value7" 32 | } 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/MongoCollectionExtensionsTests.FindIds_FindDuplicatedBarIdsSynchronously_ReturnsDistinctBars.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "a1c9e3e8-b448-42da-a684-716932903041", 4 | "Value": { 5 | "Id": "a1c9e3e8-b448-42da-a684-716932903041", 6 | "Name": "Bar1", 7 | "Value": "Value1" 8 | } 9 | }, 10 | { 11 | "Key": "a1c9e3e8-b448-42da-a684-716932903044", 12 | "Value": { 13 | "Id": "a1c9e3e8-b448-42da-a684-716932903044", 14 | "Name": "Bar4", 15 | "Value": "Value4" 16 | } 17 | }, 18 | { 19 | "Key": "a1c9e3e8-b448-42da-a684-716932903046", 20 | "Value": { 21 | "Id": "a1c9e3e8-b448-42da-a684-716932903046", 22 | "Name": "Bar6", 23 | "Value": "Value6" 24 | } 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/MongoCollectionExtensionsTests.FindIds_FindFourBarIdsAsynchronously_ReturnsRightBars.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "a1c9e3e8-b448-42da-a684-716932903043", 4 | "Value": { 5 | "Id": "a1c9e3e8-b448-42da-a684-716932903043", 6 | "Name": "Bar3", 7 | "Value": "Value3" 8 | } 9 | }, 10 | { 11 | "Key": "a1c9e3e8-b448-42da-a684-716932903044", 12 | "Value": { 13 | "Id": "a1c9e3e8-b448-42da-a684-716932903044", 14 | "Name": "Bar4", 15 | "Value": "Value4" 16 | } 17 | }, 18 | { 19 | "Key": "a1c9e3e8-b448-42da-a684-716932903045", 20 | "Value": { 21 | "Id": "a1c9e3e8-b448-42da-a684-716932903045", 22 | "Name": "Bar5", 23 | "Value": "Value5" 24 | } 25 | }, 26 | { 27 | "Key": "a1c9e3e8-b448-42da-a684-716932903046", 28 | "Value": { 29 | "Id": "a1c9e3e8-b448-42da-a684-716932903046", 30 | "Name": "Bar6", 31 | "Value": "Value6" 32 | } 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/MongoCollectionExtensionsTests.FindIds_FindFourBarIdsSynchronously_ReturnsRightBars.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "a1c9e3e8-b448-42da-a684-716932903043", 4 | "Value": { 5 | "Id": "a1c9e3e8-b448-42da-a684-716932903043", 6 | "Name": "Bar3", 7 | "Value": "Value3" 8 | } 9 | }, 10 | { 11 | "Key": "a1c9e3e8-b448-42da-a684-716932903044", 12 | "Value": { 13 | "Id": "a1c9e3e8-b448-42da-a684-716932903044", 14 | "Name": "Bar4", 15 | "Value": "Value4" 16 | } 17 | }, 18 | { 19 | "Key": "a1c9e3e8-b448-42da-a684-716932903045", 20 | "Value": { 21 | "Id": "a1c9e3e8-b448-42da-a684-716932903045", 22 | "Name": "Bar5", 23 | "Value": "Value5" 24 | } 25 | }, 26 | { 27 | "Key": "a1c9e3e8-b448-42da-a684-716932903046", 28 | "Value": { 29 | "Id": "a1c9e3e8-b448-42da-a684-716932903046", 30 | "Name": "Bar6", 31 | "Value": "Value6" 32 | } 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/MongoCollectionExtensionsTests.FindIds_FindFourBarNamesAsynchronously_ReturnsRightBars.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "Bar3", 4 | "Value": { 5 | "Id": "a1c9e3e8-b448-42da-a684-716932903043", 6 | "Name": "Bar3", 7 | "Value": "Value3" 8 | } 9 | }, 10 | { 11 | "Key": "Bar4", 12 | "Value": { 13 | "Id": "a1c9e3e8-b448-42da-a684-716932903044", 14 | "Name": "Bar4", 15 | "Value": "Value4" 16 | } 17 | }, 18 | { 19 | "Key": "Bar5", 20 | "Value": { 21 | "Id": "a1c9e3e8-b448-42da-a684-716932903045", 22 | "Name": "Bar5", 23 | "Value": "Value5" 24 | } 25 | }, 26 | { 27 | "Key": "Bar6", 28 | "Value": { 29 | "Id": "a1c9e3e8-b448-42da-a684-716932903046", 30 | "Name": "Bar6", 31 | "Value": "Value6" 32 | } 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/MongoCollectionExtensionsTests.FindIds_FindFourBarNamesSynchronously_ReturnsRightBars.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "Bar3", 4 | "Value": { 5 | "Id": "a1c9e3e8-b448-42da-a684-716932903043", 6 | "Name": "Bar3", 7 | "Value": "Value3" 8 | } 9 | }, 10 | { 11 | "Key": "Bar4", 12 | "Value": { 13 | "Id": "a1c9e3e8-b448-42da-a684-716932903044", 14 | "Name": "Bar4", 15 | "Value": "Value4" 16 | } 17 | }, 18 | { 19 | "Key": "Bar5", 20 | "Value": { 21 | "Id": "a1c9e3e8-b448-42da-a684-716932903045", 22 | "Name": "Bar5", 23 | "Value": "Value5" 24 | } 25 | }, 26 | { 27 | "Key": "Bar6", 28 | "Value": { 29 | "Id": "a1c9e3e8-b448-42da-a684-716932903046", 30 | "Name": "Bar6", 31 | "Value": "Value6" 32 | } 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/MongoCollectionExtensionsTests.FindIds_FindOneId_ReturnsRightBar.snap: -------------------------------------------------------------------------------- 1 | { 2 | "a1c9e3e8-b448-42da-a684-716932903043": { 3 | "Id": "a1c9e3e8-b448-42da-a684-716932903043", 4 | "Name": "Bar1", 5 | "Value": "Value1" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/MongoCollectionFindExtensionsTests.FindIds_FindDuplicatedBarIdsAsynchronously_ReturnsDistinctBars.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "a1c9e3e8-b448-42da-a684-716932903041", 4 | "Value": { 5 | "Id": "a1c9e3e8-b448-42da-a684-716932903041", 6 | "Name": "Bar1", 7 | "Value": "Value1" 8 | } 9 | }, 10 | { 11 | "Key": "a1c9e3e8-b448-42da-a684-716932903044", 12 | "Value": { 13 | "Id": "a1c9e3e8-b448-42da-a684-716932903044", 14 | "Name": "Bar4", 15 | "Value": "Value4" 16 | } 17 | }, 18 | { 19 | "Key": "a1c9e3e8-b448-42da-a684-716932903046", 20 | "Value": { 21 | "Id": "a1c9e3e8-b448-42da-a684-716932903046", 22 | "Name": "Bar6", 23 | "Value": "Value6" 24 | } 25 | }, 26 | { 27 | "Key": "a1c9e3e8-b448-42da-a684-716932903047", 28 | "Value": { 29 | "Id": "a1c9e3e8-b448-42da-a684-716932903047", 30 | "Name": "Bar7", 31 | "Value": "Value7" 32 | } 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/MongoCollectionFindExtensionsTests.FindIds_FindDuplicatedBarIdsSynchronously_ReturnsDistinctBars.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "a1c9e3e8-b448-42da-a684-716932903041", 4 | "Value": { 5 | "Id": "a1c9e3e8-b448-42da-a684-716932903041", 6 | "Name": "Bar1", 7 | "Value": "Value1" 8 | } 9 | }, 10 | { 11 | "Key": "a1c9e3e8-b448-42da-a684-716932903044", 12 | "Value": { 13 | "Id": "a1c9e3e8-b448-42da-a684-716932903044", 14 | "Name": "Bar4", 15 | "Value": "Value4" 16 | } 17 | }, 18 | { 19 | "Key": "a1c9e3e8-b448-42da-a684-716932903046", 20 | "Value": { 21 | "Id": "a1c9e3e8-b448-42da-a684-716932903046", 22 | "Name": "Bar6", 23 | "Value": "Value6" 24 | } 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/MongoCollectionFindExtensionsTests.FindIds_FindFourBarIdsAsynchronously_ReturnsRightBars.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "a1c9e3e8-b448-42da-a684-716932903043", 4 | "Value": { 5 | "Id": "a1c9e3e8-b448-42da-a684-716932903043", 6 | "Name": "Bar3", 7 | "Value": "Value3" 8 | } 9 | }, 10 | { 11 | "Key": "a1c9e3e8-b448-42da-a684-716932903044", 12 | "Value": { 13 | "Id": "a1c9e3e8-b448-42da-a684-716932903044", 14 | "Name": "Bar4", 15 | "Value": "Value4" 16 | } 17 | }, 18 | { 19 | "Key": "a1c9e3e8-b448-42da-a684-716932903045", 20 | "Value": { 21 | "Id": "a1c9e3e8-b448-42da-a684-716932903045", 22 | "Name": "Bar5", 23 | "Value": "Value5" 24 | } 25 | }, 26 | { 27 | "Key": "a1c9e3e8-b448-42da-a684-716932903046", 28 | "Value": { 29 | "Id": "a1c9e3e8-b448-42da-a684-716932903046", 30 | "Name": "Bar6", 31 | "Value": "Value6" 32 | } 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/MongoCollectionFindExtensionsTests.FindIds_FindFourBarIdsSynchronously_ReturnsRightBars.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "a1c9e3e8-b448-42da-a684-716932903043", 4 | "Value": { 5 | "Id": "a1c9e3e8-b448-42da-a684-716932903043", 6 | "Name": "Bar3", 7 | "Value": "Value3" 8 | } 9 | }, 10 | { 11 | "Key": "a1c9e3e8-b448-42da-a684-716932903044", 12 | "Value": { 13 | "Id": "a1c9e3e8-b448-42da-a684-716932903044", 14 | "Name": "Bar4", 15 | "Value": "Value4" 16 | } 17 | }, 18 | { 19 | "Key": "a1c9e3e8-b448-42da-a684-716932903045", 20 | "Value": { 21 | "Id": "a1c9e3e8-b448-42da-a684-716932903045", 22 | "Name": "Bar5", 23 | "Value": "Value5" 24 | } 25 | }, 26 | { 27 | "Key": "a1c9e3e8-b448-42da-a684-716932903046", 28 | "Value": { 29 | "Id": "a1c9e3e8-b448-42da-a684-716932903046", 30 | "Name": "Bar6", 31 | "Value": "Value6" 32 | } 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/MongoCollectionFindExtensionsTests.FindIds_FindFourBarNamesAsynchronously_ReturnsRightBars.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "Bar3", 4 | "Value": { 5 | "Id": "a1c9e3e8-b448-42da-a684-716932903043", 6 | "Name": "Bar3", 7 | "Value": "Value3" 8 | } 9 | }, 10 | { 11 | "Key": "Bar4", 12 | "Value": { 13 | "Id": "a1c9e3e8-b448-42da-a684-716932903044", 14 | "Name": "Bar4", 15 | "Value": "Value4" 16 | } 17 | }, 18 | { 19 | "Key": "Bar5", 20 | "Value": { 21 | "Id": "a1c9e3e8-b448-42da-a684-716932903045", 22 | "Name": "Bar5", 23 | "Value": "Value5" 24 | } 25 | }, 26 | { 27 | "Key": "Bar6", 28 | "Value": { 29 | "Id": "a1c9e3e8-b448-42da-a684-716932903046", 30 | "Name": "Bar6", 31 | "Value": "Value6" 32 | } 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/MongoCollectionFindExtensionsTests.FindIds_FindFourBarNamesSynchronously_ReturnsRightBars.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "Bar3", 4 | "Value": { 5 | "Id": "a1c9e3e8-b448-42da-a684-716932903043", 6 | "Name": "Bar3", 7 | "Value": "Value3" 8 | } 9 | }, 10 | { 11 | "Key": "Bar4", 12 | "Value": { 13 | "Id": "a1c9e3e8-b448-42da-a684-716932903044", 14 | "Name": "Bar4", 15 | "Value": "Value4" 16 | } 17 | }, 18 | { 19 | "Key": "Bar5", 20 | "Value": { 21 | "Id": "a1c9e3e8-b448-42da-a684-716932903045", 22 | "Name": "Bar5", 23 | "Value": "Value5" 24 | } 25 | }, 26 | { 27 | "Key": "Bar6", 28 | "Value": { 29 | "Id": "a1c9e3e8-b448-42da-a684-716932903046", 30 | "Name": "Bar6", 31 | "Value": "Value6" 32 | } 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/MongoCollectionFindExtensionsTests.FindIds_FindOneId_ReturnsRightBar.snap: -------------------------------------------------------------------------------- 1 | { 2 | "a1c9e3e8-b448-42da-a684-716932903043": { 3 | "Id": "a1c9e3e8-b448-42da-a684-716932903043", 4 | "Name": "Bar1", 5 | "Value": "Value1" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/MongoDatabaseExtensionsTests.GetProfiledOperations_GetAllExecutedOperations_ReturnsAllMongoDBOperations.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "command": { 4 | "create": "Bar" 5 | } 6 | }, 7 | { 8 | "command": { 9 | "create": "Foo" 10 | } 11 | }, 12 | { 13 | "command": { 14 | "insert": "Bar", 15 | "ordered": true 16 | } 17 | }, 18 | { 19 | "command": { 20 | "insert": "Foo", 21 | "ordered": true 22 | } 23 | }, 24 | { 25 | "command": { 26 | "find": "Foo", 27 | "filter": { 28 | "Name": "foo1" 29 | }, 30 | "readConcern": { 31 | "level": "majority" 32 | } 33 | }, 34 | "keysExamined": 0, 35 | "docsExamined": 1, 36 | "planSummary": "COLLSCAN" 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/MongoDatabaseExtensionsTests.GetProfiledOperations_GetOneExecutedOperations_ReturnsOneMongoDBOperation.snap: -------------------------------------------------------------------------------- 1 | { 2 | "command": { 3 | "create": "Bar" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/UpdateDefinitionExtensionsTests.ToDefinitionString_DefinitionStringNoIdent_Success.snap: -------------------------------------------------------------------------------- 1 | { "$set" : { "Name" : "Spain" } } 2 | -------------------------------------------------------------------------------- /src/Prime.Extensions.Tests/__snapshots__/UpdateDefinitionExtensionsTests.ToDefinitionString_DefinitionStringWithIdent_Success.snap: -------------------------------------------------------------------------------- 1 | { 2 | "$set" : { 3 | "_id" : HexData(3, "91217544-0be1-5a43-a0e9-62e7f13d41cd") 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/Prime.Extensions/AsyncCursorSourceExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using MongoDB.Driver; 6 | 7 | namespace MongoDB.Prime.Extensions 8 | { 9 | public static class AsyncCursorSourceExtensions 10 | { 11 | public static Task> ToDictionaryAsync( 12 | this IAsyncCursorSource source, 13 | Func keySelector, 14 | CancellationToken cancellationToken = default) where TKey : notnull 15 | { 16 | return ToDictionaryAsync(source, keySelector, 0, cancellationToken); 17 | } 18 | 19 | public static async Task> ToDictionaryAsync( 20 | this IAsyncCursorSource source, 21 | Func keySelector, 22 | int capacity, 23 | CancellationToken cancellationToken = default) where TKey : notnull 24 | { 25 | var documents = new Dictionary(capacity); 26 | 27 | using IAsyncCursor cursor = 28 | await source.ToCursorAsync(cancellationToken).ConfigureAwait(false); 29 | 30 | while (await cursor.MoveNextAsync(cancellationToken).ConfigureAwait(false)) 31 | { 32 | foreach (TDocument document in cursor.Current) 33 | { 34 | documents.Add(keySelector(document), document); 35 | } 36 | } 37 | 38 | return documents; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Prime.Extensions/ClientSessionHandleExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Driver; 3 | 4 | namespace MongoDB.Prime.Extensions; 5 | 6 | public static class ClientSessionHandleExtensions 7 | { 8 | public static Guid GetSessionId(this IClientSessionHandle clientSessionHandle) 9 | { 10 | return clientSessionHandle.ServerSession.Id["id"].AsGuid; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Prime.Extensions/FilterDefinitionExtensions.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson.IO; 2 | using MongoDB.Bson; 3 | using MongoDB.Driver; 4 | using MongoDB.Bson.Serialization; 5 | 6 | namespace MongoDB.Prime.Extensions 7 | { 8 | public static class FilterDefinitionExtensions 9 | { 10 | public static string ToDefinitionString( 11 | this FilterDefinition filter, bool indent = false) 12 | { 13 | BsonDocument bson = ToBsonDocument(filter); 14 | 15 | var settings = new JsonWriterSettings 16 | { 17 | Indent = indent, 18 | OutputMode = JsonOutputMode.Shell, 19 | }; 20 | 21 | string json = bson.ToJson(writerSettings: settings); 22 | 23 | return json; 24 | } 25 | 26 | public static BsonDocument ToBsonDocument( 27 | this FilterDefinition filter) 28 | { 29 | IBsonSerializerRegistry serializerRegistry = 30 | BsonSerializer.SerializerRegistry; 31 | 32 | return filter.Render(new RenderArgs(serializerRegistry 33 | .GetSerializer(), serializerRegistry)); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Prime.Extensions/FindFluentExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Driver; 3 | 4 | namespace MongoDB.Prime.Extensions; 5 | 6 | public static class FindFluentExtensions 7 | { 8 | [Obsolete("Depreceated, use ToQueryString instead.")] 9 | public static string PrintQuery( 10 | this IFindFluent findFluent) 11 | { 12 | return findFluent.ToString() ?? string.Empty; 13 | } 14 | 15 | public static string ToQueryString( 16 | this IFindFluent findFluent) 17 | { 18 | return findFluent!.ToString() ?? string.Empty; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Prime.Extensions/Prime.Extensions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MongoDB.Prime.Extensions 6 | MongoDB.Prime.Extensions 7 | MongoDB.Prime.Extensions 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Prime.Extensions/ProfileLevel.cs: -------------------------------------------------------------------------------- 1 | namespace MongoDB.Prime.Extensions 2 | { 3 | public enum ProfileLevel 4 | { 5 | Off = 0, 6 | SlowOperationsOnly = 1, 7 | All = 2 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Prime.Extensions/ProfilingStatus.cs: -------------------------------------------------------------------------------- 1 | namespace MongoDB.Prime.Extensions 2 | { 3 | public class ProfilingStatus 4 | { 5 | public ProfilingStatus( 6 | ProfileLevel level, 7 | int slowMs, 8 | double sampleRate, 9 | string filter) 10 | { 11 | Level = level; 12 | SlowMs = slowMs; 13 | SampleRate = sampleRate; 14 | Filter = filter; 15 | } 16 | 17 | public ProfileLevel Level { get; } 18 | 19 | public int SlowMs { get; } 20 | 21 | public double SampleRate { get; } 22 | 23 | public string Filter { get; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Prime.Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.Json; 6 | 7 | namespace MongoDB.Prime.Extensions 8 | { 9 | public static class StringExtensions 10 | { 11 | public static string ToJsonArray(this IEnumerable jsonArray) 12 | { 13 | return string.Concat("[", string.Join(",", jsonArray), "]"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Prime.Extensions/UpdateDefinitionExtensions.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson.IO; 2 | using MongoDB.Bson; 3 | using MongoDB.Driver; 4 | using MongoDB.Bson.Serialization; 5 | 6 | namespace MongoDB.Prime.Extensions 7 | { 8 | public static class UpdateDefinitionExtensions 9 | { 10 | public static string ToDefinitionString( 11 | this UpdateDefinition filter, bool indent = false) 12 | { 13 | BsonValue bson = ToBsonValue(filter); 14 | 15 | var settings = new JsonWriterSettings 16 | { 17 | Indent = indent, 18 | OutputMode = JsonOutputMode.Shell, 19 | }; 20 | 21 | string json = bson.ToJson(writerSettings: settings); 22 | 23 | return json; 24 | } 25 | 26 | public static BsonValue ToBsonValue( 27 | this UpdateDefinition filter) 28 | { 29 | IBsonSerializerRegistry serializerRegistry = 30 | BsonSerializer.SerializerRegistry; 31 | 32 | return filter.Render(new RenderArgs(serializerRegistry 33 | .GetSerializer(), serializerRegistry)); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ResourceProject.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(ResourceProjectTargetFrameworks) 5 | true 6 | 10.0 7 | enable 8 | 9 | 10 | 11 | portable 12 | true 13 | 14 | 15 | 16 | pdbonly 17 | true 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Session.Tests/Session.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MongoDB.Extensions.Session.Tests 6 | MongoDB.Extensions.Session.Tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Session.Tests/__snapshots__/MongoServerExtensionsTests.GiveSession_WhenRefresh_ThenOkResult.snap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "ok", 4 | "Value": 1.0 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /src/Session/ClientSessionHandleExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Driver; 3 | 4 | namespace MongoDB.Extensions.Session 5 | { 6 | public static class ClientSessionHandleExtensions 7 | { 8 | [Obsolete("This method has been moved to MongoDB.Prime.Extensions")] 9 | public static Guid GetSessionId(this IClientSessionHandle clientSessionHandle) 10 | { 11 | return clientSessionHandle.ServerSession.Id["id"].AsGuid; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Session/ISession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace MongoDB.Extensions.Session; 6 | 7 | public interface ISession : IDisposable 8 | { 9 | Task WithTransactionAsync( 10 | Func> action, 11 | CancellationToken cancellationToken); 12 | 13 | ITransactionSession StartTransaction( 14 | CancellationToken cancellationToken); 15 | } 16 | -------------------------------------------------------------------------------- /src/Session/ISessionProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace MongoDB.Extensions.Session; 5 | 6 | public interface ISessionProvider 7 | { 8 | Task BeginTransactionAsync( 9 | CancellationToken cancellationToken); 10 | 11 | Task StartSessionAsync( 12 | CancellationToken cancellationToken); 13 | } 14 | -------------------------------------------------------------------------------- /src/Session/ITransactionSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace MongoDB.Extensions.Session; 5 | 6 | public interface ITransactionSession : IDisposable 7 | { 8 | Task CommitAsync(); 9 | } 10 | -------------------------------------------------------------------------------- /src/Session/Internal/MongoSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MongoDB.Driver; 5 | 6 | namespace MongoDB.Extensions.Session; 7 | 8 | internal sealed class MongoSession : ISession 9 | { 10 | private readonly TransactionOptions _transactionOptions; 11 | private bool _disposed; 12 | 13 | public MongoSession( 14 | IClientSessionHandle clientSession, 15 | TransactionOptions transactionOptions) 16 | { 17 | _transactionOptions = transactionOptions; 18 | Session = clientSession; 19 | } 20 | 21 | public IClientSessionHandle Session { get; } 22 | 23 | public Task WithTransactionAsync( 24 | Func> action, 25 | CancellationToken cancellationToken) 26 | { 27 | return Session.WithTransactionAsync( 28 | (_, ct) => action(this, ct), _transactionOptions, cancellationToken); 29 | } 30 | 31 | public ITransactionSession StartTransaction( 32 | CancellationToken cancellationToken) 33 | { 34 | Session.StartTransaction(_transactionOptions); 35 | 36 | return new MongoTransactionSession(Session, cancellationToken); 37 | } 38 | 39 | private void Dispose(bool disposing) 40 | { 41 | if (!_disposed) 42 | { 43 | if (disposing) 44 | { 45 | Session.Dispose(); 46 | } 47 | 48 | _disposed = true; 49 | } 50 | } 51 | 52 | public void Dispose() 53 | { 54 | Dispose(true); 55 | GC.SuppressFinalize(this); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Session/Internal/MongoSessionProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MongoDB.Driver; 5 | using MongoDB.Extensions.Context; 6 | 7 | namespace MongoDB.Extensions.Session; 8 | 9 | public class MongoSessionProvider : ISessionProvider 10 | where TContext : IMongoDbContext 11 | { 12 | private readonly IMongoClient _mongoClient; 13 | 14 | public MongoSessionProvider(TContext context) 15 | { 16 | _mongoClient = context.Client; 17 | } 18 | 19 | protected virtual TransactionOptions TransactionOptions { get; } = new( 20 | ReadConcern.Majority, 21 | ReadPreference.Primary, 22 | WriteConcern.WMajority.With(journal: true), 23 | TimeSpan.FromSeconds(180)); 24 | 25 | public async Task BeginTransactionAsync( 26 | CancellationToken cancellationToken) 27 | { 28 | IClientSessionHandle clientSession = await _mongoClient 29 | .StartSessionAsync(cancellationToken: cancellationToken); 30 | 31 | clientSession.StartTransaction(TransactionOptions); 32 | 33 | return new MongoTransactionSession(clientSession, cancellationToken); 34 | } 35 | 36 | public async Task StartSessionAsync( 37 | CancellationToken cancellationToken) 38 | { 39 | IClientSessionHandle clientSession = await _mongoClient 40 | .StartSessionAsync(cancellationToken: cancellationToken); 41 | 42 | return new MongoSession(clientSession, TransactionOptions); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Session/Internal/MongoTransactionSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MongoDB.Driver; 5 | 6 | namespace MongoDB.Extensions.Session; 7 | 8 | internal sealed class MongoTransactionSession : ITransactionSession 9 | { 10 | private readonly CancellationToken _cancellationToken; 11 | private bool _disposed; 12 | 13 | public MongoTransactionSession( 14 | IClientSessionHandle clientSession, 15 | CancellationToken cancellationToken) 16 | { 17 | Session = clientSession; 18 | _cancellationToken = cancellationToken; 19 | } 20 | 21 | public IClientSessionHandle Session { get; } 22 | 23 | public async Task CommitAsync() 24 | { 25 | await Session.CommitTransactionAsync(_cancellationToken); 26 | } 27 | 28 | private void Dispose(bool disposing) 29 | { 30 | if (!_disposed) 31 | { 32 | if (disposing) 33 | { 34 | Session.Dispose(); 35 | } 36 | 37 | _disposed = true; 38 | } 39 | } 40 | 41 | public void Dispose() 42 | { 43 | Dispose(true); 44 | GC.SuppressFinalize(this); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Session/Models/RefreshSession.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson.Serialization.Attributes; 2 | 3 | namespace MongoDB.Extensions.Session 4 | { 5 | internal class RefreshSession 6 | { 7 | internal RefreshSession(params SessionId[] sessionIds) 8 | { 9 | SessionIds = sessionIds; 10 | } 11 | 12 | [BsonElement("refreshSessions")] 13 | public SessionId[] SessionIds { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Session/Models/SessionId.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Bson; 3 | using MongoDB.Bson.Serialization.Attributes; 4 | 5 | namespace MongoDB.Extensions.Session 6 | { 7 | [BsonNoId] 8 | internal readonly struct SessionId 9 | { 10 | internal SessionId(Guid id) 11 | { 12 | Id = id; 13 | } 14 | 15 | [BsonId] 16 | [BsonElement("id")] 17 | [BsonGuidRepresentation(GuidRepresentation.Standard)] 18 | public Guid Id { get; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Session/MongoServerSessionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using MongoDB.Bson; 7 | using MongoDB.Driver; 8 | 9 | namespace MongoDB.Extensions.Session 10 | { 11 | public static class MongoServerSessionExtensions 12 | { 13 | public static BsonDocument RefreshSession( 14 | this IMongoClient mongoClient, 15 | Guid sessionId) 16 | { 17 | return mongoClient.RefreshSessions(new[] { sessionId }); 18 | } 19 | 20 | public static BsonDocument RefreshSessions( 21 | this IMongoClient mongoClient, 22 | IEnumerable sessionIds) 23 | { 24 | ObjectCommand command = CreateRefreshCommand(sessionIds); 25 | 26 | return mongoClient 27 | .GetDatabase("admin") 28 | .RunCommand(command); 29 | } 30 | 31 | public static Task RefreshSessionAsync( 32 | this IMongoClient mongoClient, 33 | Guid sessionId, 34 | CancellationToken cancellationToken) 35 | { 36 | return mongoClient 37 | .RefreshSessionsAsync(new []{ sessionId }, cancellationToken); 38 | } 39 | 40 | public static Task RefreshSessionsAsync( 41 | this IMongoClient mongoClient, 42 | IEnumerable sessionIds, 43 | CancellationToken cancellationToken) 44 | { 45 | ObjectCommand command = CreateRefreshCommand(sessionIds); 46 | 47 | return mongoClient 48 | .GetDatabase("admin") 49 | .RunCommandAsync(command, cancellationToken: cancellationToken); 50 | } 51 | 52 | private static ObjectCommand CreateRefreshCommand( 53 | IEnumerable sessionIds) 54 | { 55 | SessionId[] sessions = sessionIds.Select(id => new SessionId(id)).ToArray(); 56 | var refreshSession = new RefreshSession(sessions); 57 | 58 | return new ObjectCommand(refreshSession); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Session/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.DependencyInjection.Extensions; 3 | using MongoDB.Extensions.Context; 4 | 5 | namespace MongoDB.Extensions.Session; 6 | 7 | public static class ServiceCollectionExtensions 8 | { 9 | public static IServiceCollection AddMongoSessionProvider( 10 | this IServiceCollection services) 11 | where TContext : class, IMongoDbContext 12 | { 13 | services.TryAddSingleton(); 14 | services.TryAddSingleton, MongoSessionProvider>(); 15 | 16 | return services; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Session/Session.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MongoDB.Extensions.Session 6 | MongoDB.Extensions.Session 7 | MongoDB.Extensions.Session 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Session/TransactionSessionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Driver; 3 | 4 | namespace MongoDB.Extensions.Session; 5 | 6 | public static class TransactionSessionExtensions 7 | { 8 | public static IClientSessionHandle GetSessionHandle( 9 | this ITransactionSession session) 10 | { 11 | if (session is MongoTransactionSession mongoTransactionSession) 12 | { 13 | return mongoTransactionSession.Session; 14 | } 15 | 16 | throw new InvalidOperationException( 17 | $"Unknown session type {session.GetType().Name}"); 18 | } 19 | 20 | public static IClientSessionHandle GetSessionHandle( 21 | this ISession session) 22 | { 23 | if (session is MongoSession mongoSession) 24 | { 25 | return mongoSession.Session; 26 | } 27 | 28 | throw new InvalidOperationException( 29 | $"Unknown session type {session.GetType().Name}"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/TestProject.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(TestProjectTargetFrameworks) 5 | false 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Transactions.Tests/Transactions.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MongoDB.Extensions.Transactions.Tests 6 | MongoDB.Extensions.Transactions.Tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Transactions.Tests/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Bson; 3 | using MongoDB.Bson.Serialization.Attributes; 4 | 5 | namespace MongoDB.Extensions.Transactions.Tests 6 | { 7 | public class User 8 | { 9 | public User(Guid id, string name) 10 | { 11 | Id = id; 12 | Name = name; 13 | } 14 | 15 | [BsonGuidRepresentation(GuidRepresentation.CSharpLegacy)] 16 | public Guid Id { get; } 17 | 18 | public string Name { get; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Transactions/MongoDbEnlistmentScope.cs: -------------------------------------------------------------------------------- 1 | using System.Transactions; 2 | using MongoDB.Driver; 3 | 4 | namespace MongoDB.Extensions.Transactions; 5 | 6 | public class MongoDbEnlistmentScope : IEnlistmentNotification 7 | { 8 | public delegate void Unregister(); 9 | 10 | private readonly Unregister _unregister; 11 | private readonly IClientSessionHandle _sessionHandle; 12 | 13 | public MongoDbEnlistmentScope( 14 | IClientSessionHandle sessionHandle, 15 | Unregister unregister) 16 | { 17 | _sessionHandle = sessionHandle; 18 | _unregister = unregister; 19 | } 20 | 21 | public void Commit(Enlistment enlistment) 22 | { 23 | try 24 | { 25 | _sessionHandle.CommitTransaction(); 26 | enlistment.Done(); 27 | } 28 | finally 29 | { 30 | _unregister(); 31 | } 32 | } 33 | 34 | public void InDoubt(Enlistment enlistment) 35 | { 36 | try 37 | { 38 | _sessionHandle.AbortTransaction(); 39 | enlistment.Done(); 40 | } 41 | finally 42 | { 43 | _unregister(); 44 | } 45 | } 46 | 47 | public void Prepare(PreparingEnlistment preparingEnlistment) 48 | { 49 | preparingEnlistment.Prepared(); 50 | } 51 | 52 | public void Rollback(Enlistment enlistment) 53 | { 54 | try 55 | { 56 | _sessionHandle.AbortTransaction(); 57 | enlistment.Done(); 58 | } 59 | finally 60 | { 61 | _unregister(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Transactions/MongoTransactionFilteredCollection.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | 3 | namespace MongoDB.Extensions.Transactions; 4 | 5 | public class MongoTransactionFilteredCollection 6 | : MongoTransactionCollection 7 | , IFilteredMongoCollection 8 | { 9 | private readonly IFilteredMongoCollection _filteredCollection; 10 | 11 | public MongoTransactionFilteredCollection( 12 | IFilteredMongoCollection filteredCollection) 13 | : base(filteredCollection) 14 | { 15 | _filteredCollection = filteredCollection; 16 | } 17 | 18 | public MongoTransactionFilteredCollection( 19 | IFilteredMongoCollection filteredCollection, 20 | IClientSessionHandle clientSessionHandle) 21 | : base(filteredCollection, clientSessionHandle) 22 | { 23 | _filteredCollection = filteredCollection; 24 | } 25 | 26 | public FilterDefinition Filter => _filteredCollection.Filter; 27 | } 28 | -------------------------------------------------------------------------------- /src/Transactions/TransactionClientExtensions.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | 3 | namespace MongoDB.Extensions.Transactions; 4 | 5 | public static class TransactionClientExtensions 6 | { 7 | public static IMongoClient AsTransactionClient( 8 | this IMongoClient collection) 9 | { 10 | return new MongoTransactionClient(collection); 11 | } 12 | 13 | public static IMongoClient AsTransactionClient( 14 | this IMongoClient collection, 15 | IClientSessionHandle clientSessionHandle) 16 | { 17 | return new MongoTransactionClient(collection, clientSessionHandle); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Transactions/TransactionCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | 3 | namespace MongoDB.Extensions.Transactions; 4 | 5 | public static class TransactionCollectionExtensions 6 | { 7 | public static IMongoCollection AsTransactionCollection( 8 | this IMongoCollection collection) 9 | { 10 | return new MongoTransactionCollection(collection); 11 | } 12 | 13 | public static IMongoCollection AsTransactionCollection( 14 | this IMongoCollection collection, 15 | IClientSessionHandle clientSessionHandle) 16 | { 17 | return new MongoTransactionCollection(collection, clientSessionHandle); 18 | } 19 | 20 | public static IFilteredMongoCollection AsTransactionCollection( 21 | this IFilteredMongoCollection collection) 22 | { 23 | return new MongoTransactionFilteredCollection(collection); 24 | } 25 | 26 | public static IFilteredMongoCollection AsTransactionCollection( 27 | this IFilteredMongoCollection collection, 28 | IClientSessionHandle clientSessionHandle) 29 | { 30 | return new MongoTransactionFilteredCollection(collection, clientSessionHandle); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Transactions/TransactionDatabaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | 3 | namespace MongoDB.Extensions.Transactions; 4 | 5 | public static class TransactionDatabaseExtensions 6 | { 7 | public static IMongoDatabase AsTransactionDatabase( 8 | this IMongoDatabase collection) 9 | { 10 | return new MongoTransactionDatabase(collection); 11 | } 12 | 13 | public static IMongoDatabase AsTransactionDatabase( 14 | this IMongoDatabase collection, 15 | IClientSessionHandle clientSessionHandle) 16 | { 17 | return new MongoTransactionDatabase(collection, clientSessionHandle); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Transactions/TransactionStore.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Transactions; 3 | using MongoDB.Driver; 4 | 5 | namespace MongoDB.Extensions.Transactions; 6 | 7 | internal static class TransactionStore 8 | { 9 | private static readonly ConcurrentDictionary 10 | Sessions = new(); 11 | 12 | public static bool TryGetSession( 13 | IMongoClient client, 14 | out IClientSessionHandle sessionHandle) 15 | { 16 | if (Transaction.Current?.TransactionInformation.LocalIdentifier is { } id) 17 | { 18 | sessionHandle = GetOrCreateTransaction(client, id); 19 | return true; 20 | } 21 | 22 | sessionHandle = null!; 23 | return false; 24 | } 25 | 26 | private static IClientSessionHandle GetOrCreateTransaction( 27 | IMongoClient mongoClient, 28 | string id) 29 | { 30 | return Sessions.GetOrAdd(id, CreateAndRegister); 31 | 32 | IClientSessionHandle CreateAndRegister(string idToRegister) 33 | { 34 | if (Transaction.Current is null) 35 | { 36 | throw new TransactionException( 37 | "Cannot open a transaction without a valid scope"); 38 | } 39 | 40 | IClientSessionHandle? session = mongoClient.StartSession(); 41 | session.StartTransaction(); 42 | MongoDbEnlistmentScope enlistment = new(session, Unregister); 43 | 44 | Transaction.Current.EnlistVolatile(enlistment, EnlistmentOptions.None); 45 | 46 | return session; 47 | 48 | void Unregister() 49 | { 50 | if (Sessions.TryRemove(idToRegister, out session)) 51 | { 52 | session.Dispose(); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Transactions/Transactions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MongoDB.Extensions.Transactions 6 | MongoDB.Extensions.Transactions 7 | MongoDB.Extensions.Transactions 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0.0.0 5 | 6 | 7 | --------------------------------------------------------------------------------