Repository: BlueshiftSoftware/EntityFrameworkCore Branch: develop Commit: 69cfc1c3772f Files: 200 Total size: 700.6 KB Directory structure: gitextract_yaxh7ovu/ ├── .dockerignore ├── .gitattributes ├── .gitignore ├── .travis.yml ├── Blueshift.EntityFrameworkCore.sln ├── Blueshift.EntityFrameworkCore.sln.DotSettings ├── Directory.Build.props ├── Directory.Build.targets ├── LICENSE.txt ├── NuGet.config ├── README.md ├── appveyor.yml ├── build/ │ ├── dependencies.props │ ├── repo.beforecommon.props │ ├── repo.props │ ├── repo.targets │ └── sources.props ├── build.cmd ├── build.sh ├── docker-compose.dcproj ├── docker-compose.yml ├── korebuild-lock.txt ├── korebuild.json ├── run.cmd ├── run.ps1 ├── run.sh ├── src/ │ ├── Blueshift.EntityFrameworkCore.MongoDB/ │ │ ├── Adapter/ │ │ │ ├── Conventions/ │ │ │ │ ├── AbstractBaseClassConvention.cs │ │ │ │ ├── BsonClassMapAttributeConvention.cs │ │ │ │ ├── BsonMemberMapAttributeConvention.cs │ │ │ │ ├── IgnoreEmptyEnumerablesConvention.cs │ │ │ │ ├── IgnoreNullOrEmptyStringsConvention.cs │ │ │ │ ├── KeyAttributeConvention.cs │ │ │ │ ├── NavigationSrializationMemberMapConvention.cs │ │ │ │ └── NotMappedAttributeConvention.cs │ │ │ ├── EntityFrameworkConventionPack.cs │ │ │ ├── Serialization/ │ │ │ │ ├── BsonSerializerExtensions.cs │ │ │ │ ├── DenormalizingBsonClassMapSerializer.cs │ │ │ │ └── NavigationBsonMemberMapSerializer.cs │ │ │ └── Update/ │ │ │ ├── DeleteOneModelFactory.cs │ │ │ ├── IMongoDbWriteModelFactory.cs │ │ │ ├── IMongoDbWriteModelFactoryCache.cs │ │ │ ├── IMongoDbWriteModelFactorySelector.cs │ │ │ ├── InsertOneModelFactory.cs │ │ │ ├── MongoDbWriteModelFactory.cs │ │ │ ├── MongoDbWriteModelFactoryCache.cs │ │ │ ├── MongoDbWriteModelFactorySelector.cs │ │ │ └── ReplaceOneModelFactory.cs │ │ ├── Annotations/ │ │ │ ├── DenormalizeAttribute.cs │ │ │ ├── MongoCollectionAttribute.cs │ │ │ └── MongoDatabaseAttribute.cs │ │ ├── Blueshift.EntityFrameworkCore.MongoDB.csproj │ │ ├── ChangeTracking/ │ │ │ └── MongoDbInternalEntityEntryFactory.cs │ │ ├── DbContextOptionsExtensions.cs │ │ ├── DependencyInjection/ │ │ │ └── MongoDbEfServiceCollectionExtensions.cs │ │ ├── Infrastructure/ │ │ │ ├── EntityFrameworkMongoDbServicesBuilder.cs │ │ │ ├── MongoDbContextOptionsBuilder.cs │ │ │ ├── MongoDbContextOptionsBuilderExtensions.cs │ │ │ ├── MongoDbModelValidator.cs │ │ │ └── MongoDbOptionsExtension.cs │ │ ├── ListExtensions.cs │ │ ├── Metadata/ │ │ │ ├── Builders/ │ │ │ │ ├── DocumentDbInternalMetadataBuilderExtensions.cs │ │ │ │ ├── DocumentEntityTypeBuilderExtensions.cs │ │ │ │ ├── DocumentInternalKeyBuilderExtensions.cs │ │ │ │ ├── MongoDbConventionSetBuilder.cs │ │ │ │ ├── MongoDbConventionSetBuilderDependencies.cs │ │ │ │ ├── MongoDbEntityTypeBuilderExtensions.cs │ │ │ │ ├── MongoDbInternalMetadataBuilderExtensions.cs │ │ │ │ └── MongoDbModelBuilderExtensions.cs │ │ │ ├── Conventions/ │ │ │ │ ├── BsonDiscriminatorAttributeConvention.cs │ │ │ │ ├── BsonIgnoreAttributeConvention.cs │ │ │ │ ├── BsonKnownTypesAttributeConvention.cs │ │ │ │ ├── BsonRequiredAttributeConvention.cs │ │ │ │ ├── DocumentPropertyMappingValidationConvention.cs │ │ │ │ ├── MongoCollectionAttributeConvention.cs │ │ │ │ ├── MongoDatabaseConvention.cs │ │ │ │ ├── MongoDbDatabaseGeneratedAttributeConvention.cs │ │ │ │ ├── MongoDbKeyAttributeConvention.cs │ │ │ │ ├── MongoDbRelationshipDiscoveryConvention.cs │ │ │ │ └── OwnedDocumentConvention.cs │ │ │ ├── DocumentAnnotationNames.cs │ │ │ ├── DocumentAnnotations.cs │ │ │ ├── DocumentEntityTypeAnnotations.cs │ │ │ ├── DocumentKeyAnnotations.cs │ │ │ ├── MongoDbAnnotationNames.cs │ │ │ ├── MongoDbEntityTypeAnnotations.cs │ │ │ └── MongoDbModelAnnotations.cs │ │ ├── MethodHelper.cs │ │ ├── MongoDbUtilities.cs │ │ ├── ObjectIdTypeConverter.cs │ │ ├── Properties/ │ │ │ ├── AssemblyInfo.cs │ │ │ ├── Blueshift.EntityFrameworkCore.MongoDB.rd.xml │ │ │ ├── DocumentDbStrings.Designer.cs │ │ │ ├── DocumentDbStrings.cs │ │ │ ├── DocumentDbStrings.resx │ │ │ └── DocumentDbStrings.tt │ │ ├── Query/ │ │ │ ├── EntityLoadInfoFactory.cs │ │ │ ├── ExpressionVisitors/ │ │ │ │ ├── DocumentNavigationRewritingExpressionVisitor.cs │ │ │ │ ├── DocumentNavigationRewritingExpressionVisitorFactory.cs │ │ │ │ ├── IMongoDbDenormalizedCollectionCompensatingVisitorFactory.cs │ │ │ │ ├── MongoDbDenormalizedCollectionCompensatingVisitor.cs │ │ │ │ ├── MongoDbDenormalizedCollectionCompensatingVisitorFactory.cs │ │ │ │ ├── MongoDbEntityQueryableExpressionVisitor.cs │ │ │ │ ├── MongoDbEntityQueryableExpressionVisitorFactory.cs │ │ │ │ ├── MongoDbMemberAccessBindingExpressionVisitor.cs │ │ │ │ └── MongoDbMemberAccessBindingExpressionVisitorFactory.cs │ │ │ ├── Expressions/ │ │ │ │ ├── DocumentQueryExpression.cs │ │ │ │ ├── IDocumentQueryExpressionFactory.cs │ │ │ │ └── MongoDbDocumentQueryExpressionFactory.cs │ │ │ ├── IEntityLoadInfoFactory.cs │ │ │ ├── IValueBufferFactory.cs │ │ │ ├── LinqQueryCompilationContextFactory.cs │ │ │ ├── MongoDbEntityQueryModelVisitor.cs │ │ │ ├── MongoDbEntityQueryModelVisitorDependencies.cs │ │ │ ├── MongoDbEntityQueryModelVisitorFactory.cs │ │ │ ├── MongoDbQueryBuffer.cs │ │ │ ├── MongoDbQueryContext.cs │ │ │ ├── MongoDbQueryContextFactory.cs │ │ │ ├── QueryableLinqOperatorProvider.cs │ │ │ └── ValueBufferFactory.cs │ │ ├── Storage/ │ │ │ ├── IMongoDbConnection.cs │ │ │ ├── IMongoDbTypeMappingSource.cs │ │ │ ├── MongoDbConnection.cs │ │ │ ├── MongoDbDatabase.cs │ │ │ ├── MongoDbDatabaseCreator.cs │ │ │ └── MongoDbTypeMappingSource.cs │ │ └── ValueGeneration/ │ │ ├── HashCodeValueGenerator.cs │ │ ├── IntegerValueGenerator.cs │ │ ├── MongoDbValueGeneratorSelector.cs │ │ └── ObjectIdValueGenerator.cs │ ├── Blueshift.EntityFrameworkCore.MongoDB.SampleDomain/ │ │ ├── Blueshift.EntityFrameworkCore.MongoDB.SampleDomain.csproj │ │ ├── ZooDbContext.cs │ │ ├── ZooDbDependencyInjection.cs │ │ ├── ZooEntityFixture.cs │ │ └── _Comparers.cs │ ├── Blueshift.Identity.MongoDB/ │ │ ├── Blueshift.Identity.MongoDB.csproj │ │ ├── DependencyInjection/ │ │ │ └── IdentityEntityFrameworkMongoDbBuilderExtensions.cs │ │ ├── IdentityMongoDbContext.cs │ │ ├── MongoDbIdentityClaim.cs │ │ ├── MongoDbIdentityRole.cs │ │ ├── MongoDbIdentityUser.cs │ │ ├── MongoDbIdentityUserLogin.cs │ │ ├── MongoDbIdentityUserRole.cs │ │ ├── MongoDbIdentityUserToken.cs │ │ ├── MongoDbRoleStore.cs │ │ └── MongoDbUserStore.cs │ ├── Directory.Build.props │ └── Shared/ │ ├── Check.cs │ ├── CodeAnnotations.cs │ ├── MemberInfoExtensions.cs │ ├── PropertyInfoExtensions.cs │ ├── SharedTypeExtensions.cs │ └── StringBuilderExtensions.cs ├── test/ │ ├── Blueshift.EntityFrameworkCore.MongoDB.Tests/ │ │ ├── Adapter/ │ │ │ ├── Conventions/ │ │ │ │ ├── AbstractClassConventionTest.cs │ │ │ │ ├── IgnoreEmptyEnumerablesConventionTests.cs │ │ │ │ ├── IgnoreNullOrEmptyStringsConventionTests.cs │ │ │ │ └── KeyAttributeConventionTests.cs │ │ │ ├── EntityFrameworkConventionPackTests.cs │ │ │ ├── Serialization/ │ │ │ │ ├── BsonSerializerExtensionsTests.cs │ │ │ │ └── DenormalizingBsonClassMapSerializerTests.cs │ │ │ └── Update/ │ │ │ └── MongoDbWriteModelFactoryTests.cs │ │ ├── ApiConsistencyTest.cs │ │ ├── Blueshift.EntityFrameworkCore.MongoDB.Tests.csproj │ │ ├── Metadata/ │ │ │ ├── Conventions/ │ │ │ │ └── MongoDatabaseConventionTests.cs │ │ │ ├── MongoDbEntityTypeAnnotationsTests.cs │ │ │ └── MongoDbModelAnnotationsTests.cs │ │ ├── MongoDbContextTestBase.cs │ │ ├── MongoDbContextTests.cs │ │ ├── MongoDbUtilitiesTests.cs │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── Storage/ │ │ │ ├── MongoDbConnectionTests.cs │ │ │ ├── MongoDbDatabaseCreatorTests.cs │ │ │ ├── MongoDbDatabaseTests.cs │ │ │ └── MongoDbTypeMappingSourceTests.cs │ │ ├── ValueGeneration/ │ │ │ ├── MongoDbValueGeneratorSelectorTests.cs │ │ │ └── ObjectIdValueGeneratorTests.cs │ │ └── xunit.runner.json │ ├── Blueshift.Identity.MongoDB.Tests/ │ │ ├── Blueshift.Identity.MongoDB.Tests.csproj │ │ ├── MongoDbIdentityFixture.cs │ │ ├── MongoDbIdentityTestBase.cs │ │ ├── MongoDbIdentityTestCollection.cs │ │ ├── MongoDbRoleClaimStoreTests.cs │ │ ├── MongoDbRoleStoreTests.cs │ │ ├── MongoDbUserAuthenticationTokenStoreTests.cs │ │ ├── MongoDbUserAuthenticatorKeyStoreTests.cs │ │ ├── MongoDbUserClaimStoreTests.cs │ │ ├── MongoDbUserEmailStoreTests.cs │ │ ├── MongoDbUserLockoutStoreTests.cs │ │ ├── MongoDbUserLoginStoreTests.cs │ │ ├── MongoDbUserPasswordStoreTests.cs │ │ ├── MongoDbUserPhoneNumberStoreTests.cs │ │ ├── MongoDbUserRoleStoreTests.cs │ │ ├── MongoDbUserSecurityStampStoreTests.cs │ │ ├── MongoDbUserStoreTests.cs │ │ ├── MongoDbUserTwoFactorRecoveryCodeStoreTests.cs │ │ ├── MongoDbUserTwoFactorStoreTests.cs │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── _Comparers.cs │ │ └── xunit.runner.json │ └── Directory.Build.props └── version.props ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ .dockerignore .env .git .gitignore .vs .vscode docker-compose.yml docker-compose.*.yml */bin */obj ================================================ FILE: .gitattributes ================================================ * text=auto *.sh text eol=lf ================================================ FILE: .gitignore ================================================ /.build/ /global.json QueryBaseline.cs ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo *.user *.userosscache *.sln.docstates *.user.sln* /test.ps1 *.stackdump # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # BenchmarkDotNet Results [Bb]enchmarkDotNet.Artifacts/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # .NET Core project.lock.json project.fragment.lock.json artifacts/ **/Properties/launchSettings.json *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Typescript v1 declaration files typings/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # JetBrains Rider .idea/ *.sln.iml # CodeRush .cr/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs test/Microsoft.EntityFrameworkCore.MongoDB.Tests/.data/ ================================================ FILE: .travis.yml ================================================ language: csharp sudo: false dotnet: 2.1.2 dist: trusty env: global: - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true - DOTNET_CLI_TELEMETRY_OPTOUT: 1 mono: none services: - mongodb os: - linux # - osx osx_image: xcode8.1 addons: apt: packages: - libunwind8 branches: only: - master - release - develop - /^rel\/.*/ - /^(.*\/)?ci-.*$/ script: - ./build.sh ================================================ FILE: Blueshift.EntityFrameworkCore.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27004.2010 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{FAF9C31C-A1AF-42A9-9306-EC190DCACCC7}" ProjectSection(SolutionItems) = preProject build\dependencies.props = build\dependencies.props build\repo.beforecommon.props = build\repo.beforecommon.props build\repo.props = build\repo.props build\repo.targets = build\repo.targets build\sources.props = build\sources.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Globals", "Globals", "{6841D576-C833-4614-89D8-4CF4DECD1CFA}" ProjectSection(SolutionItems) = preProject .travis.yml = .travis.yml appveyor.yml = appveyor.yml build.cmd = build.cmd build.sh = build.sh Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets global.json = global.json korebuild-lock.txt = korebuild-lock.txt korebuild.json = korebuild.json NuGet.config = NuGet.config README.md = README.md run.cmd = run.cmd run.ps1 = run.ps1 run.sh = run.sh version.props = version.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}" ProjectSection(SolutionItems) = preProject src\Directory.Build.props = src\Directory.Build.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{258D5057-81B9-40EC-A872-D21E27452749}" ProjectSection(SolutionItems) = preProject test\Directory.Build.props = test\Directory.Build.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{2B598BE4-9107-4F65-90E7-749F3A41F9D6}" ProjectSection(SolutionItems) = preProject src\Shared\Check.cs = src\Shared\Check.cs src\Shared\CodeAnnotations.cs = src\Shared\CodeAnnotations.cs src\Shared\MemberInfoExtensions.cs = src\Shared\MemberInfoExtensions.cs src\Shared\PropertyInfoExtensions.cs = src\Shared\PropertyInfoExtensions.cs src\Shared\SharedTypeExtensions.cs = src\Shared\SharedTypeExtensions.cs src\Shared\StringBuilderExtensions.cs = src\Shared\StringBuilderExtensions.cs EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blueshift.EntityFrameworkCore.MongoDB", "src\Blueshift.EntityFrameworkCore.MongoDB\Blueshift.EntityFrameworkCore.MongoDB.csproj", "{E0841FBC-A266-41BF-AA5F-4514692D2161}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blueshift.EntityFrameworkCore.MongoDB.SampleDomain", "src\Blueshift.EntityFrameworkCore.MongoDB.SampleDomain\Blueshift.EntityFrameworkCore.MongoDB.SampleDomain.csproj", "{169261F6-0B97-47A5-98B8-E6AA084FF6A4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blueshift.EntityFrameworkCore.MongoDB.Tests", "test\Blueshift.EntityFrameworkCore.MongoDB.Tests\Blueshift.EntityFrameworkCore.MongoDB.Tests.csproj", "{314B80D1-CBB7-4EF5-A5BD-3AD9F14069E8}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blueshift.Identity.MongoDB", "src\Blueshift.Identity.MongoDB\Blueshift.Identity.MongoDB.csproj", "{85A1B2BB-BF46-445A-85C3-CFB1CCCB33CE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blueshift.Identity.MongoDB.Tests", "test\Blueshift.Identity.MongoDB.Tests\Blueshift.Identity.MongoDB.Tests.csproj", "{E7AF1BE9-6017-4FDF-BDEC-B3FAC1888A25}" EndProject Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{04CEB217-71E2-4DBD-B35A-737EE7D258B8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {E0841FBC-A266-41BF-AA5F-4514692D2161}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E0841FBC-A266-41BF-AA5F-4514692D2161}.Debug|Any CPU.Build.0 = Debug|Any CPU {E0841FBC-A266-41BF-AA5F-4514692D2161}.Release|Any CPU.ActiveCfg = Release|Any CPU {E0841FBC-A266-41BF-AA5F-4514692D2161}.Release|Any CPU.Build.0 = Release|Any CPU {169261F6-0B97-47A5-98B8-E6AA084FF6A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {169261F6-0B97-47A5-98B8-E6AA084FF6A4}.Debug|Any CPU.Build.0 = Debug|Any CPU {169261F6-0B97-47A5-98B8-E6AA084FF6A4}.Release|Any CPU.ActiveCfg = Release|Any CPU {169261F6-0B97-47A5-98B8-E6AA084FF6A4}.Release|Any CPU.Build.0 = Release|Any CPU {314B80D1-CBB7-4EF5-A5BD-3AD9F14069E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {314B80D1-CBB7-4EF5-A5BD-3AD9F14069E8}.Debug|Any CPU.Build.0 = Debug|Any CPU {314B80D1-CBB7-4EF5-A5BD-3AD9F14069E8}.Release|Any CPU.ActiveCfg = Release|Any CPU {314B80D1-CBB7-4EF5-A5BD-3AD9F14069E8}.Release|Any CPU.Build.0 = Release|Any CPU {85A1B2BB-BF46-445A-85C3-CFB1CCCB33CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {85A1B2BB-BF46-445A-85C3-CFB1CCCB33CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {85A1B2BB-BF46-445A-85C3-CFB1CCCB33CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {85A1B2BB-BF46-445A-85C3-CFB1CCCB33CE}.Release|Any CPU.Build.0 = Release|Any CPU {E7AF1BE9-6017-4FDF-BDEC-B3FAC1888A25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E7AF1BE9-6017-4FDF-BDEC-B3FAC1888A25}.Debug|Any CPU.Build.0 = Debug|Any CPU {E7AF1BE9-6017-4FDF-BDEC-B3FAC1888A25}.Release|Any CPU.ActiveCfg = Release|Any CPU {E7AF1BE9-6017-4FDF-BDEC-B3FAC1888A25}.Release|Any CPU.Build.0 = Release|Any CPU {04CEB217-71E2-4DBD-B35A-737EE7D258B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {04CEB217-71E2-4DBD-B35A-737EE7D258B8}.Debug|Any CPU.Build.0 = Debug|Any CPU {04CEB217-71E2-4DBD-B35A-737EE7D258B8}.Release|Any CPU.ActiveCfg = Release|Any CPU {04CEB217-71E2-4DBD-B35A-737EE7D258B8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {2B598BE4-9107-4F65-90E7-749F3A41F9D6} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC} {E0841FBC-A266-41BF-AA5F-4514692D2161} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC} {169261F6-0B97-47A5-98B8-E6AA084FF6A4} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC} {314B80D1-CBB7-4EF5-A5BD-3AD9F14069E8} = {258D5057-81B9-40EC-A872-D21E27452749} {85A1B2BB-BF46-445A-85C3-CFB1CCCB33CE} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC} {E7AF1BE9-6017-4FDF-BDEC-B3FAC1888A25} = {258D5057-81B9-40EC-A872-D21E27452749} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AA7DD3E1-6124-4D69-A1C3-E4D643A5C4D5} EndGlobalSection EndGlobal ================================================ FILE: Blueshift.EntityFrameworkCore.sln.DotSettings ================================================  True ================================================ FILE: Directory.Build.props ================================================  $([System.DateTime]::Now.ToString('yyyy')) git $(MSBuildThisFileDirectory) https://github.com/crhairr/EntityFrameworkCore.MongoDb.git True Blueshift Software, LLC $(Company) © $(Company) @ $(BuildYear) - all rights reserved. Blueshift;MongoDB;Entity Framework Core;entity-framework-core;EF;Data;O/RM Blueshift Software MongoDB Provider for Entity Framework Core full 7.2 $(NoWarn);CS8032; $(MSBuildProjectName) $(MSBuildProjectName) ================================================ FILE: Directory.Build.targets ================================================  $(MicrosoftNETCoreApp20PackageVersion) $(NETStandardLibrary20PackageVersion) 99.9 ================================================ FILE: LICENSE.txt ================================================ Copyright (c) 2017 Blueshift Software, LLC. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: NuGet.config ================================================  ================================================ FILE: README.md ================================================ This repository has been defunct for some time. Due to a lack of both public interest and general support from the Microsoft Entity Framework team, I have decided to formally end support for the solution and archive the repository. # Document Database Providers for Entity Framework Core Welcome to the home of Document Database (NoSQL) Providers for EntityFrameworkCore! [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/iip86emc94ncp0ao?svg=true&style=flat-square)](https://ci.appveyor.com/project/crhairr/entityframeworkcore-mongodb/) [![Travis CI Build Status](https://travis-ci.org/BlueshiftSoftware/EntityFrameworkCore.svg?branch=develop&label=travis-ci&style=flat-square)](https://travis-ci.org/BlueshiftSoftware/EntityFrameworkCore) This repository currently only contains a MongoDB provider for EF Core. However, there are plans in the current roadmap to expand this with further NoSQL provider offerings. MongoDb is a highly popular No-SQL database solution for storing structured, non-relational document data. This provider enables applications built with EntityFrameworkCore to use MongoDb instances as a backing data store. Find out how to get started by visiting the [Wiki pages](https://github.com/crhairr/EntityFrameworkCore.MongoDb/wiki). Feel free to contribute to this repository with code, comments, wiki entries, and/or issues. The latest release and CI previews builds are available at the EntityFrameworkCore.MongoDb [MyGet Feed](https://www.myget.org/gallery/efcore-mongodb/). ================================================ FILE: appveyor.yml ================================================ init: - git config --global core.autocrlf true clone_depth: 1 test: off services: - mongodb environment: global: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: 1 matrix: fast_finish: true for: - matrix: only: - image: Ubuntu build_script: - sh: chmod +x ./run.sh - sh: ./run.sh default-build - matrix: only: - image: Visual Studio 2017 build_script: - ps: .\run.ps1 default-build artifacts: - path: 'artifacts\build\*.nupkg' name: MyGet deploy: - provider: Environment name: MyGet artifact: 'artifacts\build\*.nupkg' on: APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 image: - Ubuntu - Visual Studio 2017 ================================================ FILE: build/dependencies.props ================================================  $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 2.1.1 2.0.3 2.1.3 2.1.2 2.1.2 2.1.1 2.7.3 15.9.0 4.10.1 2.4.1 2.4.1 2.4.1 ================================================ FILE: build/repo.beforecommon.props ================================================ true ================================================ FILE: build/repo.props ================================================ False true ================================================ FILE: build/repo.targets ================================================ ================================================ FILE: build/sources.props ================================================ $(DotNetRestoreSources) $(RestoreSources); https://dotnet.myget.org/F/dotnet-core/api/v3/index.json; https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json; https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json; https://www.myget.org/F/efcore-mongodb/api/v3/index.json; $(RestoreSources); https://api.nuget.org/v3/index.json; ================================================ FILE: build.cmd ================================================ @ECHO OFF PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' default-build %*; exit $LASTEXITCODE" ================================================ FILE: build.sh ================================================ #!/usr/bin/env bash set -euo pipefail DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # Call "sync" between "chmod" and execution to prevent "text file busy" error in Docker (aufs) chmod +x "$DIR/run.sh"; sync "$DIR/run.sh" default-build "$@" ================================================ FILE: docker-compose.dcproj ================================================ 2.1 Linux 04ceb217-71e2-4dbd-b35a-737ee7d258b8 LaunchBrowser {Scheme}://localhost:{ServicePort} blueshift.authoring ================================================ FILE: docker-compose.yml ================================================ version: '3.4' services: mongo-efcore: image: mongo:4.1.2-xenial restart: always ports: - 27017:27017 mongo-efcore-express: image: mongo-express restart: always ports: - 27027:8081 environment: ME_CONFIG_MONGODB_SERVER: mongo-efcore ================================================ FILE: korebuild-lock.txt ================================================ version:2.1.3-rtm-15802 commithash:a7c08b45b440a7d2058a0aa1eaa3eb6ba811976a ================================================ FILE: korebuild.json ================================================ { "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/release/2.1/tools/korebuild.schema.json", "channel": "release/2.1" } ================================================ FILE: run.cmd ================================================ @ECHO OFF PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE" ================================================ FILE: run.ps1 ================================================ #!/usr/bin/env powershell #requires -version 4 <# .SYNOPSIS Executes KoreBuild commands. .DESCRIPTION Downloads korebuild if required. Then executes the KoreBuild command. To see available commands, execute with `-Command help`. .PARAMETER Command The KoreBuild command to run. .PARAMETER Path The folder to build. Defaults to the folder containing this script. .PARAMETER Channel The channel of KoreBuild to download. Overrides the value from the config file. .PARAMETER DotNetHome The directory where .NET Core tools will be stored. .PARAMETER ToolsSource The base url where build tools can be downloaded. Overrides the value from the config file. .PARAMETER Update Updates KoreBuild to the latest version even if a lock file is present. .PARAMETER Reinstall Re-installs KoreBuild .PARAMETER ConfigFile The path to the configuration file that stores values. Defaults to korebuild.json. .PARAMETER ToolsSourceSuffix The Suffix to append to the end of the ToolsSource. Useful for query strings in blob stores. .PARAMETER CI Sets up CI specific settings and variables. .PARAMETER Arguments Arguments to be passed to the command .NOTES This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be. When the lockfile is not present, KoreBuild will create one using latest available version from $Channel. The $ConfigFile is expected to be an JSON file. It is optional, and the configuration values in it are optional as well. Any options set in the file are overridden by command line parameters. .EXAMPLE Example config file: ```json { "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/dev/tools/korebuild.schema.json", "channel": "dev", "toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools" } ``` #> [CmdletBinding(PositionalBinding = $false)] param( [Parameter(Mandatory = $true, Position = 0)] [string]$Command, [string]$Path = $PSScriptRoot, [Alias('c')] [string]$Channel, [Alias('d')] [string]$DotNetHome, [Alias('s')] [string]$ToolsSource, [Alias('u')] [switch]$Update, [switch]$Reinstall, [string]$ToolsSourceSuffix, [string]$ConfigFile = $null, [switch]$CI, [Parameter(ValueFromRemainingArguments = $true)] [string[]]$Arguments ) Set-StrictMode -Version 2 $ErrorActionPreference = 'Stop' # # Functions # function Get-KoreBuild { $lockFile = Join-Path $Path 'korebuild-lock.txt' if (!(Test-Path $lockFile) -or $Update) { Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile $ToolsSourceSuffix } $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1 if (!$version) { Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'" } $version = $version.TrimStart('version:').Trim() $korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version) if ($Reinstall -and (Test-Path $korebuildPath)) { Remove-Item -Force -Recurse $korebuildPath } if (!(Test-Path $korebuildPath)) { Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version" New-Item -ItemType Directory -Path $korebuildPath | Out-Null $remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip" try { $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip" Get-RemoteFile $remotePath $tmpfile $ToolsSourceSuffix if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) { # Use built-in commands where possible as they are cross-plat compatible Microsoft.PowerShell.Archive\Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath } else { # Fallback to old approach for old installations of PowerShell Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory($tmpfile, $korebuildPath) } } catch { Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore throw } finally { Remove-Item $tmpfile -ErrorAction Ignore } } return $korebuildPath } function Join-Paths([string]$path, [string[]]$childPaths) { $childPaths | ForEach-Object { $path = Join-Path $path $_ } return $path } function Get-RemoteFile([string]$RemotePath, [string]$LocalPath, [string]$RemoteSuffix) { if ($RemotePath -notlike 'http*') { Copy-Item $RemotePath $LocalPath return } $retries = 10 while ($retries -gt 0) { $retries -= 1 try { Invoke-WebRequest -UseBasicParsing -Uri $($RemotePath + $RemoteSuffix) -OutFile $LocalPath return } catch { Write-Verbose "Request failed. $retries retries remaining" } } Write-Error "Download failed: '$RemotePath'." } # # Main # # Load configuration or set defaults $Path = Resolve-Path $Path if (!$ConfigFile) { $ConfigFile = Join-Path $Path 'korebuild.json' } if (Test-Path $ConfigFile) { try { $config = Get-Content -Raw -Encoding UTF8 -Path $ConfigFile | ConvertFrom-Json if ($config) { if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel } if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource} } } catch { Write-Warning "$ConfigFile could not be read. Its settings will be ignored." Write-Warning $Error[0] } } if (!$DotNetHome) { $DotNetHome = if ($env:DOTNET_HOME) { $env:DOTNET_HOME } ` elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dotnet'} ` elseif ($env:HOME) {Join-Path $env:HOME '.dotnet'}` else { Join-Path $PSScriptRoot '.dotnet'} } if (!$Channel) { $Channel = 'dev' } if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' } # Execute $korebuildPath = Get-KoreBuild Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1') try { Set-KoreBuildSettings -ToolsSource $ToolsSource -DotNetHome $DotNetHome -RepoPath $Path -ConfigFile $ConfigFile -CI:$CI Invoke-KoreBuildCommand $Command @Arguments } finally { Remove-Module 'KoreBuild' -ErrorAction Ignore } ================================================ FILE: run.sh ================================================ #!/usr/bin/env bash set -euo pipefail # # variables # RESET="\033[0m" RED="\033[0;31m" YELLOW="\033[0;33m" MAGENTA="\033[0;95m" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" [ -z "${DOTNET_HOME:-}" ] && DOTNET_HOME="$HOME/.dotnet" verbose=false update=false reinstall=false repo_path="$DIR" channel='' tools_source='' tools_source_suffix='' ci=false # # Functions # __usage() { echo "Usage: $(basename "${BASH_SOURCE[0]}") command [options] [[--] ...]" echo "" echo "Arguments:" echo " command The command to be run." echo " ... Arguments passed to the command. Variable number of arguments allowed." echo "" echo "Options:" echo " --verbose Show verbose output." echo " -c|--channel The channel of KoreBuild to download. Overrides the value from the config file.." echo " --config-file The path to the configuration file that stores values. Defaults to korebuild.json." echo " -d|--dotnet-home The directory where .NET Core tools will be stored. Defaults to '\$DOTNET_HOME' or '\$HOME/.dotnet." echo " --path The directory to build. Defaults to the directory containing the script." echo " -s|--tools-source|-ToolsSource The base url where build tools can be downloaded. Overrides the value from the config file." echo " --tools-source-suffix|-ToolsSourceSuffix The suffix to append to tools-source. Useful for query strings." echo " -u|--update Update to the latest KoreBuild even if the lock file is present." echo " --reinstall Reinstall KoreBuild." echo " --ci Apply CI specific settings and environment variables." echo "" echo "Description:" echo " This function will create a file \$DIR/korebuild-lock.txt. This lock file can be committed to source, but does not have to be." echo " When the lockfile is not present, KoreBuild will create one using latest available version from \$channel." if [[ "${1:-}" != '--no-exit' ]]; then exit 2 fi } get_korebuild() { local version local lock_file="$repo_path/korebuild-lock.txt" if [ ! -f "$lock_file" ] || [ "$update" = true ]; then __get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" "$lock_file" "$tools_source_suffix" fi version="$(grep 'version:*' -m 1 "$lock_file")" if [[ "$version" == '' ]]; then __error "Failed to parse version from $lock_file. Expected a line that begins with 'version:'" return 1 fi version="$(echo "${version#version:}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" local korebuild_path="$DOTNET_HOME/buildtools/korebuild/$version" if [ "$reinstall" = true ] && [ -d "$korebuild_path" ]; then rm -rf "$korebuild_path" fi { if [ ! -d "$korebuild_path" ]; then mkdir -p "$korebuild_path" local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip" tmpfile="$(mktemp)" echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}" if __get_remote_file "$remote_path" "$tmpfile" "$tools_source_suffix"; then unzip -q -d "$korebuild_path" "$tmpfile" fi rm "$tmpfile" || true fi source "$korebuild_path/KoreBuild.sh" } || { if [ -d "$korebuild_path" ]; then echo "Cleaning up after failed installation" rm -rf "$korebuild_path" || true fi return 1 } } __error() { echo -e "${RED}error: $*${RESET}" 1>&2 } __warn() { echo -e "${YELLOW}warning: $*${RESET}" } __machine_has() { hash "$1" > /dev/null 2>&1 return $? } __get_remote_file() { local remote_path=$1 local local_path=$2 local remote_path_suffix=$3 if [[ "$remote_path" != 'http'* ]]; then cp "$remote_path" "$local_path" return 0 fi local failed=false if __machine_has wget; then wget --tries 10 --quiet -O "$local_path" "${remote_path}${remote_path_suffix}" || failed=true else failed=true fi if [ "$failed" = true ] && __machine_has curl; then failed=false curl --retry 10 -sSL -f --create-dirs -o "$local_path" "${remote_path}${remote_path_suffix}" || failed=true fi if [ "$failed" = true ]; then __error "Download failed: $remote_path" 1>&2 return 1 fi } # # main # command="${1:-}" shift while [[ $# -gt 0 ]]; do case $1 in -\?|-h|--help) __usage --no-exit exit 0 ;; -c|--channel|-Channel) shift channel="${1:-}" [ -z "$channel" ] && __usage ;; --config-file|-ConfigFile) shift config_file="${1:-}" [ -z "$config_file" ] && __usage if [ ! -f "$config_file" ]; then __error "Invalid value for --config-file. $config_file does not exist." exit 1 fi ;; -d|--dotnet-home|-DotNetHome) shift DOTNET_HOME="${1:-}" [ -z "$DOTNET_HOME" ] && __usage ;; --path|-Path) shift repo_path="${1:-}" [ -z "$repo_path" ] && __usage ;; -s|--tools-source|-ToolsSource) shift tools_source="${1:-}" [ -z "$tools_source" ] && __usage ;; --tools-source-suffix|-ToolsSourceSuffix) shift tools_source_suffix="${1:-}" [ -z "$tools_source_suffix" ] && __usage ;; -u|--update|-Update) update=true ;; --reinstall|-[Rr]einstall) reinstall=true ;; --ci) ci=true ;; --verbose|-Verbose) verbose=true ;; --) shift break ;; *) break ;; esac shift done if ! __machine_has unzip; then __error 'Missing required command: unzip' exit 1 fi if ! __machine_has curl && ! __machine_has wget; then __error 'Missing required command. Either wget or curl is required.' exit 1 fi [ -z "${config_file:-}" ] && config_file="$repo_path/korebuild.json" if [ -f "$config_file" ]; then if __machine_has jq ; then if jq '.' "$config_file" >/dev/null ; then config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")" config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")" else __warn "$config_file is invalid JSON. Its settings will be ignored." fi elif __machine_has python ; then if python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" else __warn "$config_file is invalid JSON. Its settings will be ignored." fi else __warn 'Missing required command: jq or pyton. Could not parse the JSON file. Its settings will be ignored.' fi [ ! -z "${config_channel:-}" ] && channel="$config_channel" [ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source" fi [ -z "$channel" ] && channel='dev' [ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools' get_korebuild set_korebuildsettings "$tools_source" "$DOTNET_HOME" "$repo_path" "$config_file" "$ci" invoke_korebuild_command "$command" "$@" ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Conventions/AbstractBaseClassConvention.cs ================================================ using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Conventions; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Conventions { /// /// /// /// A convention that specifies that a discriminator is required when the given type is abstract. /// public class AbstractBaseClassConvention : BsonClassMapAttributeConvention { /// /// /// Process the conventions on according to the given . /// /// The to which the conventions will be assigned. /// The that defines the convention. protected override void Apply(BsonClassMap classMap, BsonKnownTypesAttribute attribute) { Check.NotNull(classMap, nameof(classMap)); if (!classMap.DiscriminatorIsRequired) { classMap.SetDiscriminatorIsRequired(classMap.ClassType.IsAbstract); } } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Conventions/BsonClassMapAttributeConvention.cs ================================================ using System; using System.Collections.Generic; using System.Reflection; using System.Text.RegularExpressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Conventions; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Conventions { /// /// /// /// Base class for attribute-based convention processing. /// /// The type of attribute to process. public abstract class BsonClassMapAttributeConvention : ConventionBase, IClassMapConvention where TAttribute : Attribute { /// /// /// Initializes a new instance of the . /// protected BsonClassMapAttributeConvention() : base(Regex.Replace(typeof(TAttribute).Name, "Attribute$", "")) { } /// /// /// Processes each defined on the given /// member info and /// /// The to public virtual void Apply(BsonClassMap classMap) { Check.NotNull(classMap, nameof(classMap)); IEnumerable memberMapAttributes = classMap .ClassType .GetCustomAttributes(); foreach (TAttribute attribute in memberMapAttributes) { Apply(classMap, attribute); } } /// /// Process the conventions on according to the given . /// /// The to which the conventions will be assigned. /// The that defines the convention. protected abstract void Apply([NotNull] BsonClassMap classMap, [NotNull] TAttribute attribute); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Conventions/BsonMemberMapAttributeConvention.cs ================================================ using System; using System.Collections.Generic; using System.Reflection; using System.Text.RegularExpressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Conventions; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Conventions { /// /// Base class for attribute-based convention processing. /// /// The type of attribute to process. public abstract class BsonMemberMapAttributeConvention : ConventionBase, IMemberMapConvention where TAttribute : Attribute { /// /// Initializes a new instance of the . /// protected BsonMemberMapAttributeConvention() : base(Regex.Replace(typeof(TAttribute).Name, "Attribute$", "")) { } /// /// Processes each defined on the given /// member info and /// /// The to public virtual void Apply([NotNull] BsonMemberMap memberMap) { Check.NotNull(memberMap, nameof(memberMap)); IEnumerable memberMapAttributes = memberMap.MemberInfo .GetCustomAttributes(); foreach (TAttribute attribute in memberMapAttributes) { Apply(memberMap, attribute); } } /// /// Process the conventions on according to the given . /// /// The to which the conventions will be assigned. /// The that defines the convention. protected abstract void Apply([NotNull] BsonMemberMap memberMap, [NotNull] TAttribute attribute); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Conventions/IgnoreEmptyEnumerablesConvention.cs ================================================ using System; using System.Collections; using System.Text.RegularExpressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Conventions; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Conventions { /// /// A convention that ignores empty instances when serializing Bson documents. /// public class IgnoreEmptyEnumerablesConvention : ConventionBase, IMemberMapConvention { /// /// Initializes a new instance of the class. /// public IgnoreEmptyEnumerablesConvention() : base(Regex.Replace(nameof(IgnoreEmptyEnumerablesConvention), "Convention$", "")) { } /// /// Applies the Ignore Empty Enumerables convention to the given . /// /// The to which the convention will be applied. public virtual void Apply([NotNull] BsonMemberMap memberMap) { Check.NotNull(memberMap, nameof(memberMap)); if (memberMap.MemberType.TryGetSequenceType() != null) { memberMap.SetShouldSerializeMethod(@object => { object value = memberMap.Getter(@object); return (value as IEnumerable)?.GetEnumerator().MoveNext() ?? false; }); } } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Conventions/IgnoreNullOrEmptyStringsConvention.cs ================================================ using System.Text.RegularExpressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Conventions; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Conventions { /// /// Instructs the MongoDb C# driver to ignore null, empty, or default values of properties. /// public class IgnoreNullOrEmptyStringsConvention : ConventionBase, IMemberMapConvention { /// /// Initializes a new instance of the class. /// public IgnoreNullOrEmptyStringsConvention() : base(Regex.Replace(nameof(IgnoreNullOrEmptyStringsConvention), "Convention$", "")) { } /// /// Applies the Ignore Null or Empty Strings convention to the given . /// /// The to which the convention will be applied. public virtual void Apply([NotNull] BsonMemberMap memberMap) { Check.NotNull(memberMap, nameof(memberMap)); if (memberMap.MemberType == typeof(string)) { SetShouldSerializeMethod(memberMap); } } private static void SetShouldSerializeMethod(BsonMemberMap memberMap) { var defaultString = memberMap.DefaultValue as string; if (!string.IsNullOrEmpty(defaultString)) { ShouldSerializeIfNotDefault(memberMap, defaultString); } else { ShouldSerializeIfNotEmpty(memberMap); } } private static void ShouldSerializeIfNotEmpty(BsonMemberMap memberMap) => memberMap.SetShouldSerializeMethod(@object => !string.IsNullOrEmpty(memberMap.Getter(@object) as string)); private static void ShouldSerializeIfNotDefault(BsonMemberMap memberMap, string defaultString) => memberMap.SetShouldSerializeMethod(@object => !string.Equals(defaultString, memberMap.Getter(@object) as string)); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Conventions/KeyAttributeConvention.cs ================================================ using System.ComponentModel.DataAnnotations; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson.Serialization; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Conventions { /// /// A convention that sets the of a /// if that property has been decorated with a . /// public class KeyAttributeConvention : BsonMemberMapAttributeConvention { /// /// Applies the Key Attribute convention to the given . /// /// The to which the convention will be applied. /// The to apply. protected override void Apply(BsonMemberMap memberMap, KeyAttribute attribute) => Check.NotNull(memberMap, nameof(memberMap)) .ClassMap .SetIdMember(memberMap); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Conventions/NavigationSrializationMemberMapConvention.cs ================================================ using System; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; using Blueshift.EntityFrameworkCore.MongoDB.Adapter.Serialization; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Conventions; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Conventions { /// /// /// /// A convention for specifying how to serialize navigation properties. /// public class NavigationSrializationMemberMapConvention : ConventionBase, IMemberMapConvention { /// /// /// Checks whether the member map represents a navigation, and sets the member map's serializer. /// /// The member map. public void Apply(BsonMemberMap memberMap) { Type memberTargetType = Check.NotNull(memberMap, nameof(memberMap)).MemberType.TryGetSequenceType() ?? memberMap.MemberType; if (!memberTargetType.IsPrimitive && HasIdMember(memberTargetType)) { IBsonSerializer memberMapSerializer = (IBsonSerializer) Activator.CreateInstance( typeof(NavigationBsonMemberMapSerializer<>).MakeGenericType(memberTargetType), memberMap); memberMap.SetSerializer(memberMapSerializer); } } private bool HasIdMember(Type type) => !type.IsPrimitive && type .GetMembers(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance) .Any(memberInfo => memberInfo.IsDefined(typeof(BsonIdAttribute)) || memberInfo.IsDefined(typeof(KeyAttribute))); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Conventions/NotMappedAttributeConvention.cs ================================================ using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson.Serialization; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Conventions { /// /// Marks a as ignored during serialization. /// public class NotMappedAttributeConvention : BsonMemberMapAttributeConvention { /// /// Applies the Not Mapped convention to the given . /// /// The to which the convention will be applied. /// The to apply. protected override void Apply(BsonMemberMap memberMap, NotMappedAttribute attribute) => Check.NotNull(memberMap, nameof(memberMap)) .ClassMap .UnmapMember(memberMap.MemberInfo); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/EntityFrameworkConventionPack.cs ================================================ using System; using Blueshift.EntityFrameworkCore.MongoDB.Adapter.Conventions; using MongoDB.Bson.Serialization.Conventions; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter { /// /// /// Provides a set of conventions that configures the MongoDb C# Driver to work appropriately with the EntityFrameworkCore. /// public class EntityFrameworkConventionPack : ConventionPack { /// /// Registers the . /// /// public static void Register(Func typeFilter) { ConventionRegistry.Register( "Blueshift.EntityFrameworkCore.MongoDb.Conventions", Instance, typeFilter); } /// /// The singleton instance of . /// public static EntityFrameworkConventionPack Instance { get; } = new EntityFrameworkConventionPack(); private EntityFrameworkConventionPack() { AddRange(new IConvention[] { new AbstractBaseClassConvention(), new KeyAttributeConvention(), new NavigationSrializationMemberMapConvention(), new NotMappedAttributeConvention() }); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Serialization/BsonSerializerExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; // ReSharper disable once CheckNamespace namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Serialization { /// /// Provides extended functionality to . /// public static class BsonSerializerExtensions { /// /// Modifies an instance of to only use the supplied members when serializing instances. /// /// The to modify. /// An of that lists the members /// required for serialization. /// A new instance of that serializes the information in . public static IBsonSerializer AsDenormalizingBsonClassMapSerializer( [NotNull] this IBsonSerializer bsonSerializer, [CanBeNull] IEnumerable denormalizedMemberNames = null) { TypeInfo typeInfo = Check.NotNull(bsonSerializer, nameof(bsonSerializer)).GetType().GetTypeInfo(); if (bsonSerializer is IChildSerializerConfigurable childSerializerConfigurable) { bsonSerializer = childSerializerConfigurable.WithChildSerializer( childSerializerConfigurable.ChildSerializer.AsDenormalizingBsonClassMapSerializer(denormalizedMemberNames)); } else if (typeInfo.TryGetImplementationType(typeof(ReadOnlyCollectionSerializer<>), out Type readOnlyCollectionSerializerType) || typeInfo.TryGetImplementationType(typeof(ReadOnlyCollectionSubclassSerializer<,>), out readOnlyCollectionSerializerType)) { bsonSerializer = (IBsonSerializer) Activator.CreateInstance(readOnlyCollectionSerializerType, ((IBsonSerializer) readOnlyCollectionSerializerType .GetProperty(nameof(EnumerableSerializerBase.ItemSerializer)) .GetValue(bsonSerializer)) .AsDenormalizingBsonClassMapSerializer(denormalizedMemberNames)); } else { BsonClassMap bsonClassMap = BsonClassMap.LookupClassMap(bsonSerializer.ValueType); bsonSerializer = (IBsonSerializer) Activator.CreateInstance( typeof(DenormalizingBsonClassMapSerializer<>).MakeGenericType(bsonSerializer.ValueType), bsonClassMap, denormalizedMemberNames); } return bsonSerializer; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Serialization/DenormalizingBsonClassMapSerializer.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using MongoDB.Bson; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Conventions; using MongoDB.Bson.Serialization.Serializers; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Serialization { /// /// /// A serializer for writing navigation properties used by MongoDB. /// public class DenormalizingBsonClassMapSerializer : BsonClassMapSerializer { private readonly BsonClassMap _classMap; /// /// /// Initializes a new instance of the class. /// /// The to serialize. /// Optional. An of referencing the members /// to be denormalized by this serializer. public DenormalizingBsonClassMapSerializer( [NotNull] BsonClassMap bsonClassMap, [CanBeNull] IEnumerable denormalizedMemberNames = null) : base(bsonClassMap) { _classMap = bsonClassMap; DenormalizedMemberMaps = (denormalizedMemberNames ?? new string[0]) .Select(bsonClassMap.GetMemberMap) .Except(new[] { null, _classMap.IdMemberMap }) .OrderBy(bsonMemberMap => bsonMemberMap.MemberName) .ToList(); } /// /// Gets the of that this serializer is configured to denormalize. /// public IEnumerable DenormalizedMemberMaps { get; } /// public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TClass value) { IBsonWriter bsonWriter = context.Writer; Type actualType = value?.GetType(); if (actualType == null) { bsonWriter.WriteNull(); } else if (typeof(TClass).IsAssignableFrom(actualType)) { SerializeClass(context, args, value); } else { throw new BsonSerializationException( $"Expected an object derived from {typeof(TClass).FullName}, but received a value of type {actualType.FullName} instead."); } } private void SerializeClass(BsonSerializationContext context, BsonSerializationArgs args, TClass document) { IBsonWriter bsonWriter = context.Writer; bsonWriter.WriteStartDocument(); SerializeMember(context, document, _classMap.IdMemberMap); if (ShouldSerializeDiscriminator(args.NominalType)) { SerializeDiscriminator(context, args.NominalType, document); } foreach (BsonMemberMap memberMap in DenormalizedMemberMaps) { SerializeMember(context, document, memberMap); } bsonWriter.WriteEndDocument(); } private void SerializeMember(BsonSerializationContext context, TClass document, BsonMemberMap memberMap) { IBsonWriter bsonWriter = context.Writer; object value = memberMap.Getter(document); if (memberMap.ShouldSerialize(document, value)) { bsonWriter.WriteName(memberMap.ElementName); memberMap.GetSerializer().Serialize(context, value); } } private bool ShouldSerializeDiscriminator(Type nominalType) => (nominalType != _classMap.ClassType || _classMap.DiscriminatorIsRequired || _classMap.HasRootClass) && !_classMap.IsAnonymous; private void SerializeDiscriminator(BsonSerializationContext context, Type nominalType, object obj) { IDiscriminatorConvention discriminatorConvention = BsonSerializer.LookupDiscriminatorConvention(_classMap.ClassType); BsonValue discriminator = discriminatorConvention?.GetDiscriminator(nominalType, obj.GetType()); if (discriminator != null) { context.Writer.WriteName(discriminatorConvention.ElementName); BsonValueSerializer.Instance.Serialize(context, discriminator); } } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Serialization/NavigationBsonMemberMapSerializer.cs ================================================ using System; using System.Collections.Generic; using System.Reflection; using Blueshift.EntityFrameworkCore.MongoDB.Annotations; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson.Serialization; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Serialization { /// /// /// A serializer for writing navigation properties used by MongoDB. /// /// The type of the member to be serialized by this . public class NavigationBsonMemberMapSerializer : IBsonSerializer { private readonly Lazy _lazyBsonSerializer; /// /// Initializes a new instance of the class. /// /// The representing the member to serialize. public NavigationBsonMemberMapSerializer(BsonMemberMap bsonMemberMap) { MemberMap = Check.NotNull(bsonMemberMap, nameof(bsonMemberMap)); _lazyBsonSerializer = new Lazy(() => { IEnumerable denormalizedMemberNames = bsonMemberMap.MemberInfo .GetCustomAttribute() ?.MemberNames ?? new string[0]; return BsonSerializer.LookupSerializer(bsonMemberMap.MemberType) .AsDenormalizingBsonClassMapSerializer(denormalizedMemberNames); }); } /// /// The representing the member to serialize. /// public BsonMemberMap MemberMap { get; } /// TClass IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => (TClass) Deserialize(context, args); /// public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TClass value) => _lazyBsonSerializer.Value.Serialize(context, args, value); /// public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => _lazyBsonSerializer.Value.Deserialize(context, args); /// public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) => _lazyBsonSerializer.Value.Serialize(context, args, value); /// public Type ValueType => MemberMap.MemberType; } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Update/DeleteOneModelFactory.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Update; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.EntityFrameworkCore.ValueGeneration; using MongoDB.Driver; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Update { /// /// Creates from a given . /// /// The type of entity being added. public class DeleteOneModelFactory : MongoDbWriteModelFactory { /// /// Initializes a new instance of the class. /// /// The to use for populating concurrency tokens. /// The for which this will be used. public DeleteOneModelFactory( [NotNull] IValueGeneratorSelector valueGeneratorSelector, [NotNull] IEntityType entityType) : base( Check.NotNull(valueGeneratorSelector, nameof(valueGeneratorSelector)), Check.NotNull(entityType, nameof(entityType))) { } /// /// Creates an that maps the given . /// /// The to map. /// A new containing the inserted values represented /// by . public override WriteModel CreateWriteModel(IUpdateEntry updateEntry) => new DeleteOneModel(GetLookupFilter(updateEntry)); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Update/IMongoDbWriteModelFactory.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Update; using MongoDB.Driver; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Update { /// /// Interface for generating instances from instances. /// /// The type of entity being updated public interface IMongoDbWriteModelFactory { /// /// Converts an instance to a instance. /// /// The entry to convert. /// A new that contains the updates in . WriteModel CreateWriteModel([NotNull] IUpdateEntry updateEntry); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Update/IMongoDbWriteModelFactoryCache.cs ================================================ using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using MongoDB.Driver; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Update { /// /// Caches instances. /// public interface IMongoDbWriteModelFactoryCache { /// /// Returns a cached or newly created instance of for the given /// and . /// /// The type of entity being written. /// The that contains the entity metadata. /// The describing the type of /// that returned the factory will produce. /// A that can /// be used to create a new factory instance if one has not previously been cached. /// A new or cached instance of . IMongoDbWriteModelFactory GetOrAdd( IEntityType entityType, EntityState entityState, Func> factoryFunc); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Update/IMongoDbWriteModelFactorySelector.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Update; using MongoDB.Driver; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Update { /// /// Interface for selecting an instance of . /// public interface IMongoDbWriteModelFactorySelector { /// /// Select an instance for the given . /// /// The that the write model factory will be used to translate. /// The type of entity for which to create a instance. /// An instance of that can be used to convert /// instances to instances. IMongoDbWriteModelFactory Select([NotNull] IUpdateEntry updateEntry); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Update/InsertOneModelFactory.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Update; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.EntityFrameworkCore.ValueGeneration; using MongoDB.Driver; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Update { /// /// /// Creates from a given . /// /// The type of entity being added. public class InsertOneModelFactory : MongoDbWriteModelFactory { /// /// /// Initializes a new instance of the class. /// /// The to use for populating concurrency tokens. /// The for which this will be used. public InsertOneModelFactory( [NotNull] IValueGeneratorSelector valueGeneratorSelector, [NotNull] IEntityType entityType) : base( Check.NotNull(valueGeneratorSelector, nameof(valueGeneratorSelector)), Check.NotNull(entityType, nameof(entityType))) { } /// /// /// Creates an that maps the given . /// /// The to map. /// A new containing the inserted values represented /// by . public override WriteModel CreateWriteModel(IUpdateEntry updateEntry) { InternalEntityEntry internalEntityEntry = Check.Is(updateEntry, nameof(updateEntry)); UpdateDbGeneratedProperties(internalEntityEntry); return new InsertOneModel((TEntity)internalEntityEntry.Entity); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Update/MongoDbWriteModelFactory.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Update; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.EntityFrameworkCore.ValueGeneration; using MongoDB.Driver; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Update { /// /// Base class for generating instances. /// /// The type of entity to write. public abstract class MongoDbWriteModelFactory : IMongoDbWriteModelFactory { private static readonly MethodInfo GenericEqMethodInfo = MethodHelper .GetGenericMethodDefinition, object>( filterDefinitionBuilder => filterDefinitionBuilder.Eq( (Expression>) null, null)); private readonly IValueGeneratorSelector _valueGeneratorSelector; private readonly IEnumerable _keyProperties; private readonly IEnumerable _dbGeneratedProperties; private readonly IEnumerable _concurrencyProperties; /// /// Initializes a new instance of the class. /// /// The to use for populating concurrency tokens. /// The for which this will be used. protected MongoDbWriteModelFactory( [NotNull] IValueGeneratorSelector valueGeneratorSelector, [NotNull] IEntityType entityType) { _valueGeneratorSelector = Check.NotNull(valueGeneratorSelector, nameof(valueGeneratorSelector)); _keyProperties = Check.NotNull(entityType, nameof(entityType)).FindPrimaryKey().Properties; _dbGeneratedProperties = entityType .GetProperties() .Where(property => property.ValueGenerated == ValueGenerated.OnAddOrUpdate) .ToList(); _concurrencyProperties = _dbGeneratedProperties .Where(property => property.IsConcurrencyToken) .ToList(); } /// /// Converts an instance to a instance. /// /// The entry to convert. /// A new that contains the updates in . public abstract WriteModel CreateWriteModel(IUpdateEntry updateEntry); /// /// Generates a for . /// /// The for the document being updated. /// A new that can matches the document in . protected FilterDefinition GetLookupFilter([NotNull] IUpdateEntry updateEntry) { IEnumerable lookupProperties = Check.NotNull(updateEntry, nameof(updateEntry)).EntityState == EntityState.Added ? _keyProperties : _keyProperties.Concat(_concurrencyProperties); IList> filterDefinitions = lookupProperties .Select(property => GetPropertyFilterDefinition( property, property.IsConcurrencyToken ? updateEntry.GetOriginalValue(property) : updateEntry.GetCurrentValue(property))) .DefaultIfEmpty(Builders.Filter.Empty) .ToList(); return filterDefinitions.Count > 1 ? Builders.Filter.And(filterDefinitions) : filterDefinitions[0]; } /// /// Updates the database-generated properties for . /// /// The the representing the document being updated. protected void UpdateDbGeneratedProperties(InternalEntityEntry internalEntityEntry) { var entityEntry = internalEntityEntry.ToEntityEntry(); foreach (IProperty property in _dbGeneratedProperties) { ValueGenerator valueGenerator = _valueGeneratorSelector.Select(property, internalEntityEntry.EntityType); object dbGeneratedValue = valueGenerator.Next(entityEntry); internalEntityEntry.SetProperty(property, dbGeneratedValue, true); property.GetSetter().SetClrValue(internalEntityEntry.Entity, dbGeneratedValue); } } private FilterDefinition GetPropertyFilterDefinition( IPropertyBase property, object propertyValue) { ParameterExpression parameterExpression = Expression.Parameter(typeof(TEntity), name: "entity"); LambdaExpression lambdaExpression = Expression.Lambda( Expression.MakeMemberAccess(parameterExpression, property.PropertyInfo), parameterExpression); return (FilterDefinition)GenericEqMethodInfo .MakeGenericMethod(property.ClrType) .Invoke(Builders.Filter, new[] { lambdaExpression, propertyValue }); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Update/MongoDbWriteModelFactoryCache.cs ================================================ using System; using System.Collections.Concurrent; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Driver; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Update { /// /// Caches instances. /// public class MongoDbWriteModelFactoryCache : IMongoDbWriteModelFactoryCache { private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); /// /// Returns a cached or newly created instance of for the given /// and . /// /// The type of entity being written. /// The that contains the entity metadata. /// The describing the type of /// that returned the factory will produce. /// A that can /// be used to create a new factory instance if one has not previously been cached. /// A new or cached instance of . public IMongoDbWriteModelFactory GetOrAdd( IEntityType entityType, EntityState entityState, Func> factoryFunc) => _cache.GetOrAdd( new CacheKey( Check.NotNull(entityType, nameof(entityType)), entityState, Check.NotNull(factoryFunc, nameof(factoryFunc))), cacheKey => cacheKey.FactoryFunc(entityType, entityState)) as IMongoDbWriteModelFactory; private struct CacheKey : IEquatable { private readonly IEntityType _entityType; private readonly EntityState _entityState; public CacheKey( IEntityType entityType, EntityState entityState, Func factoryFunc) { _entityType = entityType; _entityState = entityState; FactoryFunc = factoryFunc; } public Func FactoryFunc { get; } public override bool Equals(object obj) => Equals((CacheKey)obj); public bool Equals(CacheKey other) => Equals(_entityType, other._entityType) && Equals(_entityState, other._entityState); public override int GetHashCode() { unchecked { return (_entityType.GetHashCode() * 492) ^ _entityState.GetHashCode(); } } } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Update/MongoDbWriteModelFactorySelector.cs ================================================ using System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Update; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.EntityFrameworkCore.ValueGeneration; using MongoDB.Driver; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Update { /// /// Selects an instance of for a given . /// public class MongoDbWriteModelFactorySelector : IMongoDbWriteModelFactorySelector { private readonly IValueGeneratorSelector _valueGeneratorSelector; private readonly IMongoDbWriteModelFactoryCache _mongoDbWriteModelFactoryCache; /// /// Initializes a new instance of the class. /// /// The to use for populating concurrency tokens. /// A that can be used to cache the /// factory instances returned by this . public MongoDbWriteModelFactorySelector( [NotNull] IValueGeneratorSelector valueGeneratorSelector, [NotNull] IMongoDbWriteModelFactoryCache mongoDbWriteModelFactoryCache) { _valueGeneratorSelector = Check.NotNull(valueGeneratorSelector, nameof(valueGeneratorSelector)); _mongoDbWriteModelFactoryCache = Check.NotNull(mongoDbWriteModelFactoryCache, nameof(mongoDbWriteModelFactoryCache)); } /// /// Select an instance for the given . /// /// The that the write model factory will be used to translate. /// The type of entity for which to create a instance. /// An instance of that can be used to convert /// instances to instances. public IMongoDbWriteModelFactory Select(IUpdateEntry updateEntry) => _mongoDbWriteModelFactoryCache.GetOrAdd( Check.NotNull(updateEntry, nameof(updateEntry)).EntityType, updateEntry.EntityState, Create); private IMongoDbWriteModelFactory Create( [NotNull] IEntityType entityType, EntityState entityState) { Check.NotNull(entityType, nameof(entityType)); if (entityState != EntityState.Added && entityState != EntityState.Modified && entityState != EntityState.Unchanged && entityState != EntityState.Deleted) { throw new InvalidOperationException($"The value provided for entityState must be Added, Modified, Unchanged, or Deleted, but was {entityState}."); } IMongoDbWriteModelFactory mongoDbWriteModelFactory; switch (entityState) { case EntityState.Added: mongoDbWriteModelFactory = new InsertOneModelFactory(_valueGeneratorSelector, entityType); break; case EntityState.Deleted: mongoDbWriteModelFactory = new DeleteOneModelFactory(_valueGeneratorSelector, entityType); break; default: mongoDbWriteModelFactory = new ReplaceOneModelFactory(_valueGeneratorSelector, entityType); break; } return mongoDbWriteModelFactory; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Adapter/Update/ReplaceOneModelFactory.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Update; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.EntityFrameworkCore.ValueGeneration; using MongoDB.Driver; namespace Blueshift.EntityFrameworkCore.MongoDB.Adapter.Update { /// /// /// Creates from a given . /// /// The type of entity being added. public class ReplaceOneModelFactory : MongoDbWriteModelFactory { /// /// /// Initializes a new instance of the class. /// /// The to use for populating /// concurrency tokens. /// The for which this /// will be used. public ReplaceOneModelFactory( [NotNull] IValueGeneratorSelector valueGeneratorSelector, [NotNull] IEntityType entityType) : base( Check.NotNull(valueGeneratorSelector, nameof(valueGeneratorSelector)), Check.NotNull(entityType, nameof(entityType))) { } /// /// /// Creates an that maps the given . /// /// The to map. /// A new containing the inserted values represented /// by . public override WriteModel CreateWriteModel(IUpdateEntry updateEntry) { InternalEntityEntry internalEntityEntry = Check.Is(updateEntry, nameof(updateEntry)); UpdateDbGeneratedProperties(internalEntityEntry); return new ReplaceOneModel( GetLookupFilter(updateEntry), (TEntity)internalEntityEntry.Entity); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Annotations/DenormalizeAttribute.cs ================================================ using System; namespace Blueshift.EntityFrameworkCore.MongoDB.Annotations { /// /// Declares that a member of a navigation property should be denormalized when serializing the property. /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public class DenormalizeAttribute : Attribute { /// /// Initializes a new instance of the class. /// /// The names of sub-document members to denormalize when serializing the parent document. public DenormalizeAttribute(params string[] memberNames) { MemberNames = memberNames ?? new string[0]; } /// /// The name of the member to denormalize. /// public string[] MemberNames { get; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Annotations/MongoCollectionAttribute.cs ================================================ using System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Annotations { /// /// When applied to an entity class, sets the name of MongoDB collection name used to store instances of the entity. /// [AttributeUsage(AttributeTargets.Class)] public class MongoCollectionAttribute : Attribute { /// /// Initializes a new instance of the class. /// /// The MongoDb database name to use with the . public MongoCollectionAttribute([NotNull] string collectionName) { CollectionName = Check.NotEmpty(collectionName, nameof(collectionName)); } /// /// The MongoDb database name to use with the . /// public virtual string CollectionName { get; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Annotations/MongoDatabaseAttribute.cs ================================================ using System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Annotations { /// /// When applied to a , sets the database name to use with the context's . /// [AttributeUsage(AttributeTargets.Class)] public class MongoDatabaseAttribute : Attribute { /// /// Initializes a new instance of the class. /// /// The MongoDb database name to use with the . public MongoDatabaseAttribute([NotNull] string database) { Database = Check.NotEmpty(database, nameof(database)); } /// /// The MongoDb database name to use with the . /// public virtual string Database { get; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Blueshift.EntityFrameworkCore.MongoDB.csproj ================================================  $(BuildFrameworks) Blueshift MongoDb Provider for EntityFrameworkCore Blueshift Software MongoDb Provider for Microsoft EntityFramework Core true True True DocumentDbStrings.tt True True DocumentDbStrings.resx ResXFileCodeGenerator DocumentDbStrings.Designer.cs TextTemplatingFileGenerator DocumentDbStrings.cs ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/ChangeTracking/MongoDbInternalEntityEntryFactory.cs ================================================ using System.Linq; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; namespace Blueshift.EntityFrameworkCore.MongoDB.ChangeTracking { /// public class MongoDbInternalEntityEntryFactory : InternalEntityEntryFactory { /// public override InternalEntityEntry Create( IStateManager stateManager, IEntityType entityType, object entity, in ValueBuffer valueBuffer) => base.Create( stateManager, entityType, entity, valueBuffer.IsEmpty ? CreateValueBuffer(entityType, entity) : valueBuffer); /// public override InternalEntityEntry Create( IStateManager stateManager, IEntityType entityType, object entity) => Create(stateManager, entityType, entity, ValueBuffer.Empty); private ValueBuffer CreateValueBuffer(IEntityType entityType, object entity) { object[] values = new object[entityType.PropertyCount()]; foreach (IProperty property in entityType.GetProperties()) { values[property.GetIndex()] = GetPropertyValue(entity, property); } return new ValueBuffer(values); } private object GetPropertyValue(object entity, IProperty property) { if (property.IsShadowProperty && property.IsForeignKey()) { IForeignKey foreignKey = property.AsProperty().ForeignKeys.First(); INavigation navigationProperty = property.DeclaringEntityType == foreignKey.PrincipalEntityType && !foreignKey.IsSelfPrimaryKeyReferencing() ? foreignKey.PrincipalToDependent : foreignKey.DependentToPrincipal; if (navigationProperty != null) { entity = navigationProperty.GetGetter().GetClrValue(entity); IEntityType targetEntityType = navigationProperty.GetTargetType(); property = targetEntityType.FindPrimaryKey().Properties.Single(); } else { entity = null; } } return entity == null ? null : property.IsShadowProperty ? entity.GetHashCode() : property.GetGetter().GetClrValue(entity); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/DbContextOptionsExtensions.cs ================================================ //using System; //using System.Collections.Generic; //using System.Linq; //using JetBrains.Annotations; //using Microsoft.EntityFrameworkCore.Infrastructure; //using Microsoft.EntityFrameworkCore.Utilities; //// ReSharper disable once CheckNamespace //namespace Blueshift.EntityFrameworkCore.MongoDB //{ // /// // /// Provides a set of extension methods for instances of the interface. // /// // public static class DbContextOptionsExtensions // { // /// // /// Extracts a single instance of the from this . // /// // /// The type of to be extracted. // /// An instance of to search for the given extension. // /// The single instance of that was found. // /// // /// Thrown if no instance of the could be found, or if more than one was found. // /// // public static TExtension Extract([NotNull] this IDbContextOptions dbContextOptions) // where TExtension : IDbContextOptionsExtension // { // Check.NotNull(dbContextOptions, nameof(dbContextOptions)); // IList extensions = dbContextOptions.Extensions // .OfType() // .ToList(); // if (extensions.Count == 0) // { // throw new InvalidOperationException($"No provider has been configured with a {nameof(TExtension)} extension."); // } // if (extensions.Count > 1) // { // throw new InvalidOperationException($"Multiple providers have been configured with a {nameof(TExtension)} extension."); // } // return extensions[index: 0]; // } // } //} ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/DependencyInjection/MongoDbEfServiceCollectionExtensions.cs ================================================ using System.ComponentModel; using System.Reflection; using Blueshift.EntityFrameworkCore.MongoDB.Adapter; using Blueshift.EntityFrameworkCore.MongoDB.Infrastructure; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; using MongoDB.Bson; // ReSharper disable once CheckNamespace namespace Blueshift.EntityFrameworkCore.MongoDB.DependencyInjection { /// /// Extends with methods for use with the MongoDb EntityFrameworkCore provider. /// public static class MongoDbEfServiceCollectionExtensions { static MongoDbEfServiceCollectionExtensions() { if (!typeof(ObjectId).GetTypeInfo().IsDefined(typeof(TypeConverterAttribute))) { TypeDescriptor.AddAttributes(typeof(ObjectId), new TypeConverterAttribute(typeof(ObjectIdTypeConverter))); } EntityFrameworkConventionPack.Register(type => true); } /// /// Populates the given instance with the service dependencies for /// the MongoDb provider for EntityFrameworkCore. /// /// The instance of populate. /// The populated with the MongoDb EntityFrameworkCore dependencies. public static IServiceCollection AddEntityFrameworkMongoDb([NotNull] this IServiceCollection serviceCollection) { Check.NotNull(serviceCollection, nameof(serviceCollection)); var entityFrameworkServicesBuilder = new EntityFrameworkMongoDbServicesBuilder(serviceCollection); entityFrameworkServicesBuilder.TryAddCoreServices(); return serviceCollection; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Infrastructure/EntityFrameworkMongoDbServicesBuilder.cs ================================================ using System; using System.Collections.Generic; using Blueshift.EntityFrameworkCore.MongoDB.Adapter.Update; using Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders; using Blueshift.EntityFrameworkCore.MongoDB.Query; using Blueshift.EntityFrameworkCore.MongoDB.Query.Expressions; using Blueshift.EntityFrameworkCore.MongoDB.Query.ExpressionVisitors; using Blueshift.EntityFrameworkCore.MongoDB.Storage; using Blueshift.EntityFrameworkCore.MongoDB.ValueGeneration; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.ValueGeneration; using Microsoft.Extensions.DependencyInjection; using MongoDB.Driver; namespace Blueshift.EntityFrameworkCore.MongoDB.Infrastructure { /// /// /// A builder API that populates an with a set of EntityFrameworkCore /// provider dependencies for MongoDb. /// public class EntityFrameworkMongoDbServicesBuilder : EntityFrameworkServicesBuilder { private static readonly IDictionary RelationalServices = new Dictionary { { typeof(IMongoClient), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IMongoDbConnection), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IMongoDbTypeMappingSource), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(MongoDbEntityQueryModelVisitorDependencies), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(MongoDbConventionSetBuilderDependencies), new ServiceCharacteristics(ServiceLifetime.Scoped) } }; /// /// Initializes a new instance of the class. /// /// The instance to populate. public EntityFrameworkMongoDbServicesBuilder([NotNull] IServiceCollection serviceCollection) : base(serviceCollection) { } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used directly from your code. /// This API may change or be removed in future releases. /// protected override ServiceCharacteristics GetServiceCharacteristics(Type serviceType) => RelationalServices.TryGetValue(serviceType, out ServiceCharacteristics characteristics) ? characteristics : base.GetServiceCharacteristics(serviceType); /// /// Registers default implementations of all services not already registered by the provider. Database providers must call /// this method as the last step of service registration--that is, after all provider services have been registered. /// /// This builder, such that further calls can be chained. public override EntityFrameworkServicesBuilder TryAddCoreServices() { TryAdd>(); TryAdd(); TryAdd(serviceProvider => serviceProvider.GetRequiredService()); TryAdd(); TryAdd(); TryAdd(); TryAdd(); TryAdd(); TryAdd(); TryAdd(); TryAdd(); TryAdd(); TryAdd(); TryAddProviderSpecificServices(serviceCollectionMap => { serviceCollectionMap.TryAddScoped(serviceProvider => { MongoDbOptionsExtension extension = serviceProvider .GetRequiredService() .FindExtension(); return extension?.MongoClient ?? new MongoClient(); }); serviceCollectionMap.TryAddScoped(); serviceCollectionMap.TryAddScoped(); serviceCollectionMap.TryAddScoped(); serviceCollectionMap.TryAddScoped(); serviceCollectionMap.TryAddScoped(); serviceCollectionMap.TryAddScoped(); serviceCollectionMap.TryAddScoped(); }); ServiceCollectionMap.GetInfrastructure() .AddDependencyScoped() .AddDependencyScoped(); return base.TryAddCoreServices(); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Infrastructure/MongoDbContextOptionsBuilder.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Utilities; // ReSharper disable once CheckNamespace namespace Blueshift.EntityFrameworkCore.MongoDB.Infrastructure { /// /// /// Allows MongoDb-specific configuration to be performed on . /// /// /// Instances of this class are returned from a call to /// /// and it is not designed to be directly constructed in your application code. /// /// public class MongoDbContextOptionsBuilder { /// /// Initializes a new instance of the class. /// /// The core class. public MongoDbContextOptionsBuilder([NotNull] DbContextOptionsBuilder optionsBuilder) { OptionsBuilder = Check.NotNull(optionsBuilder, nameof(optionsBuilder)); } /// /// Gets the core that supplied to the constructor. /// protected virtual DbContextOptionsBuilder OptionsBuilder { get; } /// /// Sets the name of the MongoDB database to use with the being configured. /// /// The name of the MongoDB database instance to use with the current . /// This , so that calls can be chained. public MongoDbContextOptionsBuilder UseDatabase([NotNull] string databaseName) { Check.NotEmpty(databaseName, nameof(databaseName)); MongoDbOptionsExtension extension = CloneExtension(); extension.DatabaseName = databaseName; ((IDbContextOptionsBuilderInfrastructure)OptionsBuilder).AddOrUpdateExtension(extension); return this; } /// /// Clones the used to configure this builder. /// /// A cloned instance of this builder's . protected virtual MongoDbOptionsExtension CloneExtension() => new MongoDbOptionsExtension(OptionsBuilder.Options.GetExtension()); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Infrastructure/MongoDbContextOptionsBuilderExtensions.cs ================================================ using System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Driver; // ReSharper disable once CheckNamespace namespace Blueshift.EntityFrameworkCore.MongoDB.Infrastructure { /// /// MongoDb-specific extension methods for . /// public static class MongoDbContextOptionsBuilderExtensions { /// /// Configures the context to connect to a MongoDb instance. /// /// The builder being used to configure the context. /// The connection string of the MongoDb instance to connect to. /// An optional action to allow additional MongoDb-specific configuration. /// The options builder so that further configuration can be chained. public static DbContextOptionsBuilder UseMongoDb( [NotNull] this DbContextOptionsBuilder optionsBuilder, [NotNull] string connectionString, [CanBeNull] Action mongoDbOptionsAction = null) { Check.NotEmpty(connectionString, nameof(connectionString)); return SetupMongoDb(Check.NotNull(optionsBuilder, nameof(optionsBuilder)), extension => extension.ConnectionString = connectionString, mongoDbOptionsAction); } /// /// Configures the context to connect to a MongoDb instance. /// /// The builder being used to configure the context. /// The to use when connecting to MongoDb. /// An optional action to allow additional MongoDb-specific configuration. /// The options builder so that further configuration can be chained. public static DbContextOptionsBuilder UseMongoDb( [NotNull] this DbContextOptionsBuilder optionsBuilder, [NotNull] IMongoClient mongoClient, [CanBeNull] Action mongoDbOptionsAction = null) { Check.NotNull(mongoClient, nameof(mongoClient)); return SetupMongoDb(Check.NotNull(optionsBuilder, nameof(optionsBuilder)), extension => extension.MongoClient = mongoClient, mongoDbOptionsAction); } /// /// Configures the context to connect to a MongoDb instance. /// /// The builder being used to configure the context. /// The to use when connecting to MongoDb. /// An optional action to allow additional MongoDb-specific configuration. /// The options builder so that further configuration can be chained. public static DbContextOptionsBuilder UseMongoDb( [NotNull] this DbContextOptionsBuilder optionsBuilder, [NotNull] MongoClientSettings mongoClientSettings, [CanBeNull] Action mongoDbOptionsAction = null) { Check.NotNull(mongoClientSettings, nameof(mongoClientSettings)); return SetupMongoDb(Check.NotNull(optionsBuilder, nameof(optionsBuilder)), extension => extension.MongoClientSettings = mongoClientSettings, mongoDbOptionsAction); } /// /// Configures the context to connect to a MongoDb instance. /// /// The builder being used to configure the context. /// The to use to connect to MongoDb. /// An optional action to allow additional MongoDb-specific configuration. /// The options builder so that further configuration can be chained. public static DbContextOptionsBuilder UseMongoDb( [NotNull] this DbContextOptionsBuilder optionsBuilder, [NotNull] MongoUrl mongoUrl, [CanBeNull] Action mongoDbOptionsAction = null) { Check.NotNull(mongoUrl, nameof(mongoUrl)); return SetupMongoDb(Check.NotNull(optionsBuilder, nameof(optionsBuilder)), extension => extension.MongoUrl = mongoUrl, mongoDbOptionsAction); } private static DbContextOptionsBuilder SetupMongoDb( [NotNull] DbContextOptionsBuilder optionsBuilder, [NotNull] Action mongoDbOptionsExtensionAction, [CanBeNull] Action mongoDbOptionsAction) { MongoDbOptionsExtension extension = GetOrCreateExtension(optionsBuilder); mongoDbOptionsExtensionAction(extension); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); ConfigureWarnings(optionsBuilder); mongoDbOptionsAction?.Invoke(new MongoDbContextOptionsBuilder(optionsBuilder)); return optionsBuilder; } private static MongoDbOptionsExtension GetOrCreateExtension([NotNull] DbContextOptionsBuilder optionsBuilder) { var existing = optionsBuilder.Options.FindExtension(); return existing != null ? new MongoDbOptionsExtension(existing) : new MongoDbOptionsExtension(); } private static void ConfigureWarnings([NotNull] DbContextOptionsBuilder optionsBuilder) => Check.NotNull(optionsBuilder, nameof(optionsBuilder)) .ConfigureWarnings(warningsConfigurationBuilder => { warningsConfigurationBuilder.Default(WarningBehavior.Log); }); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Infrastructure/MongoDbModelValidator.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Blueshift.EntityFrameworkCore.MongoDB.Metadata; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Infrastructure { /// /// /// A validator that enforces rules for all MongoDb provider. /// public class MongoDbModelValidator : ModelValidator { /// /// /// Initializes a new instance of the class. /// /// Parameter object containing dependencies for this service. public MongoDbModelValidator( [NotNull] ModelValidatorDependencies modelValidatorDependencies) : base(Check.NotNull(modelValidatorDependencies, nameof(modelValidatorDependencies))) { } /// /// /// Validates a model, throwing an exception if any errors are found. /// /// The to validate. public override void Validate(IModel model) { base.Validate(Check.NotNull(model, nameof(model))); EnsureDistinctCollectionNames(model); ValidateDerivedTypes(model); } /// protected override void ValidateNoShadowKeys(IModel model) { Check.NotNull(model, nameof(model)); IEnumerable nonComplexEntityTypes = model .GetEntityTypes() .Where(entityType => entityType.ClrType != null && !entityType.IsOwned()); foreach (var entityType in nonComplexEntityTypes) { foreach (var key in entityType.GetDeclaredKeys()) { if (key.Properties.Any(p => p.IsShadowProperty) && key is Key concreteKey && ConfigurationSource.Convention.Overrides(concreteKey.GetConfigurationSource()) && !key.IsPrimaryKey()) { var referencingFk = key.GetReferencingForeignKeys().FirstOrDefault(); if (referencingFk != null) { throw new InvalidOperationException( CoreStrings.ReferencedShadowKey( referencingFk.DeclaringEntityType.DisplayName() + (referencingFk.DependentToPrincipal == null ? "" : "." + referencingFk.DependentToPrincipal.Name), entityType.DisplayName() + (referencingFk.PrincipalToDependent == null ? "" : "." + referencingFk.PrincipalToDependent.Name), Property.Format(referencingFk.Properties, includeTypes: true), Property.Format(entityType.FindPrimaryKey().Properties, includeTypes: true))); } } } } } /// protected override void ValidateOwnership(IModel model) { Check.NotNull(model, nameof(model)); IList ownedEntityTypes = model .GetEntityTypes() .Where(entityType => entityType.HasClrType() ? model.ShouldBeOwnedType(entityType.ClrType) : model.ShouldBeOwnedType(entityType.Name)) .ToList(); foreach (IEntityType entityType in ownedEntityTypes) { List ownerships = entityType .GetForeignKeys() .Where(fk => fk.IsOwnership) .ToList(); foreach (IForeignKey ownership in ownerships) { IForeignKey principalToDependentForeignKey = entityType .GetDeclaredForeignKeys() .FirstOrDefault(foreignKey => !foreignKey.IsOwnership && foreignKey.PrincipalToDependent != null); if (principalToDependentForeignKey != null) { throw new InvalidOperationException( CoreStrings.InverseToOwnedType( principalToDependentForeignKey.PrincipalEntityType.DisplayName(), principalToDependentForeignKey.PrincipalToDependent.Name, entityType.DisplayName(), ownership.PrincipalEntityType.DisplayName())); } } } } /// /// Ensures that each in the given has a unique collection name. /// /// The to validate. protected virtual void EnsureDistinctCollectionNames([NotNull] IModel model) { Check.NotNull(model, nameof(model)); var tables = new HashSet(); var duplicateCollectionNames = model .GetEntityTypes() .Where(et => et.BaseType == null) .Select(entityType => new { new MongoDbEntityTypeAnnotations(entityType).CollectionName, DisplayName = entityType.DisplayName() }) .Where(tuple => !tables.Add(tuple.CollectionName)); foreach (var tuple in duplicateCollectionNames) { throw new InvalidOperationException($"Duplicate collection name \"{tuple.CollectionName}\" defined on entity type \"{tuple.DisplayName}\"."); } } /// /// Ensures that all entities in the given have unique discriminators. /// /// The to validate. protected virtual void ValidateDerivedTypes([NotNull] IModel model) { IEnumerable derivedTypes = Check.NotNull(model, nameof(model)) .GetEntityTypes() .Where(entityType => entityType.BaseType != null && entityType.ClrType.IsInstantiable()); var discriminatorSet = new HashSet>(); foreach (IEntityType entityType in derivedTypes) { ValidateDiscriminator(entityType, discriminatorSet); } } private void ValidateDiscriminator(IEntityType entityType, ISet> discriminatorSet) { var annotations = new MongoDbEntityTypeAnnotations(entityType); if (string.IsNullOrWhiteSpace(annotations.Discriminator)) { throw new InvalidOperationException($"Missing discriminator value for entity type {entityType.DisplayName()}."); } if (!discriminatorSet.Add(Tuple.Create(entityType.RootType(), annotations.Discriminator))) { throw new InvalidOperationException($"Duplicate discriminator value {annotations.Discriminator} for root entity type {entityType.RootType().DisplayName()} (defined on {entityType.DisplayName()})."); } } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Infrastructure/MongoDbOptionsExtension.cs ================================================ using System.Linq; using System.Text; using Blueshift.EntityFrameworkCore.MongoDB.DependencyInjection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; using MongoDB.Driver; // ReSharper disable once CheckNamespace namespace Blueshift.EntityFrameworkCore.MongoDB.Infrastructure { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public class MongoDbOptionsExtension : IDbContextOptionsExtension { private IMongoClient _mongoClient; private string _databaseName; private string _logFragment; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public MongoDbOptionsExtension([CanBeNull]MongoDbOptionsExtension existing = null) { if (existing != null) { _mongoClient = existing.MongoClient; _databaseName = existing.DatabaseName; } } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual string ConnectionString { get => MongoUrl?.ToString(); [param: NotNull] set => MongoUrl = MongoUrl.Create(Check.NotEmpty(value, nameof(ConnectionString))); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual IMongoClient MongoClient { get => _mongoClient; [param: NotNull] set => _mongoClient = Check.NotNull(value, nameof(MongoClient)); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual MongoClientSettings MongoClientSettings { get => _mongoClient?.Settings; [param: NotNull] set => _mongoClient = new MongoClient(Check.NotNull(value, nameof(MongoClientSettings)).Clone()); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual MongoUrl MongoUrl { get => _mongoClient == null ? null : MongoUrl.Create($"mongodb://{string.Join(",", _mongoClient.Settings.Servers.Select(server => $"{server.Host}:{server.Port}"))}"); [param: NotNull] set => MongoClientSettings = MongoClientSettings.FromUrl(Check.NotNull(value, nameof(MongoUrl))); } /// /// Gets or sets the name of the database that the being configured should use. /// public string DatabaseName { get => _databaseName; [param: NotNull] set => _databaseName = Check.NotEmpty(value, nameof(value)); } /// public string LogFragment { get { if (_logFragment == null) { var logBuilder = new StringBuilder(); if (_mongoClient?.Settings != null) { logBuilder.Append("MongoClient.Settings=").Append(_mongoClient.Settings.ToString()); } if (!string.IsNullOrEmpty(DatabaseName)) { logBuilder.Append("DatabaseName=").Append(DatabaseName); } _logFragment = logBuilder.ToString(); } return _logFragment; } } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual bool ApplyServices(IServiceCollection services) { Check.NotNull(services, nameof(services)).AddEntityFrameworkMongoDb(); return true; } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual long GetServiceProviderHashCode() => 0; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual void Validate(IDbContextOptions options) { } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/ListExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Utilities; // ReSharper disable once CheckNamespace namespace Blueshift.EntityFrameworkCore.MongoDB { /// /// Provides a set of extensions for . /// public static class ListExtensions { /// /// Inserts the given before the first instance of . /// /// The type of items contained in the list. /// The type of the existing item. /// An whose values will be replaced. /// The new value to be inserted in . /// The modified , such that calls can be chained. public static IList InsertBefore([NotNull] this IList list, [NotNull] T item) where TExisting : T { Check.NotNull(list, nameof(list)); Check.NotNull(item, nameof(item)); TExisting existing = list.OfType().FirstOrDefault(); int index = existing != null ? list.IndexOf(existing) : list.Count; list.Insert(index, item); return list; } /// /// Replaces instances of in with the /// given . /// /// The base type of instances to be replaced. /// The type of the replacement. /// An whose values will be replaced. /// The new value to be inserted in . /// The modified , such that calls can be chained. public static IList Replace([NotNull] this IList list, [NotNull] TReplacement replacement) where TReplacement : TBase { Check.NotNull(list, nameof(list)); Check.NotNull(replacement, nameof(replacement)); list .OfType() .Select(item => list.IndexOf(item)) .ToList() .ForEach(index => list[index] = replacement); return list; } /// /// Adds an to the given . /// /// The type of items contained in . /// The to which will be added. /// The instance of to add to . /// The modified , such that calls can be chained. public static IList With([NotNull] this IList list, [NotNull] T item) { Check.NotNull(list, nameof(list)); Check.NotNull(item, nameof(item)); list.Add(item); return list; } /// /// Removes all items that match from the given . /// /// The type of items contained in . /// The from which items will be removed. /// A to test for elements to remove. /// The modified , such that calls can be chained. public static IList Without([NotNull] this IList list, Func predicate) { Check.NotNull(list, nameof(list)) .Where(predicate) .ToList() .ForEach(item => list.Remove(item)); return list; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Builders/DocumentDbInternalMetadataBuilderExtensions.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static class DocumentInternalMetadataBuilderExtensions { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static DocumentEntityTypeAnnotations Document([NotNull] this InternalEntityTypeBuilder internalEntityTypeBuilder) => Check.NotNull(internalEntityTypeBuilder, nameof(internalEntityTypeBuilder)).Metadata.Document(); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static DocumentEntityTypeAnnotations Document([NotNull] this EntityTypeBuilder entityTypeBuilder) => Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)).Metadata.Document(); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static DocumentEntityTypeAnnotations Document([NotNull] this EntityType entityType) => Check.NotNull(entityType, nameof(entityType)).Document(); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static DocumentEntityTypeAnnotations Document([NotNull] this IEntityType entityType) => new DocumentEntityTypeAnnotations(Check.NotNull(entityType, nameof(entityType))); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static DocumentKeyAnnotations Document([NotNull] this InternalKeyBuilder internalKeyBuilder) => Check.NotNull(internalKeyBuilder, nameof(internalKeyBuilder)).Metadata.Document(); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static DocumentKeyAnnotations Document([NotNull] this Key key) => Check.NotNull(key, nameof(key)).Document(); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static DocumentKeyAnnotations Document([NotNull] this IKey key) => new DocumentKeyAnnotations(Check.NotNull(key, nameof(key))); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Builders/DocumentEntityTypeBuilderExtensions.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders { /// /// Provides a set of MongoDB-specific extension methods for . /// public static class DocumentEntityTypeBuilderExtensions { /// /// Sets whether the is a complex type (i.e.: not a valid queryable root document entity). /// /// The to annotate. /// /// true if the is a complex type; /// otherwise false. /// /// The , such that calls be chained. public static EntityTypeBuilder IsDocumentComplexType( [NotNull] this EntityTypeBuilder entityTypeBuilder, bool isDocumentComplexType) { Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); entityTypeBuilder.Document().IsComplexType = isDocumentComplexType; return entityTypeBuilder; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Builders/DocumentInternalKeyBuilderExtensions.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static class DocumentInternalKeyBuilderExtensions { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static InternalKeyBuilder IsDocumentOwnershipKey( [NotNull] this InternalKeyBuilder internalKeyBuilder, bool isDocumentOwnershipKey) { DocumentKeyAnnotations documentKeyAnnotations = Check.NotNull(internalKeyBuilder, nameof(internalKeyBuilder)).Document(); documentKeyAnnotations.IsOwnershipKey = isDocumentOwnershipKey; return internalKeyBuilder; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Builders/MongoDbConventionSetBuilder.cs ================================================ using Blueshift.EntityFrameworkCore.MongoDB.Metadata.Conventions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders { /// /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public class MongoDbConventionSetBuilder : IConventionSetBuilder { private readonly MongoDbConventionSetBuilderDependencies _mongoDbConventionSetBuilderDependencies; /// /// Initializes a new instance of the class. /// /// Parameter object containing dependencies for this service. public MongoDbConventionSetBuilder( [NotNull] MongoDbConventionSetBuilderDependencies mongoDbConventionSetBuilderDependencies) { _mongoDbConventionSetBuilderDependencies = Check.NotNull(mongoDbConventionSetBuilderDependencies, nameof(mongoDbConventionSetBuilderDependencies)); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual ConventionSet AddConventions(ConventionSet conventionSet) { Check.NotNull(conventionSet, nameof(conventionSet)); var mongoDbRelationshipDiscoveryConvention = new MongoDbRelationshipDiscoveryConvention( _mongoDbConventionSetBuilderDependencies.MemberClassifier, _mongoDbConventionSetBuilderDependencies.ModelLogger); RelationshipDiscoveryConvention relationshipDiscoveryConvention = mongoDbRelationshipDiscoveryConvention; var ownedDocumentConvention = new OwnedDocumentConvention(); DatabaseGeneratedAttributeConvention databaseGeneratedAttributeConvention = new MongoDbDatabaseGeneratedAttributeConvention(); KeyAttributeConvention keyAttributeConvention = new MongoDbKeyAttributeConvention(); var mongoDatabaseConvention = new MongoDatabaseConvention(_mongoDbConventionSetBuilderDependencies.CurrentDbContext.Context); var bsonRequiredAttributeConvention = new BsonRequiredAttributeConvention(); PropertyMappingValidationConvention propertyMappingValidationConvention = new DocumentPropertyMappingValidationConvention( _mongoDbConventionSetBuilderDependencies.MongoDbTypeMapperSource, _mongoDbConventionSetBuilderDependencies.MemberClassifier); conventionSet.ModelInitializedConventions .With(mongoDatabaseConvention); conventionSet.EntityTypeAddedConventions .Replace(relationshipDiscoveryConvention) .With(ownedDocumentConvention) .With(new MongoCollectionAttributeConvention()) .With(new BsonDiscriminatorAttributeConvention()) .With(new BsonIgnoreAttributeConvention()) .With(new BsonKnownTypesAttributeConvention()); conventionSet.BaseEntityTypeChangedConventions .Replace(relationshipDiscoveryConvention) .With(ownedDocumentConvention); conventionSet.EntityTypeMemberIgnoredConventions .Replace(relationshipDiscoveryConvention); conventionSet.KeyAddedConventions .With(ownedDocumentConvention); conventionSet.KeyRemovedConventions .With(ownedDocumentConvention); conventionSet.ForeignKeyAddedConventions .With(ownedDocumentConvention); conventionSet.ForeignKeyOwnershipChangedConventions .With(mongoDbRelationshipDiscoveryConvention) .Without(item => item is NavigationEagerLoadingConvention); conventionSet.PropertyAddedConventions .Replace(databaseGeneratedAttributeConvention) .Replace(keyAttributeConvention) .With(bsonRequiredAttributeConvention); conventionSet.PropertyFieldChangedConventions .Replace(databaseGeneratedAttributeConvention) .Replace(keyAttributeConvention) .With(bsonRequiredAttributeConvention); conventionSet.NavigationAddedConventions .Replace(relationshipDiscoveryConvention); conventionSet.NavigationRemovedConventions .Replace(relationshipDiscoveryConvention); conventionSet.ModelBuiltConventions .Replace(keyAttributeConvention) .Replace(propertyMappingValidationConvention) .With(ownedDocumentConvention); return conventionSet; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Builders/MongoDbConventionSetBuilderDependencies.cs ================================================ using Blueshift.EntityFrameworkCore.MongoDB.Storage; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders { /// /// /// Service dependencies parameter class for /// /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// /// /// Do not construct instances of this class directly from either provider or application code as the /// constructor signature may change as new dependencies are added. Instead, use this type in /// your constructor so that an instance will be created and injected automatically by the /// dependency injection container. To create an instance with some dependent services replaced, /// first resolve the object from the dependency injection container, then replace selected /// services using the 'With...' methods. Do not call the constructor at any point in this process. /// /// public class MongoDbConventionSetBuilderDependencies { /// /// Initializes a new instance of the class. /// /// Indirection to the current instance. /// Maps .NET types to their corresponding database provider types. /// Determines the property types for candidate navigation properties. /// Traces the process of building a . public MongoDbConventionSetBuilderDependencies( [NotNull] ICurrentDbContext currentDbContext, [NotNull] IMongoDbTypeMappingSource mongoDbTypeMappingSource, [NotNull] IMemberClassifier memberClassifier, [NotNull] IDiagnosticsLogger modelLogger) { CurrentDbContext = Check.NotNull(currentDbContext, nameof(currentDbContext)); MongoDbTypeMapperSource = Check.NotNull(mongoDbTypeMappingSource, nameof(mongoDbTypeMappingSource)); MemberClassifier = Check.NotNull(memberClassifier, nameof(memberClassifier)); ModelLogger = Check.NotNull(modelLogger, nameof(modelLogger)); } /// /// Indirection to the current instance. /// public ICurrentDbContext CurrentDbContext { get; } /// /// Maps .NET types to their corresponding database provider types. /// public IMongoDbTypeMappingSource MongoDbTypeMapperSource { get; } /// /// The to use to determine the property types for candidate navigation properties. /// public IMemberClassifier MemberClassifier { get; set; } /// /// The used for tracing the process of building a . /// public IDiagnosticsLogger ModelLogger { get; set; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Builders/MongoDbEntityTypeBuilderExtensions.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders { /// /// Provides a set of MongoDB-specific extension methods for . /// public static class MongoDbEntityTypeBuilderExtensions { /// /// Sets the name of the MongoDB collection used to store the being built. /// /// The to annotate. /// The name of the MongoDB collection. /// The , such that calls be chained. public static EntityTypeBuilder ForMongoDbFromCollection( [NotNull] this EntityTypeBuilder entityTypeBuilder, [NotNull] string collectionName) { Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); Check.NotEmpty(collectionName, nameof(collectionName)); entityTypeBuilder.MongoDb().CollectionName = collectionName; return entityTypeBuilder; } /// /// Sets the discriminator used to query instances of the being built. /// /// The to annotate. /// The discriminator for the . /// The , such that calls be chained. public static EntityTypeBuilder ForMongoDbHasDiscriminator( [NotNull] this EntityTypeBuilder entityTypeBuilder, [NotNull] string discriminator) { Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); Check.NotEmpty(discriminator, nameof(discriminator)); entityTypeBuilder.MongoDb().Discriminator = discriminator; return entityTypeBuilder; } /// /// Sets the whether or not a discriminator is required to query instances of the being built. /// /// The to annotate. /// /// true if a discriminator is required to query instances of the entity; otherwise false. /// /// The , such that calls be chained. public static EntityTypeBuilder ForMongoDbDiscriminatorIsRequired( [NotNull] this EntityTypeBuilder entityTypeBuilder, bool discriminatorIsRequired) { Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); entityTypeBuilder.MongoDb().DiscriminatorIsRequired = discriminatorIsRequired; return entityTypeBuilder; } /// /// Sets whether the being built is a root type of a polymorphic hierarchy. /// /// The to annotate. /// /// true if the is the root entity type; otherwise false. /// /// The , such that calls be chained. public static EntityTypeBuilder ForMongoDbIsRootType( [NotNull] this EntityTypeBuilder entityTypeBuilder, bool isRootType) { Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); entityTypeBuilder.MongoDb().IsRootType = isRootType; return entityTypeBuilder; } /// /// Sets whether the identity of the being built should be assigned by MongoDb on insert. /// /// The to annotate. /// /// true if the identity of the is assigned on insert; /// otherwise false. /// /// The , such that calls be chained. public static EntityTypeBuilder ForMongoDbAssignIdOnInsert( [NotNull] this EntityTypeBuilder entityTypeBuilder, bool assignIdOnInsert) { Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); entityTypeBuilder.MongoDb().AssignIdOnInsert = assignIdOnInsert; return entityTypeBuilder; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Builders/MongoDbInternalMetadataBuilderExtensions.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; // ReSharper disable once CheckNamespace namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static class MongoDbInternalMetadataBuilderExtensions { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static MongoDbEntityTypeAnnotations MongoDb([NotNull] this InternalEntityTypeBuilder internalEntityTypeBuilder) => MongoDb(Check.NotNull(internalEntityTypeBuilder, nameof(internalEntityTypeBuilder)).Metadata); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static MongoDbEntityTypeAnnotations MongoDb([NotNull] this EntityTypeBuilder entityTypeBuilder) => MongoDb(Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)).Metadata); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static MongoDbEntityTypeAnnotations MongoDb([NotNull] this IEntityType entityType) => MongoDb(Check.Is(entityType, nameof(entityType))); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static MongoDbEntityTypeAnnotations MongoDb([NotNull] this IMutableEntityType mutableEntityType) => new MongoDbEntityTypeAnnotations(Check.NotNull(mutableEntityType, nameof(mutableEntityType))); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static MongoDbModelAnnotations MongoDb([NotNull] this InternalModelBuilder internalModelBuilder) => MongoDb(Check.NotNull(internalModelBuilder, nameof(internalModelBuilder)).Metadata); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static MongoDbModelAnnotations MongoDb([NotNull] this ModelBuilder modelBuilder) => MongoDb(Check.NotNull(modelBuilder, nameof(modelBuilder)).Model); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static MongoDbModelAnnotations MongoDb([NotNull] this IModel model) => MongoDb(Check.Is(model, nameof(model))); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static MongoDbModelAnnotations MongoDb([NotNull] this IMutableModel mutableModel) => new MongoDbModelAnnotations(Check.NotNull(mutableModel, nameof(mutableModel))); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Builders/MongoDbModelBuilderExtensions.cs ================================================ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Utilities; // ReSharper disable once CheckNamespace namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders { /// /// MongoDb-specific extension methods for . /// public static class MongoDbModelBuilderExtensions { /// /// Configures the database to use when connecting to MongoDb. /// /// The to configure. /// The name of the database. /// This , such that calls can be chained. public static ModelBuilder ForMongoDbFromDatabase(this ModelBuilder modelBuilder, string database) { Check.NotNull(modelBuilder, nameof(modelBuilder)); modelBuilder.Model.MongoDb().Database = database; return modelBuilder; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Conventions/BsonDiscriminatorAttributeConvention.cs ================================================ using Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson.Serialization.Attributes; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Conventions { /// public class BsonDiscriminatorAttributeConvention : EntityTypeAttributeConvention { /// public override InternalEntityTypeBuilder Apply(InternalEntityTypeBuilder entityTypeBuilder, BsonDiscriminatorAttribute attribute) { Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); Check.NotNull(attribute, nameof(attribute)); MongoDbEntityTypeAnnotations annotations = entityTypeBuilder.MongoDb(); if (!string.IsNullOrWhiteSpace(attribute.Discriminator)) { annotations.Discriminator = attribute.Discriminator; } if (!annotations.DiscriminatorIsRequired) { annotations.DiscriminatorIsRequired = attribute.Required; } annotations.IsRootType = attribute.RootClass; return entityTypeBuilder; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Conventions/BsonIgnoreAttributeConvention.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson.Serialization.Attributes; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Conventions { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public class BsonIgnoreAttributeConvention : IEntityTypeAddedConvention { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual InternalEntityTypeBuilder Apply(InternalEntityTypeBuilder entityTypeBuilder) { Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); Type clrType = entityTypeBuilder.Metadata.ClrType; if (clrType == null) { return entityTypeBuilder; } IEnumerable members = clrType.GetRuntimeProperties() .Cast() .Concat(clrType.GetRuntimeFields()) .Where(memberInfo => memberInfo.IsDefined(typeof(BsonIgnoreAttribute), true)); foreach (MemberInfo member in members) { entityTypeBuilder.Ignore(member.Name, ConfigurationSource.DataAnnotation); } return entityTypeBuilder; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Conventions/BsonKnownTypesAttributeConvention.cs ================================================ using System; using System.Reflection; using Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson.Serialization.Attributes; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Conventions { /// public class BsonKnownTypesAttributeConvention : EntityTypeAttributeConvention { /// public override InternalEntityTypeBuilder Apply(InternalEntityTypeBuilder entityTypeBuilder) { Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); var type = entityTypeBuilder.Metadata.ClrType; if (type == null || !Attribute.IsDefined(type, typeof(BsonKnownTypesAttribute), inherit: false)) { return entityTypeBuilder; } var attributes = type.GetTypeInfo().GetCustomAttributes(false); foreach (var attribute in attributes) { entityTypeBuilder = Apply(entityTypeBuilder, attribute); if (entityTypeBuilder == null) { break; } } return entityTypeBuilder; } /// public override InternalEntityTypeBuilder Apply( InternalEntityTypeBuilder entityTypeBuilder, BsonKnownTypesAttribute bsonKnownTypesAttribute) { MongoDbEntityTypeAnnotations annotations = entityTypeBuilder.MongoDb(); if (!annotations.DiscriminatorIsRequired) { annotations.DiscriminatorIsRequired = entityTypeBuilder.Metadata.IsAbstract(); } if (bsonKnownTypesAttribute.KnownTypes != null) { InternalModelBuilder modelBuilder = entityTypeBuilder.ModelBuilder; Type baseType = entityTypeBuilder.Metadata.ClrType; foreach (Type derivedType in bsonKnownTypesAttribute.KnownTypes) { if (!baseType.IsAssignableFrom(derivedType)) { throw new InvalidOperationException($"Known type {derivedType} declared on base type {baseType} does not inherit from base type."); } modelBuilder .Entity(derivedType, ConfigurationSource.DataAnnotation) .MongoDb() .IsDerivedType = true; } } return entityTypeBuilder; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Conventions/BsonRequiredAttributeConvention.cs ================================================ using System.Reflection; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson.Serialization.Attributes; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Conventions { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public class BsonRequiredAttributeConvention : PropertyAttributeConvention { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public override InternalPropertyBuilder Apply(InternalPropertyBuilder propertyBuilder, BsonRequiredAttribute attribute, MemberInfo clrMember) { Check.NotNull(propertyBuilder, nameof(propertyBuilder)); Check.NotNull(attribute, nameof(attribute)); Check.NotNull(clrMember, nameof(clrMember)); propertyBuilder.IsRequired(true, ConfigurationSource.DataAnnotation); return propertyBuilder; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Conventions/DocumentPropertyMappingValidationConvention.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Conventions { /// public class DocumentPropertyMappingValidationConvention : PropertyMappingValidationConvention { private readonly ITypeMappingSource _typeMappingSource; private readonly IMemberClassifier _memberClassifier; /// public DocumentPropertyMappingValidationConvention( [NotNull] ITypeMappingSource typeMappingSource, [NotNull] IMemberClassifier memberClassifier) : base( Check.NotNull(typeMappingSource, nameof(typeMappingSource)), Check.NotNull(memberClassifier, nameof(memberClassifier))) { _typeMappingSource = typeMappingSource; _memberClassifier = memberClassifier; } /// public override InternalModelBuilder Apply(InternalModelBuilder modelBuilder) { foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) { var unmappedProperty = entityType.GetProperties().FirstOrDefault(p => (!ConfigurationSource.Convention.Overrides(p.GetConfigurationSource()) || !p.IsShadowProperty) && !IsMappedPrimitiveProperty(p)); if (unmappedProperty != null) { throw new InvalidOperationException( CoreStrings.PropertyNotMapped( entityType.DisplayName(), unmappedProperty.Name, unmappedProperty.ClrType.ShortDisplayName())); } if (!entityType.HasClrType()) { continue; } var clrProperties = new HashSet(); clrProperties.UnionWith( entityType.GetRuntimeProperties().Values .Where(pi => pi.IsCandidateProperty()) .Select(pi => pi.Name)); clrProperties.ExceptWith(entityType.GetProperties().Select(p => p.Name)); clrProperties.ExceptWith(entityType.GetNavigations().Select(p => p.Name)); clrProperties.ExceptWith(entityType.GetServiceProperties().Select(p => p.Name)); clrProperties.RemoveWhere(p => entityType.Builder.IsIgnored(p, ConfigurationSource.Convention)); if (clrProperties.Count <= 0) { continue; } foreach (var clrProperty in clrProperties) { var actualProperty = entityType.GetRuntimeProperties()[clrProperty]; var propertyType = actualProperty.PropertyType; var targetSequenceType = propertyType.TryGetSequenceType(); if (modelBuilder.IsIgnored(modelBuilder.Metadata.GetDisplayName(propertyType), ConfigurationSource.Convention) || (targetSequenceType != null && modelBuilder.IsIgnored(modelBuilder.Metadata.GetDisplayName(targetSequenceType), ConfigurationSource.Convention))) { continue; } var targetType = base.FindCandidateNavigationPropertyType(actualProperty); var isTargetWeakOrOwned = targetType != null && (modelBuilder.Metadata.HasEntityTypeWithDefiningNavigation(targetType) || modelBuilder.Metadata.ShouldBeOwnedType(targetType)); if (targetType != null && targetType.IsValidEntityType() && (isTargetWeakOrOwned || modelBuilder.Metadata.FindEntityType(targetType) != null || targetType.GetRuntimeProperties().Any(p => p.IsCandidateProperty()))) { if ((!isTargetWeakOrOwned || !targetType.GetTypeInfo().Equals(entityType.ClrType.GetTypeInfo())) && entityType.GetDerivedTypes().All( dt => dt.FindDeclaredNavigation(actualProperty.Name) == null) && !entityType.IsInDefinitionPath(targetType)) { throw new InvalidOperationException( CoreStrings.NavigationNotAdded( entityType.DisplayName(), actualProperty.Name, propertyType.ShortDisplayName())); } } else if (targetSequenceType == null && propertyType.GetTypeInfo().IsInterface || targetSequenceType != null && targetSequenceType.GetTypeInfo().IsInterface) { throw new InvalidOperationException( CoreStrings.InterfacePropertyNotAdded( entityType.DisplayName(), actualProperty.Name, propertyType.ShortDisplayName())); } else { throw new InvalidOperationException( CoreStrings.PropertyNotAdded( entityType.DisplayName(), actualProperty.Name, propertyType.ShortDisplayName())); } } } return modelBuilder; } /// protected override bool IsMappedPrimitiveProperty([NotNull] IProperty property) => _typeMappingSource.FindMapping(property) != null; /// protected override Type FindCandidateNavigationPropertyType([NotNull] PropertyInfo propertyInfo) => _memberClassifier.FindCandidateNavigationPropertyType(propertyInfo); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Conventions/MongoCollectionAttributeConvention.cs ================================================ using Blueshift.EntityFrameworkCore.MongoDB.Annotations; using Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Conventions { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public class MongoCollectionAttributeConvention : EntityTypeAttributeConvention { /// public override InternalEntityTypeBuilder Apply( InternalEntityTypeBuilder entityTypeBuilder, MongoCollectionAttribute mongoCollectionAttribute) { Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); Check.NotNull(mongoCollectionAttribute, nameof(mongoCollectionAttribute)); entityTypeBuilder.MongoDb().CollectionName = mongoCollectionAttribute.CollectionName; return entityTypeBuilder; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Conventions/MongoDatabaseConvention.cs ================================================ using System.Reflection; using System.Text.RegularExpressions; using Blueshift.EntityFrameworkCore.MongoDB.Annotations; using Blueshift.EntityFrameworkCore.MongoDB.Infrastructure; using Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Conventions { /// /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public class MongoDatabaseConvention : IModelInitializedConvention { private readonly DbContext _dbContext; private readonly MongoDbOptionsExtension _mongoDbOptionsExtension; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public MongoDatabaseConvention([NotNull] DbContext dbContext) { _dbContext = Check.NotNull(dbContext, nameof(dbContext)); _mongoDbOptionsExtension = _dbContext .GetService() .ContextOptions .FindExtension(); } /// /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public InternalModelBuilder Apply(InternalModelBuilder modelBuilder) { Check.NotNull(modelBuilder, nameof(modelBuilder)); MongoDatabaseAttribute mongoDatabaseAttribute = _dbContext.GetType() .GetTypeInfo() .GetCustomAttribute(); string databaseName = _mongoDbOptionsExtension.DatabaseName ?? mongoDatabaseAttribute?.Database ?? GetDefaultDatabaseName(); Check.NotNull(modelBuilder, nameof(modelBuilder)) .MongoDb() .Database = databaseName; return modelBuilder; } private string GetDefaultDatabaseName() => MongoDbUtilities.ToLowerCamelCase(Regex.Replace(_dbContext.GetType().Name, "(?:Mongo)?(?:Db)?(?:Context)?$", "")); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Conventions/MongoDbDatabaseGeneratedAttributeConvention.cs ================================================ using System.ComponentModel.DataAnnotations.Schema; using System.Reflection; using Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Conventions { /// public class MongoDbDatabaseGeneratedAttributeConvention : DatabaseGeneratedAttributeConvention { /// public override InternalPropertyBuilder Apply( InternalPropertyBuilder propertyBuilder, DatabaseGeneratedAttribute attribute, MemberInfo clrMember) { if (attribute.DatabaseGeneratedOption == DatabaseGeneratedOption.Identity) { propertyBuilder.Metadata .DeclaringEntityType .MongoDb() .AssignIdOnInsert = true; } return base.Apply(propertyBuilder, attribute, clrMember); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Conventions/MongoDbKeyAttributeConvention.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; using Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson.Serialization.Attributes; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Conventions { /// public class MongoDbKeyAttributeConvention : KeyAttributeConvention { private static readonly KeyAttribute KeyAttribute = new KeyAttribute(); /// public override InternalPropertyBuilder Apply(InternalPropertyBuilder propertyBuilder) { Check.NotNull(propertyBuilder, nameof(propertyBuilder)); MemberInfo memberInfo = propertyBuilder.Metadata.GetIdentifyingMemberInfo(); return (memberInfo?.IsDefined(typeof(BsonIdAttribute), true) ?? false) ? Apply(propertyBuilder, KeyAttribute, memberInfo) : base.Apply(propertyBuilder); } /// public override InternalModelBuilder Apply(InternalModelBuilder modelBuilder) { IEnumerable entityTypes = modelBuilder.Metadata .GetEntityTypes() .Where(entityType => entityType.MongoDb().IsDerivedType); foreach (EntityType entityType in entityTypes) { foreach (Property declaredProperty in entityType.GetDeclaredProperties()) { if (declaredProperty.GetIdentifyingMemberInfo()?.IsDefined(typeof(BsonIdAttribute), true) ?? false) { throw new InvalidOperationException( CoreStrings.KeyAttributeOnDerivedEntity(entityType.DisplayName(), declaredProperty.Name)); } } } return base.Apply(modelBuilder); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Conventions/MongoDbRelationshipDiscoveryConvention.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Conventions { /// /// public class MongoDbRelationshipDiscoveryConvention : RelationshipDiscoveryConvention, IForeignKeyOwnershipChangedConvention { /// public MongoDbRelationshipDiscoveryConvention( [NotNull] IMemberClassifier memberClassifier, [NotNull] IDiagnosticsLogger logger) : base(memberClassifier, logger) { } /// InternalRelationshipBuilder IForeignKeyOwnershipChangedConvention.Apply(InternalRelationshipBuilder relationshipBuilder) { Apply(relationshipBuilder.Metadata.DeclaringEntityType.Builder); return relationshipBuilder; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/Conventions/OwnedDocumentConvention.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders; using Blueshift.EntityFrameworkCore.MongoDB.ValueGeneration; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata.Conventions { /// /// /// /// /// /// public class OwnedDocumentConvention : IBaseTypeChangedConvention, IEntityTypeAddedConvention, IForeignKeyAddedConvention, IKeyAddedConvention, IKeyRemovedConvention, IModelBuiltConvention { /// public InternalEntityTypeBuilder Apply(InternalEntityTypeBuilder entityTypeBuilder) { Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); EntityType entityType = entityTypeBuilder.Metadata; Key primaryKey = entityType.FindPrimaryKey(); bool isComplexType = primaryKey == null || primaryKey.Document().IsOwnershipKey; entityTypeBuilder.Document().IsComplexType = isComplexType; return entityTypeBuilder; } /// public bool Apply( InternalEntityTypeBuilder entityTypeBuilder, EntityType oldBaseType) { Apply(entityTypeBuilder); return true; } /// public InternalKeyBuilder Apply(InternalKeyBuilder keyBuilder) { Apply(Check.NotNull(keyBuilder, nameof(keyBuilder)).Metadata.DeclaringEntityType.Builder); return keyBuilder; } /// public void Apply(InternalEntityTypeBuilder entityTypeBuilder, Key key) { Apply(entityTypeBuilder); } /// public InternalRelationshipBuilder Apply(InternalRelationshipBuilder relationshipBuilder) { Check.NotNull(relationshipBuilder, nameof(relationshipBuilder)); IEntityType principalEntityType = relationshipBuilder.Metadata.PrincipalEntityType; bool principalIsOwned = principalEntityType.IsOwned() || principalEntityType.MongoDb().IsComplexType; if (principalIsOwned) { relationshipBuilder.IsOwnership(true, ConfigurationSource.Explicit); } return relationshipBuilder; } /// public InternalModelBuilder Apply(InternalModelBuilder modelBuilder) { IModel model = Check.NotNull(modelBuilder, nameof(modelBuilder)).Metadata; IEnumerable ownedEntityTypes = model .GetEntityTypes() .Where(entityType => entityType.IsOwned() || entityType.MongoDb().IsComplexType) .Select(entityType => entityType.AsEntityType()) .ToList(); foreach (EntityType ownedEntityType in ownedEntityTypes) { bool isOwnedType = ownedEntityType.HasClrType() ? model.ShouldBeOwnedType(ownedEntityType.ClrType) : model.ShouldBeOwnedType(ownedEntityType.Name); if (!isOwnedType) { if (ownedEntityType.HasClrType()) { modelBuilder.Owned(ownedEntityType.ClrType, ConfigurationSource.Convention); } else { modelBuilder.Owned(ownedEntityType.Name, ConfigurationSource.Convention); } } if (ownedEntityType.BaseType == null) { IKey primaryKey = ownedEntityType.FindPrimaryKey(); string ownershipKeyName = $"{ownedEntityType.ShortName()}Id"; if (primaryKey != null && primaryKey.Properties.Any(property => !property.IsShadowProperty)) { throw new InvalidOperationException( $"Owned entity type {ownedEntityType.Name} has a non-shadow primary key defined."); } if (primaryKey == null || !string.Equals(ownershipKeyName, primaryKey.Properties.First().Name)) { ownedEntityType.Builder .Property( ownershipKeyName, typeof(int?), ConfigurationSource.Convention) .HasValueGenerator( typeof(HashCodeValueGenerator), ConfigurationSource.Convention); ownedEntityType.Builder .PrimaryKey( new[] { ownershipKeyName }, ConfigurationSource.Convention) .IsDocumentOwnershipKey(true); } } } return modelBuilder; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/DocumentAnnotationNames.cs ================================================ namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public class DocumentAnnotationNames { private const string Prefix = "Document:"; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public const string IsComplexType = Prefix + nameof(IsComplexType); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public const string IsOwnershipKey = Prefix + nameof(IsOwnershipKey); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/DocumentAnnotations.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public class DocumentAnnotations where TAnnotatable : IAnnotatable { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// protected DocumentAnnotations([NotNull] TAnnotatable metadata) { Annotatable = Check.Is(metadata, nameof(metadata)); Metadata = Check.NotNull(metadata, nameof(metadata)); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual TAnnotatable Metadata { get; } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// protected virtual IMutableAnnotatable Annotatable { get; } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual T GetAnnotation([CanBeNull] string annotationName) => (T)Annotatable[Check.NullButNotEmpty(annotationName, nameof(annotationName))]; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual bool SetAnnotation([NotNull] string annotationName, [CanBeNull] T value) { Check.NotEmpty(annotationName, nameof(annotationName)); Annotatable[annotationName] = value; return true; } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual bool CanSetAnnotation([NotNull] string annotationName, [CanBeNull] object value) => true; } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/DocumentEntityTypeAnnotations.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Driver; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public class DocumentEntityTypeAnnotations : DocumentAnnotations { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public DocumentEntityTypeAnnotations([NotNull] IEntityType entityType) : base(entityType) { } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual bool IsComplexType { get => GetAnnotation(DocumentAnnotationNames.IsComplexType) ?? false; set => SetAnnotation(DocumentAnnotationNames.IsComplexType, value); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/DocumentKeyAnnotations.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public class DocumentKeyAnnotations : DocumentAnnotations { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public DocumentKeyAnnotations([NotNull] IKey key) : base(key) { } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual bool IsOwnershipKey { get => GetAnnotation(DocumentAnnotationNames.IsOwnershipKey) ?? false; set => SetAnnotation(DocumentAnnotationNames.IsOwnershipKey, value); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/MongoDbAnnotationNames.cs ================================================ namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public class MongoDbAnnotationNames { private const string Prefix = "MongoDb:"; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public const string CollectionName = Prefix + nameof(CollectionName); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public const string CollectionSettings = Prefix + nameof(CollectionSettings); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public const string Database = Prefix + nameof(Database); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public const string DatabaseSettings = Prefix + nameof(DatabaseSettings); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public const string Discriminator = Prefix + nameof(Discriminator); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public const string DiscriminatorIsRequired = Prefix + nameof(DiscriminatorIsRequired); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public const string IsDerivedType = Prefix + nameof(IsDerivedType); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public const string IsRootType = Prefix + nameof(IsRootType); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public const string Namespace = Prefix + nameof(Namespace); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public const string NavigationName = Prefix + nameof(NavigationName); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/MongoDbEntityTypeAnnotations.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Driver; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata { /// public class MongoDbEntityTypeAnnotations : DocumentEntityTypeAnnotations { /// public MongoDbEntityTypeAnnotations([NotNull] IEntityType entityType) : base(entityType) { } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual bool AssignIdOnInsert { get => CollectionSettings?.AssignIdOnInsert ?? false; set => GetOrCreateCollectionSettings().AssignIdOnInsert = value; } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual string CollectionName { get => GetAnnotation(MongoDbAnnotationNames.CollectionName) ?? MongoDbUtilities.Pluralize(MongoDbUtilities.ToLowerCamelCase(Metadata.ClrType.Name)); [param: NotNull] set => SetAnnotation(MongoDbAnnotationNames.CollectionName, Check.NotEmpty(value, nameof(CollectionName))); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual MongoCollectionSettings CollectionSettings { get => GetAnnotation(MongoDbAnnotationNames.CollectionSettings); [param: NotNull] set => SetAnnotation(MongoDbAnnotationNames.CollectionSettings, Check.NotNull(value, nameof(CollectionSettings))); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual string Discriminator { get => GetAnnotation(MongoDbAnnotationNames.Discriminator) ?? Metadata.ClrType.Name; [param: NotNull] set => SetAnnotation(MongoDbAnnotationNames.Discriminator, Check.NotEmpty(value, nameof(Discriminator))); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual bool DiscriminatorIsRequired { get => GetAnnotation(MongoDbAnnotationNames.DiscriminatorIsRequired) ?? false; set => SetAnnotation(MongoDbAnnotationNames.DiscriminatorIsRequired, value); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual bool IsDerivedType { get => GetAnnotation(MongoDbAnnotationNames.IsDerivedType) ?? false; set => SetAnnotation(MongoDbAnnotationNames.IsDerivedType, value); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual bool IsRootType { get => GetAnnotation(MongoDbAnnotationNames.IsRootType) ?? false; set => SetAnnotation(MongoDbAnnotationNames.IsRootType, value); } private MongoCollectionSettings GetOrCreateCollectionSettings() => CollectionSettings ?? (CollectionSettings = new MongoCollectionSettings()); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Metadata/MongoDbModelAnnotations.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Driver; namespace Blueshift.EntityFrameworkCore.MongoDB.Metadata { /// /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public class MongoDbModelAnnotations : DocumentAnnotations { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public MongoDbModelAnnotations([NotNull] IModel model) : base(model) { } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual string Database { get => GetAnnotation(MongoDbAnnotationNames.Database); [param: NotNull] set => SetAnnotation(MongoDbAnnotationNames.Database, Check.NotEmpty(value, nameof(Database))); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual MongoDatabaseSettings DatabaseSettings { get => GetAnnotation(MongoDbAnnotationNames.DatabaseSettings); [param: NotNull] set => SetAnnotation(MongoDbAnnotationNames.DatabaseSettings, Check.NotNull(value, nameof(DatabaseSettings))); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/MethodHelper.cs ================================================ using System; using System.Linq.Expressions; using System.Reflection; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB { /// /// Provides utility methods for retrieving method info. /// public static class MethodHelper { /// /// Gets a generic method definition from a given delegate. /// /// An expression representing a given method call. /// The generic method definition for the method in public static MethodInfo GetMethodInfo(Expression delegateExpression) => ((MethodCallExpression)delegateExpression.Body) .Method; /// /// Gets a generic method definition from a given delegate. /// /// The type of the delegate's target. /// An expression representing a given method call. /// The generic method definition for the method in public static MethodInfo GetMethodInfo(Expression> delegateExpression) => ((MethodCallExpression)delegateExpression.Body) .Method; /// /// Gets a generic method definition from a given delegate. /// /// An expression representing a given method call. /// The generic method definition for the method in public static MethodInfo GetGenericMethodDefinition(Expression delegateExpression) => ((MethodCallExpression)delegateExpression.Body) .Method .GetGenericMethodDefinition(); /// /// Gets a generic method definition from a given delegate. /// /// The type of the delegate's return value. /// An expression representing a given method call. /// The generic method definition for the method in public static MethodInfo GetGenericMethodDefinition(Expression> delegateExpression) => ((MethodCallExpression)delegateExpression.Body) .Method .GetGenericMethodDefinition(); /// /// Gets a generic method definition from a given delegate. /// /// The type of item on which the delegate is called. /// The type of the delegate's return value. /// An expression representing a given method call. /// The generic method definition for the method in public static MethodInfo GetGenericMethodDefinition(Expression> delegateExpression) => ((MethodCallExpression)delegateExpression.Body) .Method .GetGenericMethodDefinition(); /// /// Gets a constructor from a given delegate expression. /// /// An for a delegate that returns a new object. /// The type of item being constructed. /// The for the constructor referenced in public static ConstructorInfo GetConstructor(Expression> newExpression) => Check.Is(newExpression?.Body, nameof(newExpression)).Constructor; } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/MongoDbUtilities.cs ================================================ using System; using System.Text.RegularExpressions; using Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Utilities; // ReSharper disable once CheckNamespace namespace Blueshift.EntityFrameworkCore.MongoDB { /// /// A set of utilities to assist with MongoDb values. /// public static class MongoDbUtilities { private static readonly Regex LeadingUppercaseRegex = new Regex(pattern: "^(([A-Z](?![a-z]))+|([A-Z](?=[a-z])))", options: RegexOptions.Compiled); private static readonly Regex SingularRegex = new Regex(pattern: "(ey|.)(? /// Converts the first character or group of capital characters of a string to lower camel case. /// /// The string to convert to lower camel case. /// A string containing a lower camel case version of the original . public static string ToLowerCamelCase([NotNull] string value) => LeadingUppercaseRegex.Replace( Check.NotNull(value, nameof(value)), match => match.Value.ToLower()); /// /// Pluralizes a string by replacing any trailing 'y' with 'ies'. /// /// The string to pluralize. /// A pluralized version of the original . public static string Pluralize([NotNull] string value) => SingularRegex.Replace( Check.NotNull(value, nameof(value)), match => string.Equals(a: "y", b: match.Value, comparisonType: StringComparison.OrdinalIgnoreCase) ? "ies" : $"{match.Value}s"); /// /// Determines whether or not the current represents a MongoDB root document. /// /// The current . /// true if represents a root document; or false. public static bool IsDocumentRootEntityType(this IEntityType entityType) { Check.NotNull(entityType, nameof(entityType)); while (entityType.MongoDb().IsDerivedType && entityType.BaseType != null) { entityType = entityType.BaseType; } return !entityType.IsOwned(); } /// /// Gets the that represents the least-derived, non-abstract base representation of a given class. /// /// The to start from. /// The least-derived, non-abstract root document type for . public static IEntityType GetMongoDbCollectionEntityType(this IEntityType entityType) { Check.NotOwned(entityType, nameof(entityType)); while (entityType.MongoDb().IsDerivedType && entityType.BaseType != null) { entityType = entityType.BaseType; } return entityType; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/ObjectIdTypeConverter.cs ================================================ using System; using System.ComponentModel; using System.Globalization; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson; namespace Blueshift.EntityFrameworkCore.MongoDB { /// /// Provides methods for converting instances to other types. /// public class ObjectIdTypeConverter : TypeConverter { /// /// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context. /// /// An that provides a format context. /// A that represents the type you want to convert from. /// true if the value can be converted; otherwise false. public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => (sourceType == typeof(string) || base.CanConvertFrom(context, sourceType)); /// /// Converts the given object to the type of this converter, using the specified context and culture information. /// /// An that provides a format context. /// The to use as the current culture. /// The object to convert. /// A new represented by . public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (!CanConvertFrom(context, Check.NotNull(value, nameof(value)).GetType())) { throw new InvalidOperationException($"Cannot convert {value} to {typeof(ObjectId)}."); } return ObjectId.Parse(value as string); } /// /// Returns whether this converter can convert the object to the specified type, using the specified context. /// /// An that provides a format context. /// A that represents the type you want to convert to. /// true if the value can be converted; otherwise false. public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) => (destinationType == typeof(string)) || base.CanConvertTo(context, destinationType); /// /// Converts the given value object to the specified type, using the specified context and culture info. /// /// An that provides a format context. /// The to use as the current culture. /// The object to convert. /// The type to convert the value parameter to. /// A new instance of the specified that represents . public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { if (!CanConvertTo(context, destinationType)) { throw new InvalidOperationException($"Cannot convert {value} to {destinationType}."); } return value.ToString(); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Properties/AssemblyInfo.cs ================================================ // Copyright (c) Blueshift Software, LLC. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Reflection; using System.Resources; [assembly: NeutralResourcesLanguage("en-US")] [assembly: AssemblyMetadata("Serviceable", "True")] ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Properties/Blueshift.EntityFrameworkCore.MongoDB.rd.xml ================================================  ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Properties/DocumentDbStrings.Designer.cs ================================================ // using System.Reflection; using System.Resources; using JetBrains.Annotations; namespace Microsoft.EntityFrameworkCore.Cosmos.Internal { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public static class DocumentDbStrings { private static readonly ResourceManager _resourceManager = new ResourceManager( "Blueshift.EntityFrameworkCore.MongoDb.Properties.CosmosStrings", typeof(DocumentDbStrings).GetTypeInfo().Assembly); /// /// The entity of type '{entityType}' is mapped as a part of the document mapped to '{missingEntityType}', but there is no tracked entity of this type with the corresponding key value. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values. /// public static string OrphanedNestedDocument( [CanBeNull] object entityType, [CanBeNull] object missingEntityType) => string.Format( GetString("OrphanedNestedDocument", nameof(entityType), nameof(missingEntityType)), entityType, missingEntityType); /// /// The entity of type '{entityType}' is mapped as a part of the document mapped to '{missingEntityType}', but there is no tracked entity of this type with the key value '{keyValue}'. /// public static string OrphanedNestedDocumentSensitive( [CanBeNull] object entityType, [CanBeNull] object missingEntityType, [CanBeNull] object keyValue) => string.Format( GetString("OrphanedNestedDocumentSensitive", nameof(entityType), nameof(missingEntityType), nameof(keyValue)), entityType, missingEntityType, keyValue); /// /// The entity of type '{entityType}' cannot be queried directly because it is mapped as a part of the document mapped to '{principalEntityType}'. Rewrite the query to start with '{principalEntityType}'. /// public static string QueryRootNestedEntityType( [CanBeNull] object entityType, [CanBeNull] object principalEntityType) => string.Format( GetString("QueryRootNestedEntityType", nameof(entityType), nameof(principalEntityType)), entityType, principalEntityType); /// /// No matching discriminator values where found for this instance of '{entityType}'. /// public static string UnableToDiscriminate([CanBeNull] object entityType) => string.Format( GetString("UnableToDiscriminate", nameof(entityType)), entityType); private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); for (var i = 0; i < formatterNames.Length; i++) { value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); } return value; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Properties/DocumentDbStrings.cs ================================================  ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Properties/DocumentDbStrings.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 The entity of type '{entityType}' is mapped as a part of the document mapped to '{missingEntityType}', but there is no tracked entity of this type with the corresponding key value. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values. The entity of type '{entityType}' is mapped as a part of the document mapped to '{missingEntityType}', but there is no tracked entity of this type with the key value '{keyValue}'. The entity of type '{entityType}' cannot be queried directly because it is mapped as a part of the document mapped to '{principalEntityType}'. Rewrite the query to start with '{principalEntityType}'. No matching discriminator values where found for this instance of '{entityType}'. ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Properties/DocumentDbStrings.tt ================================================ <# Session["ResourceFile"] = "DocumentDbStrings.resx"; Session["NoDiagnostics"] = true; #> ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/EntityLoadInfoFactory.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Query { /// public class EntityLoadInfoFactory : IEntityLoadInfoFactory { [NotNull] private readonly ICurrentDbContext _currentDbContext; [NotNull] private readonly IValueBufferFactory _valueBufferFactory; /// /// Initializes a new instance of . /// /// Used to get the current instance. /// An that can be used to create /// for loading documents. public EntityLoadInfoFactory( [NotNull] ICurrentDbContext currentDbContext, [NotNull] IValueBufferFactory valueBufferFactory) { _currentDbContext = Check.NotNull(currentDbContext, nameof(currentDbContext)); _valueBufferFactory = Check.NotNull(valueBufferFactory, nameof(valueBufferFactory)); } /// public EntityLoadInfo Create(object document, IEntityType entityType, object owner, INavigation owningNavigation) { Check.NotNull(document, nameof(document)); Check.NotNull(entityType, nameof(entityType)); if (document.GetType() != entityType.ClrType) { Check.IsInstanceOfType(document, entityType.ClrType, nameof(document)); entityType = entityType.Model.FindEntityType(document.GetType()); } return new EntityLoadInfo( new MaterializationContext( _valueBufferFactory.CreateFromInstance( document, entityType, owner, owningNavigation), _currentDbContext.Context), materializationContext => document, null); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/ExpressionVisitors/DocumentNavigationRewritingExpressionVisitor.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Extensions.Internal; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.Expressions.Internal; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Query.ResultOperators.Internal; using Microsoft.EntityFrameworkCore.Utilities; using Remotion.Linq; using Remotion.Linq.Clauses; using Remotion.Linq.Clauses.Expressions; using Remotion.Linq.Clauses.ExpressionVisitors; using Remotion.Linq.Clauses.ResultOperators; namespace Blueshift.EntityFrameworkCore.MongoDB.Query.ExpressionVisitors { /// public class DocumentNavigationRewritingExpressionVisitor : NavigationRewritingExpressionVisitor { private readonly EntityQueryModelVisitor _queryModelVisitor; private readonly NavigationJoins _navigationJoins = new NavigationJoins(); private readonly DocumentNavigationRewritingQueryModelVisitor _documentNavigationRewritingQueryModelVisitor; private QueryModel _queryModel; private QueryModel _parentQueryModel; private bool _insideInnerKeySelector; private bool _insideOrderBy; private bool _insideMaterializeCollectionNavigation; private class NavigationJoins : IEnumerable { private readonly Dictionary _navigationJoins = new Dictionary(); public void Add(NavigationJoin navigationJoin) { _navigationJoins.TryGetValue(navigationJoin, out int count); _navigationJoins[navigationJoin] = ++count; } public bool Remove(NavigationJoin navigationJoin) { if (_navigationJoins.TryGetValue(navigationJoin, out int count)) { if (count > 1) { _navigationJoins[navigationJoin] = --count; } else { _navigationJoins.Remove(navigationJoin); } return true; } return false; } public IEnumerator GetEnumerator() => _navigationJoins.Keys.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _navigationJoins.Keys.GetEnumerator(); } private class NavigationJoin { public static void RemoveNavigationJoin( NavigationJoins navigationJoins, NavigationJoin navigationJoin) { if (!navigationJoins.Remove(navigationJoin)) { foreach (NavigationJoin nj in navigationJoins) { nj.Children.Remove(navigationJoin); } } } public NavigationJoin( IQuerySource querySource, INavigation navigation, JoinClause joinClause, IEnumerable additionalBodyClauses, bool dependentToPrincipal, QuerySourceReferenceExpression querySourceReferenceExpression) : this( querySource, navigation, joinClause, null, additionalBodyClauses, dependentToPrincipal, querySourceReferenceExpression) { } public NavigationJoin( IQuerySource querySource, INavigation navigation, GroupJoinClause groupJoinClause, IEnumerable additionalBodyClauses, bool dependentToPrincipal, QuerySourceReferenceExpression querySourceReferenceExpression) : this( querySource, navigation, null, groupJoinClause, additionalBodyClauses, dependentToPrincipal, querySourceReferenceExpression) { } private NavigationJoin( IQuerySource querySource, INavigation navigation, JoinClause joinClause, GroupJoinClause groupJoinClause, IEnumerable additionalBodyClauses, bool dependentToPrincipal, QuerySourceReferenceExpression querySourceReferenceExpression) { QuerySource = querySource; Navigation = navigation; JoinClause = joinClause; GroupJoinClause = groupJoinClause; AdditionalBodyClauses = additionalBodyClauses; DependentToPrincipal = dependentToPrincipal; QuerySourceReferenceExpression = querySourceReferenceExpression; } public IQuerySource QuerySource { get; } public INavigation Navigation { get; } public JoinClause JoinClause { get; } public GroupJoinClause GroupJoinClause { get; } public bool DependentToPrincipal { get; } public QuerySourceReferenceExpression QuerySourceReferenceExpression { get; } public readonly NavigationJoins Children = new NavigationJoins(); private IEnumerable AdditionalBodyClauses { get; } private bool IsInserted { get; set; } public IEnumerable Iterate() { yield return this; foreach (NavigationJoin navigationJoin in Children.SelectMany(nj => nj.Iterate())) { yield return navigationJoin; } } public void Insert(QueryModel queryModel) { int insertionIndex = 0; if (QuerySource is IBodyClause bodyClause) { insertionIndex = queryModel.BodyClauses.IndexOf(bodyClause) + 1; } if (queryModel.MainFromClause == QuerySource || insertionIndex > 0) { foreach (NavigationJoin nj in Iterate()) { nj.Insert(queryModel, ref insertionIndex); } } } private void Insert(QueryModel queryModel, ref int insertionIndex) { if (IsInserted) { return; } queryModel.BodyClauses.Insert(insertionIndex++, JoinClause ?? (IBodyClause)GroupJoinClause); foreach (IBodyClause additionalBodyClause in AdditionalBodyClauses) { queryModel.BodyClauses.Insert(insertionIndex++, additionalBodyClause); } IsInserted = true; } } /// public DocumentNavigationRewritingExpressionVisitor([NotNull] EntityQueryModelVisitor queryModelVisitor) : this(queryModelVisitor, navigationExpansionSubquery: false) { } /// public DocumentNavigationRewritingExpressionVisitor( [NotNull] EntityQueryModelVisitor queryModelVisitor, bool navigationExpansionSubquery) : base( Check.NotNull(queryModelVisitor, nameof(queryModelVisitor)), navigationExpansionSubquery) { Check.NotNull(queryModelVisitor, nameof(queryModelVisitor)); _queryModelVisitor = queryModelVisitor; _documentNavigationRewritingQueryModelVisitor = new DocumentNavigationRewritingQueryModelVisitor(this, _queryModelVisitor, navigationExpansionSubquery); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// protected virtual bool ShouldRewrite(INavigation navigation) => !navigation.ForeignKey.IsOwnership; /// public override void Rewrite(QueryModel queryModel, QueryModel parentQueryModel) { _queryModel = queryModel; _parentQueryModel = parentQueryModel; _documentNavigationRewritingQueryModelVisitor.VisitQueryModel(_queryModel); foreach (NavigationJoin navigationJoin in _navigationJoins) { navigationJoin.Insert(_queryModel); } if (parentQueryModel != null) { _queryModel = parentQueryModel; } } /// protected override Expression VisitUnary(UnaryExpression node) { Expression newOperand = Visit(node.Operand); return node.NodeType == ExpressionType.Convert && newOperand.Type == node.Type ? newOperand : node.Update(newOperand); } /// protected override Expression VisitSubQuery(SubQueryExpression expression) { bool oldInsideInnerKeySelector = _insideInnerKeySelector; _insideInnerKeySelector = false; Rewrite(expression.QueryModel, _queryModel); _insideInnerKeySelector = oldInsideInnerKeySelector; return expression; } /// protected override Expression VisitBinary(BinaryExpression node) { Expression newLeft = Visit(node.Left); Expression newRight = Visit(node.Right); if (newLeft == node.Left && newRight == node.Right) { return node; } NavigationJoin leftNavigationJoin = _navigationJoins .SelectMany(nj => nj.Iterate()) .FirstOrDefault(nj => ReferenceEquals(nj.QuerySourceReferenceExpression, newLeft)); NavigationJoin rightNavigationJoin = _navigationJoins .SelectMany(nj => nj.Iterate()) .FirstOrDefault(nj => ReferenceEquals(nj.QuerySourceReferenceExpression, newRight)); JoinClause leftJoin = leftNavigationJoin?.JoinClause ?? leftNavigationJoin?.GroupJoinClause?.JoinClause; JoinClause rightJoin = rightNavigationJoin?.JoinClause ?? rightNavigationJoin?.GroupJoinClause?.JoinClause; if (leftNavigationJoin?.Navigation.GetTargetType().IsOwned() == false) { if (newRight.IsNullConstantExpression()) { if (leftNavigationJoin.DependentToPrincipal) { newLeft = leftJoin?.OuterKeySelector; NavigationJoin.RemoveNavigationJoin(_navigationJoins, leftNavigationJoin); if (newLeft != null && IsCompositeKey(newLeft.Type)) { newRight = CreateNullCompositeKey(newLeft); } } } else { newLeft = leftJoin?.InnerKeySelector; } } if (rightNavigationJoin?.Navigation.GetTargetType().IsOwned() == false) { if (newLeft.IsNullConstantExpression()) { if (rightNavigationJoin.DependentToPrincipal) { newRight = rightJoin?.OuterKeySelector; NavigationJoin.RemoveNavigationJoin(_navigationJoins, rightNavigationJoin); if (newRight != null && IsCompositeKey(newRight.Type)) { newLeft = CreateNullCompositeKey(newRight); } } } else { newRight = rightJoin?.InnerKeySelector; } } if (node.NodeType != ExpressionType.ArrayIndex && node.NodeType != ExpressionType.Coalesce && newLeft != null && newRight != null && newLeft.Type != newRight.Type) { if (newLeft.Type.IsNullableType() && !newRight.Type.IsNullableType()) { newRight = Expression.Convert(newRight, newLeft.Type); } else if (!newLeft.Type.IsNullableType() && newRight.Type.IsNullableType()) { newLeft = Expression.Convert(newLeft, newRight.Type); } } return Expression.MakeBinary(node.NodeType, newLeft, newRight, node.IsLiftedToNull, node.Method); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// protected override Expression VisitConditional(ConditionalExpression node) { Expression test = Visit(node.Test); if (test.Type == typeof(bool?)) { test = Expression.Equal(test, Expression.Constant(true, typeof(bool?))); } Expression ifTrue = Visit(node.IfTrue); Expression ifFalse = Visit(node.IfFalse); if (ifTrue.Type.IsNullableType() && !ifFalse.Type.IsNullableType()) { ifFalse = Expression.Convert(ifFalse, ifTrue.Type); } if (ifFalse.Type.IsNullableType() && !ifTrue.Type.IsNullableType()) { ifTrue = Expression.Convert(ifTrue, ifFalse.Type); } return test != node.Test || ifTrue != node.IfTrue || ifFalse != node.IfFalse ? Expression.Condition(test, ifTrue, ifFalse) : node; } private static NewExpression CreateNullCompositeKey(Expression otherExpression) => Expression.New( AnonymousObject.AnonymousObjectCtor, Expression.NewArrayInit( typeof(object), Enumerable.Repeat( Expression.Constant(null), ((NewArrayExpression)((NewExpression)otherExpression).Arguments.Single()).Expressions.Count))); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// protected override Expression VisitMember(MemberExpression node) { Check.NotNull(node, nameof(node)); Expression result = _queryModelVisitor.BindNavigationPathPropertyExpression( node, (ps, qs) => { return qs != null ? RewriteNavigationProperties( ps, qs, node, node.Expression, node.Member.Name, node.Type, e => e.MakeMemberAccess(node.Member), e => new NullConditionalExpression(e, e.MakeMemberAccess(node.Member))) : null; }); if (result != null) { return result; } Expression newExpression = Visit(node.Expression); MemberExpression newMemberExpression = newExpression != node.Expression ? newExpression.MakeMemberAccess(node.Member) : node; result = NeedsNullCompensation(newExpression) ? (Expression)new NullConditionalExpression( newExpression, newMemberExpression) : newMemberExpression; return result.Type == typeof(bool?) && node.Type == typeof(bool) ? Expression.Equal(result, Expression.Constant(true, typeof(bool?))) : result; } private readonly Dictionary _nullCompensationNecessityMap = new Dictionary(); private bool NeedsNullCompensation(Expression expression) { if (expression is QuerySourceReferenceExpression qsre) { if (_nullCompensationNecessityMap.TryGetValue(qsre, out bool result)) { return result; } SubQueryExpression subQuery = (qsre.ReferencedQuerySource as FromClauseBase)?.FromExpression as SubQueryExpression ?? (qsre.ReferencedQuerySource as JoinClause)?.InnerSequence as SubQueryExpression; // if qsre is pointing to a subquery, look for DefaulIfEmpty result operators inside // if such operator is found then we need to add null-compensation logic // unless the query model has a GroupBy operator - qsre coming from groupby can never be null if (subQuery != null && !(subQuery.QueryModel.ResultOperators.LastOrDefault() is GroupResultOperator)) { ContainsDefaultIfEmptyCheckingVisitor containsDefaultIfEmptyChecker = new ContainsDefaultIfEmptyCheckingVisitor(); containsDefaultIfEmptyChecker.VisitQueryModel(subQuery.QueryModel); if (!containsDefaultIfEmptyChecker.ContainsDefaultIfEmpty) { subQuery.QueryModel.TransformExpressions( e => new TransformingQueryModelExpressionVisitor(containsDefaultIfEmptyChecker).Visit(e)); } _nullCompensationNecessityMap[qsre] = containsDefaultIfEmptyChecker.ContainsDefaultIfEmpty; return containsDefaultIfEmptyChecker.ContainsDefaultIfEmpty; } _nullCompensationNecessityMap[qsre] = false; } return false; } private class ContainsDefaultIfEmptyCheckingVisitor : QueryModelVisitorBase { public bool ContainsDefaultIfEmpty { get; private set; } public override void VisitResultOperator(ResultOperatorBase resultOperator, QueryModel queryModel, int index) { if (resultOperator is DefaultIfEmptyResultOperator) { ContainsDefaultIfEmpty = true; } } } /// protected override MemberAssignment VisitMemberAssignment(MemberAssignment node) { Expression newExpression = CompensateForNullabilityDifference( Visit(node.Expression), node.Expression.Type); return node.Update(newExpression); } /// protected override ElementInit VisitElementInit(ElementInit node) { List originalArgumentTypes = node.Arguments.Select(a => a.Type).ToList(); List newArguments = VisitAndConvert(node.Arguments, nameof(VisitElementInit)).ToList(); for (int i = 0; i < newArguments.Count; i++) { newArguments[i] = CompensateForNullabilityDifference(newArguments[i], originalArgumentTypes[i]); } return node.Update(newArguments); } /// protected override Expression VisitNewArray(NewArrayExpression node) { List originalExpressionTypes = node.Expressions.Select(e => e.Type).ToList(); List newExpressions = VisitAndConvert(node.Expressions, nameof(VisitNewArray)).ToList(); for (int i = 0; i < newExpressions.Count; i++) { newExpressions[i] = CompensateForNullabilityDifference(newExpressions[i], originalExpressionTypes[i]); } return node.Update(newExpressions); } /// protected override Expression VisitMethodCall(MethodCallExpression node) { Check.NotNull(node, nameof(node)); if (node.Method.IsEFPropertyMethod()) { Expression result = _queryModelVisitor.BindNavigationPathPropertyExpression( node, (ps, qs) => { return qs != null ? RewriteNavigationProperties( ps, qs, node, node.Arguments[0], (string)((ConstantExpression)node.Arguments[1]).Value, node.Type, e => node.Arguments[0].Type != e.Type ? Expression.Call(node.Method, Expression.Convert(e, node.Arguments[0].Type), node.Arguments[1]) : Expression.Call(node.Method, e, node.Arguments[1]), e => node.Arguments[0].Type != e.Type ? new NullConditionalExpression( Expression.Convert(e, node.Arguments[0].Type), Expression.Call(node.Method, Expression.Convert(e, node.Arguments[0].Type), node.Arguments[1])) : new NullConditionalExpression(e, Expression.Call(node.Method, e, node.Arguments[1]))) : null; }); if (result != null) { return result; } List propertyArguments = VisitAndConvert(node.Arguments, nameof(VisitMethodCall)).ToList(); MethodCallExpression newPropertyExpression = propertyArguments[0] != node.Arguments[0] || propertyArguments[1] != node.Arguments[1] ? Expression.Call(node.Method, propertyArguments[0], node.Arguments[1]) : node; result = NeedsNullCompensation(propertyArguments[0]) ? (Expression)new NullConditionalExpression(propertyArguments[0], newPropertyExpression) : newPropertyExpression; return result.Type == typeof(bool?) && node.Type == typeof(bool) ? Expression.Equal(result, Expression.Constant(true, typeof(bool?))) : result; } bool insideMaterializeCollectionNavigation = _insideMaterializeCollectionNavigation; if (node.Method.MethodIsClosedFormOf(CollectionNavigationSubqueryInjector.MaterializeCollectionNavigationMethodInfo)) { _insideMaterializeCollectionNavigation = true; } Expression newObject = Visit(node.Object); List newArguments = VisitAndConvert(node.Arguments, nameof(VisitMethodCall)).ToList(); for (int i = 0; i < newArguments.Count; i++) { if (newArguments[i].Type != node.Arguments[i].Type) { if (newArguments[i] is NullConditionalExpression nullConditionalArgument) { newArguments[i] = nullConditionalArgument.AccessOperation; } if (newArguments[i].Type != node.Arguments[i].Type) { newArguments[i] = Expression.Convert(newArguments[i], node.Arguments[i].Type); } } } if (newObject != node.Object) { if (newObject is NullConditionalExpression nullConditionalExpression) { MethodCallExpression newMethodCallExpression = node.Update(nullConditionalExpression.AccessOperation, newArguments); return new NullConditionalExpression(newObject, newMethodCallExpression); } } MethodCallExpression newExpression = node.Update(newObject, newArguments); if (node.Method.MethodIsClosedFormOf(CollectionNavigationSubqueryInjector.MaterializeCollectionNavigationMethodInfo)) { _insideMaterializeCollectionNavigation = insideMaterializeCollectionNavigation; } return newExpression; } private Expression RewriteNavigationProperties( IReadOnlyList properties, IQuerySource querySource, Expression expression, Expression declaringExpression, string propertyName, Type propertyType, Func propertyCreator, Func conditionalAccessPropertyCreator) { List navigations = properties.OfType().ToList(); if (navigations.Count > 0) { QuerySourceReferenceExpression outerQuerySourceReferenceExpression = new QuerySourceReferenceExpression(querySource); AdditionalFromClause additionalFromClauseBeingProcessed = _documentNavigationRewritingQueryModelVisitor.AdditionalFromClauseBeingProcessed; if (additionalFromClauseBeingProcessed != null && navigations.Last().IsCollection() && !_insideMaterializeCollectionNavigation) { if (additionalFromClauseBeingProcessed.FromExpression is SubQueryExpression fromSubqueryExpression) { if (fromSubqueryExpression.QueryModel.SelectClause.Selector is QuerySourceReferenceExpression) { return RewriteSelectManyInsideSubqueryIntoJoins( fromSubqueryExpression, outerQuerySourceReferenceExpression, navigations, additionalFromClauseBeingProcessed); } } else { return RewriteSelectManyNavigationsIntoJoins( outerQuerySourceReferenceExpression, navigations, additionalFromClauseBeingProcessed); } } if (navigations.Count == 1 && navigations[0].IsDependentToPrincipal()) { Expression foreignKeyMemberAccess = TryCreateForeignKeyMemberAccess(propertyName, declaringExpression, navigations[0]); if (foreignKeyMemberAccess != null) { return foreignKeyMemberAccess; } } if (_insideInnerKeySelector && !_insideMaterializeCollectionNavigation) { return CreateSubqueryForNavigations( outerQuerySourceReferenceExpression, properties, propertyCreator); } Expression navigationResultExpression = RewriteNavigationsIntoJoins( outerQuerySourceReferenceExpression, navigations, properties.Count == navigations.Count ? null : propertyType, propertyCreator, conditionalAccessPropertyCreator); if (navigationResultExpression is QuerySourceReferenceExpression resultQsre) { foreach (IncludeResultOperator includeResultOperator in _queryModelVisitor.QueryCompilationContext.QueryAnnotations .OfType() .Where(o => ExpressionEqualityComparer.Instance.Equals(o.PathFromQuerySource, expression))) { includeResultOperator.PathFromQuerySource = resultQsre; includeResultOperator.QuerySource = resultQsre.ReferencedQuerySource; } } return navigationResultExpression; } return default; } private class QsreWithNavigationFindingExpressionVisitor : ExpressionVisitorBase { private readonly QuerySourceReferenceExpression _searchedQsre; private readonly INavigation _navigation; private bool _navigationFound; public QsreWithNavigationFindingExpressionVisitor([NotNull] QuerySourceReferenceExpression searchedQsre, [NotNull] INavigation navigation) { _searchedQsre = searchedQsre; _navigation = navigation; _navigationFound = false; SearchedQsreFound = false; } public bool SearchedQsreFound { get; private set; } protected override Expression VisitMember(MemberExpression node) { if (!_navigationFound && node.Member.Name == _navigation.Name) { _navigationFound = true; return base.VisitMember(node); } _navigationFound = false; return node; } protected override Expression VisitMethodCall(MethodCallExpression node) { if (node.Method.IsEFPropertyMethod() && !_navigationFound && (string)((ConstantExpression)node.Arguments[1]).Value == _navigation.Name) { _navigationFound = true; return base.VisitMethodCall(node); } _navigationFound = false; return node; } protected override Expression VisitQuerySourceReference(QuerySourceReferenceExpression expression) { if (_navigationFound && expression.ReferencedQuerySource == _searchedQsre.ReferencedQuerySource) { SearchedQsreFound = true; } else { _navigationFound = false; } return expression; } } private Expression TryCreateForeignKeyMemberAccess(string propertyName, Expression declaringExpression, INavigation navigation) { bool canPerformOptimization = true; if (_insideOrderBy) { QuerySourceReferenceExpression qsre = (declaringExpression as MemberExpression)?.Expression as QuerySourceReferenceExpression; if (qsre == null) { if (declaringExpression is MethodCallExpression methodCallExpression && methodCallExpression.Method.IsEFPropertyMethod()) { qsre = methodCallExpression.Arguments[0] as QuerySourceReferenceExpression; } } if (qsre != null) { QsreWithNavigationFindingExpressionVisitor qsreFindingVisitor = new QsreWithNavigationFindingExpressionVisitor(qsre, navigation); qsreFindingVisitor.Visit(_queryModel.SelectClause.Selector); if (qsreFindingVisitor.SearchedQsreFound) { canPerformOptimization = false; } } } if (canPerformOptimization) { Expression foreignKeyMemberAccess = CreateForeignKeyMemberAccess(propertyName, declaringExpression, navigation); if (foreignKeyMemberAccess != null) { return foreignKeyMemberAccess; } } return null; } private static Expression CreateForeignKeyMemberAccess(string propertyName, Expression declaringExpression, INavigation navigation) { IKey principalKey = navigation.ForeignKey.PrincipalKey; if (principalKey.Properties.Count == 1) { Debug.Assert(navigation.ForeignKey.Properties.Count == 1); IProperty principalKeyProperty = principalKey.Properties[0]; if (principalKeyProperty.Name == propertyName && principalKeyProperty.ClrType == navigation.ForeignKey.Properties[0].ClrType.UnwrapNullableType()) { Expression parentDeclaringExpression = declaringExpression is MethodCallExpression declaringMethodCallExpression && declaringMethodCallExpression.Method.IsEFPropertyMethod() ? declaringMethodCallExpression.Arguments[0] : (declaringExpression as MemberExpression)?.Expression; if (parentDeclaringExpression != null) { Expression foreignKeyPropertyExpression = CreateKeyAccessExpression(parentDeclaringExpression, navigation.ForeignKey.Properties); return foreignKeyPropertyExpression; } } } return null; } private Expression CreateSubqueryForNavigations( Expression outerQuerySourceReferenceExpression, IReadOnlyList properties, Func propertyCreator) { List navigations = properties.OfType().ToList(); INavigation firstNavigation = navigations.First(); IEntityType targetEntityType = firstNavigation.GetTargetType(); MainFromClause mainFromClause = new MainFromClause( "subQuery", targetEntityType.ClrType, NullAsyncQueryProvider.Instance.CreateEntityQueryableExpression(targetEntityType.ClrType)); _queryModelVisitor.QueryCompilationContext.AddOrUpdateMapping(mainFromClause, targetEntityType); QuerySourceReferenceExpression querySourceReference = new QuerySourceReferenceExpression(mainFromClause); QueryModel subQueryModel = new QueryModel(mainFromClause, new SelectClause(querySourceReference)); Expression leftKeyAccess = CreateKeyAccessExpression( querySourceReference, firstNavigation.IsDependentToPrincipal() ? firstNavigation.ForeignKey.PrincipalKey.Properties : firstNavigation.ForeignKey.Properties); Expression rightKeyAccess = CreateKeyAccessExpression( outerQuerySourceReferenceExpression, firstNavigation.IsDependentToPrincipal() ? firstNavigation.ForeignKey.Properties : firstNavigation.ForeignKey.PrincipalKey.Properties); subQueryModel.BodyClauses.Add( new WhereClause( CreateKeyComparisonExpressionForCollectionNavigationSubquery( leftKeyAccess, rightKeyAccess, querySourceReference))); subQueryModel.ResultOperators.Add(new FirstResultOperator(returnDefaultWhenEmpty: true)); Expression selectClauseExpression = (Expression)querySourceReference; selectClauseExpression = navigations .Skip(1) .Aggregate( selectClauseExpression, (current, navigation) => Expression.Property(current, navigation.Name)); subQueryModel.SelectClause = new SelectClause(selectClauseExpression); if (properties.Count > navigations.Count) { subQueryModel.SelectClause = new SelectClause(propertyCreator(subQueryModel.SelectClause.Selector)); } if (navigations.Count > 1) { NavigationRewritingExpressionVisitor subQueryVisitor = CreateVisitorForSubQuery(navigationExpansionSubquery: true); subQueryVisitor.Rewrite(subQueryModel, parentQueryModel: null); } return new SubQueryExpression(subQueryModel); } /// public override NavigationRewritingExpressionVisitor CreateVisitorForSubQuery(bool navigationExpansionSubquery) => new NavigationRewritingExpressionVisitor( _queryModelVisitor, navigationExpansionSubquery); private static Expression CreateKeyComparisonExpressionForCollectionNavigationSubquery( Expression leftExpression, Expression rightExpression, Expression leftQsre) { if (leftExpression.Type != rightExpression.Type) { if (leftExpression.Type.IsNullableType()) { Debug.Assert(leftExpression.Type.UnwrapNullableType() == rightExpression.Type); rightExpression = Expression.Convert(rightExpression, leftExpression.Type); } else { Debug.Assert(rightExpression.Type.IsNullableType()); Debug.Assert(rightExpression.Type.UnwrapNullableType() == leftExpression.Type); leftExpression = Expression.Convert(leftExpression, rightExpression.Type); } } BinaryExpression outerNullProtection = Expression.NotEqual( leftQsre, Expression.Constant(null, leftQsre.Type)); return new NullSafeEqualExpression(outerNullProtection, Expression.Equal(leftExpression, rightExpression)); } private Expression RewriteNavigationsIntoJoins( QuerySourceReferenceExpression outerQuerySourceReferenceExpression, IEnumerable navigations, Type propertyType, Func propertyCreator, Func conditionalAccessPropertyCreator) { QuerySourceReferenceExpression sourceQsre = outerQuerySourceReferenceExpression; Expression sourceExpression = outerQuerySourceReferenceExpression; NavigationJoins navigationJoins = _navigationJoins; bool optionalNavigationInChain = NeedsNullCompensation(outerQuerySourceReferenceExpression); foreach (INavigation navigation in navigations) { bool addNullCheckToOuterKeySelector = optionalNavigationInChain; if (!navigation.ForeignKey.IsRequired || !navigation.IsDependentToPrincipal() || (navigation.DeclaringEntityType.ClrType != sourceExpression.Type && navigation.DeclaringEntityType.GetAllBaseTypes().Any(t => t.ClrType == sourceExpression.Type))) { optionalNavigationInChain = true; } if (!ShouldRewrite(navigation)) { if (sourceExpression.Type != navigation.DeclaringEntityType.ClrType) { sourceExpression = Expression.Condition( Expression.TypeIs(sourceExpression, navigation.DeclaringEntityType.ClrType), Expression.Convert(sourceExpression, navigation.DeclaringEntityType.ClrType), Expression.Constant(null, navigation.DeclaringEntityType.ClrType)); } sourceExpression = new NullConditionalExpression(sourceExpression, Expression.Property(sourceExpression, navigation.PropertyInfo)); continue; } IEntityType targetEntityType = navigation.GetTargetType(); if (navigation.IsCollection()) { _queryModel.MainFromClause.FromExpression = NullAsyncQueryProvider.Instance.CreateEntityQueryableExpression(targetEntityType.ClrType); QuerySourceReferenceExpression innerQuerySourceReferenceExpression = new QuerySourceReferenceExpression(_queryModel.MainFromClause); _queryModelVisitor.QueryCompilationContext.AddOrUpdateMapping(_queryModel.MainFromClause, targetEntityType); Expression leftKeyAccess = CreateKeyAccessExpression( sourceExpression, navigation.IsDependentToPrincipal() ? navigation.ForeignKey.Properties : navigation.ForeignKey.PrincipalKey.Properties); Expression rightKeyAccess = CreateKeyAccessExpression( innerQuerySourceReferenceExpression, navigation.IsDependentToPrincipal() ? navigation.ForeignKey.PrincipalKey.Properties : navigation.ForeignKey.Properties); _queryModel.BodyClauses.Add( new WhereClause( CreateKeyComparisonExpressionForCollectionNavigationSubquery( leftKeyAccess, rightKeyAccess, sourceExpression))); return _queryModel.MainFromClause.FromExpression; } NavigationJoin navigationJoin = navigationJoins .FirstOrDefault(nj => nj.QuerySource == (sourceExpression as QuerySourceReferenceExpression ?? sourceQsre).ReferencedQuerySource && nj.Navigation == navigation); if (navigationJoin == null) { JoinClause joinClause = BuildJoinFromNavigation( sourceExpression, navigation, targetEntityType, addNullCheckToOuterKeySelector, out QuerySourceReferenceExpression innerQuerySourceReferenceExpression); if (optionalNavigationInChain) { RewriteNavigationIntoGroupJoin( joinClause, navigation, targetEntityType, sourceExpression as QuerySourceReferenceExpression ?? sourceQsre, null, new List(), new List { new DefaultIfEmptyResultOperator(null) }, out navigationJoin); } else { navigationJoin = new NavigationJoin( (sourceExpression as QuerySourceReferenceExpression ?? sourceQsre).ReferencedQuerySource, navigation, joinClause, new List(), navigation.IsDependentToPrincipal(), innerQuerySourceReferenceExpression); } } navigationJoins.Add(navigationJoin); sourceExpression = navigationJoin.QuerySourceReferenceExpression; sourceQsre = navigationJoin.QuerySourceReferenceExpression; navigationJoins = navigationJoin.Children; } return propertyType == null ? sourceExpression : optionalNavigationInChain ? conditionalAccessPropertyCreator(sourceExpression) : propertyCreator(sourceExpression); } private void RewriteNavigationIntoGroupJoin( JoinClause joinClause, INavigation navigation, IEntityType targetEntityType, QuerySourceReferenceExpression querySourceReferenceExpression, MainFromClause groupJoinSubqueryMainFromClause, ICollection groupJoinSubqueryBodyClauses, ICollection groupJoinSubqueryResultOperators, out NavigationJoin navigationJoin) { GroupJoinClause groupJoinClause = new GroupJoinClause( joinClause.ItemName + "_group", typeof(IEnumerable<>).MakeGenericType(targetEntityType.ClrType), joinClause); QuerySourceReferenceExpression groupReferenceExpression = new QuerySourceReferenceExpression(groupJoinClause); MainFromClause groupJoinSubqueryModelMainFromClause = new MainFromClause(joinClause.ItemName + "_groupItem", joinClause.ItemType, groupReferenceExpression); QuerySourceReferenceExpression newQuerySourceReferenceExpression = new QuerySourceReferenceExpression(groupJoinSubqueryModelMainFromClause); _queryModelVisitor.QueryCompilationContext.AddOrUpdateMapping(groupJoinSubqueryModelMainFromClause, targetEntityType); QueryModel groupJoinSubqueryModel = new QueryModel( groupJoinSubqueryModelMainFromClause, new SelectClause(newQuerySourceReferenceExpression)); foreach (IBodyClause groupJoinSubqueryBodyClause in groupJoinSubqueryBodyClauses) { groupJoinSubqueryModel.BodyClauses.Add(groupJoinSubqueryBodyClause); } foreach (ResultOperatorBase groupJoinSubqueryResultOperator in groupJoinSubqueryResultOperators) { groupJoinSubqueryModel.ResultOperators.Add(groupJoinSubqueryResultOperator); } if (groupJoinSubqueryMainFromClause != null && (groupJoinSubqueryBodyClauses.Any() || groupJoinSubqueryResultOperators.Any())) { QuerySourceMapping querySourceMapping = new QuerySourceMapping(); querySourceMapping.AddMapping(groupJoinSubqueryMainFromClause, newQuerySourceReferenceExpression); groupJoinSubqueryModel.TransformExpressions( e => ReferenceReplacingExpressionVisitor .ReplaceClauseReferences(e, querySourceMapping, throwOnUnmappedReferences: false)); } SubQueryExpression defaultIfEmptySubquery = new SubQueryExpression(groupJoinSubqueryModel); AdditionalFromClause defaultIfEmptyAdditionalFromClause = new AdditionalFromClause(joinClause.ItemName, joinClause.ItemType, defaultIfEmptySubquery); navigationJoin = new NavigationJoin( querySourceReferenceExpression.ReferencedQuerySource, navigation, groupJoinClause, new[] { defaultIfEmptyAdditionalFromClause }, navigation.IsDependentToPrincipal(), new QuerySourceReferenceExpression(defaultIfEmptyAdditionalFromClause)); _queryModelVisitor.QueryCompilationContext.AddOrUpdateMapping(defaultIfEmptyAdditionalFromClause, targetEntityType); } private Expression RewriteSelectManyNavigationsIntoJoins( QuerySourceReferenceExpression outerQuerySourceReferenceExpression, IEnumerable navigations, AdditionalFromClause additionalFromClauseBeingProcessed) { Expression sourceExpression = outerQuerySourceReferenceExpression; QuerySourceReferenceExpression querySourceReferenceExpression = outerQuerySourceReferenceExpression; int additionalJoinIndex = _queryModel.BodyClauses.IndexOf(additionalFromClauseBeingProcessed); List joinClauses = new List(); foreach (INavigation navigation in navigations) { IEntityType targetEntityType = navigation.GetTargetType(); if (!ShouldRewrite(navigation)) { sourceExpression = Expression.Property(sourceExpression, navigation.PropertyInfo); continue; } JoinClause joinClause = BuildJoinFromNavigation( sourceExpression, navigation, targetEntityType, false, out QuerySourceReferenceExpression innerQuerySourceReferenceExpression); joinClauses.Add(joinClause); querySourceReferenceExpression = innerQuerySourceReferenceExpression; sourceExpression = innerQuerySourceReferenceExpression; } if (ShouldRewrite(navigations.Last())) { _queryModel.BodyClauses.RemoveAt(additionalJoinIndex); } else { ((AdditionalFromClause)_queryModel.BodyClauses[additionalJoinIndex]).FromExpression = sourceExpression; } for (int i = 0; i < joinClauses.Count; i++) { _queryModel.BodyClauses.Insert(additionalJoinIndex + i, joinClauses[i]); } if (ShouldRewrite(navigations.Last())) { QuerySourceMapping querySourceMapping = new QuerySourceMapping(); querySourceMapping.AddMapping(additionalFromClauseBeingProcessed, querySourceReferenceExpression); _queryModel.TransformExpressions( e => ReferenceReplacingExpressionVisitor .ReplaceClauseReferences(e, querySourceMapping, throwOnUnmappedReferences: false)); AdjustQueryCompilationContextStateAfterSelectMany( querySourceMapping, additionalFromClauseBeingProcessed, querySourceReferenceExpression.ReferencedQuerySource); } return ShouldRewrite(navigations.Last()) ? querySourceReferenceExpression : sourceExpression; } private Expression RewriteSelectManyInsideSubqueryIntoJoins( SubQueryExpression fromSubqueryExpression, QuerySourceReferenceExpression outerQuerySourceReferenceExpression, ICollection navigations, AdditionalFromClause additionalFromClauseBeingProcessed) { INavigation collectionNavigation = navigations.Last(); List adddedJoinClauses = new List(); foreach (INavigation navigation in navigations) { IEntityType targetEntityType = navigation.GetTargetType(); JoinClause joinClause = BuildJoinFromNavigation( outerQuerySourceReferenceExpression, navigation, targetEntityType, false, out QuerySourceReferenceExpression innerQuerySourceReferenceExpression); if (navigation == collectionNavigation) { RewriteNavigationIntoGroupJoin( joinClause, navigations.Last(), targetEntityType, outerQuerySourceReferenceExpression, fromSubqueryExpression.QueryModel.MainFromClause, fromSubqueryExpression.QueryModel.BodyClauses, fromSubqueryExpression.QueryModel.ResultOperators, out NavigationJoin navigationJoin); _navigationJoins.Add(navigationJoin); int additionalFromClauseIndex = _parentQueryModel.BodyClauses.IndexOf(additionalFromClauseBeingProcessed); _parentQueryModel.BodyClauses.Remove(additionalFromClauseBeingProcessed); int i = additionalFromClauseIndex; foreach (IBodyClause addedJoinClause in adddedJoinClauses) { _parentQueryModel.BodyClauses.Insert(i++, addedJoinClause); } QuerySourceMapping querySourceMapping = new QuerySourceMapping(); querySourceMapping.AddMapping(additionalFromClauseBeingProcessed, navigationJoin.QuerySourceReferenceExpression); _parentQueryModel.TransformExpressions( e => ReferenceReplacingExpressionVisitor .ReplaceClauseReferences(e, querySourceMapping, throwOnUnmappedReferences: false)); AdjustQueryCompilationContextStateAfterSelectMany( querySourceMapping, additionalFromClauseBeingProcessed, navigationJoin.QuerySourceReferenceExpression.ReferencedQuerySource); return navigationJoin.QuerySourceReferenceExpression; } DefaultIfEmptyResultOperator defaultIfEmptyOperator = fromSubqueryExpression.QueryModel.ResultOperators.OfType().FirstOrDefault(); if (defaultIfEmptyOperator != null) { RewriteNavigationIntoGroupJoin( joinClause, navigation, targetEntityType, outerQuerySourceReferenceExpression, null, new List(), new List { defaultIfEmptyOperator }, out NavigationJoin navigationJoin); _navigationJoins.Add(navigationJoin); outerQuerySourceReferenceExpression = navigationJoin.QuerySourceReferenceExpression; } else { adddedJoinClauses.Add(joinClause); outerQuerySourceReferenceExpression = innerQuerySourceReferenceExpression; } } return outerQuerySourceReferenceExpression; } private void AdjustQueryCompilationContextStateAfterSelectMany(QuerySourceMapping querySourceMapping, IQuerySource querySourceBeingProcessed, IQuerySource resultQuerySource) { foreach (IncludeResultOperator includeResultOperator in _queryModelVisitor.QueryCompilationContext.QueryAnnotations.OfType()) { includeResultOperator.PathFromQuerySource = ReferenceReplacingExpressionVisitor.ReplaceClauseReferences( includeResultOperator.PathFromQuerySource, querySourceMapping, throwOnUnmappedReferences: false); if (includeResultOperator.QuerySource == querySourceBeingProcessed) { includeResultOperator.QuerySource = resultQuerySource; } } } private JoinClause BuildJoinFromNavigation( Expression sourceExpression, INavigation navigation, IEntityType targetEntityType, bool addNullCheckToOuterKeySelector, out QuerySourceReferenceExpression innerQuerySourceReferenceExpression) { Expression outerKeySelector = CreateKeyAccessExpression( sourceExpression, navigation.IsDependentToPrincipal() ? navigation.ForeignKey.Properties : navigation.ForeignKey.PrincipalKey.Properties, addNullCheckToOuterKeySelector); string itemName = sourceExpression is QuerySourceReferenceExpression qsre && !qsre.ReferencedQuerySource.HasGeneratedItemName() ? qsre.ReferencedQuerySource.ItemName : navigation.DeclaringEntityType.ShortName()[0].ToString().ToLowerInvariant(); JoinClause joinClause = new JoinClause( $"{itemName}.{navigation.Name}", targetEntityType.ClrType, NullAsyncQueryProvider.Instance.CreateEntityQueryableExpression(targetEntityType.ClrType), outerKeySelector, Expression.Constant(null)); innerQuerySourceReferenceExpression = new QuerySourceReferenceExpression(joinClause); _queryModelVisitor.QueryCompilationContext.AddOrUpdateMapping(joinClause, targetEntityType); Expression innerKeySelector = CreateKeyAccessExpression( innerQuerySourceReferenceExpression, navigation.IsDependentToPrincipal() ? navigation.ForeignKey.PrincipalKey.Properties : navigation.ForeignKey.Properties); if (innerKeySelector.Type != joinClause.OuterKeySelector.Type) { if (innerKeySelector.Type.IsNullableType()) { joinClause.OuterKeySelector = Expression.Convert( joinClause.OuterKeySelector, innerKeySelector.Type); } else { innerKeySelector = Expression.Convert( innerKeySelector, joinClause.OuterKeySelector.Type); } } joinClause.InnerKeySelector = innerKeySelector; return joinClause; } private static Expression CreateKeyAccessExpression( Expression target, IReadOnlyList properties, bool addNullCheck = false) => properties.Count == 1 ? CreatePropertyExpression(target, properties[0], addNullCheck) : Expression.New( AnonymousObject.AnonymousObjectCtor, Expression.NewArrayInit( typeof(object), properties .Select(p => Expression.Convert(CreatePropertyExpression(target, p, addNullCheck), typeof(object))) .Cast() .ToArray())); private static Expression CreatePropertyExpression(Expression target, IProperty property, bool addNullCheck) { Expression propertyExpression = target.CreateEFPropertyExpression(property, makeNullable: false); Type propertyDeclaringType = property.DeclaringType.ClrType; if (propertyDeclaringType != target.Type && target.Type.GetTypeInfo().IsAssignableFrom(propertyDeclaringType.GetTypeInfo())) { if (!propertyExpression.Type.IsNullableType()) { propertyExpression = Expression.Convert(propertyExpression, propertyExpression.Type.MakeNullable()); } return Expression.Condition( Expression.TypeIs(target, propertyDeclaringType), propertyExpression, Expression.Constant(null, propertyExpression.Type)); } return addNullCheck ? new NullConditionalExpression(target, propertyExpression) : propertyExpression; } private static bool IsCompositeKey([NotNull] Type type) { Check.NotNull(type, nameof(type)); return type == typeof(AnonymousObject); } private static Expression CompensateForNullabilityDifference(Expression expression, Type originalType) { Type newType = expression.Type; bool needsTypeCompensation = originalType != newType && !originalType.IsNullableType() && newType.IsNullableType() && originalType == newType.UnwrapNullableType(); return needsTypeCompensation ? Expression.Convert(expression, originalType) : expression; } private class DocumentNavigationRewritingQueryModelVisitor : ExpressionTransformingQueryModelVisitor { private readonly CollectionNavigationSubqueryInjector _subqueryInjector; private readonly bool _navigationExpansionSubquery; private readonly QueryCompilationContext _queryCompilationContext; public AdditionalFromClause AdditionalFromClauseBeingProcessed { get; private set; } public DocumentNavigationRewritingQueryModelVisitor( DocumentNavigationRewritingExpressionVisitor transformingVisitor, EntityQueryModelVisitor queryModelVisitor, bool navigationExpansionSubquery) : base(transformingVisitor) { _subqueryInjector = new CollectionNavigationSubqueryInjector(queryModelVisitor, shouldInject: true); _navigationExpansionSubquery = navigationExpansionSubquery; _queryCompilationContext = queryModelVisitor.QueryCompilationContext; } public override void VisitMainFromClause(MainFromClause fromClause, QueryModel queryModel) { base.VisitMainFromClause(fromClause, queryModel); QueryCompilationContext queryCompilationContext = TransformingVisitor._queryModelVisitor.QueryCompilationContext; if (queryCompilationContext.FindEntityType(fromClause) == null && fromClause.FromExpression is SubQueryExpression subQuery) { IEntityType entityType = MemberAccessBindingExpressionVisitor.GetEntityType( subQuery.QueryModel.SelectClause.Selector, queryCompilationContext); if (entityType != null) { queryCompilationContext.AddOrUpdateMapping(fromClause, entityType); } } } public override void VisitAdditionalFromClause(AdditionalFromClause fromClause, QueryModel queryModel, int index) { // ReSharper disable once PatternAlwaysOfType if (fromClause.TryGetFlattenedGroupJoinClause()?.JoinClause is JoinClause joinClause // ReSharper disable once PatternAlwaysOfType && _queryCompilationContext.FindEntityType(joinClause) is IEntityType entityType) { _queryCompilationContext.AddOrUpdateMapping(fromClause, entityType); } AdditionalFromClause oldAdditionalFromClause = AdditionalFromClauseBeingProcessed; AdditionalFromClauseBeingProcessed = fromClause; fromClause.TransformExpressions(TransformingVisitor.Visit); AdditionalFromClauseBeingProcessed = oldAdditionalFromClause; } public override void VisitWhereClause(WhereClause whereClause, QueryModel queryModel, int index) { base.VisitWhereClause(whereClause, queryModel, index); if (whereClause.Predicate.Type == typeof(bool?)) { whereClause.Predicate = Expression.Equal(whereClause.Predicate, Expression.Constant(true, typeof(bool?))); } } public override void VisitOrderByClause(OrderByClause orderByClause, QueryModel queryModel, int index) { List originalTypes = orderByClause.Orderings.Select(o => o.Expression.Type).ToList(); bool oldInsideOrderBy = TransformingVisitor._insideOrderBy; TransformingVisitor._insideOrderBy = true; base.VisitOrderByClause(orderByClause, queryModel, index); TransformingVisitor._insideOrderBy = oldInsideOrderBy; for (int i = 0; i < orderByClause.Orderings.Count; i++) { orderByClause.Orderings[i].Expression = CompensateForNullabilityDifference( orderByClause.Orderings[i].Expression, originalTypes[i]); } } public override void VisitJoinClause(JoinClause joinClause, QueryModel queryModel, int index) => VisitJoinClauseInternal(joinClause); public override void VisitJoinClause(JoinClause joinClause, QueryModel queryModel, GroupJoinClause groupJoinClause) => VisitJoinClauseInternal(joinClause); private void VisitJoinClauseInternal(JoinClause joinClause) { joinClause.InnerSequence = TransformingVisitor.Visit(joinClause.InnerSequence); QueryCompilationContext queryCompilationContext = TransformingVisitor._queryModelVisitor.QueryCompilationContext; if (queryCompilationContext.FindEntityType(joinClause) == null && joinClause.InnerSequence is SubQueryExpression subQuery) { IEntityType entityType = MemberAccessBindingExpressionVisitor.GetEntityType( subQuery.QueryModel.SelectClause.Selector, queryCompilationContext); if (entityType != null) { queryCompilationContext.AddOrUpdateMapping(joinClause, entityType); } } joinClause.OuterKeySelector = TransformingVisitor.Visit(joinClause.OuterKeySelector); bool oldInsideInnerKeySelector = TransformingVisitor._insideInnerKeySelector; TransformingVisitor._insideInnerKeySelector = true; joinClause.InnerKeySelector = TransformingVisitor.Visit(joinClause.InnerKeySelector); if (joinClause.OuterKeySelector.Type.IsNullableType() && !joinClause.InnerKeySelector.Type.IsNullableType()) { joinClause.InnerKeySelector = Expression.Convert(joinClause.InnerKeySelector, joinClause.InnerKeySelector.Type.MakeNullable()); } if (joinClause.InnerKeySelector.Type.IsNullableType() && !joinClause.OuterKeySelector.Type.IsNullableType()) { joinClause.OuterKeySelector = Expression.Convert(joinClause.OuterKeySelector, joinClause.OuterKeySelector.Type.MakeNullable()); } TransformingVisitor._insideInnerKeySelector = oldInsideInnerKeySelector; } public override void VisitSelectClause(SelectClause selectClause, QueryModel queryModel) { selectClause.Selector = _subqueryInjector.Visit(selectClause.Selector); if (_navigationExpansionSubquery) { base.VisitSelectClause(selectClause, queryModel); return; } Type originalType = selectClause.Selector.Type; base.VisitSelectClause(selectClause, queryModel); selectClause.Selector = CompensateForNullabilityDifference(selectClause.Selector, originalType); } public override void VisitResultOperator(ResultOperatorBase resultOperator, QueryModel queryModel, int index) { if (resultOperator is AllResultOperator allResultOperator) { Expression expressionExtractor(AllResultOperator o) => o.Predicate; void adjuster(AllResultOperator o, Expression e) => o.Predicate = e; VisitAndAdjustResultOperatorType(allResultOperator, expressionExtractor, adjuster); return; } if (resultOperator is ContainsResultOperator containsResultOperator) { Expression expressionExtractor(ContainsResultOperator o) => o.Item; void adjuster(ContainsResultOperator o, Expression e) => o.Item = e; VisitAndAdjustResultOperatorType(containsResultOperator, expressionExtractor, adjuster); return; } if (resultOperator is SkipResultOperator skipResultOperator) { Expression expressionExtractor(SkipResultOperator o) => o.Count; void adjuster(SkipResultOperator o, Expression e) => o.Count = e; VisitAndAdjustResultOperatorType(skipResultOperator, expressionExtractor, adjuster); return; } if (resultOperator is TakeResultOperator takeResultOperator) { Expression expressionExtractor(TakeResultOperator o) => o.Count; void adjuster(TakeResultOperator o, Expression e) => o.Count = e; VisitAndAdjustResultOperatorType(takeResultOperator, expressionExtractor, adjuster); return; } if (resultOperator is GroupResultOperator groupResultOperator) { groupResultOperator.ElementSelector = _subqueryInjector.Visit(groupResultOperator.ElementSelector); Type originalKeySelectorType = groupResultOperator.KeySelector.Type; Type originalElementSelectorType = groupResultOperator.ElementSelector.Type; base.VisitResultOperator(resultOperator, queryModel, index); groupResultOperator.KeySelector = CompensateForNullabilityDifference( groupResultOperator.KeySelector, originalKeySelectorType); groupResultOperator.ElementSelector = CompensateForNullabilityDifference( groupResultOperator.ElementSelector, originalElementSelectorType); return; } base.VisitResultOperator(resultOperator, queryModel, index); } private void VisitAndAdjustResultOperatorType( TResultOperator resultOperator, Func expressionExtractor, Action adjuster) where TResultOperator : ResultOperatorBase { Expression originalExpression = expressionExtractor(resultOperator); Type originalType = originalExpression.Type; Expression translatedExpression = CompensateForNullabilityDifference( TransformingVisitor.Visit(originalExpression), originalType); adjuster(resultOperator, translatedExpression); } } private class ProjectionSubqueryInjectingQueryModelVisitor : QueryModelVisitorBase { private readonly CollectionNavigationSubqueryInjector _subqueryInjector; public ProjectionSubqueryInjectingQueryModelVisitor(EntityQueryModelVisitor queryModelVisitor) { _subqueryInjector = new CollectionNavigationSubqueryInjector(queryModelVisitor, shouldInject: true); } public override void VisitSelectClause(SelectClause selectClause, QueryModel queryModel) { selectClause.Selector = _subqueryInjector.Visit(selectClause.Selector); base.VisitSelectClause(selectClause, queryModel); } } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/ExpressionVisitors/DocumentNavigationRewritingExpressionVisitorFactory.cs ================================================ using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal; namespace Blueshift.EntityFrameworkCore.MongoDB.Query.ExpressionVisitors { /// public class DocumentNavigationRewritingExpressionVisitorFactory : NavigationRewritingExpressionVisitorFactory { /// public override NavigationRewritingExpressionVisitor Create(EntityQueryModelVisitor queryModelVisitor) => new DocumentNavigationRewritingExpressionVisitor(queryModelVisitor); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/ExpressionVisitors/IMongoDbDenormalizedCollectionCompensatingVisitorFactory.cs ================================================ namespace Blueshift.EntityFrameworkCore.MongoDB.Query.ExpressionVisitors { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public interface IMongoDbDenormalizedCollectionCompensatingVisitorFactory { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// MongoDbDenormalizedCollectionCompensatingVisitor Create(); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/ExpressionVisitors/MongoDbDenormalizedCollectionCompensatingVisitor.cs ================================================ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; using Remotion.Linq.Parsing; namespace Blueshift.EntityFrameworkCore.MongoDB.Query.ExpressionVisitors { /// /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public class MongoDbDenormalizedCollectionCompensatingVisitor : RelinqExpressionVisitor { /// protected override Expression VisitBlock(BlockExpression node) { if (TryFindAddToCollectionExpression(node.Expressions, out MethodCallExpression methodCallExpression)) { Expression callReplaceDenormalizedInstances = Expression.Call( null, RemoveDenormalizedInstancesMethodInfo, methodCallExpression.Arguments); IList newBlockExpressions = new List(node.Expressions); newBlockExpressions.Add(callReplaceDenormalizedInstances); node = node.Update(node.Variables, newBlockExpressions); } return base.VisitBlock(node); } private bool TryFindAddToCollectionExpression(IReadOnlyList expressions, out MethodCallExpression methodCallExpression) { methodCallExpression = null; foreach (Expression expression in expressions) { if (expression is MethodCallExpression candidateMethodCallExpression && candidateMethodCallExpression.Method == AddToCollectionSnapshotMethodInfo) { methodCallExpression = candidateMethodCallExpression; break; } } return methodCallExpression != null; } private static void RemoveDenormalizedInstances( IStateManager stateManager, INavigation navigation, object entity, object instance) { InternalEntityEntry internalEntityEntry = stateManager.TryGetEntry(entity); internalEntityEntry.EnsureRelationshipSnapshot(); INavigation inverse = navigation.FindInverse(); IKey primaryKey = inverse.DeclaringEntityType.FindPrimaryKey(); IProperty primaryKeyProperty = primaryKey.Properties.Single(); object keyValue = primaryKeyProperty.GetGetter().GetClrValue(instance); IClrCollectionAccessor collectionAccessor = navigation.GetCollectionAccessor(); ICollection list = (ICollection) collectionAccessor.GetOrCreate(entity); IList toRemove = list .OfType() .Where(item => (Equals( keyValue, primaryKeyProperty.GetGetter().GetClrValue(item)) && !ReferenceEquals(instance, item))) .ToList(); foreach (object item in toRemove) { collectionAccessor.Remove(entity, item); internalEntityEntry.RemoveFromCollectionSnapshot(navigation, item); } } private static readonly MethodInfo RemoveDenormalizedInstancesMethodInfo = MethodHelper .GetMethodInfo(() => RemoveDenormalizedInstances(default, default, default, default)); private static readonly MethodInfo AddToCollectionSnapshotMethodInfo = (MethodInfo) typeof(IncludeCompiler) .GetNestedType("IncludeLoadTreeNode", BindingFlags.NonPublic) ?.GetField("_addToCollectionSnapshotMethodInfo", BindingFlags.NonPublic | BindingFlags.Static) ?.GetValue(null); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/ExpressionVisitors/MongoDbDenormalizedCollectionCompensatingVisitorFactory.cs ================================================ namespace Blueshift.EntityFrameworkCore.MongoDB.Query.ExpressionVisitors { /// public class MongoDbDenormalizedCollectionCompensatingVisitorFactory : IMongoDbDenormalizedCollectionCompensatingVisitorFactory { /// public MongoDbDenormalizedCollectionCompensatingVisitor Create() => new MongoDbDenormalizedCollectionCompensatingVisitor(); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/ExpressionVisitors/MongoDbEntityQueryableExpressionVisitor.cs ================================================ using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Blueshift.EntityFrameworkCore.MongoDB.Query.Expressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors; using Microsoft.EntityFrameworkCore.Utilities; using Remotion.Linq.Clauses; namespace Blueshift.EntityFrameworkCore.MongoDB.Query.ExpressionVisitors { /// public class MongoDbEntityQueryableExpressionVisitor : EntityQueryableExpressionVisitor { private readonly IModel _model; private readonly IQuerySource _querySource; private readonly IDocumentQueryExpressionFactory _documentQueryExpressionFactory; /// public MongoDbEntityQueryableExpressionVisitor( // ReSharper disable once SuggestBaseTypeForParameter [NotNull] MongoDbEntityQueryModelVisitor entityQueryModelVisitor, [NotNull] IModel model, [CanBeNull] IQuerySource querySource, [NotNull] IDocumentQueryExpressionFactory documentQueryExpressionFactory) : base(entityQueryModelVisitor) { _model = Check.NotNull(model, nameof(model)); _querySource = querySource; _documentQueryExpressionFactory = Check.NotNull(documentQueryExpressionFactory, nameof(documentQueryExpressionFactory)); } private new MongoDbEntityQueryModelVisitor QueryModelVisitor => (MongoDbEntityQueryModelVisitor)base.QueryModelVisitor; /// protected override Expression VisitEntityQueryable(Type elementType) { Check.NotNull(elementType, nameof(elementType)); IEntityType entityType = QueryModelVisitor.QueryCompilationContext.FindEntityType(_querySource) ?? _model.FindEntityType(elementType); entityType = entityType.GetMongoDbCollectionEntityType(); var documentQueryExpression = _documentQueryExpressionFactory .CreateDocumentQueryExpression(entityType); if (entityType.ClrType != elementType) { MethodInfo ofTypeMethodInfo = MethodHelper .GetGenericMethodDefinition(() => Enumerable.OfType(null)) .MakeGenericMethod(elementType); documentQueryExpression = Expression.Call( null, ofTypeMethodInfo, documentQueryExpression); } return documentQueryExpression; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/ExpressionVisitors/MongoDbEntityQueryableExpressionVisitorFactory.cs ================================================ using System.Linq.Expressions; using Blueshift.EntityFrameworkCore.MongoDB.Query.Expressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors; using Microsoft.EntityFrameworkCore.Utilities; using Remotion.Linq.Clauses; namespace Blueshift.EntityFrameworkCore.MongoDB.Query.ExpressionVisitors { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public class MongoDbEntityQueryableExpressionVisitorFactory : IEntityQueryableExpressionVisitorFactory { private readonly IModel _model; private readonly IDocumentQueryExpressionFactory _documentQueryExpressionFactory; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public MongoDbEntityQueryableExpressionVisitorFactory( [NotNull] IModel model, [NotNull] IDocumentQueryExpressionFactory documentQueryExpressionFactory) { _model = Check.NotNull(model, nameof(model)); _documentQueryExpressionFactory = Check.NotNull(documentQueryExpressionFactory, nameof(documentQueryExpressionFactory)); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public virtual ExpressionVisitor Create( EntityQueryModelVisitor entityQueryModelVisitor, IQuerySource querySource) => new MongoDbEntityQueryableExpressionVisitor( Check.Is(entityQueryModelVisitor, nameof(entityQueryModelVisitor)), _model, querySource, _documentQueryExpressionFactory); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/ExpressionVisitors/MongoDbMemberAccessBindingExpressionVisitor.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Extensions.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal; using Microsoft.EntityFrameworkCore.Utilities; using Remotion.Linq.Clauses; namespace Blueshift.EntityFrameworkCore.MongoDB.Query.ExpressionVisitors { /// public class MongoDbMemberAccessBindingExpressionVisitor : MemberAccessBindingExpressionVisitor { private readonly IModel _model; /// public MongoDbMemberAccessBindingExpressionVisitor( [NotNull] QuerySourceMapping querySourceMapping, [NotNull] MongoDbEntityQueryModelVisitor mongoDbEntityQueryModelVisitor, bool inProjection) : base( Check.NotNull(querySourceMapping, nameof(querySourceMapping)), Check.NotNull(mongoDbEntityQueryModelVisitor, nameof(mongoDbEntityQueryModelVisitor)), inProjection) { _model = mongoDbEntityQueryModelVisitor.QueryCompilationContext.Model; } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) { Expression newExpression = null; if (methodCallExpression.Method.IsEFPropertyMethod()) { var newArguments = VisitAndConvert( new List { methodCallExpression.Arguments[0], methodCallExpression.Arguments[1] }.AsReadOnly(), nameof(VisitMethodCall)); Expression targetExpression = newArguments[0]; IEntityType entityType = _model .FindEntityType(targetExpression.Type); IProperty property = entityType .FindProperty((string)((ConstantExpression)newArguments[1]).Value); PropertyInfo propertyInfo = property.PropertyInfo; if (property.IsShadowProperty && property.IsForeignKey()) { IForeignKey foreignKey = property.AsProperty().ForeignKeys.Single(); INavigation navigation = foreignKey.PrincipalEntityType == entityType || foreignKey.IsSelfPrimaryKeyReferencing() ? foreignKey.PrincipalToDependent : foreignKey.DependentToPrincipal; if (navigation != null) { targetExpression = Expression.MakeMemberAccess(targetExpression, navigation.PropertyInfo); IEntityType targetEntityType = navigation.GetTargetType(); property = targetEntityType.FindPrimaryKey().Properties.Single(); propertyInfo = property.PropertyInfo; } } newExpression = Expression.Convert( Expression.MakeMemberAccess(targetExpression, propertyInfo), typeof(Nullable<>).MakeGenericType(propertyInfo.PropertyType)); } return newExpression ?? base.VisitMethodCall(methodCallExpression); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/ExpressionVisitors/MongoDbMemberAccessBindingExpressionVisitorFactory.cs ================================================ using System; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal; using Remotion.Linq.Clauses; namespace Blueshift.EntityFrameworkCore.MongoDB.Query.ExpressionVisitors { /// public class MongoDbMemberAccessBindingExpressionVisitorFactory : MemberAccessBindingExpressionVisitorFactory { /// public override ExpressionVisitor Create( QuerySourceMapping querySourceMapping, EntityQueryModelVisitor queryModelVisitor, bool inProjection) => new MongoDbMemberAccessBindingExpressionVisitor( querySourceMapping, queryModelVisitor is MongoDbEntityQueryModelVisitor mongoDbEntityQueryModelVisitor ? mongoDbEntityQueryModelVisitor : throw new ArgumentException( @"EntityQueryModelVisitor must be an instance of MongoDbEntityQueryModelVisitor.", nameof(queryModelVisitor)), inProjection); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/Expressions/DocumentQueryExpression.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Query.Expressions { /// /// /// Represents a query for a set of documents from a document database. /// public class DocumentQueryExpression : Expression { private readonly IDocumentQueryExpressionFactory _documentQueryExpressionFactory; private readonly IEntityType _entityType; /// /// /// Creates a new instance of the class. /// /// The to use to create /// the root document query expression. /// The representing the type of entities to query. public DocumentQueryExpression( [NotNull] IDocumentQueryExpressionFactory documentQueryExpressionFactory, [NotNull] IEntityType entityType) { _documentQueryExpressionFactory = Check.NotNull(documentQueryExpressionFactory, nameof(documentQueryExpressionFactory)); _entityType = Check.NotNull(entityType, nameof(entityType)); } /// public override bool CanReduce => true; /// public override Expression Reduce() => _documentQueryExpressionFactory .CreateDocumentQueryExpression(_entityType); /// public override Type Type => typeof(IQueryable<>).MakeGenericType(_entityType.ClrType); /// protected override Expression VisitChildren(ExpressionVisitor visitor) => this; /// public override ExpressionType NodeType => ExpressionType.Extension; } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/Expressions/IDocumentQueryExpressionFactory.cs ================================================ using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Metadata; namespace Blueshift.EntityFrameworkCore.MongoDB.Query.Expressions { /// /// Interface for a service that can be used to generate a document query expression. /// public interface IDocumentQueryExpressionFactory { /// /// Creates an that represents a query for documents of a given entity type. /// /// The that represents that documents to query. /// An that represents a query for documents of a given entity type. Expression CreateDocumentQueryExpression(IEntityType entityType); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/Expressions/MongoDbDocumentQueryExpressionFactory.cs ================================================ using System.Linq; using System.Linq.Expressions; using System.Reflection; using Blueshift.EntityFrameworkCore.MongoDB.Metadata; using Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders; using Blueshift.EntityFrameworkCore.MongoDB.Storage; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Driver; namespace Blueshift.EntityFrameworkCore.MongoDB.Query.Expressions { /// public class MongoDbDocumentQueryExpressionFactory : IDocumentQueryExpressionFactory { private static readonly MethodInfo GetCollectionMethodInfo = MethodHelper.GetGenericMethodDefinition( mongoDatabase => mongoDatabase.GetCollection("", null)); private static readonly MethodInfo AsQueryableMethodInfo = MethodHelper.GetGenericMethodDefinition, object>( mongoCollection => mongoCollection.AsQueryable(null)); private static readonly MethodInfo OfTypeMethodInfo = MethodHelper.GetGenericMethodDefinition, object>( queryable => queryable.OfType()); private readonly IMongoDbConnection _mongoDbConnection; /// /// Initializes a new instance of the class. /// /// The used to connect to the instance of MongoDb. public MongoDbDocumentQueryExpressionFactory( [NotNull] IMongoDbConnection mongoDbConnection) { _mongoDbConnection = Check.NotNull(mongoDbConnection, nameof(mongoDbConnection)); } /// public Expression CreateDocumentQueryExpression(IEntityType entityType) { MongoDbEntityTypeAnnotations annotations = Check.NotNull(entityType, nameof(entityType)).MongoDb(); IEntityType queryEntityType = entityType; if (!entityType.IsDocumentRootEntityType()) { entityType = entityType.GetMongoDbCollectionEntityType(); } Expression queryExpression = Expression.Call( Expression.Constant(_mongoDbConnection.GetDatabase()), GetCollectionMethodInfo.MakeGenericMethod(entityType.ClrType), new Expression[] { Expression.Constant(annotations.CollectionName), Expression.Constant( annotations.CollectionSettings, typeof(MongoCollectionSettings)) }); queryExpression = Expression.Call( null, AsQueryableMethodInfo.MakeGenericMethod(entityType.ClrType), new [] { queryExpression, Expression.Constant( null, typeof(AggregateOptions)) }); if (queryEntityType != entityType) { queryExpression = Expression.Call( queryExpression, OfTypeMethodInfo.MakeGenericMethod(queryEntityType.ClrType)); } return queryExpression; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/IEntityLoadInfoFactory.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; namespace Blueshift.EntityFrameworkCore.MongoDB.Query { /// /// Interface for a service that can create instances of . /// public interface IEntityLoadInfoFactory { /// /// Creates a new instance of for the given instance. /// /// The object for which the will be created. /// The representing the type of . /// The entity instance that owns , if any. /// The that describes the ownership, if any. /// A new instance of that can be used to load the given . EntityLoadInfo Create( [NotNull] object document, [NotNull] IEntityType entityType, [CanBeNull] object owner, [CanBeNull] INavigation owningNavigation); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/IValueBufferFactory.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage; namespace Blueshift.EntityFrameworkCore.MongoDB.Query { /// /// Interface for a service that can create instances. /// public interface IValueBufferFactory { /// /// Creates an instance of from the given . /// /// An existing object instance to use to build the value buffer. /// The containing the metadata for . /// The entity that owns , if any. /// The that describes the ownership, if any. /// A new that reflects the properties of the given . ValueBuffer CreateFromInstance( [NotNull] object instance, [NotNull] IEntityType entityType, [CanBeNull] object owner, [CanBeNull] INavigation owningNavigation); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/LinqQueryCompilationContextFactory.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.Internal; namespace Blueshift.EntityFrameworkCore.MongoDB.Query { /// public class LinqQueryCompilationContextFactory : QueryCompilationContextFactory { /// public LinqQueryCompilationContextFactory( [NotNull] QueryCompilationContextDependencies dependencies) : base(dependencies) { } /// public override QueryCompilationContext Create(bool async) => new QueryCompilationContext( Dependencies, new QueryableLinqOperatorProvider(), TrackQueryResults); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/MongoDbEntityQueryModelVisitor.cs ================================================ using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Blueshift.EntityFrameworkCore.MongoDB.Query.ExpressionVisitors; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Utilities; using Remotion.Linq; using Remotion.Linq.Clauses; using Remotion.Linq.Clauses.Expressions; using Remotion.Linq.Clauses.ResultOperators; namespace Blueshift.EntityFrameworkCore.MongoDB.Query { /// public class MongoDbEntityQueryModelVisitor : EntityQueryModelVisitor { private readonly IProjectionExpressionVisitorFactory _projectionExpressionVisitorFactory; private readonly IMongoDbDenormalizedCollectionCompensatingVisitorFactory _mongoDbDenormalizedCollectionCompensatingVisitorFactory; /// public MongoDbEntityQueryModelVisitor( [NotNull] EntityQueryModelVisitorDependencies entityQueryModelVisitorDependencies, [NotNull] QueryCompilationContext queryCompilationContext, [NotNull] MongoDbEntityQueryModelVisitorDependencies mongoDbEntityQueryModelVisitorDependencies) : base( Check.NotNull(entityQueryModelVisitorDependencies, nameof(entityQueryModelVisitorDependencies)), Check.NotNull(queryCompilationContext, nameof(queryCompilationContext)) ) { _projectionExpressionVisitorFactory = entityQueryModelVisitorDependencies .ProjectionExpressionVisitorFactory; _mongoDbDenormalizedCollectionCompensatingVisitorFactory = Check.NotNull(mongoDbEntityQueryModelVisitorDependencies, nameof(mongoDbEntityQueryModelVisitorDependencies)) .MongoDbDenormalizedCollectionCompensatingVisitorFactory; } /// public override void VisitSelectClause( SelectClause selectClause, QueryModel queryModel) { Check.NotNull(selectClause, nameof(selectClause)); Check.NotNull(queryModel, nameof(queryModel)); if (selectClause.Selector.Type == Expression.Type.GetSequenceType() && selectClause.Selector is QuerySourceReferenceExpression) { return; } Expression selector = ReplaceClauseReferences( _projectionExpressionVisitorFactory .Create(this, queryModel.MainFromClause) .Visit(selectClause.Selector), inProjection: true); if ((Expression.Type.TryGetSequenceType() != null || !(selectClause.Selector is QuerySourceReferenceExpression)) && !queryModel.ResultOperators .Select(ro => ro.GetType()) .Any( t => t == typeof(GroupResultOperator) || t == typeof(AllResultOperator))) { Expression = Expression.Call( LinqOperatorProvider.Select .MakeGenericMethod(CurrentParameter.Type, selector.Type), Expression, Expression.Lambda(ConvertToRelationshipAssignments(selector), CurrentParameter)); } } private Expression ConvertToRelationshipAssignments(Expression expression) { if (expression is MethodCallExpression methodCallExpression && IncludeCompiler.IsIncludeMethod(methodCallExpression)) { expression = (MethodCallExpression) _mongoDbDenormalizedCollectionCompensatingVisitorFactory .Create() .Visit(methodCallExpression); } return expression; } /// protected override Expression CallCreateTransparentIdentifier( Type transparentIdentifierType, Expression outerExpression, Expression innerExpression) { ConstructorInfo constructorInfo = transparentIdentifierType.GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, new [] { outerExpression.Type, innerExpression.Type }, Array.Empty()); return Expression.New( constructorInfo, outerExpression, innerExpression); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/MongoDbEntityQueryModelVisitorDependencies.cs ================================================ using Blueshift.EntityFrameworkCore.MongoDB.Query.ExpressionVisitors; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Query { /// /// /// Service dependencies parameter class for /// /// /// This type is typically used by database providers (and other extensions). It is generally /// not used in application code. /// /// /// Do not construct instances of this class directly from either provider or application code as the /// constructor signature may change as new dependencies are added. Instead, use this type in /// your constructor so that an instance will be created and injected automatically by the /// dependency injection container. To create an instance with some dependent services replaced, /// first resolve the object from the dependency injection container, then replace selected /// services using the 'With...' methods. Do not call the constructor at any point in this process. /// /// public class MongoDbEntityQueryModelVisitorDependencies { /// /// /// Creates the service dependencies parameter object for a . /// /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// /// /// Do not call this constructor directly from either provider or application code as it may change /// as new dependencies are added. Instead, use this type in your constructor so that an instance /// will be created and injected automatically by the dependency injection container. To create /// an instance with some dependent services replaced, first resolve the object from the dependency /// injection container, then replace selected services using the 'With...' methods. Do not call /// the constructor at any point in this process. /// /// /// /// The to be used when processing the query. /// public MongoDbEntityQueryModelVisitorDependencies( [NotNull] IMongoDbDenormalizedCollectionCompensatingVisitorFactory mongoDbDenormalizedCollectionCompensatingVisitorFactory ) { MongoDbDenormalizedCollectionCompensatingVisitorFactory = Check.NotNull(mongoDbDenormalizedCollectionCompensatingVisitorFactory, nameof(mongoDbDenormalizedCollectionCompensatingVisitorFactory)); } /// /// Gets the to be used when processing a query. /// public IMongoDbDenormalizedCollectionCompensatingVisitorFactory MongoDbDenormalizedCollectionCompensatingVisitorFactory { get; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/MongoDbEntityQueryModelVisitorFactory.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Query { /// public class MongoDbEntityQueryModelVisitorFactory : EntityQueryModelVisitorFactory { /// public MongoDbEntityQueryModelVisitorFactory( [NotNull] EntityQueryModelVisitorDependencies entityQueryModelVisitorDependencies, [NotNull] MongoDbEntityQueryModelVisitorDependencies mongoDbEntityQueryModelVisitorDependencies) : base(Check.NotNull(entityQueryModelVisitorDependencies, nameof(entityQueryModelVisitorDependencies))) { MongoDbDependencies = Check.NotNull(mongoDbEntityQueryModelVisitorDependencies, nameof(mongoDbEntityQueryModelVisitorDependencies)); } /// /// Dependencies used to create a . /// public MongoDbEntityQueryModelVisitorDependencies MongoDbDependencies { get; } /// public override EntityQueryModelVisitor Create( QueryCompilationContext queryCompilationContext, EntityQueryModelVisitor parentEntityQueryModelVisitor) => new MongoDbEntityQueryModelVisitor( Dependencies, Check.NotNull(queryCompilationContext, nameof(queryCompilationContext)), MongoDbDependencies); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/MongoDbQueryBuffer.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Query { /// public class MongoDbQueryBuffer : QueryBuffer { private readonly IModel _model; private readonly IStateManager _stateManager; [NotNull] private readonly IEntityLoadInfoFactory _entityLoadInfoFactory; /// public MongoDbQueryBuffer( [NotNull] QueryContextDependencies dependencies, [NotNull] IEntityLoadInfoFactory entityLoadInfoFactory) : base(dependencies) { _model = dependencies.CurrentDbContext.Context.Model; _stateManager = dependencies.StateManager; _entityLoadInfoFactory = Check.NotNull(entityLoadInfoFactory, nameof(entityLoadInfoFactory)); } /// public override void IncludeCollection( int includeId, INavigation navigation, INavigation inverseNavigation, IEntityType targetEntityType, IClrCollectionAccessor clrCollectionAccessor, IClrPropertySetter inverseClrPropertySetter, bool tracking, TEntity entity, Func> relatedEntitiesFactory, Func joinPredicate) { Check.NotNull(clrCollectionAccessor, nameof(clrCollectionAccessor)); Check.NotNull(inverseNavigation, nameof(inverseNavigation)); Check.NotNull(inverseClrPropertySetter, nameof(inverseClrPropertySetter)); ICollection collection = (ICollection) clrCollectionAccessor .GetOrCreate(entity); IClrPropertyGetter primaryKeyPropertyGetter = navigation .GetTargetType() .FindPrimaryKey() .Properties .Single() .GetGetter(); IDictionary replacementMap = relatedEntitiesFactory() .ToDictionary( related => primaryKeyPropertyGetter.GetClrValue(related)); IEnumerable newCollectionItems = collection .Select(original => replacementMap.TryGetValue( primaryKeyPropertyGetter.GetClrValue(original), out TRelated related) ? related : original) .Cast() .ToList(); collection.Clear(); foreach (TRelated item in newCollectionItems) { inverseClrPropertySetter.SetClrValue(item, entity); if (tracking) { InternalEntityEntry originalEntry = _stateManager.TryGetEntry(item); if (originalEntry != null) { _stateManager.StopTracking(originalEntry); } base.StartTracking( LoadEntity( item, targetEntityType, entity, inverseNavigation), targetEntityType); } } } /// public override void StartTracking(object entity, EntityTrackingInfo entityTrackingInfo) => base.StartTracking( LoadEntity( Check.NotNull(entity, nameof(entity)), _model.FindEntityType(entity.GetType()), null, null), entityTrackingInfo); /// public override void StartTracking(object entity, IEntityType entityType) => base.StartTracking( LoadEntity(entity, entityType, null, null), entityType); private object LoadEntity(object entity, IEntityType entityType, object owner, INavigation owningNavigation) { if (entity.GetType() != entityType.ClrType) { entityType = _model.FindEntityType(entity.GetType()); } entity = GetEntity( entityType.FindPrimaryKey(), _entityLoadInfoFactory.Create(entity, entityType, owner, owningNavigation), true, true); IEnumerable ownedNavigations = entityType .GetNavigations() .Where(navigation => navigation.ForeignKey.IsOwnership); foreach (INavigation ownedNavigation in ownedNavigations) { IEntityType targetEntityType = ownedNavigation.GetTargetType(); var documentOrCollection = ownedNavigation.GetGetter().GetClrValue(entity); if (ownedNavigation.IsCollection()) { IEnumerable collection = (IEnumerable) documentOrCollection; foreach (object document in collection) { base.StartTracking( LoadEntity(document, targetEntityType, entity, ownedNavigation), targetEntityType); } } else { base.StartTracking( LoadEntity(documentOrCollection, targetEntityType, entity, ownedNavigation), targetEntityType); } } return entity; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/MongoDbQueryContext.cs ================================================ using System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Query { /// public class MongoDbQueryContext : QueryContext { /// public MongoDbQueryContext( [NotNull] QueryContextDependencies queryContextDependencies, [NotNull] Func queryBufferFactory) : base( Check.NotNull(queryContextDependencies, nameof(queryContextDependencies)), Check.NotNull(queryBufferFactory, nameof(queryBufferFactory)) ) { } /// public override void BeginTrackingQuery() { Check.NotNull(QueryBuffer, nameof(QueryBuffer)); base.BeginTrackingQuery(); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/MongoDbQueryContextFactory.cs ================================================ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Query { /// public class MongoDbQueryContextFactory : QueryContextFactory { [NotNull] private readonly IEntityLoadInfoFactory _entityLoadInfoFactory; /// public MongoDbQueryContextFactory( [NotNull] QueryContextDependencies queryContextDependencies, [NotNull] IEntityLoadInfoFactory entityLoadInfoFactory) : base( Check.NotNull(queryContextDependencies, nameof(queryContextDependencies))) { _entityLoadInfoFactory = Check.NotNull(entityLoadInfoFactory, nameof(entityLoadInfoFactory)); } /// public override QueryContext Create() => new MongoDbQueryContext( Dependencies, CreateQueryBuffer); /// protected override IQueryBuffer CreateQueryBuffer() => new MongoDbQueryBuffer( Dependencies, _entityLoadInfoFactory); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/QueryableLinqOperatorProvider.cs ================================================ using System; using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore.Query.Internal; namespace Blueshift.EntityFrameworkCore.MongoDB.Query { /// public class QueryableLinqOperatorProvider : LinqOperatorProvider { private static readonly MethodInfo AllMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.All(null, obj => false)); private static readonly MethodInfo AnyMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.Any(null, obj => false)); private static readonly MethodInfo CastMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.Cast(null)); private static readonly MethodInfo ConcatMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.Concat(null, null)); private static readonly MethodInfo ContainsMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.Contains(null, null)); private static readonly MethodInfo CountMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.Count(null)); private static readonly MethodInfo DefaultIfEmptyMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.DefaultIfEmpty(null)); private static readonly MethodInfo ParameterizedDefaultIfEmptyMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.DefaultIfEmpty(null, null)); private static readonly MethodInfo DistinctMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.Distinct(null)); private static readonly MethodInfo ExceptMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.Except(null, null)); private static readonly MethodInfo FirstMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.First(null, obj => false)); private static readonly MethodInfo FirstOrDefaultMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.FirstOrDefault(null, obj => false)); private static readonly MethodInfo GroupByMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.GroupBy( null, obj => null, (obj1, obj2) => null)); private static readonly MethodInfo GroupJoinMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.GroupJoin( null, null, obj => null, obj => null, (@obj1, @obj2) => null)); private static readonly MethodInfo IntersectByMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.Intersect(null, null)); private static readonly MethodInfo JoinMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.Join( null, null, obj => null, obj => null, (@obj1, @obj2) => null)); private static readonly MethodInfo LastMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.Last(null, obj => false)); private static readonly MethodInfo LastOrDefaultMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.LastOrDefault(null, obj => false)); private static readonly MethodInfo LongCountMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.LongCount(null, obj => false)); private static readonly MethodInfo OfTypeMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.OfType(null)); private static readonly MethodInfo OrderByMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.OrderBy(null, obj => null)); private static readonly MethodInfo SelectMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.Select(null, obj => null)); private static readonly MethodInfo SelectManyMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.SelectMany(null, obj => null)); private static readonly MethodInfo SingleMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.Single(null, obj => false)); private static readonly MethodInfo SingleOrDefaultMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.SingleOrDefault(null, obj => false)); private static readonly MethodInfo SkipMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.Skip(null, 0)); private static readonly MethodInfo TakeMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.Take(null, 0)); private static readonly MethodInfo UnionMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.Union(null, null)); private static readonly MethodInfo WhereMethodInfo = MethodHelper.GetGenericMethodDefinition( () => Queryable.Where(null, obj => false)); /// public override MethodInfo GetAggregateMethod(string methodName, Type elementType) => base.GetAggregateMethod(methodName, elementType); /// public override Type MakeSequenceType(Type elementType) => base.MakeSequenceType(elementType); /// public override MethodInfo All => AllMethodInfo; /// public override MethodInfo Any => AnyMethodInfo; /// public override MethodInfo Cast => CastMethodInfo; /// public override MethodInfo Concat => ConcatMethodInfo; /// public override MethodInfo Contains => ContainsMethodInfo; /// public override MethodInfo Count => CountMethodInfo; /// public override MethodInfo DefaultIfEmpty => DefaultIfEmptyMethodInfo; /// public override MethodInfo DefaultIfEmptyArg => ParameterizedDefaultIfEmptyMethodInfo; /// public override MethodInfo Distinct => DistinctMethodInfo; /// public override MethodInfo Except => ExceptMethodInfo; /// public override MethodInfo First => FirstMethodInfo; /// public override MethodInfo FirstOrDefault => FirstOrDefaultMethodInfo; /// public override MethodInfo GroupBy => GroupByMethodInfo; /// public override MethodInfo GroupJoin => GroupJoinMethodInfo; /// public override MethodInfo Intersect => IntersectByMethodInfo; /// public override MethodInfo Join => JoinMethodInfo; /// public override MethodInfo Last => LastMethodInfo; /// public override MethodInfo LastOrDefault => LastOrDefaultMethodInfo; /// public override MethodInfo LongCount => LongCountMethodInfo; /// public override MethodInfo OfType => OfTypeMethodInfo; /// public override MethodInfo OrderBy => OrderByMethodInfo; /// public override MethodInfo Select => SelectMethodInfo; /// public override MethodInfo SelectMany => SelectManyMethodInfo; /// public override MethodInfo Single => SingleMethodInfo; /// public override MethodInfo SingleOrDefault => SingleOrDefaultMethodInfo; /// public override MethodInfo Skip => SkipMethodInfo; /// public override MethodInfo Take => TakeMethodInfo; /// public override MethodInfo ThenBy => TakeMethodInfo; /// public override MethodInfo Union => UnionMethodInfo; /// public override MethodInfo Where => WhereMethodInfo; /// public override MethodInfo ToSequence => base.ToSequence; /// public override MethodInfo ToOrdered => base.ToOrdered; /// public override MethodInfo ToEnumerable => base.ToEnumerable; /// public override MethodInfo ToQueryable => base.ToQueryable; } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Query/ValueBufferFactory.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Query { /// public class ValueBufferFactory : IValueBufferFactory { /// public ValueBuffer CreateFromInstance( object instance, IEntityType entityType, object owner, INavigation owningNavigation) { Check.NotNull(instance, nameof(instance)); Check.NotNull(entityType, nameof(entityType)); PropertyCounts propertyCounts = entityType.AsEntityType().Counts; var propertyValues = new object[propertyCounts.PropertyCount]; IEnumerable properties = entityType .GetProperties() .Where(property => !property.IsForeignKey()) .Select(property => property.AsProperty()); foreach (Property property in properties) { if (!property.IsShadowProperty) { propertyValues[property.GetIndex()] = property.Getter.GetClrValue(instance); } else if (property.IsPrimaryKey()) { Debug.Assert(entityType.IsOwned(), $"Non-owned entity type {entityType.Name} with a shadow primary key."); propertyValues[property.GetShadowIndex()] = instance.GetHashCode(); } } if (owningNavigation != null) { Check.NotNull(owner, nameof(owner)); IForeignKey foreignKey = owningNavigation.ForeignKey; for (int i = 0; i < foreignKey.Properties.Count; i++) { IProperty foreignKeyProperty = foreignKey.Properties[i]; IProperty principalKeyProperty = foreignKey.PrincipalKey.Properties[i]; propertyValues[foreignKeyProperty.GetIndex()] = foreignKey.IsOwnership && principalKeyProperty.IsShadowProperty ? owner.GetHashCode() : principalKeyProperty.GetGetter().GetClrValue(owner); } } IEnumerable navigations = entityType .GetNavigations() .Where(navigation => navigation.IsDependentToPrincipal() && !navigation.IsCollection()); foreach (INavigation navigation in navigations) { var related = navigation.GetGetter().GetClrValue(instance); if (related != null) { IForeignKey foreignKey = navigation.ForeignKey; for (int i = 0; i < foreignKey.Properties.Count; i++) { IProperty foreignKeyProperty = foreignKey.Properties[i]; IProperty principalKeyProperty = foreignKey.PrincipalKey.Properties[i]; propertyValues[foreignKeyProperty.GetIndex()] = foreignKey.IsOwnership && principalKeyProperty.IsShadowProperty ? related.GetHashCode() : principalKeyProperty.GetGetter().GetClrValue(related); } } } return new ValueBuffer(propertyValues, 0); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Storage/IMongoDbConnection.cs ================================================ using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; using MongoDB.Driver.Linq; namespace Blueshift.EntityFrameworkCore.MongoDB.Storage { /// /// An interface for a service that can be used to interact with a MongoDB instance. /// public interface IMongoDbConnection { /// /// Gets the used by the current model. /// /// The used by the MongoDB C# driver to communicate with the MongoDB instance. IMongoDatabase GetDatabase(); /// /// Asynchronously gets the used by the current model. /// /// A to observe while waiting for the task to complete. /// /// A representing the state of the operation. The result contains The /// used by the MongoDB C# driver to communicate with the MongoDB instance. /// Task GetDatabaseAsync(CancellationToken cancellationToken = default(CancellationToken)); /// /// Drops the database used by this model from the MongoDB instance. /// void DropDatabase(); /// /// Asynchronously drops the database used by this model from the MongoDB instance. /// /// A to observe while waiting for the task to complete. /// A representing the state of the operation. Task DropDatabaseAsync(CancellationToken cancellationToken = default(CancellationToken)); /// /// Gets a instance that can be used to store instances of . /// /// The type of entity stored in the collection. /// The instance that can store . IMongoCollection GetCollection(); } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Storage/IMongoDbTypeMappingSource.cs ================================================ using Microsoft.EntityFrameworkCore.Storage; namespace Blueshift.EntityFrameworkCore.MongoDB.Storage { /// public interface IMongoDbTypeMappingSource : ITypeMappingSource { } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Storage/MongoDbConnection.cs ================================================ using System.Threading; using System.Threading.Tasks; using Blueshift.EntityFrameworkCore.MongoDB.Metadata; using Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Driver; namespace Blueshift.EntityFrameworkCore.MongoDB.Storage { /// /// A service that can be used to interact with a MongoDB instance. /// public class MongoDbConnection : IMongoDbConnection { private readonly IMongoClient _mongoClient; private readonly IMongoDatabase _mongoDatabase; private readonly IModel _model; /// /// Initializes a new instance of the class. /// /// The used to communicate with the MongoDB instance. /// The used by this connection. public MongoDbConnection( [NotNull] IMongoClient mongoClient, [NotNull] IModel model) { _model = Check.NotNull(model, nameof(model)); _mongoClient = Check.NotNull(mongoClient, nameof(mongoClient)); _mongoDatabase = _mongoClient.GetDatabase(new MongoDbModelAnnotations(model).Database); } /// public virtual IMongoDatabase GetDatabase() => _mongoDatabase; /// public virtual Task GetDatabaseAsync(CancellationToken cancellationToken = default(CancellationToken)) => Task.FromResult(_mongoDatabase); /// public virtual void DropDatabase() => _mongoClient.DropDatabase(new MongoDbModelAnnotations(_model).Database); /// public virtual Task DropDatabaseAsync(CancellationToken cancellationToken = default(CancellationToken)) => _mongoClient.DropDatabaseAsync(new MongoDbModelAnnotations(_model).Database, cancellationToken); /// public virtual IMongoCollection GetCollection() { IEntityType collectionEntityType = _model .FindEntityType(typeof(TEntity)) .GetMongoDbCollectionEntityType(); MongoDbEntityTypeAnnotations annotations = collectionEntityType.MongoDb(); return _mongoDatabase.GetCollection(annotations.CollectionName, annotations.CollectionSettings); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Storage/MongoDbDatabase.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Blueshift.EntityFrameworkCore.MongoDB.Adapter.Update; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Update; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Driver; using Remotion.Linq; namespace Blueshift.EntityFrameworkCore.MongoDB.Storage { /// /// The main interaction point between a context and MongoDB. /// This type is typically used by database providers (and other extensions). It /// is generally not used in application code. /// public class MongoDbDatabase : Database { private static readonly MethodInfo GenericUpdateEntries = MethodHelper.GetGenericMethodDefinition( (MongoDbDatabase mongoDbDatabase) => mongoDbDatabase.UpdateEntries(null)); private static readonly MethodInfo GenericUpdateEntriesAsync = MethodHelper.GetGenericMethodDefinition( (MongoDbDatabase mongoDbDatabase) => mongoDbDatabase.UpdateEntriesAsync(null, CancellationToken.None)); private readonly IMongoDbConnection _mongoDbConnection; private readonly IMongoDbWriteModelFactorySelector _mongoDbWriteModelFactorySelector; /// /// Initializes a new instance of hte class. /// /// Parameter object containing dependencies for this service. /// A used to communicate with the MongoDB instance. /// The to use to create /// instances. public MongoDbDatabase( [NotNull] DatabaseDependencies databaseDependencies, [NotNull] IMongoDbConnection mongoDbConnection, [NotNull] IMongoDbWriteModelFactorySelector mongoDbWriteModelFactorySelector) : base(Check.NotNull(databaseDependencies, nameof(databaseDependencies))) { _mongoDbConnection = Check.NotNull(mongoDbConnection, nameof(mongoDbConnection)); _mongoDbWriteModelFactorySelector = Check.NotNull(mongoDbWriteModelFactorySelector, nameof(mongoDbWriteModelFactorySelector)); } /// /// Persists changes from the supplied entries to the database. /// /// A list of entries to be persisted. /// The number of entries that were persisted. public override int SaveChanges(IReadOnlyList entries) => GetDocumentUpdateDefinitions(entries) .ToLookup(entry => entry.EntityType.GetMongoDbCollectionEntityType()) .Sum(grouping => (int)GenericUpdateEntries.MakeGenericMethod(grouping.Key.ClrType) .Invoke(this, new object[] { grouping })); private int UpdateEntries(IEnumerable entries) { IEnumerable> writeModels = entries .Select(entry => _mongoDbWriteModelFactorySelector.Select(entry).CreateWriteModel(entry)) .ToList(); BulkWriteResult result = _mongoDbConnection.GetCollection() .BulkWrite(writeModels); return (int) (result.DeletedCount + result.InsertedCount + result.ModifiedCount); } private ISet GetDocumentUpdateDefinitions(IReadOnlyCollection entries) { Check.NotNull(entries, nameof(entries)); ISet rootEntries = new HashSet(); foreach (IUpdateEntry updateEntry in entries) { if (updateEntry.EntityType.IsDocumentRootEntityType()) { rootEntries.Add(updateEntry); } else if (updateEntry is InternalEntityEntry internalEntityEntry) { rootEntries.Add(GetRootDocument(internalEntityEntry)); } else { // TBD - throw error } } return rootEntries; } /// /// Asynchronously persists changes from the supplied entries to the database. /// /// A list of entries to be persisted. /// A to observe while waiting for the task to complete. /// /// A representing the state of the operation. The result contains the number /// of entries that were persisted to the database. /// public override async Task SaveChangesAsync( IReadOnlyList entries, CancellationToken cancellationToken = default) { IEnumerable> tasks = GetDocumentUpdateDefinitions(entries) .ToLookup(entry => entry.EntityType.GetMongoDbCollectionEntityType()) .Select(async grouping => await InvokeUpdateEntriesAsync(grouping, cancellationToken)) .ToList(); int[] totals = await Task.WhenAll(tasks); return totals.Sum(); } private Task InvokeUpdateEntriesAsync(IGrouping entryGrouping, CancellationToken cancellationToken) => (Task)GenericUpdateEntriesAsync.MakeGenericMethod(entryGrouping.Key.ClrType) .Invoke(this, new object[] {entryGrouping, cancellationToken}); private async Task UpdateEntriesAsync( IEnumerable entries, CancellationToken cancellationToken) { IEnumerable> writeModels = entries .Select(entry => _mongoDbWriteModelFactorySelector.Select(entry).CreateWriteModel(entry)) .ToList(); BulkWriteResult result = await _mongoDbConnection.GetCollection() .BulkWriteAsync(writeModels, options: null, cancellationToken: cancellationToken); return (int) (result.DeletedCount + result.InsertedCount + result.ModifiedCount); } /// public override Func> CompileAsyncQuery(QueryModel queryModel) => queryContext => CompileQuery(queryModel)(queryContext).ToAsyncEnumerable(); private IUpdateEntry GetRootDocument(InternalEntityEntry entry) { var stateManager = entry.StateManager; InternalEntityEntry owningEntityEntry = entry.EntityType .GetForeignKeys() .Where(foreignKey => foreignKey.IsOwnership) .Select(foreignKey => stateManager.GetPrincipal(entry, foreignKey)) .SingleOrDefault(owner => owner != null); if (owningEntityEntry == null) { throw new InvalidOperationException($"Encountered orphaned document of type {entry.EntityType.DisplayName()}."); } return owningEntityEntry.EntityType.IsDocumentRootEntityType() ? owningEntityEntry : GetRootDocument(owningEntityEntry); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Storage/MongoDbDatabaseCreator.cs ================================================ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.EntityFrameworkCore.MongoDB.Storage { /// /// Creates and deletes databases for a given database provider. /// This interface is typically used by database providers (and other extensions). /// It is generally not used in application code. /// public class MongoDbDatabaseCreator : IDatabaseCreator { private readonly IMongoDbConnection _mongoDbConnection; /// /// Initializes a new instance of the class. /// /// The used to communicate with the MongoDB instance. public MongoDbDatabaseCreator([NotNull] IMongoDbConnection mongoDbConnection) { _mongoDbConnection = Check.NotNull(mongoDbConnection, nameof(mongoDbConnection)); } /// /// Ensures that the database for the context exists. /// /// /// MongoDB databases will always be created when they are first referenced, so this method will always /// return false. /// public virtual bool EnsureCreated() { _mongoDbConnection.GetDatabase(); return false; } /// /// Asynchronously ensures that the database for the context exists. /// /// A to observe while waiting for the task to complete. /// /// A task that represents the asynchronous save operation. MongoDB databases will always be created when they are /// first referenced, so the result will always contain false. /// public virtual async Task EnsureCreatedAsync(CancellationToken cancellationToken = new CancellationToken()) { await _mongoDbConnection.GetDatabaseAsync(cancellationToken); return false; } /// /// Ensures that the database for the context does not exist. /// /// /// MongoDB database are always created when they are first referenced, so this method /// will always return true. /// public virtual bool EnsureDeleted() { _mongoDbConnection.DropDatabase(); return true; } /// /// Asynchronously ensures that the database for the context does not exist. /// /// A to observe while waiting for the task to complete. /// /// A task that represents the asynchronous save operation. MongoDB database are always created when they are first /// referenced, so the result will always contain true. /// public virtual async Task EnsureDeletedAsync(CancellationToken cancellationToken = new CancellationToken()) { await _mongoDbConnection.DropDatabaseAsync(cancellationToken); return true; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/Storage/MongoDbTypeMappingSource.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using MongoDB.Bson; namespace Blueshift.EntityFrameworkCore.MongoDB.Storage { /// /// /// /// Determines whether a .NET type can be mapped to a MongoDB database type. /// public class MongoDbTypeMappingSource : TypeMappingSource, IMongoDbTypeMappingSource { private readonly ConcurrentDictionary _typeCache = new ConcurrentDictionary { [typeof(string)] = new PassThruTypeMapping(typeof(string)), [typeof(IEnumerable)] = new PassThruTypeMapping(typeof(IEnumerable)), [typeof(ObjectId)] = new PassThruTypeMapping(typeof(ObjectId)), [typeof(IEnumerable)] = new PassThruTypeMapping(typeof(IEnumerable)), [typeof(byte[])] = new PassThruTypeMapping(typeof(byte[])), [typeof(IEnumerable)] = new PassThruTypeMapping(typeof(IEnumerable)) }; /// public MongoDbTypeMappingSource([NotNull] TypeMappingSourceDependencies dependencies) : base(dependencies) { } /// protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo) => _typeCache.GetOrAdd( mappingInfo.ClrType, clrType => { TypeInfo typeInfo = (clrType.TryGetSequenceType() ?? clrType).UnwrapNullableType().GetTypeInfo(); return typeInfo.IsPrimitive || typeInfo.IsValueType ? new PassThruTypeMapping(clrType) : null; }); private class PassThruTypeMapping : CoreTypeMapping { private PassThruTypeMapping(CoreTypeMappingParameters parameters) : base(parameters) { } public PassThruTypeMapping([NotNull] Type clrType) : base(new CoreTypeMappingParameters(clrType)) { } public override CoreTypeMapping Clone(ValueConverter converter) => new PassThruTypeMapping(Parameters.WithComposedConverter(converter)); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/ValueGeneration/HashCodeValueGenerator.cs ================================================ using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ValueGeneration; using MongoDB.Bson; namespace Blueshift.EntityFrameworkCore.MongoDB.ValueGeneration { /// public class HashCodeValueGenerator : ValueGenerator { /// /// /// Generates a new value. /// /// The whose value is to be generated. /// A new for . public override int? Next(EntityEntry entry) => entry.Entity?.GetHashCode(); /// /// /// true if this generates temporary values; /// otherwise false. /// /// Always returns true. public override bool GeneratesTemporaryValues => false; } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/ValueGeneration/IntegerValueGenerator.cs ================================================ using System; using System.Globalization; using System.Threading; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ValueGeneration; using MongoDB.Bson; namespace Blueshift.EntityFrameworkCore.MongoDB.ValueGeneration { /// public class IntegerValueGenerator : ValueGenerator { private long _currentValue; /// /// /// Generates a new value. /// /// The whose value is to be generated. /// A new for . public override TValue Next(EntityEntry entry) => (TValue) Convert.ChangeType( Interlocked.Increment(ref _currentValue), typeof(TValue), CultureInfo.InvariantCulture); /// /// /// true if this generates temporary values; /// otherwise false. /// /// Always returns false. public override bool GeneratesTemporaryValues => false; } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/ValueGeneration/MongoDbValueGeneratorSelector.cs ================================================ using System; using System.Collections.Generic; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.EntityFrameworkCore.ValueGeneration; using MongoDB.Bson; namespace Blueshift.EntityFrameworkCore.MongoDB.ValueGeneration { /// public class MongoDbValueGeneratorSelector : ValueGeneratorSelector { private readonly IDictionary> _valueGeneratorMap = new Dictionary> { [typeof(ObjectId)] = () => new ObjectIdValueGenerator(), [typeof(short)] = () => new IntegerValueGenerator(), [typeof(int)] = () => new IntegerValueGenerator(), [typeof(long)] = () => new IntegerValueGenerator(), }; private readonly IDictionary> _shadowKeyGeneratorMap = new Dictionary> { [typeof(int)] = () => new HashCodeValueGenerator() }; /// /// /// Initializes a new instance of the class. /// /// Parameter object containing dependencies for this service. public MongoDbValueGeneratorSelector([NotNull] ValueGeneratorSelectorDependencies dependencies) : base(dependencies) { } /// /// /// Creates a new value generator for the given property. /// /// The property to get the value generator for. /// /// The entity type that the value generator will be used for. When called on inherited /// properties on derived entity types, this entity type may be different from the /// declared entity type on property /// /// The newly created value generator. public override ValueGenerator Create( IProperty property, IEntityType entityType) { Check.NotNull(property, nameof(property)); Check.NotNull(entityType, nameof(entityType)); IDictionary> valueGeneratorCreator = property.IsShadowProperty ? _shadowKeyGeneratorMap : _valueGeneratorMap; return valueGeneratorCreator .TryGetValue( property.ClrType.UnwrapNullableType(), out Func valueGeneratorFactory) ? valueGeneratorFactory() : base.Create(property, entityType); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB/ValueGeneration/ObjectIdValueGenerator.cs ================================================ using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ValueGeneration; using MongoDB.Bson; namespace Blueshift.EntityFrameworkCore.MongoDB.ValueGeneration { /// /// Generates values for properties when an entity is added to a context. /// public class ObjectIdValueGenerator : ValueGenerator { /// /// Generates a new value. /// /// The whose value is to be generated. /// A new for . public override ObjectId Next(EntityEntry entry) => ObjectId.GenerateNewId(); /// /// true if this generates temporary values; /// otherwise false. /// /// Always returns false. public override bool GeneratesTemporaryValues => false; } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB.SampleDomain/Blueshift.EntityFrameworkCore.MongoDB.SampleDomain.csproj ================================================  $(BuildFrameworks) Blueshift MongoDb Provider for EntityFrameworkCore Sample Domain Library for EntityFramework Core MongoDb Provider ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB.SampleDomain/ZooDbContext.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Blueshift.EntityFrameworkCore.MongoDB.Annotations; using Blueshift.EntityFrameworkCore.MongoDB.Infrastructure; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; namespace Blueshift.EntityFrameworkCore.MongoDB.SampleDomain { [MongoDatabase("zooDb")] public class ZooDbContext : DbContext { public DbSet Animals { get; set; } public DbSet Employees { get; set; } public DbSet Enclosures { get; set; } public ZooDbContext() : this(new DbContextOptions()) { } public ZooDbContext(DbContextOptions zooDbContextOptions) : base(zooDbContextOptions) { } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { const string connectionString = "mongodb://localhost"; //optionsBuilder.UseMongoDb(connectionString); var mongoUrl = new MongoUrl(connectionString); //optionsBuilder.UseMongoDb(mongoUrl); MongoClientSettings settings = MongoClientSettings.FromUrl(mongoUrl); //settings.SslSettings = new SslSettings //{ // EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12 //}; //optionsBuilder.UseMongoDb(settings); var mongoClient = new MongoClient(settings); optionsBuilder.UseMongoDb(mongoClient); } } [BsonKnownTypes(typeof(Tiger), typeof(PolarBear), typeof(Otter))] [BsonDiscriminator(RootClass = true)] public abstract class Animal { // When using attribute-driven data modeling, either [BsonId] or [Key] // is required for the primary key field; either will work with the // MongoDb C# driver EFCore adapter [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public ObjectId AnimalId { get; [UsedImplicitly] private set; } public string Name { get; set; } public decimal Age { get; set; } public decimal Height { get; set; } public decimal Weight { get; set; } [ConcurrencyCheck, DatabaseGenerated(DatabaseGeneratedOption.Computed)] public string ConcurrencyField { get; [UsedImplicitly] private set; } [Denormalize(nameof(SampleDomain.Enclosure.Name))] public Enclosure Enclosure { get; set; } } [BsonDiscriminator("panthera tigris")] public class Tiger : Animal { } [BsonDiscriminator("Ursus maritimus")] public class PolarBear : Animal { } [BsonDiscriminator("Lutrinae")] [BsonKnownTypes(typeof(SeaOtter), typeof(EurasianOtter))] public abstract class Otter : Animal { } [BsonDiscriminator("Enhydra lutris")] public class SeaOtter : Otter { } [BsonDiscriminator("Lutra lutra")] public class EurasianOtter : Otter { } public class Employee { // When using attribute-driven data modeling, either [BsonId] or [Key] // is required for the primary key field; either will work with the // MongoDb C# driver EFCore adapter [BsonId, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public ObjectId EmployeeId { get; [UsedImplicitly] private set; } public string FirstName { get; set; } public string LastName { get; set; } [BsonElement] public string FullName => string.IsNullOrWhiteSpace(FirstName) ? LastName : $"{LastName}, {FirstName}"; public decimal Age { get; set; } public IList Specialties { get; set; } = new List(); [BsonIgnore] public string Ignored => $"This string should never show up in the database."; public Employee Manager { get; set; } public IList DirectReports { get; set; } = new List(); } public enum ZooTask { Feeding, Training, Exercise, TourGuide } [Owned] public class Specialty { public string AnimalType { get; set; } public ZooTask Task { get; set; } } public class Enclosure { // When using attribute-driven data modeling, either [BsonId] or [Key] // is required for the primary key field; either will work with the // MongoDb C# driver EFCore adapter [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public ObjectId EnclosureId { get; [UsedImplicitly] private set; } public string Name { get; set; } public string AnimalEnclosureType { get; set; } [Denormalize(nameof(Animal.Name))] public IList Animals { get; [UsedImplicitly] private set; } = new List(); public Schedule WeeklySchedule { get; set; } } [Owned] public class Schedule { public IList Assignments { get; [UsedImplicitly] private set; } = new List(); [Denormalize(nameof(Employee.FirstName), nameof(Employee.LastName))] public Employee Approver { get; set; } } public class ZooAssignment { public TimeSpan Offset { get; set; } public ZooTask Task { get; set; } [Denormalize(nameof(Employee.FirstName), nameof(Employee.LastName))] public Employee Assignee { get; set; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB.SampleDomain/ZooDbDependencyInjection.cs ================================================ using Blueshift.EntityFrameworkCore.MongoDB.Infrastructure; using Microsoft.Extensions.DependencyInjection; namespace Blueshift.EntityFrameworkCore.MongoDB.SampleDomain { public static class ZooDbDependencyInjection { public static IServiceCollection AddZooDbContext(this IServiceCollection serviceCollection) { return serviceCollection .AddDbContext(options => options.UseMongoDb("mongodb://localhost")); } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB.SampleDomain/ZooEntityFixture.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Blueshift.EntityFrameworkCore.MongoDB.Adapter; namespace Blueshift.EntityFrameworkCore.MongoDB.SampleDomain { internal static class ZooExtensions { public static TAnimal WithEnclosure(this TAnimal animal, Enclosure enclosure) where TAnimal : Animal { animal.Enclosure = enclosure; enclosure.Animals.Add(animal); return animal; } public static Enclosure WithSchedule(this Enclosure enclosure, Action configurator = null) { enclosure.WeeklySchedule = new Schedule(); configurator?.Invoke(enclosure.WeeklySchedule); return enclosure; } public static Employee WithManager(this Employee employee, Employee manager) { employee.Manager = manager; manager.DirectReports.Add(employee); return employee; } public static Schedule WithApprover(this Schedule schedule, Employee manager) { schedule.Approver = manager; return schedule; } public static Schedule WithAssignment(this Schedule schedule, ZooAssignment zooAssignment) { schedule.Assignments.Add(zooAssignment); return schedule; } public static Schedule WithAssignment( this Schedule schedule, TimeSpan offset, Employee assignee, ZooTask assignedTask) => schedule.WithAssignment(new ZooAssignment { Offset = offset, Assignee = assignee, Task = assignedTask }); } public class ZooEntityFixture { static ZooEntityFixture() { EntityFrameworkConventionPack.Register(type => true); } public ZooEntities Entities => new ZooEntities(); } public class ZooEntities { public ZooEntities() { ManAgier = new Employee { FirstName = "Man", LastName = "A'Gier", Age = 58.4M, Specialties = { // those who can't zoo, manage! } }; TaigaMasuta = new Employee { FirstName = "Taiga", LastName = "Masuta", Age = 31.7M, Specialties = { new Specialty {AnimalType = nameof(Tiger), Task = ZooTask.Feeding}, new Specialty {AnimalType = nameof(Tiger), Task = ZooTask.Exercise}, new Specialty {AnimalType = nameof(Tiger), Task = ZooTask.Training} } } .WithManager(ManAgier); OttoVonEssenmacher = new Employee { FirstName = "Otto", LastName = "von Essenmacher", Age = 22.1M, Specialties = { new Specialty {AnimalType = nameof(Otter), Task = ZooTask.Feeding}, new Specialty {AnimalType = nameof(Otter), Task = ZooTask.Exercise} } } .WithManager(ManAgier); BearOCreary = new Employee { FirstName = "Bear", LastName = "O'Creary", Age = 41.4M, Specialties = { new Specialty {AnimalType = nameof(PolarBear), Task = ZooTask.Feeding}, new Specialty {AnimalType = nameof(PolarBear), Task = ZooTask.Training} } } .WithManager(ManAgier); TurGuidry = new Employee { FirstName = "Tur", LastName = "Guidry", Age = 36.7M, Specialties = { new Specialty {AnimalType = nameof(Tiger), Task = ZooTask.TourGuide}, new Specialty {AnimalType = nameof(Otter), Task = ZooTask.TourGuide}, new Specialty {AnimalType = nameof(PolarBear), Task = ZooTask.TourGuide} } } .WithManager(ManAgier); TigerEnclosure = new Enclosure { AnimalEnclosureType = nameof(Tiger), Name = "Tiger Pen" } .WithSchedule(schedule => schedule .WithApprover(ManAgier) .WithAssignment(TimeSpan.FromHours(1), TaigaMasuta, ZooTask.Feeding) .WithAssignment(TimeSpan.FromHours(3), TaigaMasuta, ZooTask.Training) .WithAssignment(TimeSpan.FromHours(4), TurGuidry, ZooTask.TourGuide) .WithAssignment(TimeSpan.FromHours(7), TaigaMasuta, ZooTask.Feeding)); OtterEnclosure = new Enclosure { AnimalEnclosureType = nameof(Otter), Name = "Otter Tank" } .WithSchedule(schedule => schedule .WithApprover(ManAgier) .WithAssignment(TimeSpan.FromHours(1), OttoVonEssenmacher, ZooTask.Feeding) .WithAssignment(TimeSpan.FromHours(3), OttoVonEssenmacher, ZooTask.Exercise) .WithAssignment(TimeSpan.FromHours(6), TurGuidry, ZooTask.TourGuide) .WithAssignment(TimeSpan.FromHours(7), OttoVonEssenmacher, ZooTask.Feeding)); PolarBearEnclosure = new Enclosure { AnimalEnclosureType = nameof(PolarBear), Name = "Igloo" } .WithSchedule(schedule => schedule .WithApprover(ManAgier) .WithAssignment(TimeSpan.FromHours(1), BearOCreary, ZooTask.Feeding) .WithAssignment(TimeSpan.FromHours(3), BearOCreary, ZooTask.Training) .WithAssignment(TimeSpan.FromHours(5), TurGuidry, ZooTask.TourGuide) .WithAssignment(TimeSpan.FromHours(7), BearOCreary, ZooTask.Feeding)); Tigger = new Tiger { Name = "Tigger", Age = 6.4M, Weight = 270, Height = .98M } .WithEnclosure(TigerEnclosure); Ursus = new PolarBear { Name = "Ursus", Age = 4.9M, Weight = 612, Height = 2.7M } .WithEnclosure(PolarBearEnclosure); Hydron = new SeaOtter { Name = "Hydron", Age = 1.8M, Weight = 19, Height = .3M } .WithEnclosure(OtterEnclosure); Yuri = new EurasianOtter { Name = "Yuri", Age = 1.8M, Weight = 19, Height = .3M } .WithEnclosure(OtterEnclosure); Animals = new Animal[] { Tigger, Ursus, Hydron, Yuri } .OrderBy(animal => animal.Name) .ThenBy(animal => animal.Height) .ToList(); Enclosures = new[] { TigerEnclosure, PolarBearEnclosure, OtterEnclosure } .OrderBy(enclosure => enclosure.AnimalEnclosureType) .ThenBy(enclosure => enclosure.Name) .ToList(); Employees = new[] { TaigaMasuta, BearOCreary, OttoVonEssenmacher, TurGuidry, ManAgier } .OrderBy(employee => employee.FullName) .ToList(); Entities = Animals .Cast() .Concat(Enclosures) .Concat(Employees) .ToList(); } public Employee TaigaMasuta { get; } public Employee OttoVonEssenmacher { get; } public Employee BearOCreary { get; } public Employee TurGuidry { get; } public Employee ManAgier { get; } public Enclosure TigerEnclosure { get; } public Enclosure OtterEnclosure { get; } public Enclosure PolarBearEnclosure { get; } public Tiger Tigger { get; } public PolarBear Ursus { get; } public SeaOtter Hydron { get; } public EurasianOtter Yuri { get; } public ICollection Animals { get; } public ICollection Enclosures { get; } public ICollection Employees { get; } public ICollection Entities { get; } } } ================================================ FILE: src/Blueshift.EntityFrameworkCore.MongoDB.SampleDomain/_Comparers.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Blueshift.EntityFrameworkCore.MongoDB.SampleDomain { public class AnimalComparer : IComparer { public int Compare(Animal x, Animal y) => x?.GetType() == y?.GetType() ? Comparer.Default.Compare(x?.Name, y?.Name) : Comparer.Default.Compare(x?.GetType().Name, y?.GetType().Name); } public abstract class BaseEqualityComparer : EqualityComparer { public override int GetHashCode(T obj) => obj?.GetHashCode() ?? throw new ArgumentNullException(nameof(obj)); public sealed override bool Equals(T item1, T item2) => (item1 == null && item2 == null) || (item1 != null && item2 != null && MemberwiseEquals(item1, item2)); protected abstract bool MemberwiseEquals(T item1, T item2); } public static class EqualityComparerExtensions { public static bool CompareCollections( this EqualityComparer equalityComparer, ICollection collection1, ICollection collection2) => collection1.Count == collection2.Count && collection1.All(item1 => collection2.Contains(item1, equalityComparer)) && collection2.All(item2 => collection1.Contains(item2, equalityComparer)); } public class EmployeeEqualityComparer : BaseEqualityComparer { private readonly SpecialtyEqualityComparer _specialtyEqualityComparer = new SpecialtyEqualityComparer(); private EmployeeEqualityComparer _managerEqualityComparer; private EmployeeEqualityComparer _directReportsEqualityComparer; public EmployeeEqualityComparer WithDirectReportsComparer(Action configurator = null) { _directReportsEqualityComparer = new EmployeeEqualityComparer(); configurator?.Invoke(_directReportsEqualityComparer); return this; } public EmployeeEqualityComparer WithManagerComparer(Action configurator = null) { _managerEqualityComparer = new EmployeeEqualityComparer(); configurator?.Invoke(_managerEqualityComparer); return this; } protected override bool MemberwiseEquals(Employee employee1, Employee employee2) => Equals(employee1.EmployeeId, employee2.EmployeeId) && string.Equals(employee1.FirstName, employee2.FirstName, StringComparison.Ordinal) && string.Equals(employee1.LastName, employee2.LastName, StringComparison.Ordinal) && employee1.Age == employee2.Age && (_specialtyEqualityComparer?.CompareCollections(employee1.Specialties, employee2.Specialties) ?? true) && (_managerEqualityComparer?.Equals(employee1.Manager, employee2.Manager) ?? true) && (_directReportsEqualityComparer?.CompareCollections(employee1.DirectReports, employee2.DirectReports) ?? true); } public class SpecialtyEqualityComparer : BaseEqualityComparer { protected override bool MemberwiseEquals(Specialty specialty1, Specialty specialty2) => string.Equals(specialty1.AnimalType, specialty2.AnimalType, StringComparison.Ordinal) && specialty1.Task == specialty2.Task; } public class AnimalEqualityComparer : BaseEqualityComparer { private EnclosureEqualityComparer _enclosureEqualityComparer; protected override bool MemberwiseEquals(Animal animal1, Animal animal2) => Equals(animal1.AnimalId, animal2.AnimalId) && animal1.GetType() == animal2.GetType() && string.Equals(animal1.Name, animal2.Name, StringComparison.Ordinal) && animal1.Age == animal2.Age && animal1.Height == animal2.Height && animal1.Weight == animal2.Weight && Equals(animal1.Enclosure?.EnclosureId, animal2.Enclosure?.EnclosureId) && (_enclosureEqualityComparer?.Equals(animal1.Enclosure, animal2.Enclosure) ?? true); public AnimalEqualityComparer WithEnclosureEqualityComparer(Action configurator = null) { _enclosureEqualityComparer = new EnclosureEqualityComparer(); configurator?.Invoke(_enclosureEqualityComparer); return this; } } public class EnclosureEqualityComparer : BaseEqualityComparer { private AnimalEqualityComparer _animalEqualityComparer = null; private readonly ScheduleEqualityComparer _scheduleEqualityComparer = new ScheduleEqualityComparer(); protected override bool MemberwiseEquals(Enclosure enclosure1, Enclosure enclosure2) => Equals(enclosure1.EnclosureId, enclosure2.EnclosureId) && string.Equals(enclosure1.Name, enclosure2.Name, StringComparison.Ordinal) && string.Equals(enclosure1.AnimalEnclosureType, enclosure2.AnimalEnclosureType, StringComparison.Ordinal) && (_animalEqualityComparer?.CompareCollections(enclosure1.Animals, enclosure2.Animals) ?? true) && (_scheduleEqualityComparer?.Equals(enclosure1.WeeklySchedule, enclosure2.WeeklySchedule) ?? true); public EnclosureEqualityComparer WithAnimalEqualityComparer(Action configurator = null) { _animalEqualityComparer = new AnimalEqualityComparer(); configurator?.Invoke(_animalEqualityComparer); return this; } public EnclosureEqualityComparer ConfigureWeeklyScheduleEqualityComparer( Action configurator) { configurator.Invoke(_scheduleEqualityComparer); return this; } } public class ScheduleEqualityComparer : BaseEqualityComparer { private readonly ZooAssignmentEqualityComparer _zooAssignmentEqualityComparer = new ZooAssignmentEqualityComparer(); private EmployeeEqualityComparer _approverEqualityComparer; protected override bool MemberwiseEquals(Schedule schedule1, Schedule schedule2) => (_approverEqualityComparer?.Equals(schedule1.Approver, schedule2.Approver) ?? true) && _zooAssignmentEqualityComparer.CompareCollections(schedule1.Assignments, schedule2.Assignments); public ScheduleEqualityComparer WithApproverEqualityComparer( Action configurator = null) { _approverEqualityComparer = new EmployeeEqualityComparer(); configurator?.Invoke(_approverEqualityComparer); return this; } public ScheduleEqualityComparer ConfigureZooAssignmentEqualityComparer( Action configurator) { configurator.Invoke(_zooAssignmentEqualityComparer); return this; } } public class ZooAssignmentEqualityComparer : BaseEqualityComparer { private EmployeeEqualityComparer _employeeEqualityComparer; protected override bool MemberwiseEquals(ZooAssignment zooAssignment1, ZooAssignment zooAssignment2) => Equals(zooAssignment1.Offset, zooAssignment2.Offset) && Equals(zooAssignment1.Task, zooAssignment2.Task) && (_employeeEqualityComparer?.Equals(zooAssignment1.Assignee, zooAssignment2.Assignee) ?? true); public ZooAssignmentEqualityComparer WithEmployeeEqualityComparer() { _employeeEqualityComparer = new EmployeeEqualityComparer(); return this; } } } ================================================ FILE: src/Blueshift.Identity.MongoDB/Blueshift.Identity.MongoDB.csproj ================================================  $(BuildFrameworks) Blueshift MongoDB EntityFrameworkCore Identity Provider for Microsoft ASP.NET Core Blueshift Software MongoDB EntityFrameworkCore Identity Provider for Microsoft ASP.NET Core true $(PackageTags);AspNetCore;Identity;Membership ================================================ FILE: src/Blueshift.Identity.MongoDB/DependencyInjection/IdentityEntityFrameworkMongoDbBuilderExtensions.cs ================================================ using System; using System.Reflection; using Blueshift.Identity.MongoDB; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection.Extensions; // ReSharper disable once CheckNamespace namespace Microsoft.Extensions.DependencyInjection { /// /// Provides extension methods to for adding MongoDB for EntityFramework stores. /// public static class IdentityEntityFrameworkMongoDbBuilderExtensions { private static readonly Type BaseUserType = typeof(MongoDbIdentityUser<,,,,>); private static readonly Type BaseRoleType = typeof(MongoDbIdentityRole<,>); private static readonly Type BaseStoreType = typeof(MongoDbUserStore<,,,,,,,>); /// /// Adds a MongoDB for Entity Framework implementation of identity information stores. /// /// The Entity Framework database context to use. /// The instance this method extends. /// The instance this method extends. public static IdentityBuilder AddEntityFrameworkMongoDbStores(this IdentityBuilder builder) where TContext : DbContext { AddMongoDbStores(builder.Services, builder.UserType, builder.RoleType, typeof(TContext)); return builder; } /// /// Adds a default MongoDB for Entity Framework implementation of identity information stores. /// /// The Entity Framework database context to use. /// The type of the primary key used for the users and roles. /// The instance this method extends. /// The instance this method extends. public static IdentityBuilder AddEntityFrameworkDbStores(this IdentityBuilder builder) where TContext : DbContext where TKey : IEquatable { AddMongoDbStores(builder.Services, builder.UserType, builder.RoleType, typeof(TContext), typeof(TKey)); return builder; } private static void AddMongoDbStores(IServiceCollection services, Type userType, Type roleType, Type contextType, Type keyType = null) { TypeInfo identityUserType = FindGenericBaseType(userType, BaseUserType); if (identityUserType == null) { throw new InvalidOperationException($"User type does not derive from {BaseUserType.FullName}."); } TypeInfo identityRoleType = FindGenericBaseType(roleType, BaseRoleType); if (identityRoleType == null) { throw new InvalidOperationException($"Role type does not derive from {BaseRoleType.FullName}."); } Type userStoreType = BaseStoreType .MakeGenericType( userType, roleType, contextType, keyType ?? identityUserType.GenericTypeArguments[0], identityUserType.GenericTypeArguments[1], //TClaim identityUserType.GenericTypeArguments[2], //TUserRole identityUserType.GenericTypeArguments[3], //TUserLogin identityUserType.GenericTypeArguments[4]); //TUserToken Type roleStoreType = typeof(MongoDbRoleStore<,,,>) .MakeGenericType( roleType, contextType, keyType ?? identityUserType.GenericTypeArguments[0], identityRoleType.GenericTypeArguments[1]); //TClaim object GetUserStore(IServiceProvider provider) => provider.GetRequiredService(userStoreType); object GetRoleStore(IServiceProvider provider) => provider.GetRequiredService(roleStoreType); services.TryAddScoped(userStoreType); services.TryAddScoped(typeof(IQueryableUserStore<>).MakeGenericType(userType), GetUserStore); services.TryAddScoped(typeof(IUserAuthenticationTokenStore<>).MakeGenericType(userType), GetUserStore); services.TryAddScoped(typeof(IUserAuthenticatorKeyStore<>).MakeGenericType(userType), GetUserStore); services.TryAddScoped(typeof(IUserClaimStore<>).MakeGenericType(userType), GetUserStore); services.TryAddScoped(typeof(IUserEmailStore<>).MakeGenericType(userType), GetUserStore); services.TryAddScoped(typeof(IUserLoginStore<>).MakeGenericType(userType), GetUserStore); services.TryAddScoped(typeof(IUserLockoutStore<>).MakeGenericType(userType), GetUserStore); services.TryAddScoped(typeof(IUserPasswordStore<>).MakeGenericType(userType), GetUserStore); services.TryAddScoped(typeof(IUserRoleStore<>).MakeGenericType(userType), GetUserStore); services.TryAddScoped(typeof(IUserPhoneNumberStore<>).MakeGenericType(userType), GetUserStore); services.TryAddScoped(typeof(IUserSecurityStampStore<>).MakeGenericType(userType), GetUserStore); services.TryAddScoped(typeof(IUserTwoFactorStore<>).MakeGenericType(userType), GetUserStore); services.TryAddScoped(typeof(IUserTwoFactorRecoveryCodeStore<>).MakeGenericType(userType), GetUserStore); services.TryAddScoped(typeof(IUserStore<>).MakeGenericType(userType), GetUserStore); services.TryAddScoped(roleStoreType); services.TryAddScoped(typeof(IQueryableRoleStore<>).MakeGenericType(roleType), GetRoleStore); services.TryAddScoped(typeof(IRoleClaimStore<>).MakeGenericType(roleType), GetRoleStore); services.TryAddScoped(typeof(IRoleStore<>).MakeGenericType(roleType), GetRoleStore); } private static TypeInfo FindGenericBaseType(Type currentType, Type genericBaseType) { var type = currentType.GetTypeInfo(); while (type != null) { type = type.GetTypeInfo(); var genericType = type.IsGenericType ? type.GetGenericTypeDefinition() : null; if (genericType != null && genericType == genericBaseType) { return type; } type = type.BaseType.GetTypeInfo(); } return null; } } } ================================================ FILE: src/Blueshift.Identity.MongoDB/IdentityMongoDbContext.cs ================================================ using System; using Blueshift.EntityFrameworkCore.MongoDB.Annotations; using Microsoft.EntityFrameworkCore; using MongoDB.Bson; namespace Blueshift.Identity.MongoDB { /// /// /// Base class for the Entity Framework MongoDB database context used for identity. /// [MongoDatabase("__identities")] public class IdentityMongoDbContext : IdentityMongoDbContext { /// /// /// Initializes a new instance of . /// /// The options to be used by a . public IdentityMongoDbContext(DbContextOptions options) : base(options) { } /// /// Initializes a new instance of . /// /// The options to be used by a . protected IdentityMongoDbContext(DbContextOptions options) : base(options) { } /// /// Initializes a new instance of the class. /// protected IdentityMongoDbContext() { } } /// /// Base class for the Entity Framework MongoDB database context used for identity. /// /// The type of the primary key for users and roles. [MongoDatabase("__identities")] public class IdentityMongoDbContext : IdentityMongoDbContext< MongoDbIdentityUser, MongoDbIdentityRole, TKey, MongoDbIdentityClaim, MongoDbIdentityUserRole, MongoDbIdentityUserLogin, MongoDbIdentityUserToken> where TKey : IEquatable { /// /// Initializes a new instance of . /// /// The options to be used by a . public IdentityMongoDbContext(DbContextOptions> options) : base(options) { } /// /// Initializes a new instance of . /// /// The options to be used by a . protected IdentityMongoDbContext(DbContextOptions options) : base(options) { } /// /// Initializes a new instance of the class. /// protected IdentityMongoDbContext() { } } /// /// /// Base class for the Entity Framework MongoDB database context used for identity. /// /// The type of user objects. /// The type of role objects. [MongoDatabase("__identities")] public class IdentityMongoDbContext : IdentityMongoDbContext where TUser : MongoDbIdentityUser where TRole : MongoDbIdentityRole { /// /// /// Initializes a new instance of . /// /// The options to be used by a . public IdentityMongoDbContext(DbContextOptions> options) : base(options) { } /// /// /// Initializes a new instance of . /// /// The options to be used by a . protected IdentityMongoDbContext(DbContextOptions options) : base(options) { } /// /// Initializes a new instance of the class. /// protected IdentityMongoDbContext() { } } /// /// /// Base class for the Entity Framework MongoDB database context used for identity. /// /// The type of user objects. /// The type of role objects. /// The type of the primary key for users and roles. [MongoDatabase("__identities")] public class IdentityMongoDbContext : IdentityMongoDbContext< TUser, TRole, TKey, MongoDbIdentityClaim, MongoDbIdentityUserRole, MongoDbIdentityUserLogin, MongoDbIdentityUserToken> where TUser : MongoDbIdentityUser< TKey, MongoDbIdentityClaim, MongoDbIdentityUserRole, MongoDbIdentityUserLogin, MongoDbIdentityUserToken> where TRole : MongoDbIdentityRole< TKey, MongoDbIdentityClaim> where TKey : IEquatable { /// /// /// Initializes a new instance of . /// /// The options to be used by a . public IdentityMongoDbContext(DbContextOptions> options) : base(options) { } /// /// /// Initializes a new instance of . /// /// The options to be used by a . protected IdentityMongoDbContext(DbContextOptions options) : base(options) { } /// /// /// Initializes a new instance of the class. /// protected IdentityMongoDbContext() { } } /// /// /// Base class for the Entity Framework MongoDB database context used for identity. /// /// The type of user objects. /// The type of role objects. /// The type of the primary key for users and roles. /// The type of the role claim object. /// The type of the user role object. /// The type of the user login object. /// The type of the user token object. [MongoDatabase("__identities")] public class IdentityMongoDbContext : DbContext where TUser : MongoDbIdentityUser where TRole : MongoDbIdentityRole where TKey : IEquatable where TClaim : MongoDbIdentityClaim where TUserRole : MongoDbIdentityUserRole where TUserLogin : MongoDbIdentityUserLogin where TUserToken : MongoDbIdentityUserToken { /// /// /// Initializes a new instance of . /// /// The options to be used by a . public IdentityMongoDbContext(DbContextOptions> options) : base(options) { } /// /// /// Initializes a new instance of . /// /// The options to be used by a . protected IdentityMongoDbContext(DbContextOptions options) : base(options) { } /// /// /// Initializes a new instance of the class. /// protected IdentityMongoDbContext() { } /// /// Gets or sets the used to store user documents. /// public DbSet Users { get; set; } /// /// Gets or sets the of roles. /// public DbSet Roles { get; set; } } } ================================================ FILE: src/Blueshift.Identity.MongoDB/MongoDbIdentityClaim.cs ================================================ using System.Security.Claims; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.Identity.MongoDB { /// /// A representation of an authorization claim for use with a MongoDB EntityFramework provider. /// [Owned] public class MongoDbIdentityClaim { /// /// Initializes a new instance of the class. /// public MongoDbIdentityClaim() { } /// /// Initializes a new instance of the class. /// /// The security to use to initialize this role claim. public MongoDbIdentityClaim(Claim claim) { InitializeFromClaim(claim); } /// /// Gets or sets the claim type for this claim. /// public virtual string ClaimType { get; set; } /// /// Gets or sets the claim value for this claim. /// public virtual string ClaimValue { get; set; } /// /// Constructs a new claim with the type and value. /// /// A that represents this . public virtual Claim ToClaim() => new Claim(ClaimType, ClaimValue); /// /// Initializes this with values from the given . /// /// The source to use for initialization. public virtual void InitializeFromClaim(Claim claim) { Check.NotNull(claim, nameof(claim)); ClaimType = claim.Type; ClaimValue = claim.Value; } } } ================================================ FILE: src/Blueshift.Identity.MongoDB/MongoDbIdentityRole.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Blueshift.EntityFrameworkCore.MongoDB.Annotations; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; namespace Blueshift.Identity.MongoDB { /// /// A representation of an authorization role for use with a MongoDB EntityFramework provider. /// [MongoCollection("roles")] public class MongoDbIdentityRole : MongoDbIdentityRole { } /// /// A representation of an authorization role for use with a MongoDB EntityFramework provider. /// /// The type of the role's identifier. [MongoCollection("roles")] public class MongoDbIdentityRole : MongoDbIdentityRole where TKey : IEquatable { } /// /// A representation of an authorization role for use with a MongoDB EntityFramework provider. /// /// The type of the role's identifier. /// The type of authorization claims assigned to this role. [MongoCollection("roles")] public class MongoDbIdentityRole where TKey : IEquatable where TClaim : MongoDbIdentityClaim { /// /// Initializes a new instance of the class. /// public MongoDbIdentityRole() { } /// /// Initializes a new instance of the class. /// /// The name of the role. public MongoDbIdentityRole(string roleName) { RoleName = Check.NotNull(roleName, nameof(roleName)); } /// /// A collection of the security claims assigned to this role. /// public virtual ICollection Claims { get; [UsedImplicitly] private set; } = new List(); /// /// Gets or sets the primary key for this role. /// [BsonId, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public virtual TKey Id { get; [UsedImplicitly] private set; } /// /// Gets or sets the name for this role. /// public virtual string RoleName { get; set; } /// /// Gets or sets the normalized name for this role. /// public virtual string NormalizedRoleName { get; set; } /// /// A random value that should change whenever a role is persisted to the store. /// [ConcurrencyCheck] public virtual string ConcurrencyStamp { get; set; } /// /// Returns the name of this . /// /// The name of this role. public override string ToString() => RoleName; } } ================================================ FILE: src/Blueshift.Identity.MongoDB/MongoDbIdentityUser.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Blueshift.EntityFrameworkCore.MongoDB.Annotations; using JetBrains.Annotations; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; namespace Blueshift.Identity.MongoDB { /// /// A representation of a user security principal for use with a MongoDB EntityFramework provider. /// [MongoCollection("users")] public class MongoDbIdentityUser : MongoDbIdentityUser { /// /// Initializes a new instance of . /// public MongoDbIdentityUser() { } /// /// Initializes a new instance of . /// /// The user name. public MongoDbIdentityUser(string userName) : base(userName) { } } /// /// A representation of a user security principal for use with a MongoDB EntityFramework provider. /// /// The type of the primary key for this object. [MongoCollection("users")] public class MongoDbIdentityUser : MongoDbIdentityUser< TKey, MongoDbIdentityClaim, MongoDbIdentityUserRole, MongoDbIdentityUserLogin, MongoDbIdentityUserToken> where TKey : IEquatable { /// /// Initializes a new instance of . /// public MongoDbIdentityUser() { } /// /// Initializes a new instance of . /// /// The user name. public MongoDbIdentityUser(string userName) : base(userName) { } } /// /// A representation of a user security principal for use with a MongoDB EntityFramework provider. /// /// The type of the primary key for this object. /// The type of security claims assigned to this user. /// The type security roles assigned to this user. /// The type of external login provider information assigned to this user. /// The type of external login provider tokens assigned to this user. [MongoCollection("users")] public class MongoDbIdentityUser where TKey : IEquatable where TClaim : MongoDbIdentityClaim where TUserRole : MongoDbIdentityUserRole where TUserLogin : MongoDbIdentityUserLogin where TUserToken : MongoDbIdentityUserToken { /// /// Initializes a new instance of . /// public MongoDbIdentityUser() { } /// /// Initializes a new instance of . /// /// The user name. public MongoDbIdentityUser(string userName) : this() { UserName = userName; } /// /// Gets or sets the primary key for this user. /// [BsonId, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public virtual TKey Id { get; [UsedImplicitly] private set; } /// /// Gets or sets the user name for this user. /// public virtual string UserName { get; set; } /// /// Gets or sets the normalized user name for this user. /// public virtual string NormalizedUserName { get; set; } /// /// Gets or sets the email address for this user. /// public virtual string Email { get; set; } /// /// Gets or sets the normalized email address for this user. /// public virtual string NormalizedEmail { get; set; } /// /// Gets or sets a flag indicating if a user has confirmed their email address. /// /// True if the email address has been confirmed, otherwise false. public virtual bool EmailConfirmed { get; set; } /// /// Gets or sets a salted and hashed representation of the password for this user. /// public virtual string PasswordHash { get; set; } /// /// A random value that must change whenever a users credentials change (password changed, login removed) /// public virtual string SecurityStamp { get; set; } /// /// A random value that must change whenever a user is persisted to the store /// [ConcurrencyCheck] public virtual string ConcurrencyStamp { get; set; } /// /// Gets or sets a telephone number for the user. /// public virtual string PhoneNumber { get; set; } /// /// Gets or sets a flag indicating if a user has confirmed their telephone address. /// /// True if the telephone number has been confirmed, otherwise false. public virtual bool PhoneNumberConfirmed { get; set; } /// /// Gets or sets a flag indicating if two factor authentication is enabled for this user. /// /// True if 2fa is enabled, otherwise false. public virtual bool TwoFactorEnabled { get; set; } /// /// Gets or sets the date and time, in UTC, when any user lockout ends. /// /// /// A value in the past means the user is not locked out. /// public virtual DateTimeOffset? LockoutEnd { get; set; } /// /// Gets or sets a flag indicating if the user could be locked out. /// /// True if the user could be locked out, otherwise false. public virtual bool LockoutEnabled { get; set; } /// /// Gets or sets the number of failed login attempts for the current user. /// public virtual int AccessFailedCount { get; set; } /// /// A collection of security roles assigned to this user. /// public virtual ICollection Roles { get; [UsedImplicitly] private set; } = new List(); /// /// A collection of security claims assigned to this user. /// public virtual ICollection Claims { get; [UsedImplicitly] private set; } = new List(); /// /// A collection of external login provider information assigned to this user. /// public virtual ICollection Logins { get; [UsedImplicitly] private set; } = new List(); /// /// Returns the for this user. /// /// The username of this user. public override string ToString() => UserName; } } ================================================ FILE: src/Blueshift.Identity.MongoDB/MongoDbIdentityUserLogin.cs ================================================ using System.Collections.Generic; using JetBrains.Annotations; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Utilities; namespace Blueshift.Identity.MongoDB { /// /// A representation of an external user login provider for use with a MongoDB EntityFramework provider. /// [Owned] public class MongoDbIdentityUserLogin : MongoDbIdentityUserLogin { /// /// Initializes a new instance of the class. /// public MongoDbIdentityUserLogin() { } /// /// Initializes a new instance of the class. /// /// The to use to initialize this user login. public MongoDbIdentityUserLogin(UserLoginInfo userLoginInfo) : base(userLoginInfo) { } } /// /// A representation of an external user login provider for use with a MongoDB EntityFramework provider. /// /// The type of tokens assigned to this external login provider information. [Owned] public class MongoDbIdentityUserLogin where TUserToken : MongoDbIdentityUserToken { /// /// Initializes a new instance of the class. /// public MongoDbIdentityUserLogin() { } /// /// Initializes a new instance of the class. /// /// The to use to initialize this user login. public MongoDbIdentityUserLogin(UserLoginInfo userLoginInfo) { InitializeFromUserLoginInfo(userLoginInfo); } /// /// Gets or sets the external login provider, such as "Facebook" or "Google".. /// public virtual string LoginProvider { get; set; } /// /// Gets or sets a unique provider identifier for this login. /// public virtual string ProviderKey { get; set; } /// /// Gets or sets a friendly name for this login that can be displayed to a user. /// public virtual string ProviderDisplayName { get; set; } /// /// A collection of assigned to this provider. /// public ICollection UserTokens { get; [UsedImplicitly] private set; } = new List(); /// /// Constructs a new claim with the type and value. /// /// A that represents this . public virtual UserLoginInfo ToUserLoginInfo() => new UserLoginInfo(LoginProvider, ProviderKey, ProviderDisplayName); /// /// Initializes this with values from the given . /// /// The source to use for initialization. public virtual void InitializeFromUserLoginInfo(UserLoginInfo userLoginInfo) { Check.NotNull(userLoginInfo, nameof(userLoginInfo)); LoginProvider = userLoginInfo.LoginProvider; ProviderKey = userLoginInfo.ProviderKey; ProviderDisplayName = userLoginInfo.ProviderDisplayName; } } } ================================================ FILE: src/Blueshift.Identity.MongoDB/MongoDbIdentityUserRole.cs ================================================ using Microsoft.EntityFrameworkCore; namespace Blueshift.Identity.MongoDB { /// /// A representation of a user's security authorization role for use with a MongoDB EntityFramework provider. /// [Owned] public class MongoDbIdentityUserRole { /// /// Gets or sets the name of the role that the user is in. /// public string RoleName { get; set; } /// /// Gets or sets the normalized name of the role that the user is in. /// public string NormalizedRoleName { get; set; } } } ================================================ FILE: src/Blueshift.Identity.MongoDB/MongoDbIdentityUserToken.cs ================================================ using Microsoft.EntityFrameworkCore; namespace Blueshift.Identity.MongoDB { /// /// A representation of an external user login provider token for use with a MongoDB EntityFramework provider. /// [Owned] public class MongoDbIdentityUserToken { /// /// Gets or sets the name of the token. /// public virtual string Name { get; set; } /// /// Gets or sets the token value. /// public virtual string Value { get; set; } } } ================================================ FILE: src/Blueshift.Identity.MongoDB/MongoDbRoleStore.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson; namespace Blueshift.Identity.MongoDB { /// /// /// Creates a new instance of a persistence store for roles. /// public class MongoDbRoleStore : MongoDbRoleStore { /// /// /// Constructs a new instance of . /// /// The . /// The . public MongoDbRoleStore(IdentityMongoDbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } /// /// Creates a new instance of a persistence store for roles. /// /// The type of the primary key for a role. public class MongoDbRoleStore : MongoDbRoleStore, IdentityMongoDbContext, TKey> where TKey : IEquatable { /// /// Constructs a new instance of . /// /// The . /// The . public MongoDbRoleStore(IdentityMongoDbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } /// /// Creates a new instance of a persistence store for roles. /// /// The type of the class representing a role. /// The type of the data context class used to access the store. public class MongoDbRoleStore : MongoDbRoleStore where TRole : MongoDbIdentityRole, new() where TContext : DbContext { /// /// Constructs a new instance of . /// /// The . /// The . public MongoDbRoleStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } /// /// Creates a new instance of a persistence store for roles. /// /// The type of the class representing a role. /// The type of the data context class used to access the store. /// The type of the primary key for a role. public class MongoDbRoleStore : MongoDbRoleStore where TRole : MongoDbIdentityRole, new() where TKey : IEquatable where TContext : DbContext { /// /// Constructs a new instance of . /// /// The . /// The . public MongoDbRoleStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } /// /// Creates a new instance of a persistence store for roles. /// /// The type of the class representing a role. /// The type of the data context class used to access the store. /// The type of the primary key for a role. /// The type of the class representing a role claim. public class MongoDbRoleStore : IQueryableRoleStore, IRoleClaimStore where TRole : MongoDbIdentityRole, new() where TKey : IEquatable where TContext : DbContext where TClaim : MongoDbIdentityClaim, new() { private bool _disposed; /// /// Constructs a new instance of . /// /// The . /// The . public MongoDbRoleStore(TContext context, IdentityErrorDescriber describer = null) { Context = Check.NotNull(context, nameof(context)); ErrorDescriber = describer ?? new IdentityErrorDescriber(); } /// /// Gets the database context for this store. /// public TContext Context { get; } /// /// Gets or sets the for any error that occurred with the current operation. /// public IdentityErrorDescriber ErrorDescriber { get; set; } /// /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called. /// /// /// True if changes should be automatically persisted, otherwise false. /// public bool AutoSaveChanges { get; set; } = true; /// /// A navigation property for the roles the store contains. /// public virtual IQueryable Roles => Context.Set(); /// /// Throws if this class has been disposed. /// protected void ThrowIfDisposed() { if (_disposed) { throw new ObjectDisposedException(GetType().Name); } } /// /// Dispose the stores /// public void Dispose() { _disposed = true; } /// Saves the current store. /// The used to propagate notifications that the operation should be canceled. /// The that represents the asynchronous operation. private async Task SaveChanges(CancellationToken cancellationToken) { if (AutoSaveChanges) { await Context.SaveChangesAsync(cancellationToken); } } /// /// Creates a new role in a store as an asynchronous operation. /// /// The role to create in the store. /// The used to propagate notifications that the operation should be canceled. /// A that represents the of the asynchronous query. public virtual async Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Context.Add(Check.NotNull(role, nameof(role))); await SaveChanges(cancellationToken); return IdentityResult.Success; } /// /// Updates a role in a store as an asynchronous operation. /// /// The role to update in the store. /// The used to propagate notifications that the operation should be canceled. /// A that represents the of the asynchronous query. public virtual async Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Context.Attach(Check.NotNull(role, nameof(role))); role.ConcurrencyStamp = Guid.NewGuid().ToString(); Context.Update(role); try { await SaveChanges(cancellationToken); } catch (DbUpdateConcurrencyException) { return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); } return IdentityResult.Success; } /// /// Deletes a role from the store as an asynchronous operation. /// /// The role to delete from the store. /// The used to propagate notifications that the operation should be canceled. /// A that represents the of the asynchronous query. public virtual async Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Context.Remove(Check.NotNull(role, nameof(role))); try { await SaveChanges(cancellationToken); } catch (DbUpdateConcurrencyException) { return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); } return IdentityResult.Success; } /// /// Gets the ID for a role from the store as an asynchronous operation. /// /// The role whose ID should be returned. /// The used to propagate notifications that the operation should be canceled. /// A that contains the ID of the role. public virtual Task GetRoleIdAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(ConvertIdToString(Check.NotNull(role, nameof(role)).Id)); } /// /// Gets the name of a role from the store as an asynchronous operation. /// /// The role whose name should be returned. /// The used to propagate notifications that the operation should be canceled. /// A that contains the name of the role. public virtual Task GetRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(Check.NotNull(role, nameof(role)).RoleName); } /// /// Sets the name of a role in the store as an asynchronous operation. /// /// The role whose name should be set. /// The name of the role. /// The used to propagate notifications that the operation should be canceled. /// The that represents the asynchronous operation. public virtual Task SetRoleNameAsync(TRole role, string roleName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(role, nameof(role)).RoleName = roleName; return Task.CompletedTask; } /// /// Converts the provided to a strongly typed key object. /// /// The id to convert. /// An instance of representing the provided . public virtual TKey ConvertIdFromString(string id) => string.IsNullOrWhiteSpace(id) ? default(TKey) : (TKey)TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(id); /// /// Converts the provided to its string representation. /// /// The id to convert. /// An representation of the provided . public virtual string ConvertIdToString(TKey id) => id.Equals(default(TKey)) ? null : id.ToString(); /// /// Finds the role who has the specified ID as an asynchronous operation. /// /// The role ID to look for. /// The used to propagate notifications that the operation should be canceled. /// A that result of the look up. public virtual Task FindByIdAsync(string id, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); var roleId = ConvertIdFromString(id); return Roles.FirstOrDefaultAsync(role => role.Id.Equals(roleId), cancellationToken); } /// /// Finds the role who has the specified normalized name as an asynchronous operation. /// /// The normalized role name to look for. /// The used to propagate notifications that the operation should be canceled. /// A that result of the look up. public virtual Task FindByNameAsync(string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Roles.FirstOrDefaultAsync(role => role.NormalizedRoleName == normalizedName, cancellationToken); } /// /// Get a role's normalized name as an asynchronous operation. /// /// The role whose normalized name should be retrieved. /// The used to propagate notifications that the operation should be canceled. /// A that contains the name of the role. public virtual Task GetNormalizedRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(Check.NotNull(role, nameof(role)).NormalizedRoleName); } /// /// Set a role's normalized name as an asynchronous operation. /// /// The role whose normalized name should be set. /// The normalized name to set /// The used to propagate notifications that the operation should be canceled. /// The that represents the asynchronous operation. public virtual Task SetNormalizedRoleNameAsync(TRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(role, nameof(role)).NormalizedRoleName = normalizedName; return Task.CompletedTask; } /// /// Get the claims associated with the specified as an asynchronous operation. /// /// The role whose claims should be retrieved. /// The used to propagate notifications that the operation should be canceled. /// A that contains the claims granted to a role. public virtual async Task> GetClaimsAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); Check.NotNull(role, nameof(role)); IList claims = role.Claims .Select(roleClaim => roleClaim.ToClaim()) .ToList(); return await Task.FromResult(claims); } /// /// Adds the given to the specified . /// /// The role to add the claim to. /// The claim to add to the role. /// The used to propagate notifications that the operation should be canceled. /// The that represents the asynchronous operation. public virtual Task AddClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); Check.NotNull(role, nameof(role)); Check.NotNull(claim, nameof(claim)); role.Claims.Add(CreateRoleClaim(role, claim)); return Task.CompletedTask; } /// /// Creates a entity representing a role claim. /// /// The associated role. /// The associated claim. /// The role claim entity. protected virtual TClaim CreateRoleClaim(TRole role, Claim claim) => new TClaim() { ClaimType = claim.Type, ClaimValue = claim.Value }; /// /// Removes the given from the specified . /// /// The role to remove the claim from. /// The claim to remove from the role. /// The used to propagate notifications that the operation should be canceled. /// The that represents the asynchronous operation. public virtual async Task RemoveClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); Check.NotNull(role, nameof(role)); Check.NotNull(claim, nameof(claim)); IList matchedClaims = role.Claims .Where(roleClaim => roleClaim.ClaimValue == claim.Value && roleClaim.ClaimType == claim.Type) .ToList(); foreach (var roleClaim in matchedClaims) { role.Claims.Remove(roleClaim); } await Task.CompletedTask; } } } ================================================ FILE: src/Blueshift.Identity.MongoDB/MongoDbUserStore.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Utilities; using MongoDB.Bson; namespace Blueshift.Identity.MongoDB { /// /// Represents a new instance of a persistence store for the specified user and role types. /// public class MongoDbUserStore : MongoDbUserStore { /// /// Creates a new instance of . /// /// The context used to access the store. /// The used to describe store errors. public MongoDbUserStore(IdentityMongoDbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } /// /// Represents a new instance of a persistence store for the specified user and role types. /// /// The type of the identifiers for this store's objects. public class MongoDbUserStore : MongoDbUserStore, MongoDbIdentityRole, IdentityMongoDbContext, TKey> where TKey : IEquatable { /// /// Creates a new instance of . /// /// The context used to access the store. /// The used to describe store errors. public MongoDbUserStore(IdentityMongoDbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } /// /// Represents a new instance of a persistence store for the specified user and role types. /// /// The type of the user objects for this store. /// The type of the role objects for this store. public class MongoDbUserStore : MongoDbUserStore< TUser, TRole, ObjectId> where TUser : MongoDbIdentityUser, new() where TRole : MongoDbIdentityRole, new() { /// /// Creates a new instance of . /// /// The context used to access the store. /// The used to describe store errors. public MongoDbUserStore(IdentityMongoDbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } /// /// Represents a new instance of a persistence store for the specified user and role types. /// /// The type of the user objects for this store. /// The type of the role objects for this store. /// The type of the identifiers for this store's objects. public class MongoDbUserStore : MongoDbUserStore< TUser, TRole, IdentityMongoDbContext< TUser, TRole, TKey, MongoDbIdentityClaim, MongoDbIdentityUserRole, MongoDbIdentityUserLogin, MongoDbIdentityUserToken>, TKey, MongoDbIdentityClaim, MongoDbIdentityUserRole, MongoDbIdentityUserLogin, MongoDbIdentityUserToken> where TUser : MongoDbIdentityUser< TKey, MongoDbIdentityClaim, MongoDbIdentityUserRole, MongoDbIdentityUserLogin, MongoDbIdentityUserToken>, new() where TRole : MongoDbIdentityRole, new() where TKey : IEquatable { /// /// Creates a new instance of . /// /// The context used to access the store. /// The used to describe store errors. public MongoDbUserStore( IdentityMongoDbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } /// /// /// Represents a new instance of a persistence store for the specified user and role types. /// /// The type of the user objects for this store. /// The type of the role objects for this store. /// The type of the with which this store communicates. /// The type of the identifiers for this store's objects. public class MongoDbUserStore : MongoDbUserStore< TUser, TRole, TContext, TKey, MongoDbIdentityClaim, MongoDbIdentityUserRole, MongoDbIdentityUserLogin, MongoDbIdentityUserToken> where TUser : MongoDbIdentityUser< TKey, MongoDbIdentityClaim, MongoDbIdentityUserRole, MongoDbIdentityUserLogin, MongoDbIdentityUserToken>, new() where TRole : MongoDbIdentityRole, new() where TContext : DbContext where TKey : IEquatable { /// /// Creates a new instance of . /// /// The context used to access the store. /// The used to describe store errors. public MongoDbUserStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } /// /// Represents a new instance of a persistence store for the specified user and role types. /// /// The type of the user objects for this store. /// The type of the role objects for this store. /// The type of the with which this store communicates. /// The type of the identifiers for this store's objects. /// The type of the security claim objects for this store. /// The type type of the user role objects for this store. /// The type of the user login objects for this store. /// The type of the user token objects for this store. public class MongoDbUserStore : IQueryableUserStore, IUserAuthenticationTokenStore, IUserAuthenticatorKeyStore, IUserClaimStore, IUserEmailStore, IUserLockoutStore, IUserLoginStore, IUserPasswordStore, IUserPhoneNumberStore, IUserRoleStore, IUserSecurityStampStore, IUserTwoFactorStore, IUserTwoFactorRecoveryCodeStore where TUser : MongoDbIdentityUser, new() where TRole : MongoDbIdentityRole, new() where TContext : DbContext where TKey : IEquatable where TClaim : MongoDbIdentityClaim, new() where TUserRole : MongoDbIdentityUserRole, new() where TUserLogin : MongoDbIdentityUserLogin, new() where TUserToken : MongoDbIdentityUserToken, new() { private const string InternalLoginProvider = "[BlueshiftMongoDbUserStore]"; private const string AuthenticatorKeyTokenName = "AuthenticatorKey"; private const string RecoveryCodeTokenName = "RecoveryCodes"; private bool _disposed; /// /// Creates a new instance of /// . /// /// The context used to access the store. /// The used to describe store errors. public MongoDbUserStore(TContext context, IdentityErrorDescriber describer = null) { Context = Check.NotNull(context, nameof(context)); ErrorDescriber = describer ?? new IdentityErrorDescriber(); } /// /// Gets the database context used by this store. /// public TContext Context { get; } /// /// Gets or sets the for any error that occurred with the current operation. /// public IdentityErrorDescriber ErrorDescriber { get; set; } private DbSet Roles => Context.Set(); /// /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are /// called. /// /// /// True if changes should be automatically persisted, otherwise false. /// public bool AutoSaveChanges { get; set; } = true; /// /// Gets the user identifier for the specified . /// /// The user whose identifier should be retrieved. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The that represents the asynchronous operation, containing the identifier for the /// specified . /// public virtual Task GetUserIdAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(ConvertIdToString(Check.NotNull(user, nameof(user)).Id)); } /// /// Gets the user name for the specified . /// /// The user whose name should be retrieved. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The that represents the asynchronous operation, containing the name for the specified /// . /// public virtual Task GetUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(Check.NotNull(user, nameof(user)).UserName); } /// /// Sets the given for the specified . /// /// The user whose name should be set. /// The user name to set. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)).UserName = userName; return Task.CompletedTask; } /// /// Gets the normalized user name for the specified . /// /// The user whose normalized name should be retrieved. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The that represents the asynchronous operation, containing the normalized user name for /// the specified . /// public virtual Task GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); return Task.FromResult(user.NormalizedUserName); } /// /// Sets the given normalized name for the specified . /// /// The user whose name should be set. /// The normalized name to set. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task SetNormalizedUserNameAsync(TUser user, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)).NormalizedUserName = normalizedName; return Task.CompletedTask; } /// /// Creates the specified in the user store. /// /// The user to create. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The that represents the asynchronous operation, containing the /// of the creation operation. /// public virtual async Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Context.Add(Check.NotNull(user, nameof(user))); await SaveChanges(cancellationToken); return IdentityResult.Success; } /// /// Updates the specified in the user store. /// /// The user to update. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The that represents the asynchronous operation, containing the /// of the update operation. /// public virtual async Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Context.Attach(Check.NotNull(user, nameof(user))); user.ConcurrencyStamp = Guid.NewGuid().ToString(); Context.Update(user); try { await SaveChanges(cancellationToken); } catch (DbUpdateConcurrencyException) { return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); } return IdentityResult.Success; } /// /// Deletes the specified from the user store. /// /// The user to delete. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The that represents the asynchronous operation, containing the /// of the update operation. /// public virtual async Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Context.Remove(Check.NotNull(user, nameof(user))); try { await SaveChanges(cancellationToken); } catch (DbUpdateConcurrencyException) { return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); } return IdentityResult.Success; } /// /// Finds and returns a user, if any, who has the specified . /// /// The user ID to search for. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The that represents the asynchronous operation, containing the user matching the specified /// if it exists. /// public virtual Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); TKey id = ConvertIdFromString(userId); return Users.FirstOrDefaultAsync(u => u.Id.Equals(id), cancellationToken); } /// /// Finds and returns a user, if any, who has the specified normalized user name. /// /// The normalized user name to search for. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The that represents the asynchronous operation, containing the user matching the specified /// if it exists. /// public virtual Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Users.FirstOrDefaultAsync(u => u.NormalizedUserName == normalizedUserName, cancellationToken); } /// /// A navigation property for the users the store contains. /// public virtual IQueryable Users => Context.Set(); /// /// Dispose this MongoDB user store. /// public void Dispose() { _disposed = true; } /// /// Sets the token value for a particular user. /// /// The user. /// The authentication provider for the token. /// The name of the token. /// The value of the token. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task SetTokenAsync(TUser user, string loginProvider, string name, string value, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); TUserLogin userLogin = Check.NotNull(user, nameof(user)) .Logins .FirstOrDefault(login => login.LoginProvider == loginProvider); TUserToken userToken = userLogin ?.UserTokens .FirstOrDefault(token => token.Name == name); if (userToken == null) userLogin.UserTokens.Add(CreateUserToken(user, loginProvider, name, value)); else userToken.Value = value; return Task.CompletedTask; } /// /// Deletes a token for a user. /// /// The user. /// The authentication provider for the token. /// The name of the token. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public Task RemoveTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); TUserLogin userLogin = Check.NotNull(user, nameof(user)) .Logins .FirstOrDefault(login => login.LoginProvider == loginProvider); TUserToken userToken = userLogin ?.UserTokens .FirstOrDefault(token => token.Name == name); if (userLogin != null && userToken != null) userLogin.UserTokens.Remove(userToken); return Task.CompletedTask; } /// /// Returns the token value. /// /// The user. /// The authentication provider for the token. /// The name of the token. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public Task GetTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); string tokenValue = Check.NotNull(user, nameof(user)) .Logins .FirstOrDefault(login => login.LoginProvider == loginProvider) ?.UserTokens .FirstOrDefault(token => token.Name == name) ?.Value; return Task.FromResult(tokenValue); } /// /// Sets the authenticator key for the specified . /// /// The user whose authenticator key should be set. /// The authenticator key to set. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task SetAuthenticatorKeyAsync(TUser user, string key, CancellationToken cancellationToken) { return SetTokenAsync(user, InternalLoginProvider, AuthenticatorKeyTokenName, key, cancellationToken); } /// /// Get the authenticator key for the specified . /// /// The user whose security stamp should be set. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The that represents the asynchronous operation, containing the security stamp for the /// specified . /// public virtual Task GetAuthenticatorKeyAsync(TUser user, CancellationToken cancellationToken) { return GetTokenAsync(user, InternalLoginProvider, AuthenticatorKeyTokenName, cancellationToken); } /// /// Asynchronously retrieves the claims associated with the specified . /// /// The user whose claims should be retrieved. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// A that contains the claims granted to a user. public virtual Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); Check.NotNull(user, nameof(user)); IList userClaims = user.Claims .Select(claim => claim.ToClaim()) .ToList(); return Task.FromResult(userClaims); } /// /// Adds the specified list of to the specified . /// /// The user to add the claim to. /// The claim to add to the user. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); Check.NotNull(user, nameof(user)); Check.NotNull(claims, nameof(claims)); foreach (Claim claim in claims) user.Claims.Add(CreateUserClaim(user, claim)); return Task.FromResult(false); } /// /// Replaces the on the specified , with the /// . /// /// The role to replace the claim on. /// The claim replace. /// The new claim replacing the . /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); Check.NotNull(user, nameof(user)); Check.NotNull(claim, nameof(claim)); Check.NotNull(newClaim, nameof(newClaim)); IList matchedClaims = user.Claims .Where(userClaim => userClaim.ClaimValue == claim.Value && userClaim.ClaimType == claim.Type) .ToList(); foreach (TClaim matchedClaim in matchedClaims) matchedClaim.InitializeFromClaim(newClaim); return Task.CompletedTask; } /// /// Removes the given from the specified . /// /// The user to remove the claims from. /// The claim to remove. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); Check.NotNull(user, nameof(user)); Check.NotNull(claims, nameof(claims)); IList matchedClaims = user.Claims .Join(claims, userClaim => new {userClaim.ClaimType, userClaim.ClaimValue}, claim => new {ClaimType = claim.Type, ClaimValue = claim.Value}, (userClaim, claim) => userClaim) .ToList(); foreach (TClaim userClaim in matchedClaims) user.Claims.Remove(userClaim); return Task.CompletedTask; } /// /// Retrieves all users with the specified claim. /// /// The claim whose users should be retrieved. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The contains a list of users, if any, that contain the specified claim. /// public virtual async Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(claim, nameof(claim)); return await Users .Where(user => user.Claims .Any(userClaim => userClaim.ClaimType == claim.Type && userClaim.ClaimValue == claim.Value)) .ToListAsync(cancellationToken); } /// /// Gets a flag indicating whether the email address for the specified has been verified, true /// if the email address is verified otherwise /// false. /// /// The user whose email confirmation status should be returned. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The task object containing the results of the asynchronous operation, a flag indicating whether the email address /// for the specified /// has been confirmed or not. /// public virtual Task GetEmailConfirmedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(Check.NotNull(user, nameof(user)).EmailConfirmed); } /// /// Sets the flag indicating whether the specified 's email address has been confirmed or not. /// /// The user whose email confirmation status should be set. /// /// A flag indicating if the email address has been confirmed, true if the address is confirmed /// otherwise false. /// /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The task object representing the asynchronous operation. public virtual Task SetEmailConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)).EmailConfirmed = confirmed; return Task.CompletedTask; } /// /// Sets the address for a . /// /// The user whose email should be set. /// The email to set. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The task object representing the asynchronous operation. public virtual Task SetEmailAsync(TUser user, string email, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)).Email = email; return Task.CompletedTask; } /// /// Gets the email address for the specified . /// /// The user whose email should be returned. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The task object containing the results of the asynchronous operation, the email address for the specified /// . /// public virtual Task GetEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(Check.NotNull(user, nameof(user)).Email); } /// /// Returns the normalized email for the specified . /// /// The user whose email address to retrieve. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The task object containing the results of the asynchronous lookup operation, the normalized email address if any /// associated with the specified user. /// public virtual Task GetNormalizedEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(Check.NotNull(user, nameof(user)).NormalizedEmail); } /// /// Sets the normalized email for the specified . /// /// The user whose email address to set. /// The normalized email to set for the specified . /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The task object representing the asynchronous operation. public virtual Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)).NormalizedEmail = normalizedEmail; return Task.CompletedTask; } /// /// Gets the user, if any, associated with the specified, normalized email address. /// /// The normalized email address to return the user for. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The task object containing the results of the asynchronous lookup operation, the user if any associated with the /// specified normalized email address. /// public virtual Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Users.FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken); } /// /// Gets the last a user's last lockout expired, if any. /// Any time in the past should be indicates a user is not locked out. /// /// The user whose lockout date should be retrieved. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// A that represents the result of the asynchronous query, a /// containing the last time /// a user's lockout expired, if any. /// public virtual Task GetLockoutEndDateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(Check.NotNull(user, nameof(user)).LockoutEnd); } /// /// Locks out a user until the specified end date has passed. Setting a end date in the past immediately unlocks a /// user. /// /// The user whose lockout date should be set. /// /// The after which the 's lockout should /// end. /// /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)).LockoutEnd = lockoutEnd; return Task.CompletedTask; } /// /// Records that a failed access has occurred, incrementing the failed access count. /// /// The user whose cancellation count should be incremented. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The that represents the asynchronous operation, containing the incremented failed access /// count. /// public virtual Task IncrementAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(++Check.NotNull(user, nameof(user)).AccessFailedCount); } /// /// Resets a user's failed access count. /// /// The user whose failed access count should be reset. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. /// This is typically called after the account is successfully accessed. public virtual Task ResetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)).AccessFailedCount = 0; return Task.CompletedTask; } /// /// Retrieves the current failed access count for the specified .. /// /// The user whose failed access count should be retrieved. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation, containing the failed access count. public virtual Task GetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(Check.NotNull(user, nameof(user)).AccessFailedCount); } /// /// Retrieves a flag indicating whether user lockout can enabled for the specified user. /// /// The user whose ability to be locked out should be returned. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The that represents the asynchronous operation, true if a user can be locked out, otherwise /// false. /// public virtual Task GetLockoutEnabledAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(Check.NotNull(user, nameof(user)).LockoutEnabled); } /// /// Set the flag indicating if the specified can be locked out.. /// /// The user whose ability to be locked out should be set. /// A flag indicating if lock out can be enabled for the specified . /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task SetLockoutEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)).LockoutEnabled = enabled; return Task.CompletedTask; } /// /// Adds the given to the specified . /// /// The user to add the login to. /// The login to add to the user. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task AddLoginAsync(TUser user, UserLoginInfo login, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); Check.NotNull(login, nameof(login)); user.Logins.Add(CreateUserLogin(user, login)); return Task.CompletedTask; } /// /// Removes the given from the specified . /// /// The user to remove the login from. /// The login to remove from the user. /// The key provided by the to identify a user. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); TUserLogin userLoginInfo = Check.NotNull(user, nameof(user)) .Logins .SingleOrDefault(userLogin => userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey); if (userLoginInfo != null) user.Logins.Remove(userLoginInfo); return Task.CompletedTask; } /// /// Retrieves the associated logins for the specified /// /// . /// /// The user whose associated logins to retrieve. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The for the asynchronous operation, containing a list of for the /// specified , if any. /// public virtual Task> GetLoginsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); IList userLogins = user.Logins .Select(userLogin => userLogin.ToUserLoginInfo()) .ToList(); return Task.FromResult(userLogins); } /// /// Retrieves the user associated with the specified login provider and login provider key.. /// /// The login provider who provided the . /// The key provided by the to identify a user. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The for the asynchronous operation, containing the user, if any which matched the specified /// login provider and key. /// public virtual async Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotEmpty(loginProvider, nameof(loginProvider)); Check.NotEmpty(providerKey, nameof(providerKey)); return await Users .FirstOrDefaultAsync(user => user.Logins .Any(login => loginProvider == login.LoginProvider && providerKey == login.ProviderKey), cancellationToken); } /// /// Sets the password hash for a user. /// /// The user to set the password hash for. /// The password hash to set. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task SetPasswordHashAsync(TUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)).PasswordHash = passwordHash; return Task.CompletedTask; } /// /// Gets the password hash for a user. /// /// The user to retrieve the password hash for. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// A that contains the password hash for the user. public virtual Task GetPasswordHashAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(Check.NotNull(user, nameof(user)).PasswordHash); } /// /// Returns a flag indicating if the specified user has a password. /// /// The user to retrieve the password hash for. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// A containing a flag indicating if the specified user has a password. If the /// user has a password the returned value with be true, otherwise it will be false. /// public virtual Task HasPasswordAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); return Task.FromResult(user.PasswordHash != null); } /// /// Sets the telephone number for the specified . /// /// The user whose telephone number should be set. /// The telephone number to set. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task SetPhoneNumberAsync(TUser user, string phoneNumber, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)).PhoneNumber = phoneNumber; return Task.CompletedTask; } /// /// Gets the telephone number, if any, for the specified . /// /// The user whose telephone number should be retrieved. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The that represents the asynchronous operation, containing the user's telephone number, if /// any. /// public virtual Task GetPhoneNumberAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(Check.NotNull(user, nameof(user)).PhoneNumber); } /// /// Gets a flag indicating whether the specified 's telephone number has been confirmed. /// /// The user to return a flag for, indicating whether their telephone number is confirmed. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The that represents the asynchronous operation, returning true if the specified /// has a confirmed /// telephone number otherwise false. /// public virtual Task GetPhoneNumberConfirmedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(Check.NotNull(user, nameof(user)).PhoneNumberConfirmed); } /// /// Sets a flag indicating if the specified 's phone number has been confirmed.. /// /// The user whose telephone number confirmation status should be set. /// A flag indicating whether the user's telephone number has been confirmed. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task SetPhoneNumberConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)).PhoneNumberConfirmed = confirmed; return Task.CompletedTask; } /// /// Adds the given to the specified . /// /// The user to add the role to. /// The role to add. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual async Task AddToRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); Check.NotEmpty(normalizedRoleName, nameof(normalizedRoleName)); TRole roleEntity = await Roles.SingleOrDefaultAsync(r => r.NormalizedRoleName == normalizedRoleName, cancellationToken); if (roleEntity == null) throw new InvalidOperationException($"Could not find role \"{normalizedRoleName}\"."); user.Roles.Add(CreateUserRole(user, roleEntity)); } /// /// Removes the given from the specified . /// /// The user to remove the role from. /// The role to remove. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task RemoveFromRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); Check.NotEmpty(normalizedRoleName, nameof(normalizedRoleName)); TUserRole role = user.Roles .SingleOrDefault(r => r.NormalizedRoleName == normalizedRoleName); if (role != null) user.Roles.Remove(role); return Task.CompletedTask; } /// /// Retrieves the roles the specified is a member of. /// /// The user whose roles should be retrieved. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// A that contains the roles the user is a member of. public virtual Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); IList roleNames = user.Roles .Select(role => role.RoleName) .ToList(); return Task.FromResult(roleNames); } /// /// Returns a flag indicating if the specified user is a member of the give . /// /// The user whose role membership should be checked. /// The role to check membership of /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// A containing a flag indicating if the specified user is a member of the given group. /// If the /// user is a member of the group the returned value with be true, otherwise it will be false. /// public virtual Task IsInRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); Check.NotEmpty(normalizedRoleName, nameof(normalizedRoleName)); bool isInRole = user.Roles.Any(userRole => userRole.NormalizedRoleName == normalizedRoleName); return Task.FromResult(isInRole); } /// /// Retrieves all users in the specified role. /// /// The role whose users should be retrieved. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The contains a list of users, if any, that are in the specified role. /// public virtual async Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotEmpty(normalizedRoleName, nameof(normalizedRoleName)); return await Users .Where(user => user.Roles .Any(userRole => userRole.NormalizedRoleName == normalizedRoleName)) .ToListAsync(cancellationToken); } /// /// Sets the provided security for the specified . /// /// The user whose security stamp should be set. /// The security stamp to set. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task SetSecurityStampAsync(TUser user, string stamp, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)).SecurityStamp = stamp; return Task.CompletedTask; } /// /// Get the security stamp for the specified . /// /// The user whose security stamp should be set. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The that represents the asynchronous operation, containing the security stamp for the /// specified . /// public virtual Task GetSecurityStampAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(Check.NotNull(user, nameof(user)).SecurityStamp); } /// /// Returns the number of remaining valid recovery codes for the specified . /// /// The user who owns the recovery code. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The number of remaining valid recovery codes for the specified . public virtual Task CountCodesAsync(TUser user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); int count = user.Logins .SelectMany(userLogin => userLogin.UserTokens) .Sum(userToken => 1); return Task.FromResult(count); } /// /// Updates the recovery codes for the user while invalidating any previous recovery codes. /// /// The user to store new recovery codes for. /// The new recovery codes for the user. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The new recovery codes for the user. public virtual Task ReplaceCodesAsync(TUser user, IEnumerable recoveryCodes, CancellationToken cancellationToken) { return SetTokenAsync(user, InternalLoginProvider, RecoveryCodeTokenName, string.Join(";", recoveryCodes), cancellationToken); } /// /// Returns whether a recovery code is valid for a user. Note: recovery codes are only valid /// once, and will be invalid after use. /// /// The user who owns the recovery code. /// The recovery code to use. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// True if the recovery code was found for the user. public virtual async Task RedeemCodeAsync(TUser user, string code, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); Check.NotNull(code, nameof(code)); string originalCodes = await GetTokenAsync(user, InternalLoginProvider, RecoveryCodeTokenName, cancellationToken) ?? ""; string[] splitCodes = originalCodes.Split(';'); if (splitCodes.Contains(code)) { var updatedCodes = new List(splitCodes.Where(s => s != code)); await ReplaceCodesAsync(user, updatedCodes, cancellationToken); return true; } return false; } /// /// Sets a flag indicating whether the specified has two factor authentication enabled or not, /// as an asynchronous operation. /// /// The user whose two factor authentication enabled status should be set. /// /// A flag indicating whether the specified has two factor authentication /// enabled. /// /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. public virtual Task SetTwoFactorEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)).TwoFactorEnabled = enabled; return Task.CompletedTask; } /// /// Returns a flag indicating whether the specified has two factor authentication enabled or /// not, /// as an asynchronous operation. /// /// The user whose two factor authentication enabled status should be set. /// /// The used to propagate notifications that the operation /// should be canceled. /// /// /// The that represents the asynchronous operation, containing a flag indicating whether the /// specified /// has two factor authentication enabled or not. /// public virtual Task GetTwoFactorEnabledAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(Check.NotNull(user, nameof(user)).TwoFactorEnabled); } /// /// Creates a new instance of the that will be used to represent a user's security /// role. /// /// The user to whom the role will be assigned. /// The role to which the user will be assigned. /// A new instance of for the given . protected virtual TUserRole CreateUserRole(TUser user, TRole role) { return new TUserRole { RoleName = Check.NotNull(role, nameof(role)).RoleName, NormalizedRoleName = role.RoleName.ToUpper() }; } /// /// Creates a new instance of the that will be used to represent a user's security /// claim. /// /// The user to whom the claim will be assigned. /// The claim which will be assigned to the user. /// A new instance of for the given . protected virtual TClaim CreateUserClaim(TUser user, Claim claim) { Check.NotNull(user, nameof(user)); Check.NotNull(claim, nameof(claim)); var userClaim = new TClaim(); userClaim.InitializeFromClaim(claim); return userClaim; } /// /// Creates a new instance of the that will be used to represent an external user /// login provider. /// /// The user to whom the external login provider will be assigned. /// An object containing information about the user's external login provider. /// A new instance of for the given . protected virtual TUserLogin CreateUserLogin(TUser user, UserLoginInfo userLoginInfo) { Check.NotNull(user, nameof(user)); Check.NotNull(userLoginInfo, nameof(userLoginInfo)); var userLogin = new TUserLogin(); userLogin.InitializeFromUserLoginInfo(userLoginInfo); return userLogin; } /// /// Creates a new instance of the for use with an external user login provider. /// /// The user to whom the external login provider token will be assigned. /// The name of the external user login provider. /// The name of the external user login provider token. /// The value of the external user login provider token. /// A new instance of for the given . protected TUserToken CreateUserToken(TUser user, string loginProvider, string name, string value) { return new TUserToken { Name = Check.NotEmpty(name, nameof(name)), Value = Check.NotEmpty(value, nameof(value)) }; } /// /// Saves the current store. /// /// /// The used to propagate notifications that the operation /// should be canceled. /// /// The that represents the asynchronous operation. protected Task SaveChanges(CancellationToken cancellationToken) { return AutoSaveChanges ? Context.SaveChangesAsync(cancellationToken) : Task.CompletedTask; } /// /// Converts the provided to a strongly typed key object. /// /// The id to convert. /// An instance of representing the provided . public virtual TKey ConvertIdFromString(string id) { return id == null ? default(TKey) : (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(id); } /// /// Converts the provided to its string representation. /// /// The id to convert. /// An representation of the provided . public virtual string ConvertIdToString(TKey id) { return Equals(id, default(TKey)) ? null : id.ToString(); } /// /// Throws an if this instance has already been disposed. /// /// Thrown if this instance has already been disposed. protected void ThrowIfDisposed() { if (_disposed) throw new ObjectDisposedException(GetType().Name); } } } ================================================ FILE: src/Directory.Build.props ================================================ netstandard2.0 $(DeveloperBuildFrameworks) netstandard2.0 net461;$(BuildFrameworks) $(NoWarn);CA1822 ================================================ FILE: src/Shared/Check.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; namespace Microsoft.EntityFrameworkCore.Utilities { [DebuggerStepThrough] internal static class Check { [ContractAnnotation("value:null => halt")] public static T NotNull([NoEnumeration] T value, [InvokerParameterName] [NotNull] string parameterName) { if (ReferenceEquals(value, null)) { NotEmpty(parameterName, nameof(parameterName)); throw new ArgumentNullException(parameterName); } return value; } [ContractAnnotation("value:null => halt")] public static object IsInstanceOfType( [NoEnumeration] object value, [NotNull] Type type, [InvokerParameterName] [NotNull] string parameterName) { if (!Check.NotNull(type, nameof(type)).IsInstanceOfType(value)) { NotEmpty(parameterName, nameof(parameterName)); throw new ArgumentException($@"Argument {parameterName} is not an instance of {type.FullName}.", parameterName); } return value; } [ContractAnnotation("value:null => halt")] public static T Is( [NoEnumeration] object value, [InvokerParameterName] [NotNull] string parameterName) where T : class => IsInstanceOfType(value, typeof(T), parameterName) as T; [ContractAnnotation("value:null => halt")] public static T NotNull( [NoEnumeration] T value, [InvokerParameterName] [NotNull] string parameterName, [NotNull] string propertyName) { if (ReferenceEquals(value, null)) { NotEmpty(parameterName, nameof(parameterName)); NotEmpty(propertyName, nameof(propertyName)); throw new ArgumentException(CoreStrings.ArgumentPropertyNull(propertyName, parameterName)); } return value; } [ContractAnnotation("value:null => halt")] public static IReadOnlyList NotEmpty(IReadOnlyList value, [InvokerParameterName] [NotNull] string parameterName) { NotNull(value, parameterName); if (value.Count == 0) { NotEmpty(parameterName, nameof(parameterName)); throw new ArgumentException(CoreStrings.CollectionArgumentIsEmpty(parameterName)); } return value; } [ContractAnnotation("value:null => halt")] public static string NotEmpty(string value, [InvokerParameterName] [NotNull] string parameterName) { Exception e = null; if (ReferenceEquals(value, null)) { e = new ArgumentNullException(parameterName); } else if (value.Trim().Length == 0) { e = new ArgumentException(CoreStrings.ArgumentIsEmpty(parameterName)); } if (e != null) { NotEmpty(parameterName, nameof(parameterName)); throw e; } return value; } public static string NullButNotEmpty(string value, [InvokerParameterName] [NotNull] string parameterName) { if (!ReferenceEquals(value, null) && (value.Length == 0)) { NotEmpty(parameterName, nameof(parameterName)); throw new ArgumentException(CoreStrings.ArgumentIsEmpty(parameterName)); } return value; } public static IReadOnlyList HasNoNulls(IReadOnlyList value, [InvokerParameterName] [NotNull] string parameterName) where T : class { NotNull(value, parameterName); if (value.Any(e => e == null)) { NotEmpty(parameterName, nameof(parameterName)); throw new ArgumentException(parameterName); } return value; } public static IEntityType NotOwned(IEntityType entityType, [InvokerParameterName] [NotNull] string parameterName) => NotNull(entityType, parameterName).IsOwned() ? throw new ArgumentException($@"{entityType.Name} is an owned EntityType.", parameterName) : entityType; } } ================================================ FILE: src/Shared/CodeAnnotations.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; namespace JetBrains.Annotations { [AttributeUsage( AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field)] internal sealed class NotNullAttribute : Attribute { } [AttributeUsage( AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field)] internal sealed class CanBeNullAttribute : Attribute { } [AttributeUsage(AttributeTargets.Parameter)] internal sealed class InvokerParameterNameAttribute : Attribute { } [AttributeUsage(AttributeTargets.Parameter)] internal sealed class NoEnumerationAttribute : Attribute { } [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] internal sealed class ContractAnnotationAttribute : Attribute { public string Contract { get; } public bool ForceFullStates { get; } public ContractAnnotationAttribute([NotNull] string contract) : this(contract, false) { } public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) { Contract = contract; ForceFullStates = forceFullStates; } } [AttributeUsage(AttributeTargets.All)] internal sealed class UsedImplicitlyAttribute : Attribute { public UsedImplicitlyAttribute() : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) : this(useKindFlags, ImplicitUseTargetFlags.Default) { } public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) : this(ImplicitUseKindFlags.Default, targetFlags) { } public UsedImplicitlyAttribute( ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) { UseKindFlags = useKindFlags; TargetFlags = targetFlags; } public ImplicitUseKindFlags UseKindFlags { get; } public ImplicitUseTargetFlags TargetFlags { get; } } [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Delegate)] internal sealed class StringFormatMethodAttribute : Attribute { public StringFormatMethodAttribute([NotNull] string formatParameterName) { FormatParameterName = formatParameterName; } [NotNull] public string FormatParameterName { get; } } [Flags] internal enum ImplicitUseKindFlags { Default = Access | Assign | InstantiatedWithFixedConstructorSignature, Access = 1, Assign = 2, InstantiatedWithFixedConstructorSignature = 4, InstantiatedNoFixedConstructorSignature = 8 } [Flags] internal enum ImplicitUseTargetFlags { Default = Itself, Itself = 1, Members = 2, WithMembers = Itself | Members } } ================================================ FILE: src/Shared/MemberInfoExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace System.Reflection { internal static class MemberInfoExtensions { public static Type GetMemberType(this MemberInfo memberInfo) => (memberInfo as PropertyInfo)?.PropertyType ?? ((FieldInfo)memberInfo)?.FieldType; } } ================================================ FILE: src/Shared/PropertyInfoExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Diagnostics; using System.Linq; using JetBrains.Annotations; // ReSharper disable once CheckNamespace namespace System.Reflection { [DebuggerStepThrough] internal static class PropertyInfoExtensions { public static bool IsStatic(this PropertyInfo property) => (property.GetMethod ?? property.SetMethod).IsStatic; public static bool IsCandidateProperty(this PropertyInfo propertyInfo, bool needsWrite = true) => !propertyInfo.IsStatic() && propertyInfo.GetIndexParameters().Length == 0 && propertyInfo.CanRead && (!needsWrite || propertyInfo.CanWrite) && propertyInfo.GetMethod != null && propertyInfo.GetMethod.IsPublic; public static Type FindCandidateNavigationPropertyType(this PropertyInfo propertyInfo, Func isPrimitiveProperty) { var targetType = propertyInfo.PropertyType; var targetSequenceType = targetType.TryGetSequenceType(); if (!propertyInfo.IsCandidateProperty(targetSequenceType == null)) { return null; } targetType = targetSequenceType ?? targetType; targetType = targetType.UnwrapNullableType(); if (isPrimitiveProperty(targetType) || targetType.GetTypeInfo().IsInterface || targetType.GetTypeInfo().IsValueType || targetType == typeof(object)) { return null; } return targetType; } public static PropertyInfo FindGetterProperty([NotNull] this PropertyInfo propertyInfo) => propertyInfo.DeclaringType .GetPropertiesInHierarchy(propertyInfo.Name) .FirstOrDefault(p => p.GetMethod != null); public static PropertyInfo FindSetterProperty([NotNull] this PropertyInfo propertyInfo) => propertyInfo.DeclaringType .GetPropertiesInHierarchy(propertyInfo.Name) .FirstOrDefault(p => p.SetMethod != null); } } ================================================ FILE: src/Shared/SharedTypeExtensions.cs ================================================ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; // ReSharper disable once CheckNamespace namespace System { [DebuggerStepThrough] internal static class SharedTypeExtensions { public static Type UnwrapNullableType(this Type type) => Nullable.GetUnderlyingType(type) ?? type; public static bool IsNullableType(this Type type) { var typeInfo = type.GetTypeInfo(); return !typeInfo.IsValueType || (typeInfo.IsGenericType && (typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>))); } public static bool IsValidEntityType(this Type type) => type.GetTypeInfo().IsClass; public static Type MakeNullable(this Type type) => type.IsNullableType() ? type : typeof(Nullable<>).MakeGenericType(type); public static bool IsInteger(this Type type) { type = type.UnwrapNullableType(); return (type == typeof(int)) || (type == typeof(long)) || (type == typeof(short)) || (type == typeof(byte)) || (type == typeof(uint)) || (type == typeof(ulong)) || (type == typeof(ushort)) || (type == typeof(sbyte)) || (type == typeof(char)); } public static PropertyInfo GetAnyProperty(this Type type, string name) { var props = type.GetRuntimeProperties().Where(p => p.Name == name).ToList(); if (props.Count > 1) { throw new AmbiguousMatchException(); } return props.SingleOrDefault(); } private static bool IsNonIntegerPrimitive(this Type type) { type = type.UnwrapNullableType(); return (type == typeof(bool)) || (type == typeof(byte[])) || (type == typeof(DateTime)) || (type == typeof(DateTimeOffset)) || (type == typeof(decimal)) || (type == typeof(double)) || (type == typeof(float)) || (type == typeof(Guid)) || (type == typeof(string)) || (type == typeof(TimeSpan)) || type.GetTypeInfo().IsEnum; } public static bool IsPrimitive(this Type type) => type.IsInteger() || type.IsNonIntegerPrimitive(); public static bool IsInstantiable(this Type type) => IsInstantiable(type.GetTypeInfo()); private static bool IsInstantiable(TypeInfo type) => !type.IsAbstract && !type.IsInterface && (!type.IsGenericType || !type.IsGenericTypeDefinition); public static bool IsGrouping(this Type type) => IsGrouping(type.GetTypeInfo()); private static bool IsGrouping(TypeInfo type) => type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IGrouping<,>) || type.GetGenericTypeDefinition() == typeof(IAsyncGrouping<,>)); public static Type UnwrapEnumType(this Type type) { var isNullable = type.IsNullableType(); var underlyingNonNullableType = isNullable ? type.UnwrapNullableType() : type; if (!underlyingNonNullableType.GetTypeInfo().IsEnum) { return type; } var underlyingEnumType = Enum.GetUnderlyingType(underlyingNonNullableType); return isNullable ? MakeNullable(underlyingEnumType) : underlyingEnumType; } public static Type GetSequenceType(this Type type) { var sequenceType = TryGetSequenceType(type); if (sequenceType == null) { // TODO: Add exception message throw new ArgumentException(); } return sequenceType; } public static Type TryGetSequenceType(this Type type) => type.IsArray ? type.GetElementType() : type.TryGetElementType(typeof(IDictionary<,>), 1) ?? type.TryGetElementType(typeof(IEnumerable<>)) ?? type.TryGetElementType(typeof(IAsyncEnumerable<>)); public static Type TryGetElementType(this Type type, Type interfaceOrBaseType, int elementIndex = 0) { if (!type.GetTypeInfo().IsGenericTypeDefinition) { var types = GetGenericTypeImplementations(type, interfaceOrBaseType).ToList(); return types.Count == 1 ? types[0].GetTypeInfo().GenericTypeArguments[elementIndex] : null; } return null; } public static IEnumerable GetGenericTypeImplementations(this Type type, Type interfaceOrBaseType) { var typeInfo = type.GetTypeInfo(); if (!typeInfo.IsGenericTypeDefinition) { return (interfaceOrBaseType.GetTypeInfo().IsInterface ? typeInfo.ImplementedInterfaces : type.GetBaseTypes()) .Union(new[] { type }) .Where( t => t.GetTypeInfo().IsGenericType && (t.GetGenericTypeDefinition() == interfaceOrBaseType)); } return Enumerable.Empty(); } public static IEnumerable GetImplementationTypes(this Type type, Type interfaceOrBaseType) { TypeInfo typeInfo = type.GetTypeInfo(); return (interfaceOrBaseType.GetTypeInfo().IsInterface ? typeInfo.ImplementedInterfaces : type.GetBaseTypes()) .Union(new[] { type }) .Where( t => t.GetTypeInfo().IsGenericType && (t.GetGenericTypeDefinition() == interfaceOrBaseType)); } public static Type GetImplementationType(this Type type, Type interfaceOrBaseType) => type.GetGenericTypeImplementations(interfaceOrBaseType) .FirstOrDefault(); public static bool TryGetImplementationType(this Type type, Type interfaceOrBaseType, out Type implementedInterfaceType) => (implementedInterfaceType = type.GetImplementationType(interfaceOrBaseType)) != null; public static IEnumerable GetBaseTypes(this Type type) { type = type.GetTypeInfo().BaseType; while (type != null) { yield return type; type = type.GetTypeInfo().BaseType; } } public static IEnumerable GetTypesInHierarchy(this Type type) { while (type != null) { yield return type; type = type.GetTypeInfo().BaseType; } } public static ConstructorInfo GetDeclaredConstructor(this Type type, Type[] types) { types = types ?? new Type[0]; return type.GetTypeInfo().DeclaredConstructors .SingleOrDefault( c => !c.IsStatic && c.GetParameters().Select(p => p.ParameterType).SequenceEqual(types)); } public static IEnumerable GetPropertiesInHierarchy(this Type type, string name) { do { var typeInfo = type.GetTypeInfo(); var propertyInfo = typeInfo.GetDeclaredProperty(name); if (propertyInfo != null && !(propertyInfo.GetMethod ?? propertyInfo.SetMethod).IsStatic) { yield return propertyInfo; } type = typeInfo.BaseType; } while (type != null); } public static IEnumerable GetMembersInHierarchy(this Type type, string name) { // Do the whole hierarchy for properties first since looking for fields is slower. var currentType = type; do { var typeInfo = currentType.GetTypeInfo(); var propertyInfo = typeInfo.GetDeclaredProperty(name); if (propertyInfo != null && !(propertyInfo.GetMethod ?? propertyInfo.SetMethod).IsStatic) { yield return propertyInfo; } currentType = typeInfo.BaseType; } while (currentType != null); currentType = type; do { var fieldInfo = currentType.GetRuntimeFields().FirstOrDefault(f => f.Name == name && !f.IsStatic); if (fieldInfo != null) { yield return fieldInfo; } currentType = currentType.GetTypeInfo().BaseType; } while (currentType != null); } private static readonly Dictionary CommonTypeDictionary = new Dictionary { { typeof(int), default(int) }, { typeof(Guid), default(Guid) }, { typeof(DateTime), default(DateTime) }, { typeof(DateTimeOffset), default(DateTimeOffset) }, { typeof(long), default(long) }, { typeof(bool), default(bool) }, { typeof(double), default(double) }, { typeof(short), default(short) }, { typeof(float), default(float) }, { typeof(byte), default(byte) }, { typeof(char), default(char) }, { typeof(uint), default(uint) }, { typeof(ushort), default(ushort) }, { typeof(ulong), default(ulong) }, { typeof(sbyte), default(sbyte) } }; public static object GetDefaultValue(this Type type) { if (!type.GetTypeInfo().IsValueType) { return null; } // A bit of perf code to avoid calling Activator.CreateInstance for common types and // to avoid boxing on every call. This is about 50% faster than just calling CreateInstance // for all value types. object value; return CommonTypeDictionary.TryGetValue(type, out value) ? value : Activator.CreateInstance(type); } public static IEnumerable GetConstructableTypes(this Assembly assembly) => assembly.GetLoadableDefinedTypes().Where( t => !t.IsAbstract && !t.IsGenericTypeDefinition); public static IEnumerable GetLoadableDefinedTypes(this Assembly assembly) { try { return assembly.DefinedTypes; } catch (ReflectionTypeLoadException ex) { return ex.Types.Where(t => t != null).Select(IntrospectionExtensions.GetTypeInfo); } } } } ================================================ FILE: src/Shared/StringBuilderExtensions.cs ================================================ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; namespace System.Text { internal static class StringBuilderExtensions { public static StringBuilder AppendJoin( this StringBuilder stringBuilder, IEnumerable values, string separator = ", ") => stringBuilder.AppendJoin(values, (sb, value) => sb.Append(value), separator); public static StringBuilder AppendJoin( this StringBuilder stringBuilder, string separator, params string[] values) => stringBuilder.AppendJoin(values, (sb, value) => sb.Append(value), separator); public static StringBuilder AppendJoin( this StringBuilder stringBuilder, IEnumerable values, Action joinAction, string separator = ", ") { var appended = false; foreach (var value in values) { joinAction(stringBuilder, value); stringBuilder.Append(separator); appended = true; } if (appended) { stringBuilder.Length -= separator.Length; } return stringBuilder; } public static StringBuilder AppendJoin( this StringBuilder stringBuilder, IEnumerable values, TParam param, Action joinAction, string separator = ", ") { var appended = false; foreach (var value in values) { joinAction(stringBuilder, value, param); stringBuilder.Append(separator); appended = true; } if (appended) { stringBuilder.Length -= separator.Length; } return stringBuilder; } public static StringBuilder AppendJoin( this StringBuilder stringBuilder, IEnumerable values, TParam1 param1, TParam2 param2, Action joinAction, string separator = ", ") { var appended = false; foreach (var value in values) { joinAction(stringBuilder, value, param1, param2); stringBuilder.Append(separator); appended = true; } if (appended) { stringBuilder.Length -= separator.Length; } return stringBuilder; } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Adapter/Conventions/AbstractClassConventionTest.cs ================================================ using System; using System.Reflection; using Blueshift.EntityFrameworkCore.MongoDB.Adapter.Conventions; using Blueshift.EntityFrameworkCore.MongoDB.SampleDomain; using MongoDB.Bson.Serialization; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.Adapter.Conventions { public class AbstractClassConventionTest { [Theory] [InlineData(typeof(Animal))] [InlineData(typeof(Tiger))] [InlineData(typeof(PolarBear))] [InlineData(typeof(Otter))] [InlineData(typeof(SeaOtter))] [InlineData(typeof(EurasianOtter))] [InlineData(typeof(Employee))] public void Sets_is_root_class_and_discriminator_required_true_for_abstract_type(Type type) { var classMap = new BsonClassMap(type); var abstractClassMapConvention = new AbstractBaseClassConvention(); abstractClassMapConvention.Apply(classMap); Assert.Equal(type.GetTypeInfo().IsAbstract, classMap.DiscriminatorIsRequired); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Adapter/Conventions/IgnoreEmptyEnumerablesConventionTests.cs ================================================ using Blueshift.EntityFrameworkCore.MongoDB.Adapter.Conventions; using Blueshift.EntityFrameworkCore.MongoDB.SampleDomain; using MongoDB.Bson.Serialization; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.Adapter.Conventions { public class IgnoreEmptyEnumerablesConventionTests { [Fact] public void Should_not_serialize_empty_enumerables() { var bsonClassMap = new BsonClassMap(); BsonMemberMap bsonMemberMap = bsonClassMap.MapMember(e => e.Specialties); var ignoreEmptyEnumerableConvention = new IgnoreEmptyEnumerablesConvention(); ignoreEmptyEnumerableConvention.Apply(bsonMemberMap); var employee = new Employee(); employee.Specialties.Clear(); Assert.False(bsonMemberMap.ShouldSerialize(employee, employee.Specialties)); } [Fact] public void Should_serialize_non_empty_enumerables() { var bsonClassMap = new BsonClassMap(); BsonMemberMap bsonMemberMap = bsonClassMap.MapMember(e => e.Specialties); var ignoreEmptyEnumerableConvention = new IgnoreEmptyEnumerablesConvention(); ignoreEmptyEnumerableConvention.Apply(bsonMemberMap); var employee = new Employee { Specialties = { new Specialty { AnimalType = nameof(Tiger), Task = ZooTask.Feeding }, new Specialty { AnimalType = nameof(PolarBear), Task = ZooTask.Feeding }, new Specialty { AnimalType = nameof(Otter), Task = ZooTask.Feeding } } }; Assert.True(bsonMemberMap.ShouldSerialize(employee, employee.Specialties)); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Adapter/Conventions/IgnoreNullOrEmptyStringsConventionTests.cs ================================================ using Blueshift.EntityFrameworkCore.MongoDB.Adapter.Conventions; using Blueshift.EntityFrameworkCore.MongoDB.SampleDomain; using MongoDB.Bson.Serialization; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.Adapter.Conventions { public class IgnoreNullOrEmptyStringsConventionTests { [Theory] [InlineData(null)] [InlineData("")] [InlineData(" \t\v\r\n")] [InlineData("TestData")] public void Should_not_serialize_null_or_empty_strings(string value) { var bsonClassMap = new BsonClassMap(); BsonMemberMap bsonMemberMap = bsonClassMap.MapMember(e => e.FirstName); var ignoreNullOrEmptyStringsConvention = new IgnoreNullOrEmptyStringsConvention(); ignoreNullOrEmptyStringsConvention.Apply(bsonMemberMap); var employee = new Employee { FirstName = value }; Assert.Equal(!string.IsNullOrEmpty(value), bsonMemberMap.ShouldSerialize(employee, employee.FirstName)); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Adapter/Conventions/KeyAttributeConventionTests.cs ================================================ using System; using System.ComponentModel.DataAnnotations; using System.Reflection; using Blueshift.EntityFrameworkCore.MongoDB.Adapter.Conventions; using Blueshift.EntityFrameworkCore.MongoDB.SampleDomain; using MongoDB.Bson.Serialization; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.Adapter.Conventions { public class KeyAttributeConventionTests { [Fact] public void Should_set_id_member_when_key_attribute_present() { MemberInfo memberInfo = typeof(Animal) .GetTypeInfo() .GetProperty(nameof(Animal.AnimalId)); Assert.NotNull(memberInfo); Assert.True(memberInfo.IsDefined(typeof(KeyAttribute), false)); var keyAttributeConvention = new KeyAttributeConvention(); var bsonClasspMap = new BsonClassMap(); BsonMemberMap bsonMemberMap = bsonClasspMap.MapMember(memberInfo); keyAttributeConvention.Apply(bsonMemberMap); Assert.Same(bsonMemberMap, bsonClasspMap.IdMemberMap); } [Theory] [InlineData(nameof(Employee.FirstName))] [InlineData(nameof(Employee.Age))] public void Should_not_set_id_member_when_key_attribute_present(string memberName) { MemberInfo memberInfo = typeof(Employee) .GetTypeInfo() .GetProperty(memberName); Assert.NotNull(memberInfo); Assert.False(memberInfo.IsDefined(typeof(KeyAttribute), false)); var keyAttributeConvention = new KeyAttributeConvention(); var bsonClasspMap = new BsonClassMap(); BsonMemberMap bsonMemberMap = bsonClasspMap.MapMember(memberInfo); keyAttributeConvention.Apply(bsonMemberMap); Assert.NotSame(bsonMemberMap, bsonClasspMap.IdMemberMap); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Adapter/EntityFrameworkConventionPackTests.cs ================================================ using System; using System.Reflection; using Blueshift.EntityFrameworkCore.MongoDB.Adapter; using Blueshift.EntityFrameworkCore.MongoDB.Adapter.Conventions; using MongoDB.Bson.Serialization.Conventions; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.Adapter { public class EntityFrameworkConventionPackTests { [Theory] [InlineData(typeof(AbstractBaseClassConvention))] [InlineData(typeof(KeyAttributeConvention))] [InlineData(typeof(NavigationSrializationMemberMapConvention))] [InlineData(typeof(NotMappedAttributeConvention))] public void Singleton_contains_default_convention_set(Type conventionType) { ConventionPack conventionPack = EntityFrameworkConventionPack.Instance; Assert.Contains(conventionPack, conventionType.GetTypeInfo().IsInstanceOfType); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Adapter/Serialization/BsonSerializerExtensionsTests.cs ================================================ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Blueshift.EntityFrameworkCore.MongoDB.Adapter.Serialization; using Blueshift.EntityFrameworkCore.MongoDB.SampleDomain; using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Conventions; using MongoDB.Bson.Serialization.Serializers; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.Adapter.Serialization { public class BsonSerializerExtensionsTests : IClassFixture { private readonly ZooEntities _zooEntities; public BsonSerializerExtensionsTests(ZooEntityFixture zooEntityFixture) { _zooEntities = zooEntityFixture.Entities; } private static readonly IDiscriminatorConvention DiscriminatorConvention = BsonSerializer.LookupDiscriminatorConvention(typeof(Animal)); private static string GetDiscriminator(Animal animal) => $"\"{DiscriminatorConvention.ElementName}\" : {DiscriminatorConvention.GetDiscriminator(typeof(Animal), animal.GetType()).ToJson()}"; private static readonly string[] DenormalizedMemberNames = { nameof(Animal.Age), nameof(Animal.Height), nameof(Animal.Weight) }; private static void VerifyChildSerialier(IBsonSerializer bsonSerializer) { var childSerializerConfigurable = bsonSerializer as IChildSerializerConfigurable; while (childSerializerConfigurable?.ChildSerializer is IChildSerializerConfigurable) { childSerializerConfigurable = (IChildSerializerConfigurable)childSerializerConfigurable.ChildSerializer; } var navigationBsonDocumentSerializer = childSerializerConfigurable?.ChildSerializer as DenormalizingBsonClassMapSerializer; Assert.NotNull(navigationBsonDocumentSerializer); IEnumerable denormalizedMemberNames = navigationBsonDocumentSerializer.DenormalizedMemberMaps .Select(bsonMemberMap => bsonMemberMap.MemberName) .OrderBy(memberName => memberName) .ToList(); Assert.Equal(DenormalizedMemberNames, denormalizedMemberNames); } [Theory] [InlineData(typeof(Queue))] [InlineData(typeof(Stack))] [InlineData(typeof(SortedSet))] [InlineData(typeof(List))] [InlineData(typeof(HashSet))] public void Can_denormalize_enumerables(Type enumerableType) { IBsonSerializer defaultSerializer = BsonSerializer.LookupSerializer(enumerableType); IBsonSerializer bsonSerializer = defaultSerializer .AsDenormalizingBsonClassMapSerializer(DenormalizedMemberNames); Assert.NotNull(bsonSerializer); Assert.IsType(defaultSerializer.GetType(), bsonSerializer); VerifyChildSerialier(bsonSerializer); var originalEnumerable = (IEnumerable)(enumerableType == typeof(SortedSet) ? Activator.CreateInstance(enumerableType, _zooEntities.Animals, new AnimalComparer()) : Activator.CreateInstance(enumerableType, _zooEntities.Animals)); if (enumerableType == typeof(Stack)) { originalEnumerable = originalEnumerable.Reverse(); } IEnumerable documents = originalEnumerable .Select(animal => $"{{ \"_id\" : ObjectId(\"{animal.AnimalId}\"), {GetDiscriminator(animal)}, \"Age\" : \"{animal.Age}\", \"Height\" : \"{animal.Height}\", \"Weight\" : \"{animal.Weight}\" }}"); string expectedDocument = $"[{string.Join(", ", documents)}]"; var testEnumerable = (IEnumerable)(enumerableType == typeof(SortedSet) ? Activator.CreateInstance(enumerableType, _zooEntities.Animals, new AnimalComparer()) : Activator.CreateInstance(enumerableType, _zooEntities.Animals)); Assert.Equal(expectedDocument, testEnumerable.ToJson(enumerableType, serializer: bsonSerializer)); } [Theory] [InlineData(typeof(IEnumerable), typeof(List))] [InlineData(typeof(ICollection), typeof(List))] [InlineData(typeof(IList), typeof(List))] [InlineData(typeof(ISet), typeof(HashSet))] public void Can_denormalize_enumerable_interfaces(Type enumerableInterface, Type concreteType) { IBsonSerializer defaultSerializer = BsonSerializer.LookupSerializer(enumerableInterface); IBsonSerializer bsonSerializer = defaultSerializer .AsDenormalizingBsonClassMapSerializer(DenormalizedMemberNames); Assert.NotNull(bsonSerializer); Assert.IsType(defaultSerializer.GetType(), bsonSerializer); VerifyChildSerialier(bsonSerializer); var enumerable = (IEnumerable)Activator.CreateInstance(concreteType, _zooEntities.Animals); IEnumerable documents = enumerable .Select(animal => $"{{ \"_id\" : ObjectId(\"{animal.AnimalId}\"), {GetDiscriminator(animal)}, \"Age\" : \"{animal.Age}\", \"Height\" : \"{animal.Height}\", \"Weight\" : \"{animal.Weight}\" }}"); string expectedDocument = $"[{string.Join(", ", documents)}]"; Assert.Equal(expectedDocument, enumerable.ToJson(enumerableInterface, serializer: bsonSerializer)); } [Theory] [InlineData(typeof(Dictionary))] [InlineData(typeof(IDictionary))] public void Can_denormalize_dictionaries(Type type) { IBsonSerializer defaultSerializer = BsonSerializer.LookupSerializer(type); IBsonSerializer bsonSerializer = defaultSerializer .AsDenormalizingBsonClassMapSerializer(DenormalizedMemberNames); Assert.NotNull(bsonSerializer); Assert.IsType(defaultSerializer.GetType(), bsonSerializer); VerifyChildSerialier(bsonSerializer); Dictionary dictionary = _zooEntities.Animals.ToDictionary(animal => animal.Name); IEnumerable documents = dictionary .OrderBy(kvp => kvp.Key) .ThenBy(kvp => kvp.Value.Height) .Select(kvp => kvp.Value) .Select(animal => $"\"{animal.Name}\" : {{ \"_id\" : ObjectId(\"{animal.AnimalId}\"), {GetDiscriminator(animal)}, \"Age\" : \"{animal.Age}\", \"Height\" : \"{animal.Height}\", \"Weight\" : \"{animal.Weight}\" }}"); string expectedDocument = $"{{ {string.Join(", ", documents)} }}"; Assert.Equal(expectedDocument, dictionary.ToJson(type, serializer: bsonSerializer)); } [Theory] [InlineData(typeof(Animal[]))] [InlineData(typeof(Animal[,]))] [InlineData(typeof(Animal[,,]))] public void Can_denormalize_array(Type type) { IBsonSerializer defaultSerializer = BsonSerializer.LookupSerializer(type); IBsonSerializer bsonSerializer = defaultSerializer .AsDenormalizingBsonClassMapSerializer(DenormalizedMemberNames); Assert.NotNull(bsonSerializer); Assert.IsType(defaultSerializer.GetType(), bsonSerializer); VerifyChildSerialier(bsonSerializer); } [Fact] public void Can_denormalize_ReadOnlyCollection() { IBsonSerializer defaultSerializer = BsonSerializer.LookupSerializer(typeof(ReadOnlyCollection)); var bsonSerializer = (ReadOnlyCollectionSerializer)defaultSerializer .AsDenormalizingBsonClassMapSerializer(DenormalizedMemberNames); Assert.NotNull(bsonSerializer); Assert.IsType(defaultSerializer.GetType(), bsonSerializer); Assert.IsType>(bsonSerializer.ItemSerializer); var readOnlyCollection = new ReadOnlyCollection(_zooEntities.Animals.ToList()); IEnumerable documents = readOnlyCollection .Select(animal => $"{{ \"_id\" : ObjectId(\"{animal.AnimalId}\"), {GetDiscriminator(animal)}, \"Age\" : \"{animal.Age}\", \"Height\" : \"{animal.Height}\", \"Weight\" : \"{animal.Weight}\" }}"); string expectedDocument = $"[{string.Join(", ", documents)}]"; Assert.Equal(expectedDocument, readOnlyCollection.ToJson(typeof(ReadOnlyCollection), serializer: bsonSerializer)); } [Fact] public void Can_denormalize_ReadOnlyCollection_sub_class() { IBsonSerializer defaultSerializer = BsonSerializer.LookupSerializer(typeof(ReadOnlyObservableCollection)); var bsonSerializer = (ReadOnlyCollectionSubclassSerializer< ReadOnlyObservableCollection, Animal>)defaultSerializer .AsDenormalizingBsonClassMapSerializer(DenormalizedMemberNames); Assert.NotNull(bsonSerializer); Assert.IsType(defaultSerializer.GetType(), bsonSerializer); Assert.IsType>(bsonSerializer.ItemSerializer); var observableCollection = new ObservableCollection(_zooEntities.Animals.ToList()); var readOnlyObservableCollection = new ReadOnlyObservableCollection(observableCollection); IEnumerable documents = readOnlyObservableCollection .Select(animal => $"{{ \"_id\" : ObjectId(\"{animal.AnimalId}\"), {GetDiscriminator(animal)}, \"Age\" : \"{animal.Age}\", \"Height\" : \"{animal.Height}\", \"Weight\" : \"{animal.Weight}\" }}"); string expectedDocument = $"[{string.Join(", ", documents)}]"; Assert.Equal(expectedDocument, readOnlyObservableCollection.ToJson(typeof(ReadOnlyObservableCollection), serializer: bsonSerializer)); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Adapter/Serialization/DenormalizingBsonClassMapSerializerTests.cs ================================================ using Blueshift.EntityFrameworkCore.MongoDB.Adapter.Serialization; using Blueshift.EntityFrameworkCore.MongoDB.SampleDomain; using MongoDB.Bson; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.Adapter.Serialization { public class DenormalizingBsonClassMapSerializerTests : IClassFixture { private static readonly string[] DenormalizedMemberNames = { nameof(Animal.Age), nameof(Animal.Height), nameof(Animal.Weight) }; private readonly ZooEntities _zooEntities; public DenormalizingBsonClassMapSerializerTests(ZooEntityFixture zooEntityFixture) { _zooEntities = zooEntityFixture.Entities; } [Fact] public void Correctly_Serializes_Entity_Reference() { BsonClassMap animalClassMap = (BsonClassMap) BsonClassMap.LookupClassMap(typeof(Animal)); var animalNavigationSerializer = new DenormalizingBsonClassMapSerializer(animalClassMap); Tiger tigger = _zooEntities.Tigger; string tiggerJson = tigger.ToJson(typeof(Animal), serializer: animalNavigationSerializer); Assert.Equal($"{{ \"_id\" : ObjectId(\"{tigger.AnimalId}\"), \"_t\" : [\"Animal\", \"panthera tigris\"] }}", tiggerJson); Assert.DoesNotContain($"\"Name\": \"${tigger.Name}\"", tiggerJson); Assert.DoesNotContain($"\"Age\": \"${tigger.Age}\"", tiggerJson); Assert.DoesNotContain($"\"Height\": \"${tigger.Height}\"", tiggerJson); Assert.DoesNotContain($"\"Weight\": \"${tigger.Weight}\"", tiggerJson); } [Fact] public void Correctly_Serializes_Entity_Reference_With_Denormalized_Properties() { BsonClassMap animalClassMap = (BsonClassMap)BsonClassMap.LookupClassMap(typeof(Animal)); var animalNavigationSerializer = new DenormalizingBsonClassMapSerializer(animalClassMap, DenormalizedMemberNames); Animal tigger = _zooEntities.Tigger; string tiggerJson = tigger.ToJson(typeof(Animal), serializer: animalNavigationSerializer); Assert.Equal($"{{ \"_id\" : ObjectId(\"{tigger.AnimalId}\"), \"_t\" : [\"Animal\", \"panthera tigris\"], \"Age\" : \"{tigger.Age}\", \"Height\" : \"{tigger.Height}\", \"Weight\" : \"{tigger.Weight}\" }}", tiggerJson); Assert.DoesNotContain($"\"Name\": \"${tigger.Name}\"", tiggerJson); } [Fact] public void Deserialize_uses_default_deserializer() { BsonClassMap animalClassMap = (BsonClassMap)BsonClassMap.LookupClassMap(typeof(Animal)); Animal tigger = _zooEntities.Tigger; BsonDocument bsonDocument = tigger.ToBsonDocument(); var animalNavigationSerializer = new DenormalizingBsonClassMapSerializer(animalClassMap); Animal animal; using (var bsonReader = new BsonDocumentReader(bsonDocument)) { BsonDeserializationContext bsonDeserializationContext = BsonDeserializationContext.CreateRoot(bsonReader); var bsonDeserializationArgs = new BsonDeserializationArgs() { NominalType = typeof(Animal) }; animal = animalNavigationSerializer.Deserialize(bsonDeserializationContext, bsonDeserializationArgs); } Assert.Equal(tigger, animal, new AnimalEqualityComparer()); } [Fact] public void Deserialize_can_deserialize_partial_class() { BsonClassMap animalClassMap = (BsonClassMap)BsonClassMap.LookupClassMap(typeof(Animal)); var animalNavigationSerializer = new DenormalizingBsonClassMapSerializer(animalClassMap); Animal tigger = _zooEntities.Tigger; BsonDocument bsonDocument = tigger.ToBsonDocument(serializer: animalNavigationSerializer); Animal animal; using (var bsonReader = new BsonDocumentReader(bsonDocument)) { BsonDeserializationContext bsonDeserializationContext = BsonDeserializationContext.CreateRoot(bsonReader); var bsonDeserializationArgs = new BsonDeserializationArgs() { NominalType = typeof(Animal) }; animal = animalNavigationSerializer.Deserialize(bsonDeserializationContext, bsonDeserializationArgs); } Assert.NotNull(animal); Assert.IsType(animal); Assert.Equal(tigger.AnimalId, animal.AnimalId); Assert.Null(animal.Name); Assert.Equal(0, animal.Age); Assert.Equal(0, animal.Height); Assert.Equal(0, animal.Weight); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Adapter/Update/MongoDbWriteModelFactoryTests.cs ================================================ using System; using Blueshift.EntityFrameworkCore.MongoDB.Adapter.Update; using Blueshift.EntityFrameworkCore.MongoDB.SampleDomain; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Update; using Microsoft.EntityFrameworkCore.ValueGeneration; using MongoDB.Bson; using MongoDB.Driver; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.Adapter.Update { public class MongoDbWriteModelFactoryTests { private IUpdateEntry GetUpdateEntry(EntityState entityState, object entity) { var zooDbContext = new ZooDbContext(); var entityEntry = zooDbContext.Add(entity); entityEntry.State = entityState; return ((IInfrastructure)entityEntry).Instance; } private IMongoDbWriteModelFactory CreateMongoDbWriteModelFactory(IUpdateEntry updateEntry) => new MongoDbWriteModelFactorySelector( new ValueGeneratorSelector( new ValueGeneratorSelectorDependencies( new ValueGeneratorCache( new ValueGeneratorCacheDependencies()))), new MongoDbWriteModelFactoryCache()) .Select(updateEntry); [Fact] public void Creates_insert_one_model_for_added_entity() { var employee = new Employee(); var updateEntry = GetUpdateEntry(EntityState.Added, employee); var mongoDbWriteModelFactory = CreateMongoDbWriteModelFactory(updateEntry); var insertOneModel = mongoDbWriteModelFactory.CreateWriteModel(updateEntry) as InsertOneModel; Assert.NotNull(insertOneModel); Assert.Same(employee, insertOneModel.Document); } [Fact] public void Creates_insert_one_model_for_added_entity_and_updates_concurrency_field() { var tiger = new Tiger() { Name = "Pantheris" }; Assert.Null(tiger.ConcurrencyField); var entityEntry = GetUpdateEntry(EntityState.Added, tiger); var mongoDbWriteModelFactory = CreateMongoDbWriteModelFactory(entityEntry); var insertOneModel = mongoDbWriteModelFactory.CreateWriteModel(entityEntry) as InsertOneModel; Assert.NotNull(insertOneModel); Assert.Same(tiger, insertOneModel.Document); Assert.NotNull(tiger.ConcurrencyField); Assert.NotEmpty(tiger.ConcurrencyField); } [Fact] public void Creates_ReplaceOneModel_for_modified_entity_referencing_only_id() { var employee = new Employee(); var updateEntry = GetUpdateEntry(EntityState.Modified, employee); employee.FirstName = "Bob"; IMongoDbWriteModelFactory mongoDbWriteModelFactory = CreateMongoDbWriteModelFactory(updateEntry); ReplaceOneModel replaceOneModel = mongoDbWriteModelFactory.CreateWriteModel(updateEntry) as ReplaceOneModel; FilterDefinition filter = Builders.Filter.Eq(record => record.EmployeeId, employee.EmployeeId); Assert.NotNull(replaceOneModel); Assert.Equal(replaceOneModel.Filter.ToJson(), filter.ToJson()); Assert.Same(employee, replaceOneModel.Replacement); } [Fact] public void Creates_ReplaceOneModel_for_modified_entity_referencing_concurrency_field() { var tiger = new Tiger() { Name = "Pantheris" }; var updtaeEntry = GetUpdateEntry(EntityState.Modified, tiger); var concurrencyToken = Guid.NewGuid().ToString(); typeof(Animal).GetProperty(nameof(Animal.ConcurrencyField)).SetValue(tiger, concurrencyToken); Assert.Equal(concurrencyToken, tiger.ConcurrencyField); var mongoDbWriteModelFactory = CreateMongoDbWriteModelFactory(updtaeEntry); var replaceOneModel = mongoDbWriteModelFactory.CreateWriteModel(updtaeEntry) as ReplaceOneModel; FilterDefinition filter = Builders.Filter.And( Builders.Filter.Eq(record => record.AnimalId, tiger.AnimalId), Builders.Filter.Eq(record => record.ConcurrencyField, tiger.ConcurrencyField)); Assert.NotNull(replaceOneModel); Assert.Equal(replaceOneModel.Filter.ToJson(), filter.ToJson()); Assert.Same(tiger, replaceOneModel.Replacement); } [Fact] public void Creates_DeleteOneModel_for_deleted_entity_referencing_only_id() { var employee = new Employee(); var updateEntry = GetUpdateEntry(EntityState.Deleted, employee); var mongoDbWriteModelFactory = CreateMongoDbWriteModelFactory(updateEntry); var deleteOneModel = mongoDbWriteModelFactory.CreateWriteModel(updateEntry) as DeleteOneModel; FilterDefinition filter = Builders.Filter.Eq(record => record.EmployeeId, employee.EmployeeId); Assert.NotNull(deleteOneModel); Assert.Equal(filter.ToJson(), deleteOneModel.Filter.ToJson()); } [Fact] public void Creates_DeleteOneModel_for_deleted_entity_referencing_concurrency_field() { var tiger = new Tiger() { Name = "Pantheris" }; var concurrencyToken = Guid.NewGuid().ToString(); typeof(Animal).GetProperty(nameof(Animal.ConcurrencyField)).SetValue(tiger, concurrencyToken); Assert.Equal(concurrencyToken, tiger.ConcurrencyField); var updateEntry = GetUpdateEntry(EntityState.Deleted, tiger); var mongoDbWriteModelFactory = CreateMongoDbWriteModelFactory(updateEntry); var deleteOneModel = mongoDbWriteModelFactory.CreateWriteModel(updateEntry) as DeleteOneModel; FilterDefinition filter = Builders.Filter.And( Builders.Filter.Eq(record => record.AnimalId, tiger.AnimalId), Builders.Filter.Eq(record => record.ConcurrencyField, tiger.ConcurrencyField)); Assert.NotNull(deleteOneModel); Assert.Equal(filter.ToJson(), deleteOneModel.Filter.ToJson()); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/ApiConsistencyTest.cs ================================================ //using System.Reflection; //using Microsoft.EntityFrameworkCore.Storage; //using Microsoft.EntityFrameworkCore.Tests; //namespace Blueshift.EntityFrameworkCore.MongoDB.Tests //{ // public class ApiConsistencyTest : ApiConsistencyTestBase // { // protected override Assembly TargetAssembly => typeof(MongoDbDatabase).GetTypeInfo().Assembly; // } //} ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Blueshift.EntityFrameworkCore.MongoDB.Tests.csproj ================================================  $(TestFrameworks) MongoDb.EFCore.Tests PreserveNewest ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Metadata/Conventions/MongoDatabaseConventionTests.cs ================================================ using System; using Blueshift.EntityFrameworkCore.MongoDB.Annotations; using Blueshift.EntityFrameworkCore.MongoDB.Infrastructure; using Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders; using Microsoft.EntityFrameworkCore; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.Metadata.Conventions { public class MongoDatabaseConventionTests { [Theory] [InlineData(typeof(ZooDb), "zoo")] [InlineData(typeof(ZooDbContext), "zoo")] [InlineData(typeof(ZooContext), "zoo")] [InlineData(typeof(ZooMongo), "zoo")] [InlineData(typeof(ZooMongoDb), "zoo")] [InlineData(typeof(ZooMongoDbContext), "zoo")] [InlineData(typeof(ZooMongoContext), "zoo")] [InlineData(typeof(AnnodatedZooContext), "zoo")] [InlineData(typeof(DifferentlyAnnodatedZooContext), "zhou")] public void Should_set_expected_database_name(Type dbContextType, string expectedName) { DbContext dbContext = (DbContext) Activator.CreateInstance(dbContextType); Assert.Equal(expectedName, dbContext.Model.MongoDb().Database); } private class MongoDatabaseAttributeDbContextBase : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder .UseMongoDb("mongodb://localhost:27017"); base.OnConfiguring(optionsBuilder); } } private class ZooDb : MongoDatabaseAttributeDbContextBase { } private class ZooDbContext : MongoDatabaseAttributeDbContextBase { } private class ZooContext : MongoDatabaseAttributeDbContextBase { } private class ZooMongo : MongoDatabaseAttributeDbContextBase { } private class ZooMongoDb : MongoDatabaseAttributeDbContextBase { } private class ZooMongoDbContext : MongoDatabaseAttributeDbContextBase { } private class ZooMongoContext : MongoDatabaseAttributeDbContextBase { } [MongoDatabase("zoo")] private class AnnodatedZooContext : MongoDatabaseAttributeDbContextBase { } [MongoDatabase("zhou")] private class DifferentlyAnnodatedZooContext : MongoDatabaseAttributeDbContextBase { } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Metadata/MongoDbEntityTypeAnnotationsTests.cs ================================================ using Blueshift.EntityFrameworkCore.MongoDB.Metadata; using Blueshift.EntityFrameworkCore.MongoDB.SampleDomain; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.Metadata { public class MongoDbEntityTypeAnnotationsTests { [Fact] public void Collection_name_is_pluralized_camel_cased_entity_type_by_default() { var model = new Model(); var entityType = new EntityType(typeof(Animal), model, ConfigurationSource.Explicit); var mongoDbEntityTypeAnnotations = new MongoDbEntityTypeAnnotations(entityType); Assert.Equal(MongoDbUtilities.Pluralize(MongoDbUtilities.ToLowerCamelCase(nameof(Animal))), mongoDbEntityTypeAnnotations.CollectionName); } [Fact] public void Can_write_collection_name() { var collectionName = "myCollection"; var model = new Model(); var entityType = new EntityType(typeof(Animal), model, ConfigurationSource.Explicit); var mongoDbEntityTypeAnnotations = new MongoDbEntityTypeAnnotations(entityType) { CollectionName = collectionName }; Assert.Equal(collectionName, mongoDbEntityTypeAnnotations.CollectionName); } [Fact] public void Discriminator_is_type_name_by_default() { var model = new Model(); var entityType = new EntityType(typeof(Animal), model, ConfigurationSource.Explicit); var mongoDbEntityTypeAnnotations = new MongoDbEntityTypeAnnotations(entityType); Assert.Equal(typeof(Animal).Name, mongoDbEntityTypeAnnotations.Discriminator); } [Fact] public void Can_write_discriminator() { var discriminator = "discriminator"; var model = new Model(); var entityType = new EntityType(typeof(Animal), model, ConfigurationSource.Explicit); var mongoDbEntityTypeAnnotations = new MongoDbEntityTypeAnnotations(entityType) { Discriminator = discriminator }; Assert.Equal(discriminator, mongoDbEntityTypeAnnotations.Discriminator); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Metadata/MongoDbModelAnnotationsTests.cs ================================================ using Blueshift.EntityFrameworkCore.MongoDB.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.Metadata { public class MongoDbModelAnnotationsTests { [Fact] public void Database_name_null_by_default() { var mongoDbModelAnnotations = new MongoDbModelAnnotations(new Model()); Assert.Null(mongoDbModelAnnotations.Database); } [Fact] public void Can_write_database_name() { var mongoDbModelAnnotations = new MongoDbModelAnnotations(new Model()) { Database = "test" }; Assert.Equal(expected: "test", actual: mongoDbModelAnnotations.Database); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/MongoDbContextTestBase.cs ================================================ using System; using System.Threading.Tasks; using Blueshift.EntityFrameworkCore.MongoDB.Infrastructure; using Blueshift.EntityFrameworkCore.MongoDB.SampleDomain; using Microsoft.Extensions.DependencyInjection; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests { public abstract class MongoDbContextTestBase : IDisposable { private const string MongoUrl = "mongodb://localhost:27017"; protected IServiceProvider ServiceProvider; protected MongoDbContextTestBase() { ServiceProvider = new ServiceCollection() .AddDbContext(options => options .UseMongoDb(MongoUrl) .EnableSensitiveDataLogging(true)) .BuildServiceProvider(); ExecuteUnitOfWork(zooDbContext => zooDbContext.Database.EnsureCreated()); } public void Dispose() { ExecuteUnitOfWork(zooDbContext => zooDbContext.Database.EnsureDeleted()); } protected void ExecuteUnitOfWork(Action unitOfWork) { using (IServiceScope serviceScope = ServiceProvider.CreateScope()) { ZooDbContext zooDbContext = serviceScope.ServiceProvider.GetService(); unitOfWork(zooDbContext); } } protected async Task ExecuteUnitOfWorkAsync(Func unitOfWork) { using (IServiceScope serviceScope = ServiceProvider.CreateScope()) { ZooDbContext zooDbContext = serviceScope.ServiceProvider.GetService(); await unitOfWork(zooDbContext); } } protected async Task ExecuteUnitOfWorkAsync(Func> unitOfWork) { using (IServiceScope serviceScope = ServiceProvider.CreateScope()) { ZooDbContext zooDbContext = serviceScope.ServiceProvider.GetService(); return await unitOfWork(zooDbContext); } } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/MongoDbContextTests.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Blueshift.EntityFrameworkCore.MongoDB.SampleDomain; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Metadata.Internal; using MongoDB.Driver.Linq; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests { public class MongoDbContextTests : MongoDbContextTestBase, IClassFixture { private readonly ZooEntities _zooEntities; public MongoDbContextTests(ZooEntityFixture zooEntityFixture) { _zooEntities = zooEntityFixture.Entities; } [Fact] public async Task Can_query_from_mongodb() { await ExecuteUnitOfWorkAsync(async zooDbContext => { Assert.Empty(await zooDbContext.Animals.ToListAsync()); Assert.Empty(await zooDbContext.Employees.ToListAsync()); Assert.Empty(await zooDbContext.Enclosures.ToListAsync()); }); } [Fact] public async Task Can_write_simple_record() { var employee = new Employee { FirstName = "Taiga", LastName = "Masuta", Age = 31.7M }; await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.Add(employee); Assert.Equal(1, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { Assert.Equal(employee, await zooDbContext.Employees.SingleAsync(), new EmployeeEqualityComparer()); }); } [Fact] public async Task Can_write_complex_record() { await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.Add(_zooEntities.TaigaMasuta); Assert.Equal( 1 + _zooEntities.TaigaMasuta.Manager.DirectReports.Count, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); } [Fact] public async Task Can_write_polymorphic_records() { await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.Animals.AddRange(_zooEntities.Animals); Assert.Equal( _zooEntities.Entities.Count, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { IList queriedEntities = await zooDbContext.Animals .OrderBy(animal => animal.Name) .ThenBy(animal => animal.Height) .ToListAsync(); Assert.Equal(_zooEntities.Animals, queriedEntities, new AnimalEqualityComparer()); }); } [Fact] public async Task Can_update_existing_entity() { await ExecuteUnitOfWorkAsync(async zooDbContext => { EntityEntry entityEntry = zooDbContext.Add(_zooEntities.Tigger); Assert.Equal(EntityState.Added, entityEntry.State); Assert.Equal(7, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); Assert.Equal(EntityState.Unchanged, entityEntry.State); Assert.NotNull(_zooEntities.Tigger.ConcurrencyField); Assert.NotNull(entityEntry.OriginalValues[nameof(_zooEntities.Tigger.ConcurrencyField)]); _zooEntities.Tigger.Name = "Tigra"; zooDbContext.ChangeTracker.DetectChanges(); Assert.Equal(EntityState.Modified, entityEntry.State); Assert.Equal(1, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { Assert.Equal( _zooEntities.Tigger, await zooDbContext.Animals.OfType().FirstOrDefaultAsync(), new AnimalEqualityComparer()); }); } [Fact] public async Task Can_update_sub_document() { await ExecuteUnitOfWorkAsync(async zooDbContext => { EntityEntry entityEntry = zooDbContext.Add(_zooEntities.TaigaMasuta); Assert.Equal(EntityState.Added, entityEntry.State); Assert.Equal(5, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); Assert.Equal(EntityState.Unchanged, entityEntry.State); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { Employee taigaMasuta = await zooDbContext.Employees .FirstAsync(employee => employee.LastName == _zooEntities.TaigaMasuta.LastName && employee.FirstName == _zooEntities.TaigaMasuta.FirstName); EntityEntry entityEntry = zooDbContext.Entry(taigaMasuta); Specialty firstSpecialty = taigaMasuta.Specialties[0]; EntityEntry specialtyEntry = zooDbContext.Entry(firstSpecialty); Assert.Equal(EntityState.Unchanged, specialtyEntry.State); firstSpecialty.AnimalType = nameof(PolarBear); zooDbContext.ChangeTracker.DetectChanges(); Assert.Equal(EntityState.Modified, specialtyEntry.State); Assert.Equal(1, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { Employee taigaMasuta = await zooDbContext.Employees .FirstOrDefaultAsync(employee => employee.LastName == _zooEntities.TaigaMasuta.LastName && employee.FirstName == _zooEntities.TaigaMasuta.FirstName && employee.Specialties.Any(specialty => specialty.AnimalType == nameof(PolarBear))); Assert.NotNull(taigaMasuta); }); } [Fact] public async Task Concurrency_field_prevents_updates() { await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.Add(_zooEntities.Tigger); Assert.Equal( 3 + _zooEntities.TigerEnclosure.WeeklySchedule.Approver.DirectReports.Count, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); Assert.False(string.IsNullOrWhiteSpace(_zooEntities.Tigger.ConcurrencyField)); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { EntityEntry entityEntry = zooDbContext.Update(_zooEntities.Tigger); string newConcurrencyToken = Guid.NewGuid().ToString(); PropertyEntry propertyEntry = entityEntry.Property(nameof(Animal.ConcurrencyField)); propertyEntry.OriginalValue = newConcurrencyToken; propertyEntry.Metadata.GetSetter().SetClrValue(_zooEntities.Tigger, newConcurrencyToken); Assert.Equal(0, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); Assert.False(string.IsNullOrWhiteSpace(_zooEntities.Tigger.ConcurrencyField)); }); } [Fact] public async Task Can_query_complex_record() { await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.Add(_zooEntities.TaigaMasuta); Assert.Equal( _zooEntities.Employees.Count, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { Assert.Equal( _zooEntities.TaigaMasuta, await zooDbContext.Employees .SingleAsync(searchedEmployee => searchedEmployee.Specialties .Any(specialty => specialty.AnimalType == nameof(Tiger) && specialty.Task == ZooTask.Feeding)) , new EmployeeEqualityComparer()); }); } [Fact] public async Task Can_query_polymorphic_sub_types() { await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.Animals.AddRange(_zooEntities.Animals); Assert.Equal( _zooEntities.Entities.Count, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { Assert.Equal( _zooEntities.Animals.OfType().Single(), await zooDbContext.Animals.OfType().SingleAsync(), new AnimalEqualityComparer()); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { Assert.Equal( _zooEntities.Animals.OfType().Single(), await zooDbContext.Animals.OfType().SingleAsync(), new AnimalEqualityComparer()); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { Assert.Equal( _zooEntities.Animals.OfType().Single(), await zooDbContext.Animals.OfType().SingleAsync(), new AnimalEqualityComparer()); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { Assert.Equal( _zooEntities.Animals.OfType().Single(), await zooDbContext.Animals.OfType().SingleAsync(), new AnimalEqualityComparer()); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { IList originalOtters = _zooEntities.Animals .OfType() .OrderBy(otter => otter.Name) .ToList(); IList queriedOtters = await zooDbContext.Animals .OfType() .OrderBy(otter => otter.Name) .ToListAsync(); Assert.Equal(originalOtters, queriedOtters, new AnimalEqualityComparer()); }); } [Fact] public async Task Can_list_async() { await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.Animals.AddRange(_zooEntities.Animals); Assert.Equal( _zooEntities.Entities.Count, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { Assert.Equal(_zooEntities.Animals, await zooDbContext.Animals .OrderBy(animal => animal.Name) .ThenBy(animal => animal.Height) .ToListAsync(), new AnimalEqualityComparer()); }); } [Fact] public async Task Can_query_first_or_default_async() { await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.Animals.AddRange(_zooEntities.Animals); Assert.Equal( _zooEntities.Entities.Count, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { Assert.Equal( _zooEntities.Animals.OfType().Single(), await zooDbContext.Animals.OfType().FirstOrDefaultAsync(), new AnimalEqualityComparer()); }); } [Fact] public async Task Can_include_direct_collection() { await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.Enclosures.AddRange(_zooEntities.Enclosures); Assert.Equal( _zooEntities.Entities.Count, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { IEnumerable queriedEnclosures = await zooDbContext.Enclosures .Include(enclosure => enclosure.Animals) .OrderBy(enclosure => enclosure.AnimalEnclosureType) .ThenBy(enclosure => enclosure.Name) .ToListAsync(); Assert.Equal(_zooEntities.Enclosures, queriedEnclosures, new EnclosureEqualityComparer() .WithAnimalEqualityComparer(animalEqualityComparer => animalEqualityComparer .WithEnclosureEqualityComparer())); }); } [Fact] public async Task Can_include_direct_reference() { await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.Animals.AddRange(_zooEntities.Animals); Assert.Equal( _zooEntities.Entities.Count, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { IEnumerable queriedAnimals = await zooDbContext.Animals .Include(animal => animal.Enclosure) .OrderBy(animal => animal.Name) .ThenBy(animal => animal.Age) .ToListAsync(); Assert.Equal(_zooEntities.Animals, queriedAnimals, new AnimalEqualityComparer() .WithEnclosureEqualityComparer(enclosureEqualityComparer => enclosureEqualityComparer.WithAnimalEqualityComparer())); }); } [Fact(Skip = "Test currently fails.")] public async Task Can_include_self_reference() { await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.AddRange(_zooEntities.Employees); Assert.Equal( _zooEntities.Employees.Count, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { IEnumerable queriedEmployees = await zooDbContext.Employees .Include(employee => employee.Manager) .OrderBy(employee => employee.FullName) .ToListAsync(); Assert.Equal(_zooEntities.Employees, queriedEmployees, new EmployeeEqualityComparer() .WithManagerComparer(managerEqualityComparer => managerEqualityComparer.WithDirectReportsComparer())); }); } [Fact(Skip = "IncludeCompiler does not currently support DI or being independently overriden.")] public async Task Can_include_owned_collection() { // IncludeCompiler uses the entity metadata to generate the underlying join clauses, // however it currently does not properly support being injected through DI, being created // by a factory, or being independently overriden without also having to override several // other query-generation-related classes. This makes it virtually impossible to generate // the correct MongoDb-side query syntax for supporting Join and GroupJoin statements // against owned collections where the ownership requires a level of indirection to get to // the foreign key. await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.Enclosures.AddRange(_zooEntities.Enclosures); Assert.Equal( _zooEntities.Entities.Count, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { IEnumerable queriedEnclosures = await zooDbContext.Enclosures .Include(enclosure => enclosure.Animals) .Include(enclosure => enclosure.WeeklySchedule.Assignments) .ThenInclude(zooAssignment => zooAssignment.Assignee) .OrderBy(enclosure => enclosure.AnimalEnclosureType) .ThenBy(enclosure => enclosure.Name) .ToListAsync(); Assert.Equal(_zooEntities.Enclosures, queriedEnclosures, new EnclosureEqualityComparer() .WithAnimalEqualityComparer(animalEqualityComparer => animalEqualityComparer .WithEnclosureEqualityComparer()) .ConfigureWeeklyScheduleEqualityComparer( scheduleEqualityComparer => scheduleEqualityComparer.ConfigureZooAssignmentEqualityComparer( zooAssignmentEqualityComparer => zooAssignmentEqualityComparer.WithEmployeeEqualityComparer()))); }); } [Fact] public async Task Can_include_owned_reference() { await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.Enclosures.AddRange(_zooEntities.Enclosures); Assert.Equal( _zooEntities.Entities.Count, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { IEnumerable queriedEnclosures = await zooDbContext.Enclosures .Include(enclosure => enclosure.Animals) .Include(enclosure => enclosure.WeeklySchedule.Approver) .OrderBy(enclosure => enclosure.AnimalEnclosureType) .ThenBy(enclosure => enclosure.Name) .ToListAsync(); Assert.Equal(_zooEntities.Enclosures, queriedEnclosures, new EnclosureEqualityComparer() .WithAnimalEqualityComparer(animalEqualityComparer => animalEqualityComparer .WithEnclosureEqualityComparer()) .ConfigureWeeklyScheduleEqualityComparer( scheduleEqualityComparer => scheduleEqualityComparer .WithApproverEqualityComparer())); }); } [Fact] public async Task Can_execute_group_join_without_includes() { await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.Enclosures.AddRange(_zooEntities.Enclosures); Assert.Equal( _zooEntities.Entities.Count, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { IEnumerable queriedEnclosures = await zooDbContext.Enclosures .GroupJoin( zooDbContext.Employees .Join( zooDbContext.Enclosures.SelectMany( enclosure => enclosure.WeeklySchedule.Assignments, (enclosure, assignment) => new { EnclosureId = enclosure.EnclosureId, Assignment = assignment }), employee => employee.EmployeeId, enclosureAssignment => enclosureAssignment.Assignment.Assignee.EmployeeId, (employee, enclosureAssignment) => new { enclosureAssignment.EnclosureId, Assignment = AssignAssignee(enclosureAssignment.Assignment, employee) }), enclosure => enclosure.EnclosureId, enclosureAssignment => enclosureAssignment.EnclosureId, (enclosure, enclosureAssignments) => AssignAssignments( enclosure, enclosureAssignments.Select(enclosureAssignment => enclosureAssignment.Assignment))) .ToListAsync(); Assert.Equal(_zooEntities.Enclosures, queriedEnclosures, new EnclosureEqualityComparer() .ConfigureWeeklyScheduleEqualityComparer( scheduleEqualityComparer => scheduleEqualityComparer .ConfigureZooAssignmentEqualityComparer( zooAssignmentEqualityComparer => zooAssignmentEqualityComparer .WithEmployeeEqualityComparer()))); }); } private static Enclosure AssignAssignments(Enclosure enclosure, IEnumerable zooAssignments) { foreach (var pair in enclosure.WeeklySchedule.Assignments .Join( zooAssignments, includedAssignment => includedAssignment.Assignee.EmployeeId, denormalizedAssignment => denormalizedAssignment.Assignee.EmployeeId, (denormalizedAssignment, includedAssignment) => new { Assignment = denormalizedAssignment, includedAssignment.Assignee })) { pair.Assignment.Assignee = pair.Assignee; }; return enclosure; } private static ZooAssignment AssignAssignee(ZooAssignment assignment, Employee assignee) { assignment.Assignee = assignee; return assignment; } [Fact] public async Task Concurrent_query() { await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.Employees.AddRange(_zooEntities.Employees); Assert.Equal( _zooEntities.Employees.Count, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); Employee[] employees = await ExecuteUnitOfWorkAsync( zooDbContext => Task.WhenAll( zooDbContext.Employees.SingleAsync(employee => employee.FullName == _zooEntities.ManAgier.FullName), zooDbContext.Employees.SingleAsync(employee => employee.FullName == _zooEntities.BearOCreary.FullName), zooDbContext.Employees.SingleAsync(employee => employee.FullName == _zooEntities.OttoVonEssenmacher.FullName), zooDbContext.Employees.SingleAsync(employee => employee.FullName == _zooEntities.TaigaMasuta.FullName), zooDbContext.Employees.SingleAsync(employee => employee.FullName == _zooEntities.TurGuidry.FullName) )); Employee[] expectedEmployees = { _zooEntities.ManAgier, _zooEntities.BearOCreary, _zooEntities.OttoVonEssenmacher, _zooEntities.TaigaMasuta, _zooEntities.TurGuidry }; Assert.All(employees, Assert.NotNull); Assert.Equal(expectedEmployees, employees, new EmployeeEqualityComparer()); } [Fact(Skip = "Test currently fails.")] public async Task Can_list_async_twice() { await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.Animals.AddRange(_zooEntities.Animals); Assert.Equal( _zooEntities.Entities.Count, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { Assert.Equal(_zooEntities.Animals, await zooDbContext.Animals .OrderBy(animal => animal.Name) .ThenBy(animal => animal.Height) .ToListAsync(), new AnimalEqualityComparer()); }); await ExecuteUnitOfWorkAsync(async zooDbContext => { Assert.Equal(_zooEntities.Animals, await zooDbContext.Animals .OrderBy(animal => animal.Name) .ThenBy(animal => animal.Height) .ToListAsync(), new AnimalEqualityComparer()); }); } [Fact(Skip = "This test is a performance test and take a long time to execute.")] public async Task Can_query_multiple_concurrent_items_from_large_data_set() { IList tigerFodderEmployees = Enumerable .Range(1, 100000) .Select(index => new Employee { Age = 34.7M, FirstName = "Fodder", LastName = index.ToString() }) .ToList(); await ExecuteUnitOfWorkAsync(async zooDbContext => { zooDbContext.Employees.AddRange(tigerFodderEmployees); Assert.Equal( tigerFodderEmployees.Count, await zooDbContext.SaveChangesAsync(acceptAllChangesOnSuccess: true)); }); Employee[] employees = await ExecuteUnitOfWorkAsync(async zooDbContext => { IList> tasks = Enumerable .Range(1, 100) .Select(index => zooDbContext.Employees .FirstOrDefaultAsync(e => e.LastName == (index * 1000).ToString()) ) .ToList(); return await Task.WhenAll(tasks); }); Assert.All(employees, Assert.NotNull); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/MongoDbUtilitiesTests.cs ================================================ using System; using Blueshift.EntityFrameworkCore.MongoDB.SampleDomain; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests { public class MongoDbUtilitiesTests { [Theory] [InlineData("monkey", "monkeys")] [InlineData("laptop", "laptops")] [InlineData("cpu", "cpus")] [InlineData("horse", "horses")] [InlineData("pony", "ponies")] public void Pluralize_singular_strings(string value, string expected) => Assert.Equal(expected, MongoDbUtilities.Pluralize(value)); [Theory] [InlineData("monkeys")] [InlineData("horses")] [InlineData("ponies")] public void Pluralize_doesnt_change_plurals(string value) => Assert.Equal(value, MongoDbUtilities.Pluralize(value)); [Theory] [InlineData("CPU", "cpu")] [InlineData("ETA", "eta")] [InlineData("EPA", "epa")] [InlineData("TLA", "tla")] public void Camel_case_uppercase_strings(string value, string expected) => Assert.Equal(expected, MongoDbUtilities.ToLowerCamelCase(value)); [Theory] [InlineData("EFTests", "efTests")] [InlineData("NYCity", "nyCity")] [InlineData("TLAcronym", "tlAcronym")] [InlineData("ThreeLetterAcronym", "threeLetterAcronym")] public void Camel_case_doesnt_change_trailing_words(string value, string expected) => Assert.Equal(expected, MongoDbUtilities.ToLowerCamelCase(value)); [Theory] [InlineData(typeof(Animal), typeof(Animal))] [InlineData(typeof(Tiger), typeof(Animal))] [InlineData(typeof(PolarBear), typeof(Animal))] [InlineData(typeof(Otter), typeof(Animal))] [InlineData(typeof(EurasianOtter), typeof(Animal))] [InlineData(typeof(SeaOtter), typeof(Animal))] [InlineData(typeof(Employee), typeof(Employee))] [InlineData(typeof(Enclosure), typeof(Enclosure))] public void GetCollectionEntityType_returns_least_derived_entity_type(Type documentType, Type expectedType) { var zooDbContext = new ZooDbContext(); IEntityType documentEntityType = zooDbContext.Model.FindEntityType(documentType); IEntityType expectedEntityType = zooDbContext.Model.FindEntityType(expectedType); Assert.Equal(expectedEntityType, documentEntityType.GetMongoDbCollectionEntityType()); } [Theory] [InlineData(typeof(Animal))] [InlineData(typeof(Tiger))] [InlineData(typeof(PolarBear))] [InlineData(typeof(Otter))] [InlineData(typeof(EurasianOtter))] [InlineData(typeof(SeaOtter))] [InlineData(typeof(Employee))] [InlineData(typeof(Enclosure))] public void IsDocumentRootEntityType_returns_true_for_root_entity_types(Type documentType) { var zooDbContext = new ZooDbContext(); IEntityType documentEntityType = zooDbContext.Model.FindEntityType(documentType); Assert.True(documentEntityType.IsDocumentRootEntityType()); } [Theory] [InlineData(typeof(Schedule))] [InlineData(typeof(Specialty))] [InlineData(typeof(ZooAssignment))] public void IsDocumentRootEntityType_returns_false_for_owned_entity_types(Type documentType) { var zooDbContext = new ZooDbContext(); IEntityType documentEntityType = zooDbContext.Model.FindEntityType(documentType); Assert.False(documentEntityType.IsDocumentRootEntityType()); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Properties/AssemblyInfo.cs ================================================ // Copyright (c) Blueshift Software, LLC. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Reflection; using System.Resources; using Xunit; [assembly: NeutralResourcesLanguage("en-US")] [assembly: AssemblyMetadata("Serviceable", "True")] [assembly: TestFramework("Microsoft.EntityFrameworkCore.Specification.Tests.TestUtilities.Xunit.ConditionalTestFramework", "Microsoft.EntityFrameworkCore.Specification.Tests")] ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Storage/MongoDbConnectionTests.cs ================================================ using System.Threading; using System.Threading.Tasks; using Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders; using Blueshift.EntityFrameworkCore.MongoDB.SampleDomain; using Blueshift.EntityFrameworkCore.MongoDB.Storage; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; using MongoDB.Driver; using Moq; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.Storage { public class MongoDbConnectionTests { private readonly Mock _mockMongoDatabase; private readonly Mock _mockMongoClient; private readonly Mock> _mockEmployee; private readonly Mock _mockMongoDbTypeMappingSource; private readonly IModel _model; public MongoDbConnectionTests() { _mockMongoDbTypeMappingSource = MockMongoDbTypeMappingSource(); _model = GetModel(); _mockMongoClient = MockMongoClient(); _mockMongoDatabase = MockMongoDatabase(); _mockEmployee = MockEmployee(); } private IModel GetModel() { var model = new ModelBuilder( new MongoDbConventionSetBuilder( new MongoDbConventionSetBuilderDependencies( new CurrentDbContext( new ZooDbContext( new DbContextOptions())), _mockMongoDbTypeMappingSource.Object, Mock.Of(), Mock.Of>())) .AddConventions( new CoreConventionSetBuilder( new CoreConventionSetBuilderDependencies( _mockMongoDbTypeMappingSource.Object, null, null, null, null)) .CreateConventionSet())) .ForMongoDbFromDatabase("zooDb") .Model .AsModel(); new EntityTypeBuilder(model.Builder .Entity(typeof(Employee), ConfigurationSource.Explicit)) .ForMongoDbFromCollection("employees"); return model; } private Mock MockMongoClient() { var mockMongoClient = new Mock(); mockMongoClient .Setup(mongoClient => mongoClient.GetDatabase("zooDb", It.IsAny())) .Returns(() => _mockMongoDatabase.Object) .Verifiable(); mockMongoClient .Setup(mongoClient => mongoClient.DropDatabase("zooDb", It.IsAny())) .Verifiable(); return mockMongoClient; } private Mock MockMongoDatabase() { var mockMongoDatabase = new Mock(); mockMongoDatabase .Setup(mongoDatabase => mongoDatabase.GetCollection("employees", It.IsAny())) .Returns(() => _mockEmployee.Object) .Verifiable(); return mockMongoDatabase; } private Mock> MockEmployee() => new Mock>(); private Mock MockMongoDbTypeMappingSource() => new Mock(); [Fact] public void Get_database_calls_mongo_client_get_database() { IMongoDbConnection mongoDbConnection = new MongoDbConnection(_mockMongoClient.Object, _model); Assert.Equal(_mockMongoDatabase.Object, mongoDbConnection.GetDatabase()); _mockMongoClient .Verify(mongoClient => mongoClient.GetDatabase("zooDb", It.IsAny()), Times.Once); } [Fact] public async Task Get_database_async_calls_mongo_client_get_database() { IMongoDbConnection mongoDbConnection = new MongoDbConnection(_mockMongoClient.Object, _model); Assert.Equal(_mockMongoDatabase.Object, await mongoDbConnection.GetDatabaseAsync()); _mockMongoClient .Verify(mongoClient => mongoClient.GetDatabase("zooDb", It.IsAny()), Times.Once); } [Fact] public void Drop_database_calls_mongo_client_drop_database() { IMongoDbConnection mongoDbConnection = new MongoDbConnection(_mockMongoClient.Object, _model); mongoDbConnection.DropDatabase(); _mockMongoClient .Verify(mongoClient => mongoClient.DropDatabase("zooDb", It.IsAny()), Times.Once); } [Fact] public async Task Drop_database_async_calls_mongo_client_drop_database_async() { IMongoDbConnection mongoDbConnection = new MongoDbConnection(_mockMongoClient.Object, _model); await mongoDbConnection.DropDatabaseAsync(); _mockMongoClient .Verify(mongoClient => mongoClient.DropDatabaseAsync("zooDb", It.IsAny()), Times.Once); } [Fact] public void Get_collection_calls_mongo_database_get_collection() { IMongoDbConnection mongoDbConnection = new MongoDbConnection(_mockMongoClient.Object, _model); Assert.Equal(_mockEmployee.Object, mongoDbConnection.GetCollection()); _mockMongoDatabase .Verify(mongoDatabase => mongoDatabase.GetCollection("employees", It.IsAny()), Times.Once); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Storage/MongoDbDatabaseCreatorTests.cs ================================================ using System.Threading; using System.Threading.Tasks; using Blueshift.EntityFrameworkCore.MongoDB.Storage; using MongoDB.Driver; using Moq; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.Storage { public class MongoDbDatabaseCreatorTests { [Fact] public void Ensure_created_returns_false() { var mockMongoDbConnection = new Mock(); mockMongoDbConnection .Setup(mongoDbConnection => mongoDbConnection.GetDatabase()) .Returns(new Mock().Object) .Verifiable(); var mongoDbDatabaseCreator = new MongoDbDatabaseCreator(mockMongoDbConnection.Object); Assert.False(mongoDbDatabaseCreator.EnsureCreated()); mockMongoDbConnection.Verify( mongoDbConnection => mongoDbConnection.GetDatabase(), Times.Exactly(callCount: 1)); } [Fact] public async Task Ensure_created_async_returns_false() { var mockMongoDbConnection = new Mock(); mockMongoDbConnection .Setup(mongoDbConnection => mongoDbConnection.GetDatabaseAsync(It.IsAny())) .Returns(Task.FromResult(new Mock().Object)) .Verifiable(); var mongoDbDatabaseCreator = new MongoDbDatabaseCreator(mockMongoDbConnection.Object); Assert.False(await mongoDbDatabaseCreator.EnsureCreatedAsync()); mockMongoDbConnection.Verify( mongoDbConnection => mongoDbConnection.GetDatabaseAsync(It.IsAny()), Times.Exactly(callCount: 1)); } [Fact] public void Ensure_deleted_succeeds() { var mockMongoDbConnection = new Mock(); mockMongoDbConnection .Setup(mongoDbConnection => mongoDbConnection.DropDatabase()) .Verifiable(); var mongoDbDatabaseCreator = new MongoDbDatabaseCreator(mockMongoDbConnection.Object); Assert.True(mongoDbDatabaseCreator.EnsureDeleted()); mockMongoDbConnection.Verify( mongoDbConnection => mongoDbConnection.DropDatabase(), Times.Exactly(callCount: 1)); } [Fact] public async Task Ensure_deleted_async_succeeds() { var mockMongoDbConnection = new Mock(); mockMongoDbConnection .Setup(mongoDbConnection => mongoDbConnection.DropDatabaseAsync(It.IsAny())) .Returns(Task.FromResult(result: 0)) .Verifiable(); var mongoDbDatabaseCreator = new MongoDbDatabaseCreator(mockMongoDbConnection.Object); Assert.True(await mongoDbDatabaseCreator.EnsureDeletedAsync()); mockMongoDbConnection.Verify( mongoDbConnection => mongoDbConnection.DropDatabaseAsync(It.IsAny()), Times.Exactly(callCount: 1)); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Storage/MongoDbDatabaseTests.cs ================================================ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using Blueshift.EntityFrameworkCore.MongoDB.Adapter.Update; using Blueshift.EntityFrameworkCore.MongoDB.Metadata.Builders; using Blueshift.EntityFrameworkCore.MongoDB.SampleDomain; using Blueshift.EntityFrameworkCore.MongoDB.Storage; using Blueshift.EntityFrameworkCore.MongoDB.ValueGeneration; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Update; using Microsoft.EntityFrameworkCore.ValueGeneration; using MongoDB.Driver; using Moq; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.Storage { public class MongoDbDatabaseTests { [Fact] public void Save_changes_returns_requested_document_count() { var queryCompilationContextFactory = Mock.Of(); var mockMongoDbConnection = new Mock(); var mockMongoCollection = new Mock>(); var mockMongoDbTypeMappingSource = new Mock(); mockMongoCollection.Setup(mongoCollection => mongoCollection.BulkWrite( It.IsAny>>(), It.IsAny(), It.IsAny())) .Returns((IEnumerable> list, BulkWriteOptions options, CancellationToken token) => new BulkWriteResult.Acknowledged( list.Count(), matchedCount: 0, deletedCount: list.OfType>().Count(), insertedCount: list.OfType>().Count(), modifiedCount: list.OfType>().Count(), processedRequests: list, upserts: new List())); mockMongoDbConnection.Setup(mockedMongoDbConnection => mockedMongoDbConnection.GetCollection()) .Returns(() => mockMongoCollection.Object); var databaseDepedencies = new DatabaseDependencies(queryCompilationContextFactory); IMongoDbWriteModelFactorySelector writeModelFactorySelector = new MongoDbWriteModelFactorySelector( new MongoDbValueGeneratorSelector( new ValueGeneratorSelectorDependencies( new ValueGeneratorCache( new ValueGeneratorCacheDependencies()))), new MongoDbWriteModelFactoryCache()); var mongoDbDatabase = new MongoDbDatabase( databaseDepedencies, mockMongoDbConnection.Object, writeModelFactorySelector); var model = new Model( new MongoDbConventionSetBuilder( new MongoDbConventionSetBuilderDependencies( new CurrentDbContext( new ZooDbContext( new DbContextOptions())), mockMongoDbTypeMappingSource.Object, Mock.Of(), Mock.Of>())) .AddConventions( new CoreConventionSetBuilder( new CoreConventionSetBuilderDependencies( mockMongoDbTypeMappingSource.Object, null, null, null, null)) .CreateConventionSet())); EntityType animalEntityType = model.AddEntityType(typeof(Animal)); animalEntityType.Builder .GetOrCreateProperties(typeof(Animal).GetTypeInfo().GetProperties(), ConfigurationSource.Explicit); EntityType tigerEntityType = model.GetOrAddEntityType(typeof(Tiger)); IReadOnlyList entityEntries = new[] { EntityState.Added, EntityState.Deleted, EntityState.Modified } .Select(entityState => { var entity = new Tiger(); var mockUpdateEntry = new Mock( null, tigerEntityType); mockUpdateEntry .SetupGet(updateEntry => updateEntry.EntityState) .Returns(entityState); mockUpdateEntry .SetupGet(updateEntry => updateEntry.EntityType) .Returns(tigerEntityType); mockUpdateEntry .SetupGet(updateEntry => updateEntry.Entity) .Returns(entity); mockUpdateEntry .Setup(updateEntry => updateEntry.ToEntityEntry()) .Returns(new EntityEntry(mockUpdateEntry.Object)); return mockUpdateEntry.Object; }) .ToList(); Assert.Equal(entityEntries.Count, mongoDbDatabase.SaveChanges(entityEntries)); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/Storage/MongoDbTypeMappingSourceTests.cs ================================================ using System; using System.Collections.Generic; using Blueshift.EntityFrameworkCore.MongoDB.SampleDomain; using Blueshift.EntityFrameworkCore.MongoDB.Storage; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using MongoDB.Bson; using Moq; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.Storage { public class MongoDbTypeMappingSourceTests { private readonly MongoDbTypeMappingSource _mongoDbTypeMappingSource = new MongoDbTypeMappingSource( new TypeMappingSourceDependencies( Mock.Of())); [Theory] [InlineData(typeof(int))] [InlineData(typeof(long))] [InlineData(typeof(short))] [InlineData(typeof(byte))] [InlineData(typeof(uint))] [InlineData(typeof(ulong))] [InlineData(typeof(ushort))] [InlineData(typeof(sbyte))] [InlineData(typeof(char))] [InlineData(typeof(bool))] [InlineData(typeof(byte[]))] [InlineData(typeof(DateTime))] [InlineData(typeof(DateTimeOffset))] [InlineData(typeof(decimal))] [InlineData(typeof(double))] [InlineData(typeof(float))] [InlineData(typeof(Guid))] [InlineData(typeof(string))] [InlineData(typeof(TimeSpan))] [InlineData(typeof(ZooTask))] [InlineData(typeof(ObjectId))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IDictionary))] public void Primitives_and_enumerables_of_primitives_are_mapped(Type type) { Assert.NotNull(_mongoDbTypeMappingSource.FindMapping(type)); } [Theory] [InlineData(typeof(Employee))] [InlineData(typeof(Animal))] [InlineData(typeof(Tiger))] [InlineData(typeof(PolarBear))] [InlineData(typeof(Otter))] [InlineData(typeof(SeaOtter))] [InlineData(typeof(EurasianOtter))] [InlineData(typeof(Specialty))] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IDictionary))] public void Entities_and_complex_types_are_not_mapped(Type type) { Assert.Null(_mongoDbTypeMappingSource.FindMapping(type)); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/ValueGeneration/MongoDbValueGeneratorSelectorTests.cs ================================================ using System.Reflection; using Blueshift.EntityFrameworkCore.MongoDB.SampleDomain; using Blueshift.EntityFrameworkCore.MongoDB.ValueGeneration; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.ValueGeneration; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.ValueGeneration { public class MongoDbValueGeneratorSelectorTests { [Fact] public void X() { var model = new Model(); EntityType entityType = model.AddEntityType(typeof(Employee)); Property property = entityType.AddProperty(typeof(Employee) .GetTypeInfo() .GetProperty(nameof(Employee.EmployeeId))); var valueGeneratorCacheDependencies = new ValueGeneratorCacheDependencies(); var valueGeneratorCache = new ValueGeneratorCache(valueGeneratorCacheDependencies); var valueGeneratorSelectorDependencies = new ValueGeneratorSelectorDependencies(valueGeneratorCache); var mongoDbValueGeneratorSelector = new MongoDbValueGeneratorSelector(valueGeneratorSelectorDependencies); ValueGenerator valueGenerator = mongoDbValueGeneratorSelector.Select(property, entityType); Assert.IsAssignableFrom(valueGenerator); } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/ValueGeneration/ObjectIdValueGeneratorTests.cs ================================================ using Blueshift.EntityFrameworkCore.MongoDB.ValueGeneration; using MongoDB.Bson; using Xunit; namespace Blueshift.EntityFrameworkCore.MongoDB.Tests.ValueGeneration { public class ObjectIdValueGeneratorTests { [Fact] public void Generates_temporary_values_returns_false() { var objectIdValueGenerator = new ObjectIdValueGenerator(); Assert.False(objectIdValueGenerator.GeneratesTemporaryValues); } [Fact] public void Does_not_generate_empty_object_id() { var objectIdValueGenerator = new ObjectIdValueGenerator(); Assert.NotEqual(ObjectId.Empty, objectIdValueGenerator.Next(entry: null)); } [Fact] public void Generates_unique_object_ids() { var objectIdValueGenerator = new ObjectIdValueGenerator(); ObjectId id = ObjectId.Empty; for (var i = 0; i < 100; i++) { Assert.NotEqual(id, id = objectIdValueGenerator.Next(entry: null)); } } } } ================================================ FILE: test/Blueshift.EntityFrameworkCore.MongoDB.Tests/xunit.runner.json ================================================ { "longRunningTestSeconds": 30, "parallelizeAssembly": false } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/Blueshift.Identity.MongoDB.Tests.csproj ================================================  $(TestFrameworks) MongoDb.EFCore.Identity.Tests PreserveNewest ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbIdentityFixture.cs ================================================ using System; using Blueshift.EntityFrameworkCore.MongoDB.Infrastructure; using Microsoft.Extensions.DependencyInjection; using MongoDB.Bson; namespace Blueshift.Identity.MongoDB.Tests { public class MongoDbIdentityFixture : IDisposable { private const string MongoUrl = "mongodb://localhost:27017"; protected IServiceProvider ServiceProvider; private IdentityMongoDbContext _identityDbContext; public MongoDbIdentityFixture() { ServiceProvider = new ServiceCollection() .AddDbContext(options => options .UseMongoDb( connectionString: MongoUrl, mongoDbOptionsAction: optionsBuilder => optionsBuilder.UseDatabase("__test_identities")) .EnableSensitiveDataLogging(true)) .AddIdentity() .AddEntityFrameworkMongoDbStores() .Services .BuildServiceProvider(); _identityDbContext = ServiceProvider.GetRequiredService(); _identityDbContext.Database.EnsureCreated(); } public T GetService() => ServiceProvider.GetRequiredService(); /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { if (_identityDbContext != null) { _identityDbContext.Database.EnsureDeleted(); _identityDbContext = null; } } } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbIdentityTestBase.cs ================================================ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Xunit; namespace Blueshift.Identity.MongoDB.Tests { [Collection("MongoDB.Identity.Tests")] public abstract class MongoDbIdentityStoreTestBase { protected static readonly string RoleName = "TestRole"; protected static readonly string RoleNameNormalized = RoleName.ToUpper(); protected static readonly string UserName = "user.name@domain.com"; protected static readonly string UserNameNormalized = UserName.ToUpper(); private readonly IUserStore _userStore; private readonly IRoleStore _roleStore; protected MongoDbIdentityStoreTestBase(MongoDbIdentityFixture mongoDbIdentityFixture) { _userStore = mongoDbIdentityFixture.GetService>(); _roleStore = mongoDbIdentityFixture.GetService>(); } protected virtual MongoDbIdentityRole CreateRole() => new MongoDbIdentityRole { RoleName = RoleName, NormalizedRoleName = RoleNameNormalized }; protected async Task CreateRoleInDatabase() { MongoDbIdentityRole role = CreateRole(); Assert.Equal(IdentityResult.Success, await _roleStore.CreateAsync(role, new CancellationToken())); return role; } protected virtual MongoDbIdentityUser CreateUser() => new MongoDbIdentityUser { UserName = UserName, NormalizedUserName = UserNameNormalized }; protected async Task CreateUserInDatabase() { MongoDbIdentityUser user = CreateUser(); Assert.Equal(IdentityResult.Success, await _userStore.CreateAsync(user, new CancellationToken())); return user; } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbIdentityTestCollection.cs ================================================ using Xunit; namespace Blueshift.Identity.MongoDB.Tests { [CollectionDefinition("MongoDB.Identity.Tests")] public class MongoDdIdentityTestCollection : ICollectionFixture { } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbRoleClaimStoreTests.cs ================================================ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using MongoDB.Bson; using Xunit; namespace Blueshift.Identity.MongoDB.Tests { public class MongoDbRoleClaimStoreTests : MongoDbIdentityStoreTestBase { private const string Claim1Type = nameof(Claim1Type); private const string Claim1Value = nameof(Claim1Value); private const string Claim2Type = nameof(Claim2Type); private const string Claim2Value = nameof(Claim2Value); private const string Claim3Type = nameof(Claim3Type); private const string Claim3Value = nameof(Claim3Value); private static readonly Claim Claim1 = new Claim(Claim1Type, Claim1Value); private static readonly Claim Claim2 = new Claim(Claim2Type, Claim2Value); private static readonly Claim Claim3 = new Claim(Claim3Type, Claim3Value); private readonly IRoleClaimStore _mongoDbRoleClaimStore; public MongoDbRoleClaimStoreTests(MongoDbIdentityFixture mongoDbIdentityFixture) : base(mongoDbIdentityFixture) { _mongoDbRoleClaimStore = mongoDbIdentityFixture.GetService>(); } protected override MongoDbIdentityRole CreateRole() { var role = base.CreateRole(); role.Claims.Add(new MongoDbIdentityClaim(Claim1)); role.Claims.Add(new MongoDbIdentityClaim(Claim2)); return role; } [Fact] public async Task Can_add_claim_async() { var role = CreateRole(); await _mongoDbRoleClaimStore.AddClaimAsync(role, Claim3); var newClaims = new [] { Claim1, Claim2, Claim3 }; Assert.Equal(newClaims, role.Claims.Select(claim => claim.ToClaim()), new ClaimComparer()); } [Fact] public async Task Can_get_claims_async() { var role = CreateRole(); var claims = new [] { Claim1, Claim2 }; Assert.Equal(claims, await _mongoDbRoleClaimStore.GetClaimsAsync(role), new ClaimComparer()); } [Fact] public async Task Can_remove_claims_async() { var role = CreateRole(); await _mongoDbRoleClaimStore.RemoveClaimAsync(role, Claim1); Assert.Equal(Claim2, role.Claims.Select(claim => claim.ToClaim()).Single(), new ClaimComparer()); } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbRoleStoreTests.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Xunit; namespace Blueshift.Identity.MongoDB.Tests { public class MongoDbRoleStoreTests : MongoDbIdentityStoreTestBase { private readonly IRoleStore _mongoDbRoleStore; public MongoDbRoleStoreTests(MongoDbIdentityFixture mongoDbIdentityFixture) : base(mongoDbIdentityFixture) { _mongoDbRoleStore = mongoDbIdentityFixture.GetService>(); } [Fact] public async Task Can_create_role_async() { Assert.NotNull(await CreateRoleInDatabase()); } [Fact] public async Task Can_delete_role_async() { var role = await CreateRoleInDatabase(); Assert.Equal(IdentityResult.Success, await _mongoDbRoleStore.DeleteAsync(role, new CancellationToken())); } [Fact] public async Task Can_find_by_id_async() { var role = await CreateRoleInDatabase(); Assert.Equal(role, await _mongoDbRoleStore.FindByIdAsync(role.Id.ToString(), new CancellationToken()), new MongoDbIdentityRoleComparer()); } [Fact] public async Task Can_find_by_name_async() { var role = await CreateRoleInDatabase(); Assert.Equal(role, await _mongoDbRoleStore.FindByNameAsync(role.NormalizedRoleName, new CancellationToken()), new MongoDbIdentityRoleComparer()); } [Fact] public async Task Can_get_normalized_role_name_async() { var role = CreateRole(); Assert.Equal(role.NormalizedRoleName, await _mongoDbRoleStore.GetNormalizedRoleNameAsync(role, new CancellationToken()), StringComparer.Ordinal); } [Fact] public async Task Can_get_role_id_async() { var role = await CreateRoleInDatabase(); Assert.Equal(role.Id.ToString(), await _mongoDbRoleStore.GetRoleIdAsync(role, new CancellationToken()), StringComparer.Ordinal); } [Fact] public async Task Can_get_user_name_async() { var role = CreateRole(); Assert.Equal(role.RoleName, await _mongoDbRoleStore.GetRoleNameAsync(role, new CancellationToken()), StringComparer.Ordinal); } [Fact] public async Task Can_set_normalized_role_name_async() { var role = await CreateRoleInDatabase(); string newNormalizedRoleName = nameof(newNormalizedRoleName).ToUpper(); await _mongoDbRoleStore.SetNormalizedRoleNameAsync(role, newNormalizedRoleName, new CancellationToken()); Assert.Equal(newNormalizedRoleName, role.NormalizedRoleName, StringComparer.Ordinal); } [Fact] public async Task Can_set_role_name_async() { var role = await CreateRoleInDatabase(); string newRoleName = nameof(newRoleName); await _mongoDbRoleStore.SetRoleNameAsync(role, newRoleName, new CancellationToken()); Assert.Equal(newRoleName, role.RoleName, StringComparer.Ordinal); } [Fact] public async Task Can_update_role_async() { var role = await CreateRoleInDatabase(); var newRoleName = "New.Role.Name"; var newNormalizedRoleName = newRoleName.ToUpper(); role.RoleName = newRoleName; role.NormalizedRoleName = newNormalizedRoleName; Assert.Equal(IdentityResult.Success, await _mongoDbRoleStore.UpdateAsync(role, new CancellationToken())); Assert.Equal(role, await _mongoDbRoleStore.FindByNameAsync(newNormalizedRoleName, new CancellationToken()), new MongoDbIdentityRoleComparer()); } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbUserAuthenticationTokenStoreTests.cs ================================================ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Xunit; namespace Blueshift.Identity.MongoDB.Tests { public class MongoDbUserAuthenticationTokenStoreTests : MongoDbIdentityStoreTestBase { private const string Token1Name = nameof(Token1Name); private const string Token2Name = nameof(Token2Name); private const string Token1Value = nameof(Token1Value); private const string Token2Value = nameof(Token2Value); private readonly IUserAuthenticationTokenStore _mongoDbUserAuthenticationTokenStore; public MongoDbUserAuthenticationTokenStoreTests(MongoDbIdentityFixture mongoDbIdentityFixture) : base(mongoDbIdentityFixture) { _mongoDbUserAuthenticationTokenStore = mongoDbIdentityFixture.GetService>(); } protected override MongoDbIdentityUser CreateUser() { var user = base.CreateUser(); user.Logins.Add(new MongoDbIdentityUserLogin { LoginProvider = "Google", ProviderKey = new Guid().ToString(), ProviderDisplayName = "Google+", UserTokens = { new MongoDbIdentityUserToken() { Name = Token1Name, Value = Token1Value }, new MongoDbIdentityUserToken() { Name = Token2Name, Value = Token2Value } } }); return user; } [Fact] public async Task Can_get_token_async() { var user = CreateUser(); Assert.Equal(Token1Value, await _mongoDbUserAuthenticationTokenStore.GetTokenAsync(user, "Google", Token1Name, new CancellationToken()), StringComparer.Ordinal); Assert.Equal(Token2Value, await _mongoDbUserAuthenticationTokenStore.GetTokenAsync(user, "Google", Token2Name, new CancellationToken()), StringComparer.Ordinal); Assert.Null(await _mongoDbUserAuthenticationTokenStore.GetTokenAsync(user, "Google", "Token3", new CancellationToken())); Assert.Null(await _mongoDbUserAuthenticationTokenStore.GetTokenAsync(user, "Facebook", Token1Name, new CancellationToken())); } [Fact] public async Task Can_set_token_async() { var user = CreateUser(); string newTokenValue = nameof(newTokenValue); await _mongoDbUserAuthenticationTokenStore.SetTokenAsync(user, "Google", Token1Name, newTokenValue, new CancellationToken()); Assert.Equal(newTokenValue, user.Logins .First(login => login.LoginProvider == "Google") .UserTokens .First(userToken => userToken.Name == Token1Name).Value, StringComparer.Ordinal); } [Fact] public async Task Can_remove_token_async() { var user = CreateUser(); await _mongoDbUserAuthenticationTokenStore.RemoveTokenAsync(user, "Google", Token1Name, new CancellationToken()); var userTokens = user.Logins .First(login => login.LoginProvider == "Google") .UserTokens; Assert.DoesNotContain(userTokens, userToken => userToken.Name == Token1Name); Assert.Equal(Token2Value, userTokens.First(userToken => userToken.Name == Token2Name).Value, StringComparer.Ordinal); } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbUserAuthenticatorKeyStoreTests.cs ================================================ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Xunit; namespace Blueshift.Identity.MongoDB.Tests { public class MongoDbUserAuthenticatorKeyStoreTests : MongoDbIdentityStoreTestBase { private static readonly string AuthenticatorKey = new Guid().ToString(); private readonly IUserAuthenticatorKeyStore _mongoDbUserAuthenticatorKeyStore; public MongoDbUserAuthenticatorKeyStoreTests(MongoDbIdentityFixture mongoDbIdentityFixture) : base(mongoDbIdentityFixture) { _mongoDbUserAuthenticatorKeyStore = mongoDbIdentityFixture.GetService>(); } protected override MongoDbIdentityUser CreateUser() { var user = base.CreateUser(); user.Logins.Add(new MongoDbIdentityUserLogin { LoginProvider = "[BlueshiftMongoDbUserStore]", ProviderKey = new Guid().ToString(), ProviderDisplayName = "Blueshift MongoDb Provider", UserTokens = { new MongoDbIdentityUserToken() { Name = "AuthenticatorKey", Value = AuthenticatorKey } } }); return user; } [Fact] public async Task Can_get_authenticator_key_async() { var user = CreateUser(); Assert.Equal(AuthenticatorKey, await _mongoDbUserAuthenticatorKeyStore.GetAuthenticatorKeyAsync(user, new CancellationToken()), StringComparer.Ordinal); } [Fact] public async Task Can_set_token_async() { var user = CreateUser(); var newAuthenticatorKey = new Guid().ToString(); await _mongoDbUserAuthenticatorKeyStore.SetAuthenticatorKeyAsync(user, newAuthenticatorKey, new CancellationToken()); Assert.Equal(newAuthenticatorKey, user.Logins .First(login => login.LoginProvider == "[BlueshiftMongoDbUserStore]") .UserTokens .First(userToken => userToken.Name == "AuthenticatorKey").Value, StringComparer.Ordinal); } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbUserClaimStoreTests.cs ================================================ using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Xunit; namespace Blueshift.Identity.MongoDB.Tests { public class MongoDbUserClaimStoreTests : MongoDbIdentityStoreTestBase { private const string Claim1Type = nameof(Claim1Type); private const string Claim1Value = nameof(Claim1Value); private const string Claim2Type = nameof(Claim2Type); private const string Claim2Value = nameof(Claim2Value); private const string Claim3Type = nameof(Claim3Type); private const string Claim3Value = nameof(Claim3Value); private static readonly Claim Claim1 = new Claim(Claim1Type, Claim1Value); private static readonly Claim Claim2 = new Claim(Claim2Type, Claim2Value); private static readonly Claim Claim3 = new Claim(Claim3Type, Claim3Value); private readonly IUserClaimStore _mongoDbUserClaimStore; public MongoDbUserClaimStoreTests(MongoDbIdentityFixture mongoDbIdentityFixture) : base(mongoDbIdentityFixture) { _mongoDbUserClaimStore = mongoDbIdentityFixture.GetService>(); } protected override MongoDbIdentityUser CreateUser() { var user = base.CreateUser(); user.Claims.Add(new MongoDbIdentityClaim(Claim1)); user.Claims.Add(new MongoDbIdentityClaim(Claim2)); return user; } [Fact] public async Task Can_add_claim_async() { var user = CreateUser(); await _mongoDbUserClaimStore.AddClaimsAsync(user, new[] { Claim3 }, new CancellationToken()); var newClaims = new [] { Claim1, Claim2, Claim3 }; Assert.Equal(newClaims, user.Claims.Select(claim => claim.ToClaim()), new ClaimComparer()); } [Fact] public async Task Can_get_claims_async() { var user = CreateUser(); var claims = new [] { Claim1, Claim2 }; Assert.Equal(claims, await _mongoDbUserClaimStore.GetClaimsAsync(user, new CancellationToken()), new ClaimComparer()); } [Fact] public async Task Can_get_users_for_claim_async() { var user = await CreateUserInDatabase(); Assert.Equal(user, (await _mongoDbUserClaimStore.GetUsersForClaimAsync(Claim1, new CancellationToken())).Single(), new MongoDbIdentityUserComparer()); Assert.Equal(user, (await _mongoDbUserClaimStore.GetUsersForClaimAsync(Claim2, new CancellationToken())).Single(), new MongoDbIdentityUserComparer()); Assert.Empty(await _mongoDbUserClaimStore.GetUsersForClaimAsync(Claim3, new CancellationToken())); } [Fact] public async Task Can_remove_claims_async() { var user = CreateUser(); var claimsToRemove = new [] { Claim1, Claim2 }; await _mongoDbUserClaimStore.RemoveClaimsAsync(user, claimsToRemove, new CancellationToken()); Assert.Empty(user.Claims); } [Fact] public async Task Can_replace_claims_async() { var user = CreateUser(); var newClaims = new [] { Claim3, Claim2 }; await _mongoDbUserClaimStore.ReplaceClaimAsync(user, Claim1, Claim3, new CancellationToken()); Assert.Equal(newClaims, user.Claims.Select(claim => claim.ToClaim()), new ClaimComparer()); } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbUserEmailStoreTests.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Xunit; namespace Blueshift.Identity.MongoDB.Tests { public class MongoDbUserEmailStoreTests : MongoDbIdentityStoreTestBase { private const string EmailAddress = "test.user@domain.com"; private static readonly string EmailAddressNormalized = EmailAddress.ToUpper(); private readonly IUserEmailStore _mongoDbUserEmailStore; public MongoDbUserEmailStoreTests(MongoDbIdentityFixture mongoDbIdentityFixture) : base(mongoDbIdentityFixture) { _mongoDbUserEmailStore = mongoDbIdentityFixture.GetService>(); } protected override MongoDbIdentityUser CreateUser() { var user = base.CreateUser(); user.Email = EmailAddress; user.NormalizedEmail = EmailAddressNormalized; user.EmailConfirmed = false; return user; } [Fact] public async Task Can_get_email_async() { var user = CreateUser(); Assert.Equal(EmailAddress, await _mongoDbUserEmailStore.GetEmailAsync(user, new CancellationToken()), StringComparer.Ordinal); } [Fact] public async Task Can_set_email_async() { var user = CreateUser(); var newEmail = "new.email@address.com"; await _mongoDbUserEmailStore.SetEmailAsync(user, newEmail, new CancellationToken()); Assert.Equal(newEmail, user.Email, StringComparer.Ordinal); } [Fact] public async Task Can_get_normalized_email_async() { var user = CreateUser(); Assert.Equal(EmailAddressNormalized, await _mongoDbUserEmailStore.GetNormalizedEmailAsync(user, new CancellationToken()), StringComparer.Ordinal); } [Fact] public async Task Can_set_normalized_email_async() { var user = CreateUser(); var newEmailNormalized = "new.email@address.com".ToUpper(); await _mongoDbUserEmailStore.SetNormalizedEmailAsync(user, newEmailNormalized, new CancellationToken()); Assert.Equal(newEmailNormalized, user.NormalizedEmail, StringComparer.Ordinal); } [Fact] public async Task Can_check_email_confirmed_async() { var user = CreateUser(); user.EmailConfirmed = false; Assert.False(await _mongoDbUserEmailStore.GetEmailConfirmedAsync(user, new CancellationToken())); user.EmailConfirmed = true; Assert.True(await _mongoDbUserEmailStore.GetEmailConfirmedAsync(user, new CancellationToken())); } [Fact] public async Task Can_set_email_confirmed_async() { var user = CreateUser(); await _mongoDbUserEmailStore.SetEmailConfirmedAsync(user, false, new CancellationToken()); Assert.False(user.EmailConfirmed); await _mongoDbUserEmailStore.SetEmailConfirmedAsync(user, true, new CancellationToken()); Assert.True(user.EmailConfirmed); } [Fact] public async Task Can_find_by_email_async() { var user = await CreateUserInDatabase(); Assert.Equal(user, await _mongoDbUserEmailStore.FindByEmailAsync(EmailAddressNormalized, new CancellationToken()), new MongoDbIdentityUserComparer()); Assert.Null(await _mongoDbUserEmailStore.FindByEmailAsync("DNE@EMAIL.COM", new CancellationToken())); } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbUserLockoutStoreTests.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Xunit; namespace Blueshift.Identity.MongoDB.Tests { public class MongoDbUserLockoutStoreTests : MongoDbIdentityStoreTestBase { private static readonly DateTime LockoutEndDate = DateTime.Now.AddDays(1); private readonly IUserLockoutStore _mongoDbUserLockoutStore; public MongoDbUserLockoutStoreTests(MongoDbIdentityFixture mongoDbIdentityFixture) : base(mongoDbIdentityFixture) { _mongoDbUserLockoutStore = mongoDbIdentityFixture.GetService>(); } protected override MongoDbIdentityUser CreateUser() { var user = base.CreateUser(); user.LockoutEnd = LockoutEndDate; return user; } [Fact] public async Task Can_get_access_failed_count_async() { var user = CreateUser(); Assert.Equal(user.AccessFailedCount, await _mongoDbUserLockoutStore.GetAccessFailedCountAsync(user, new CancellationToken())); } [Fact] public async Task Can_check_lockout_enabled_async() { var user = CreateUser(); user.LockoutEnabled = false; Assert.False(await _mongoDbUserLockoutStore.GetLockoutEnabledAsync(user, new CancellationToken())); user.LockoutEnabled = true; Assert.True(await _mongoDbUserLockoutStore.GetLockoutEnabledAsync(user, new CancellationToken())); } [Fact] public async Task Can_set_lockout_enabled_async() { var user = CreateUser(); await _mongoDbUserLockoutStore.SetLockoutEnabledAsync(user, false, new CancellationToken()); Assert.False(user.LockoutEnabled); await _mongoDbUserLockoutStore.SetLockoutEnabledAsync(user, true, new CancellationToken()); Assert.True(user.LockoutEnabled); } [Fact] public async Task Can_get_lockout_end_date_async() { var user = CreateUser(); Assert.Equal(user.LockoutEnd, await _mongoDbUserLockoutStore.GetLockoutEndDateAsync(user, new CancellationToken())); } [Fact] public async Task Can_set_lockout_end_date_async() { var user = CreateUser(); var lockoutEndDate = DateTime.Now.AddDays(3); await _mongoDbUserLockoutStore.SetLockoutEndDateAsync(user, lockoutEndDate, new CancellationToken()); Assert.Equal(lockoutEndDate, user.LockoutEnd); } [Fact] public async Task Can_reset_access_failed_count_async() { var user = CreateUser(); user.AccessFailedCount = 10; await _mongoDbUserLockoutStore.ResetAccessFailedCountAsync(user, new CancellationToken()); Assert.Equal(0, user.AccessFailedCount); } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbUserLoginStoreTests.cs ================================================ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Xunit; namespace Blueshift.Identity.MongoDB.Tests { public class MongoDbUserLoginStoreTests : MongoDbIdentityStoreTestBase { private readonly IUserLoginStore _mongoDbUserLoginStore; private const string ProviderName = nameof(ProviderName); private static readonly string ProviderKey = new Guid().ToString(); private const string ProviderDisplayName = nameof(ProviderDisplayName); public MongoDbUserLoginStoreTests(MongoDbIdentityFixture mongoDbIdentityFixture) : base(mongoDbIdentityFixture) { _mongoDbUserLoginStore = mongoDbIdentityFixture.GetService>(); } protected override MongoDbIdentityUser CreateUser() { var user = base.CreateUser(); user.Logins.Add(new MongoDbIdentityUserLogin { LoginProvider = ProviderName, ProviderKey = ProviderKey, ProviderDisplayName = ProviderDisplayName }); return user; } [Fact] public async Task Can_add_login_async() { var user = CreateUser(); var userLoginInfo = new UserLoginInfo("Google", "1234567890abcdef", "Google+"); await _mongoDbUserLoginStore.AddLoginAsync(user, userLoginInfo, new CancellationToken()); Assert.NotNull(user.Logins .SingleOrDefault(identityUserLogin => userLoginInfo.LoginProvider == identityUserLogin.LoginProvider && userLoginInfo.ProviderKey == identityUserLogin.ProviderKey && userLoginInfo.ProviderDisplayName == identityUserLogin.ProviderDisplayName)); } [Fact] public async Task Can_remove_login_async() { var user = CreateUser(); await _mongoDbUserLoginStore.RemoveLoginAsync(user, ProviderName, ProviderKey, new CancellationToken()); Assert.Empty(user.Logins); } [Fact] public async Task Can_find_by_login_async() { var user = await CreateUserInDatabase(); Assert.Equal(user, await _mongoDbUserLoginStore.FindByLoginAsync(ProviderName, ProviderKey, new CancellationToken()), new MongoDbIdentityUserComparer()); } [Fact] public async Task Can_get_logins_async() { var user = CreateUser(); var login = (await _mongoDbUserLoginStore.GetLoginsAsync(user, new CancellationToken())).Single(); Assert.Equal(ProviderName, login.LoginProvider, StringComparer.Ordinal); Assert.Equal(ProviderKey, login.ProviderKey, StringComparer.Ordinal); Assert.Equal(ProviderDisplayName, login.ProviderDisplayName, StringComparer.Ordinal); } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbUserPasswordStoreTests.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Xunit; namespace Blueshift.Identity.MongoDB.Tests { public class MongoDbUserPasswordStoreTests : MongoDbIdentityStoreTestBase { private static readonly string PasswordHash = new Guid().ToString(); private readonly IUserPasswordStore _mongoDbUserPasswordStore; public MongoDbUserPasswordStoreTests(MongoDbIdentityFixture mongoDbIdentityFixture) : base(mongoDbIdentityFixture) { _mongoDbUserPasswordStore = mongoDbIdentityFixture.GetService>(); } protected override MongoDbIdentityUser CreateUser() { var user = base.CreateUser(); user.PasswordHash = PasswordHash; return user; } [Fact] public async Task Can_get_password_hash_async() { var user = CreateUser(); Assert.Equal(PasswordHash, await _mongoDbUserPasswordStore.GetPasswordHashAsync(user, new CancellationToken()), StringComparer.Ordinal); } [Fact] public async Task Can_set_password_hash_async() { var user = CreateUser(); var newPassword = new Guid().ToString(); await _mongoDbUserPasswordStore.SetPasswordHashAsync(user, newPassword, new CancellationToken()); Assert.Equal(newPassword, user.PasswordHash, StringComparer.Ordinal); } [Fact] public async Task Can_check_has_password_async() { var user = CreateUser(); Assert.True(await _mongoDbUserPasswordStore.HasPasswordAsync(user, new CancellationToken())); user.PasswordHash = null; Assert.False(await _mongoDbUserPasswordStore.HasPasswordAsync(user, new CancellationToken())); } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbUserPhoneNumberStoreTests.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Xunit; namespace Blueshift.Identity.MongoDB.Tests { public class MongoDbUserPhoneNumberStoreTests : MongoDbIdentityStoreTestBase { private const string PhoneNumber = "+1.123.456.7890"; private readonly IUserPhoneNumberStore _mongoDbUserPhoneNumberStore; public MongoDbUserPhoneNumberStoreTests(MongoDbIdentityFixture mongoDbIdentityFixture) : base(mongoDbIdentityFixture) { _mongoDbUserPhoneNumberStore = mongoDbIdentityFixture.GetService>(); } protected override MongoDbIdentityUser CreateUser() { var user = base.CreateUser(); user.PhoneNumber = PhoneNumber; user.PhoneNumberConfirmed = true; return user; } [Fact] public async Task Can_get_phone_number_async() { var user = CreateUser(); Assert.Equal(PhoneNumber, await _mongoDbUserPhoneNumberStore.GetPhoneNumberAsync(user, new CancellationToken())); } [Fact] public async Task Can_set_phone_number_async() { var user = CreateUser(); string newPhoneNumber = "+1.987.654.3210"; await _mongoDbUserPhoneNumberStore.SetPhoneNumberAsync(user, newPhoneNumber, new CancellationToken()); Assert.Equal(newPhoneNumber, user.PhoneNumber, StringComparer.Ordinal); } [Fact] public async Task Can_check_phone_number_confirmed_async() { var user = CreateUser(); user.PhoneNumberConfirmed = false; Assert.False(await _mongoDbUserPhoneNumberStore.GetPhoneNumberConfirmedAsync(user, new CancellationToken())); user.PhoneNumberConfirmed = true; Assert.True(await _mongoDbUserPhoneNumberStore.GetPhoneNumberConfirmedAsync(user, new CancellationToken())); } [Fact] public async Task Can_set_phone_number_confirmed_async() { var user = CreateUser(); await _mongoDbUserPhoneNumberStore.SetPhoneNumberConfirmedAsync(user, false, new CancellationToken()); Assert.False(user.PhoneNumberConfirmed); await _mongoDbUserPhoneNumberStore.SetPhoneNumberConfirmedAsync(user, true, new CancellationToken()); Assert.True(user.PhoneNumberConfirmed); } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbUserRoleStoreTests.cs ================================================ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Xunit; namespace Blueshift.Identity.MongoDB.Tests { public class MongoDbUserRoleStoreTests : MongoDbIdentityStoreTestBase { private static readonly string RoleName1 = nameof(RoleName1); private static readonly string NormalizedRoleName1 = RoleName1.ToUpper(); private static readonly string RoleName2 = nameof(RoleName2); private static readonly string NormalizedRoleName2 = RoleName2.ToUpper(); private readonly IUserRoleStore _mongoDbUserRoleStore; public MongoDbUserRoleStoreTests(MongoDbIdentityFixture mongoDbIdentityFixture) : base(mongoDbIdentityFixture) { _mongoDbUserRoleStore = mongoDbIdentityFixture.GetService>(); var roleStore = mongoDbIdentityFixture .GetService>(); if (roleStore.FindByNameAsync(NormalizedRoleName1, new CancellationToken()).Result == null) { roleStore.CreateAsync(new MongoDbIdentityRole { RoleName = RoleName1, NormalizedRoleName = NormalizedRoleName1 }, new CancellationToken()) .Wait(); } if (roleStore.FindByNameAsync(NormalizedRoleName2, new CancellationToken()).Result == null) { roleStore.CreateAsync(new MongoDbIdentityRole { RoleName = RoleName2, NormalizedRoleName = NormalizedRoleName2 }, new CancellationToken()) .Wait(); } } protected override MongoDbIdentityUser CreateUser() { var user = base.CreateUser(); user.Roles.Add(new MongoDbIdentityUserRole() { RoleName = RoleName2, NormalizedRoleName = NormalizedRoleName2 }); return user; } [Fact] public async Task Can_add_to_role_async() { var user = CreateUser(); await _mongoDbUserRoleStore.AddToRoleAsync(user, NormalizedRoleName1, new CancellationToken()); var userRoles = user.Roles.Select(role => role.RoleName).ToList(); Assert.Contains(RoleName1, userRoles, StringComparer.Ordinal); Assert.Contains(RoleName2, userRoles, StringComparer.Ordinal); } [Fact] public async Task Can_get_roles_async() { var user = CreateUser(); var roles = await _mongoDbUserRoleStore.GetRolesAsync(user, new CancellationToken()); Assert.DoesNotContain(RoleName1, roles, StringComparer.Ordinal); Assert.Contains(RoleName2, roles, StringComparer.Ordinal); } [Fact] public async Task Can_get_users_in_role_async() { var user = await CreateUserInDatabase(); Assert.Equal(user, (await _mongoDbUserRoleStore.GetUsersInRoleAsync(NormalizedRoleName2, new CancellationToken())).Single(), new MongoDbIdentityUserComparer()); } [Fact] public async Task Can_remove_from_role_async() { var user = CreateUser(); await _mongoDbUserRoleStore.RemoveFromRoleAsync(user, NormalizedRoleName2, new CancellationToken()); Assert.Empty(user.Roles); } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbUserSecurityStampStoreTests.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Xunit; namespace Blueshift.Identity.MongoDB.Tests { public class MongoDbUserSecurityStampStoreTests : MongoDbIdentityStoreTestBase { private static readonly string SecurityStamp = new Guid().ToString(); private readonly IUserSecurityStampStore _mongoDbUserSecurityStampStore; public MongoDbUserSecurityStampStoreTests(MongoDbIdentityFixture mongoDbIdentityFixture) : base(mongoDbIdentityFixture) { _mongoDbUserSecurityStampStore = mongoDbIdentityFixture.GetService>(); } protected override MongoDbIdentityUser CreateUser() { var user = base.CreateUser(); user.SecurityStamp = SecurityStamp; return user; } [Fact] public async Task Can_get_security_stamp_async() { var user = CreateUser(); Assert.Equal(SecurityStamp, await _mongoDbUserSecurityStampStore.GetSecurityStampAsync(user, new CancellationToken()), StringComparer.Ordinal); } [Fact] public async Task Can_set_security_stamp_async() { var user = CreateUser(); string newSecurityStamp = new Guid().ToString(); await _mongoDbUserSecurityStampStore.SetSecurityStampAsync(user, newSecurityStamp, new CancellationToken()); Assert.Equal(newSecurityStamp, user.SecurityStamp, StringComparer.Ordinal); } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbUserStoreTests.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Xunit; namespace Blueshift.Identity.MongoDB.Tests { public class MongoDbUserStoreTests : MongoDbIdentityStoreTestBase { private readonly IUserStore _mongoDbUserStore; public MongoDbUserStoreTests(MongoDbIdentityFixture mongoDbIdentityFixture) : base(mongoDbIdentityFixture) { _mongoDbUserStore = mongoDbIdentityFixture.GetService>(); } [Fact] public async Task Can_create_user_async() { Assert.NotNull(await CreateUserInDatabase()); } [Fact] public async Task Can_delete_user_async() { var user = await CreateUserInDatabase(); Assert.Equal(IdentityResult.Success, await _mongoDbUserStore.DeleteAsync(user, new CancellationToken())); } [Fact] public async Task Can_find_by_id_async() { var user = await CreateUserInDatabase(); Assert.Equal(user, await _mongoDbUserStore.FindByIdAsync(user.Id.ToString(), new CancellationToken()), new MongoDbIdentityUserComparer()); } [Fact] public async Task Can_find_by_name_async() { var user = await CreateUserInDatabase(); Assert.Equal(user, await _mongoDbUserStore.FindByNameAsync(user.NormalizedUserName, new CancellationToken()), new MongoDbIdentityUserComparer()); } [Fact] public async Task Can_get_normalized_user_name_async() { var user = CreateUser(); Assert.Equal(user.NormalizedUserName, await _mongoDbUserStore.GetNormalizedUserNameAsync(user, new CancellationToken()), StringComparer.Ordinal); } [Fact] public async Task Can_get_user_id_async() { var user = await CreateUserInDatabase(); Assert.Equal(user.Id.ToString(), await _mongoDbUserStore.GetUserIdAsync(user, new CancellationToken()), StringComparer.Ordinal); } [Fact] public async Task Can_get_user_name_async() { var user = CreateUser(); Assert.Equal(user.UserName, await _mongoDbUserStore.GetUserNameAsync(user, new CancellationToken()), StringComparer.Ordinal); } [Fact] public async Task Can_set_normalized_user_name_async() { var user = await CreateUserInDatabase(); var newNormalizedUserName = "NORMALIZED.TEST.USER"; await _mongoDbUserStore.SetNormalizedUserNameAsync(user, newNormalizedUserName, new CancellationToken()); Assert.Equal(newNormalizedUserName, user.NormalizedUserName, StringComparer.Ordinal); } [Fact] public async Task Can_set_user_name_async() { var user = await CreateUserInDatabase(); var newUserName = "another.user@different.com"; await _mongoDbUserStore.SetUserNameAsync(user, newUserName, new CancellationToken()); Assert.Equal(newUserName, user.UserName, StringComparer.Ordinal); } [Fact] public async Task Can_update_user_async() { var user = await CreateUserInDatabase(); var newUserName = "another.user@different.com"; var newNormalizedUserName = "NORMALIZED.TEST.USER"; user.UserName = newUserName; user.NormalizedUserName = newNormalizedUserName; Assert.Equal(IdentityResult.Success, await _mongoDbUserStore.UpdateAsync(user, new CancellationToken())); Assert.Equal(user, await _mongoDbUserStore.FindByNameAsync(newNormalizedUserName, new CancellationToken()), new MongoDbIdentityUserComparer()); } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbUserTwoFactorRecoveryCodeStoreTests.cs ================================================ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Xunit; namespace Blueshift.Identity.MongoDB.Tests { public class MongoDbUserTwoFactorRecoveryCodeStoreTests : MongoDbIdentityStoreTestBase { private const string RecoveryCode1 = nameof(RecoveryCode1); private const string RecoveryCode2 = nameof(RecoveryCode2); private const string RecoveryCode3 = nameof(RecoveryCode3); private static readonly string[] RecoveryCodes = new string[] { RecoveryCode1, RecoveryCode2, RecoveryCode3 }; private readonly IUserTwoFactorRecoveryCodeStore _mongoDbUserTwoFactorRecoveryCodeStore; public MongoDbUserTwoFactorRecoveryCodeStoreTests(MongoDbIdentityFixture mongoDbIdentityFixture) : base(mongoDbIdentityFixture) { _mongoDbUserTwoFactorRecoveryCodeStore = mongoDbIdentityFixture.GetService>(); } protected override MongoDbIdentityUser CreateUser() { var user = base.CreateUser(); user.Logins.Add(new MongoDbIdentityUserLogin { LoginProvider = "[BlueshiftMongoDbUserStore]", ProviderKey = new Guid().ToString(), ProviderDisplayName = "[BlueshiftMongoDbUserStore]", UserTokens = { new MongoDbIdentityUserToken() { Name = "RecoveryCodes", Value = string.Join(";", RecoveryCodes) } } }); return user; } [Theory] [InlineData(RecoveryCode1)] [InlineData(RecoveryCode2)] [InlineData(RecoveryCode3)] public async Task Can_redeem_codes_exactly_once_async(string recoveryCode) { var user = CreateUser(); Assert.True(await _mongoDbUserTwoFactorRecoveryCodeStore.RedeemCodeAsync(user, recoveryCode, new CancellationToken())); Assert.False(await _mongoDbUserTwoFactorRecoveryCodeStore.RedeemCodeAsync(user, recoveryCode, new CancellationToken())); } [Fact] public async Task Can_replace_codes_async() { var user = CreateUser(); var newRecoveryCodes = new [] { "New Code 1", "New Code 2", "New Code 3" }; await _mongoDbUserTwoFactorRecoveryCodeStore.ReplaceCodesAsync(user, newRecoveryCodes, new CancellationToken()); var recoveryCodes = user.Logins .First(login => login.LoginProvider == "[BlueshiftMongoDbUserStore]") .UserTokens .First(userToken => userToken.Name == "RecoveryCodes").Value; Assert.DoesNotContain(RecoveryCode1, recoveryCodes, StringComparison.Ordinal); Assert.DoesNotContain(RecoveryCode2, recoveryCodes, StringComparison.Ordinal); Assert.DoesNotContain(RecoveryCode3, recoveryCodes, StringComparison.Ordinal); Assert.Equal(string.Join(";", newRecoveryCodes), recoveryCodes, StringComparer.Ordinal); } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/MongoDbUserTwoFactorStoreTests.cs ================================================ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Xunit; namespace Blueshift.Identity.MongoDB.Tests { public class MongoDbUserTwoFactorStoreTests : MongoDbIdentityStoreTestBase { private readonly IUserTwoFactorStore _mongoDbUserTwoFactorStore; public MongoDbUserTwoFactorStoreTests(MongoDbIdentityFixture mongoDbIdentityFixture) : base(mongoDbIdentityFixture) { _mongoDbUserTwoFactorStore = mongoDbIdentityFixture.GetService>(); } [Fact] public async Task Can_check_two_factor_enabled_async() { var user = CreateUser(); user.TwoFactorEnabled = false; Assert.False(await _mongoDbUserTwoFactorStore.GetTwoFactorEnabledAsync(user, new CancellationToken())); user.TwoFactorEnabled = true; Assert.True(await _mongoDbUserTwoFactorStore.GetTwoFactorEnabledAsync(user, new CancellationToken())); } [Fact] public async Task Can_set_two_factor_enabled_async() { var user = CreateUser(); await _mongoDbUserTwoFactorStore.SetTwoFactorEnabledAsync(user, false, new CancellationToken()); Assert.False(user.TwoFactorEnabled); await _mongoDbUserTwoFactorStore.SetTwoFactorEnabledAsync(user, true, new CancellationToken()); Assert.True(user.TwoFactorEnabled); } } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/Properties/AssemblyInfo.cs ================================================ // Copyright (c) Blueshift Software, LLC. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Reflection; using System.Resources; using Xunit; [assembly: NeutralResourcesLanguage("en-US")] [assembly: AssemblyMetadata("Serviceable", "True")] [assembly: TestFramework("Microsoft.EntityFrameworkCore.Specification.Tests.TestUtilities.Xunit.ConditionalTestFramework", "Microsoft.EntityFrameworkCore.Specification.Tests")] ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/_Comparers.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using Blueshift.Identity.MongoDB; namespace Blueshift.Identity.MongoDB.Tests { public class ClaimComparer : EqualityComparer { public override bool Equals(Claim claim1, Claim claim2) => (claim1 != null && claim2 != null) || string.Equals(claim1.Type, claim2.Type, StringComparison.Ordinal) && string.Equals(claim1.Value, claim2.Value, StringComparison.Ordinal); public override int GetHashCode(Claim obj) => obj?.GetHashCode() ?? throw new ArgumentNullException(nameof(obj)); } public class MongoDbIdentityRoleComparer : EqualityComparer { public override bool Equals(MongoDbIdentityRole role1, MongoDbIdentityRole role2) => (role1 != null && role2 != null) || role1.Id == role2.Id && string.Equals(role1.RoleName, role2.RoleName, StringComparison.Ordinal) && string.Equals(role1.NormalizedRoleName, role2.NormalizedRoleName, StringComparison.Ordinal) && role1.Claims.Count == role2.Claims.Count && role1.Claims.All(item => role2.Claims.Contains(item, new MongoDbIdentityClaimComparer())) && role1.Claims.All(item => role2.Claims.Contains(item, new MongoDbIdentityClaimComparer())); public override int GetHashCode(MongoDbIdentityRole obj) => obj?.GetHashCode() ?? throw new ArgumentNullException(nameof(obj)); } public class MongoDbIdentityUserComparer : EqualityComparer { public override bool Equals(MongoDbIdentityUser user1, MongoDbIdentityUser user2) => (user1 != null && user2 != null) || user1.Id == user2.Id && string.Equals(user1.UserName, user2.UserName, StringComparison.Ordinal) && string.Equals(user1.NormalizedUserName, user2.NormalizedUserName, StringComparison.Ordinal) && user1.Logins.Count == user2.Logins.Count && user1.Logins.All(item => user2.Logins.Contains(item, new MongoDbIdentityUserLoginComparer())) && user1.Logins.All(item => user2.Logins.Contains(item, new MongoDbIdentityUserLoginComparer())) && user1.Claims.Count == user2.Claims.Count && user1.Claims.All(item => user2.Claims.Contains(item, new MongoDbIdentityClaimComparer())) && user1.Claims.All(item => user2.Claims.Contains(item, new MongoDbIdentityClaimComparer())); public override int GetHashCode(MongoDbIdentityUser obj) => obj?.GetHashCode() ?? throw new ArgumentNullException(nameof(obj)); } public class MongoDbIdentityUserLoginComparer : EqualityComparer { public override bool Equals(MongoDbIdentityUserLogin userLogin1, MongoDbIdentityUserLogin userLogin2) => (userLogin1 != null && userLogin2 != null) || string.Equals(userLogin1.LoginProvider, userLogin2.LoginProvider, StringComparison.Ordinal) && string.Equals(userLogin1.ProviderKey, userLogin2.ProviderKey, StringComparison.Ordinal) && string.Equals(userLogin1.ProviderDisplayName, userLogin2.ProviderDisplayName, StringComparison.Ordinal) && userLogin1.UserTokens.Count == userLogin2.UserTokens.Count && userLogin1.UserTokens.All(item => userLogin2.UserTokens.Contains(item, new MongoDbIdentityUserTokenComparer())) && userLogin1.UserTokens.All(item => userLogin2.UserTokens.Contains(item, new MongoDbIdentityUserTokenComparer())); public override int GetHashCode(MongoDbIdentityUserLogin obj) => obj?.GetHashCode() ?? throw new ArgumentNullException(nameof(obj)); } public class MongoDbIdentityUserTokenComparer : EqualityComparer { public override bool Equals(MongoDbIdentityUserToken userToken1, MongoDbIdentityUserToken userToken2) => (userToken1 != null && userToken2 != null) || string.Equals(userToken1.Name, userToken2.Name, StringComparison.Ordinal) && string.Equals(userToken1.Value, userToken2.Value, StringComparison.Ordinal); public override int GetHashCode(MongoDbIdentityUserToken obj) => obj?.GetHashCode() ?? throw new ArgumentNullException(nameof(obj)); } public class MongoDbIdentityClaimComparer : EqualityComparer { public override bool Equals(MongoDbIdentityClaim claim1, MongoDbIdentityClaim claim2) => (claim1 != null && claim2 != null) || string.Equals(claim1.ClaimType, claim2.ClaimType, StringComparison.Ordinal) && string.Equals(claim1.ClaimValue, claim2.ClaimValue, StringComparison.Ordinal); public override int GetHashCode(MongoDbIdentityClaim obj) => obj?.GetHashCode() ?? throw new ArgumentNullException(nameof(obj)); } } ================================================ FILE: test/Blueshift.Identity.MongoDB.Tests/xunit.runner.json ================================================ { "longRunningTestSeconds": 30, "parallelizeAssembly": false } ================================================ FILE: test/Directory.Build.props ================================================ netcoreapp2.1 $(DeveloperTestFrameworks) netcoreapp2.0;netcoreapp2.1 net461;$(TestFrameworks) $(NoWarn);CA1822;xUnit1004 ================================================ FILE: version.props ================================================ 2.1.0 preview2 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final $(VersionSuffix)-$(BuildNumber)