diff --git a/.github/workflows/TestCosmos.yaml b/.github/workflows/TestCosmos.yaml index 735b4ca4cfc..dec6cb5e44c 100644 --- a/.github/workflows/TestCosmos.yaml +++ b/.github/workflows/TestCosmos.yaml @@ -17,7 +17,7 @@ jobs: - name: Start Cosmos Emulator run: | Import-Module "$env:ProgramFiles\Azure Cosmos DB Emulator\PSModules\Microsoft.Azure.CosmosDB.Emulator" - Start-CosmosDbEmulator -Timeout 360 + Start-CosmosDbEmulator -Timeout 540 -NoUI -NoTelemetry -NoFirewall -EnablePreview - name: Checkout uses: actions/checkout@v4 diff --git a/Directory.Packages.props b/Directory.Packages.props index 01edcee871c..3e248f71184 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -32,6 +32,7 @@ + @@ -54,12 +55,5 @@ - - - - - - - diff --git a/EFCore.sln b/EFCore.sln index b835bb26b56..0999c19305c 100644 --- a/EFCore.sln +++ b/EFCore.sln @@ -14,6 +14,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution global.json = global.json tools\Resources.tt = tools\Resources.tt eng\Versions.props = eng\Versions.props + NuGet.config = NuGet.config EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 334a076e526..b4dc09de049 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -150,7 +150,7 @@ extends: steps: - template: /eng/common/templates-official/steps/enable-internal-sources.yml - template: /eng/common/templates-official/steps/enable-internal-runtimes.yml - - script: eng/common/cibuild.sh --configuration $(_BuildConfig) --prepareMachine $(_InternalRuntimeDownloadArgs) + - script: eng/common/build.sh --restore --build --test --pack --ci --configuration $(_BuildConfig) --prepareMachine $(_InternalRuntimeDownloadArgs) env: Test__Cosmos__DefaultConnection: $(_CosmosConnectionUrl) # Work-around for https://github.com/dotnet/runtime/issues/70758 @@ -186,7 +186,7 @@ extends: condition: and(eq(variables['_CosmosConnectionUrl'], 'true'), or(endsWith(variables['_runCounter'], '1'), endsWith(variables['_runCounter'], '3'), endsWith(variables['_runCounter'], '5'), endsWith(variables['_runCounter'], '7'), endsWith(variables['_runCounter'], '9'))) - template: /eng/common/templates-official/steps/enable-internal-sources.yml - template: /eng/common/templates-official/steps/enable-internal-runtimes.yml - - script: eng/common/cibuild.sh --configuration $(_BuildConfig) --prepareMachine $(_InternalRuntimeDownloadArgs) + - script: eng/common/build.sh --restore --build --test --pack --ci --configuration $(_BuildConfig) --prepareMachine $(_InternalRuntimeDownloadArgs) displayName: Build - task: AzureCLI@2 displayName: Run Cosmos tests diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index dbb49ebc941..46f81485861 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,83 +1,83 @@ - + https://github.com/dotnet/runtime - 088d199063dd1bf7fa00a445d23f93f915f84b31 + a50b4a078439c518d1074c43ff4740f528d37fea - + https://github.com/dotnet/runtime - 088d199063dd1bf7fa00a445d23f93f915f84b31 + a50b4a078439c518d1074c43ff4740f528d37fea - + https://github.com/dotnet/runtime - 088d199063dd1bf7fa00a445d23f93f915f84b31 + a50b4a078439c518d1074c43ff4740f528d37fea - + https://github.com/dotnet/runtime - 088d199063dd1bf7fa00a445d23f93f915f84b31 + a50b4a078439c518d1074c43ff4740f528d37fea - + https://github.com/dotnet/runtime - 088d199063dd1bf7fa00a445d23f93f915f84b31 + a50b4a078439c518d1074c43ff4740f528d37fea - + https://github.com/dotnet/runtime - 088d199063dd1bf7fa00a445d23f93f915f84b31 + a50b4a078439c518d1074c43ff4740f528d37fea - + https://github.com/dotnet/runtime - 088d199063dd1bf7fa00a445d23f93f915f84b31 + a50b4a078439c518d1074c43ff4740f528d37fea - + https://github.com/dotnet/runtime - 088d199063dd1bf7fa00a445d23f93f915f84b31 + a50b4a078439c518d1074c43ff4740f528d37fea - + https://github.com/dotnet/runtime - 088d199063dd1bf7fa00a445d23f93f915f84b31 + a50b4a078439c518d1074c43ff4740f528d37fea - + https://github.com/dotnet/runtime - 088d199063dd1bf7fa00a445d23f93f915f84b31 + a50b4a078439c518d1074c43ff4740f528d37fea - + https://github.com/dotnet/runtime - 088d199063dd1bf7fa00a445d23f93f915f84b31 + a50b4a078439c518d1074c43ff4740f528d37fea - + https://github.com/dotnet/runtime - 088d199063dd1bf7fa00a445d23f93f915f84b31 + a50b4a078439c518d1074c43ff4740f528d37fea - + https://github.com/dotnet/runtime - 088d199063dd1bf7fa00a445d23f93f915f84b31 + a50b4a078439c518d1074c43ff4740f528d37fea - + https://github.com/dotnet/runtime - 088d199063dd1bf7fa00a445d23f93f915f84b31 + a50b4a078439c518d1074c43ff4740f528d37fea - + https://github.com/dotnet/runtime - 088d199063dd1bf7fa00a445d23f93f915f84b31 + a50b4a078439c518d1074c43ff4740f528d37fea - + https://github.com/dotnet/arcade - 6d4b01bce3a29c172faff5abc0bfe2ae3d1fef3d + 235443a5c1136571cacdfd40576f263f26bf5b9b - + https://github.com/dotnet/arcade - 6d4b01bce3a29c172faff5abc0bfe2ae3d1fef3d + 235443a5c1136571cacdfd40576f263f26bf5b9b - + https://github.com/dotnet/arcade - 6d4b01bce3a29c172faff5abc0bfe2ae3d1fef3d + 235443a5c1136571cacdfd40576f263f26bf5b9b diff --git a/eng/Versions.props b/eng/Versions.props index 78f8e60baa5..e6ff4d0969d 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -2,7 +2,7 @@ 10.0.0 preview - 3 + 4 False true - 4.8.0 - 1.1.2 + 17.13.9 + 17.13.9 + + 4.13.0 + 1.1.3-beta1.24423.1 + 1.1.3-beta1.24352.1 1.13.1 1.3.2 1.8.1 diff --git a/eng/common/CIBuild.cmd b/eng/common/CIBuild.cmd index 56c2f25ac22..ac1f72bf94e 100644 --- a/eng/common/CIBuild.cmd +++ b/eng/common/CIBuild.cmd @@ -1,2 +1,2 @@ @echo off -powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0Build.ps1""" -restore -build -test -sign -pack -publish -ci %*" \ No newline at end of file +powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0Build.ps1""" -restore -build -test -sign -pack -publish -ci %*" diff --git a/eng/common/build.ps1 b/eng/common/build.ps1 index 438f9920c43..d419c3a2eff 100644 --- a/eng/common/build.ps1 +++ b/eng/common/build.ps1 @@ -7,6 +7,7 @@ Param( [string] $msbuildEngine = $null, [bool] $warnAsError = $true, [bool] $nodeReuse = $true, + [switch] $buildCheck = $false, [switch][Alias('r')]$restore, [switch] $deployDeps, [switch][Alias('b')]$build, @@ -71,6 +72,8 @@ function Print-Usage() { Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." Write-Host " -excludePrereleaseVS Set to exclude build engines in prerelease versions of Visual Studio" Write-Host " -nativeToolsOnMachine Sets the native tools on machine environment variable (indicating that the script should use native tools on machine)" + Write-Host " -nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')" + Write-Host " -buildCheck Sets /check msbuild parameter" Write-Host "" Write-Host "Command line arguments not listed above are passed thru to msbuild." @@ -97,6 +100,7 @@ function Build { $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'Build.binlog') } else { '' } $platformArg = if ($platform) { "/p:Platform=$platform" } else { '' } + $check = if ($buildCheck) { '/check' } else { '' } if ($projects) { # Re-assign properties to a new variable because PowerShell doesn't let us append properties directly for unclear reasons. @@ -113,6 +117,7 @@ function Build { MSBuild $toolsetBuildProj ` $bl ` $platformArg ` + $check ` /p:Configuration=$configuration ` /p:RepoRoot=$RepoRoot ` /p:Restore=$restore ` @@ -166,4 +171,4 @@ catch { ExitWithExitCode 1 } -ExitWithExitCode 0 +ExitWithExitCode 0 \ No newline at end of file diff --git a/eng/common/build.sh b/eng/common/build.sh index 483647daf18..e24bb68f484 100755 --- a/eng/common/build.sh +++ b/eng/common/build.sh @@ -42,6 +42,7 @@ usage() echo " --prepareMachine Prepare machine for CI run, clean up processes after build" echo " --nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')" echo " --warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" + echo " --buildCheck Sets /check msbuild parameter" echo "" echo "Command line arguments not listed above are passed thru to msbuild." echo "Arguments can also be passed in with a single hyphen." @@ -76,6 +77,7 @@ clean=false warn_as_error=true node_reuse=true +build_check=false binary_log=false exclude_ci_binary_log=false pipelines_log=false @@ -173,6 +175,9 @@ while [[ $# > 0 ]]; do node_reuse=$2 shift ;; + -buildcheck) + build_check=true + ;; -runtimesourcefeed) runtime_source_feed=$2 shift @@ -224,8 +229,14 @@ function Build { bl="/bl:\"$log_dir/Build.binlog\"" fi + local check="" + if [[ "$build_check" == true ]]; then + check="/check" + fi + MSBuild $_InitializeToolset \ $bl \ + $check \ /p:Configuration=$configuration \ /p:RepoRoot="$repo_root" \ /p:Restore=$restore \ @@ -256,4 +267,4 @@ if [[ "$restore" == true ]]; then InitializeNativeTools fi -Build +Build \ No newline at end of file diff --git a/eng/common/cibuild.sh b/eng/common/cibuild.sh index 1a02c0dec8f..66e3b0ac61c 100755 --- a/eng/common/cibuild.sh +++ b/eng/common/cibuild.sh @@ -13,4 +13,4 @@ while [[ -h $source ]]; do done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" -. "$scriptroot/build.sh" --restore --build --test --pack --publish --ci $@ \ No newline at end of file +. "$scriptroot/build.sh" --restore --build --test --pack --publish --ci $@ diff --git a/eng/common/core-templates/job/job.yml b/eng/common/core-templates/job/job.yml index 295c9a2317c..4dfb3d86836 100644 --- a/eng/common/core-templates/job/job.yml +++ b/eng/common/core-templates/job/job.yml @@ -23,7 +23,6 @@ parameters: enablePublishBuildArtifacts: false enablePublishBuildAssets: false enablePublishTestResults: false - enablePublishUsingPipelines: false enableBuildRetry: false mergeTestResults: false testRunTitle: '' diff --git a/eng/common/core-templates/job/publish-build-assets.yml b/eng/common/core-templates/job/publish-build-assets.yml index c7d59dcbfe1..5da3c2a2a20 100644 --- a/eng/common/core-templates/job/publish-build-assets.yml +++ b/eng/common/core-templates/job/publish-build-assets.yml @@ -20,9 +20,6 @@ parameters: # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. runAsPublic: false - # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing - publishUsingPipelines: false - # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing publishAssetsImmediately: false @@ -96,7 +93,6 @@ jobs: arguments: -task PublishBuildAssets -restore -msbuildEngine dotnet /p:ManifestsPath='$(Build.StagingDirectory)/AssetManifests' /p:MaestroApiEndpoint=https://maestro.dot.net - /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} /p:OfficialBuildId=$(Build.BuildNumber) condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/common/core-templates/jobs/codeql-build.yml b/eng/common/core-templates/jobs/codeql-build.yml index f2144252cc6..693b00b3704 100644 --- a/eng/common/core-templates/jobs/codeql-build.yml +++ b/eng/common/core-templates/jobs/codeql-build.yml @@ -15,7 +15,6 @@ jobs: enablePublishBuildArtifacts: false enablePublishTestResults: false enablePublishBuildAssets: false - enablePublishUsingPipelines: false enableTelemetry: true variables: diff --git a/eng/common/core-templates/jobs/jobs.yml b/eng/common/core-templates/jobs/jobs.yml index ea69be4341c..3d2deaa4a06 100644 --- a/eng/common/core-templates/jobs/jobs.yml +++ b/eng/common/core-templates/jobs/jobs.yml @@ -5,9 +5,6 @@ parameters: # Optional: Include PublishBuildArtifacts task enablePublishBuildArtifacts: false - # Optional: Enable publishing using release pipelines - enablePublishUsingPipelines: false - # Optional: Enable running the source-build jobs to build repo from source enableSourceBuild: false @@ -112,7 +109,6 @@ jobs: - Source_Build_Complete runAsPublic: ${{ parameters.runAsPublic }} - publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} publishAssetsImmediately: ${{ parameters.publishAssetsImmediately }} enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} diff --git a/eng/common/core-templates/steps/install-microbuild.yml b/eng/common/core-templates/steps/install-microbuild.yml index 2a6a529482b..dba506e74c3 100644 --- a/eng/common/core-templates/steps/install-microbuild.yml +++ b/eng/common/core-templates/steps/install-microbuild.yml @@ -5,7 +5,7 @@ parameters: # Will be ignored if 'enableMicrobuild' is false or 'Agent.Os' is 'Windows_NT' enableMicrobuildForMacAndLinux: false # Location of the MicroBuild output folder - microBuildOutputFolder: '$(Agent.TempDirectory)' + microBuildOutputFolder: '$(Build.SourcesDirectory)' continueOnError: false steps: @@ -41,7 +41,7 @@ steps: inputs: packageType: sdk version: 8.0.x - installationPath: ${{ parameters.microBuildOutputFolder }}/dotnet + installationPath: ${{ parameters.microBuildOutputFolder }}/.dotnet workingDirectory: ${{ parameters.microBuildOutputFolder }} condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) @@ -53,6 +53,7 @@ steps: feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json ${{ if and(eq(parameters.enableMicrobuildForMacAndLinux, 'true'), ne(variables['Agent.Os'], 'Windows_NT')) }}: azureSubscription: 'MicroBuild Signing Task (DevDiv)' + useEsrpCli: true env: TeamName: $(_TeamName) MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }} @@ -71,3 +72,28 @@ steps: eq(variables['_SignType'], 'real') ) )) + + # Workaround for ESRP CLI on Linux - https://github.com/dotnet/source-build/issues/4964 + - ${{ if eq(parameters.enableMicrobuildForMacAndLinux, 'true') }}: + - task: UseDotNet@2 + displayName: Install .NET 9.0 SDK for ESRP CLI Workaround + inputs: + packageType: sdk + version: 9.0.x + installationPath: ${{ parameters.microBuildOutputFolder }}/.dotnet + workingDirectory: ${{ parameters.microBuildOutputFolder }} + condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) + + - task: PowerShell@2 + displayName: Workaround for ESRP CLI on Linux + inputs: + targetType: 'inline' + script: | + Write-Host "Copying Linux Path" + $MBSIGN_APPFOLDER = '$(MBSIGN_APPFOLDER)' + $MBSIGN_APPFOLDER = $MBSIGN_APPFOLDER -replace '/build', '' + $MBSIGN_APPFOLDER = $MBSIGN_APPFOLDER + '/1.1.1032' + '/build' + $MBSIGN_APPFOLDER | Write-Host + $SignConfigPath = $MBSIGN_APPFOLDER + '/signconfig.xml' + Copy-Item -Path "$(MBSIGN_APPFOLDER)/signconfig.xml" -Destination $SignConfigPath -Force + condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) diff --git a/eng/common/cross/armel/armel.jessie.patch b/eng/common/cross/armel/armel.jessie.patch deleted file mode 100644 index 2d261561935..00000000000 --- a/eng/common/cross/armel/armel.jessie.patch +++ /dev/null @@ -1,43 +0,0 @@ -diff -u -r a/usr/include/urcu/uatomic/generic.h b/usr/include/urcu/uatomic/generic.h ---- a/usr/include/urcu/uatomic/generic.h 2014-10-22 15:00:58.000000000 -0700 -+++ b/usr/include/urcu/uatomic/generic.h 2020-10-30 21:38:28.550000000 -0700 -@@ -69,10 +69,10 @@ - #endif - #ifdef UATOMIC_HAS_ATOMIC_SHORT - case 2: -- return __sync_val_compare_and_swap_2(addr, old, _new); -+ return __sync_val_compare_and_swap_2((uint16_t*) addr, old, _new); - #endif - case 4: -- return __sync_val_compare_and_swap_4(addr, old, _new); -+ return __sync_val_compare_and_swap_4((uint32_t*) addr, old, _new); - #if (CAA_BITS_PER_LONG == 64) - case 8: - return __sync_val_compare_and_swap_8(addr, old, _new); -@@ -109,7 +109,7 @@ - return; - #endif - case 4: -- __sync_and_and_fetch_4(addr, val); -+ __sync_and_and_fetch_4((uint32_t*) addr, val); - return; - #if (CAA_BITS_PER_LONG == 64) - case 8: -@@ -148,7 +148,7 @@ - return; - #endif - case 4: -- __sync_or_and_fetch_4(addr, val); -+ __sync_or_and_fetch_4((uint32_t*) addr, val); - return; - #if (CAA_BITS_PER_LONG == 64) - case 8: -@@ -187,7 +187,7 @@ - return __sync_add_and_fetch_2(addr, val); - #endif - case 4: -- return __sync_add_and_fetch_4(addr, val); -+ return __sync_add_and_fetch_4((uint32_t*) addr, val); - #if (CAA_BITS_PER_LONG == 64) - case 8: - return __sync_add_and_fetch_8(addr, val); diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index 74f399716ba..d6f005b5dab 100755 --- a/eng/common/cross/build-rootfs.sh +++ b/eng/common/cross/build-rootfs.sh @@ -164,9 +164,13 @@ while :; do armel) __BuildArch=armel __UbuntuArch=armel - __UbuntuRepo="http://ftp.debian.org/debian/" - __CodeName=jessie + __UbuntuRepo="http://archive.debian.org/debian/" + __CodeName=buster __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" + __LLDB_Package="liblldb-6.0-dev" + __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" + __UbuntuPackages="${__UbuntuPackages// libomp5/}" + __UbuntuSuites= ;; armv6) __BuildArch=armv6 @@ -278,46 +282,23 @@ while :; do ;; xenial) # Ubuntu 16.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=xenial - fi - ;; - zesty) # Ubuntu 17.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=zesty - fi + __CodeName=xenial ;; bionic) # Ubuntu 18.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=bionic - fi + __CodeName=bionic ;; focal) # Ubuntu 20.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=focal - fi + __CodeName=focal ;; jammy) # Ubuntu 22.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=jammy - fi + __CodeName=jammy ;; noble) # Ubuntu 24.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=noble - fi + __CodeName=noble if [[ -n "$__LLDB_Package" ]]; then __LLDB_Package="liblldb-18-dev" fi ;; - jessie) # Debian 8 - __CodeName=jessie - __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" - - if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.debian.org/debian/" - fi - ;; stretch) # Debian 9 __CodeName=stretch __LLDB_Package="liblldb-6.0-dev" @@ -333,7 +314,7 @@ while :; do __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.debian.org/debian/" + __UbuntuRepo="http://archive.debian.org/debian/" fi ;; bullseye) # Debian 11 @@ -473,10 +454,6 @@ if [[ "$__AlpineVersion" =~ 3\.1[345] ]]; then __AlpinePackages="${__AlpinePackages/compiler-rt/compiler-rt-static}" fi -if [[ "$__BuildArch" == "armel" ]]; then - __LLDB_Package="lldb-3.5-dev" -fi - __UbuntuPackages+=" ${__LLDB_Package:-}" if [[ -z "$__UbuntuRepo" ]]; then @@ -850,12 +827,6 @@ EOF if [[ "$__SkipUnmount" == "0" ]]; then umount "$__RootfsDir"/* || true fi - - if [[ "$__BuildArch" == "armel" && "$__CodeName" == "jessie" ]]; then - pushd "$__RootfsDir" - patch -p1 < "$__CrossDir/$__BuildArch/armel.jessie.patch" - popd - fi elif [[ "$__Tizen" == "tizen" ]]; then ROOTFS_DIR="$__RootfsDir" "$__CrossDir/tizen-build-rootfs.sh" "$__BuildArch" else diff --git a/eng/common/native/install-dependencies.sh b/eng/common/native/install-dependencies.sh index ce661e9e5cd..477a44f335b 100644 --- a/eng/common/native/install-dependencies.sh +++ b/eng/common/native/install-dependencies.sh @@ -27,8 +27,9 @@ case "$os" in libssl-dev libkrb5-dev pigz cpio localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 - elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ]; then - dnf install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio + elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ] || [ "$ID" = "azurelinux" ]; then + pkg_mgr="$(command -v tdnf 2>/dev/null || command -v dnf)" + $pkg_mgr install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio elif [ "$ID" = "alpine" ]; then apk add build-base cmake bash curl clang llvm-dev lld lldb krb5-dev lttng-ust-dev icu-dev openssl-dev pigz cpio else @@ -44,7 +45,7 @@ case "$os" in export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 # Skip brew update for now, see https://github.com/actions/setup-python/issues/577 # brew update --preinstall - brew bundle --no-upgrade --no-lock --file=- <true true $(NoWarn);NU5128 + $(NoWarn);RS1038 diff --git a/src/EFCore.Analyzers/InternalUsageDiagnosticAnalyzer.cs b/src/EFCore.Analyzers/InternalUsageDiagnosticAnalyzer.cs index 291926112ad..2f6579ef1ef 100644 --- a/src/EFCore.Analyzers/InternalUsageDiagnosticAnalyzer.cs +++ b/src/EFCore.Analyzers/InternalUsageDiagnosticAnalyzer.cs @@ -23,8 +23,7 @@ private static readonly DiagnosticDescriptor Descriptor defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); - public override ImmutableArray SupportedDiagnostics - => ImmutableArray.Create(Descriptor); + public override ImmutableArray SupportedDiagnostics => [Descriptor]; public override void Initialize(AnalysisContext context) { diff --git a/src/EFCore.Analyzers/InterpolatedStringUsageInRawQueriesCodeFixProvider.cs b/src/EFCore.Analyzers/InterpolatedStringUsageInRawQueriesCodeFixProvider.cs index 4ad0cbd0c20..adcd46f5371 100644 --- a/src/EFCore.Analyzers/InterpolatedStringUsageInRawQueriesCodeFixProvider.cs +++ b/src/EFCore.Analyzers/InterpolatedStringUsageInRawQueriesCodeFixProvider.cs @@ -15,8 +15,7 @@ namespace Microsoft.EntityFrameworkCore; [Shared] public sealed class InterpolatedStringUsageInRawQueriesCodeFixProvider : CodeFixProvider { - public override ImmutableArray FixableDiagnosticIds - => ImmutableArray.Create(EFDiagnostics.InterpolatedStringUsageInRawQueries); + public override ImmutableArray FixableDiagnosticIds => [EFDiagnostics.InterpolatedStringUsageInRawQueries]; public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; diff --git a/src/EFCore.Analyzers/InterpolatedStringUsageInRawQueriesDiagnosticAnalyzer.cs b/src/EFCore.Analyzers/InterpolatedStringUsageInRawQueriesDiagnosticAnalyzer.cs index 4de8039d555..5d7f07b5523 100644 --- a/src/EFCore.Analyzers/InterpolatedStringUsageInRawQueriesDiagnosticAnalyzer.cs +++ b/src/EFCore.Analyzers/InterpolatedStringUsageInRawQueriesDiagnosticAnalyzer.cs @@ -21,8 +21,7 @@ private static readonly DiagnosticDescriptor Descriptor defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); - public override ImmutableArray SupportedDiagnostics - => ImmutableArray.Create(Descriptor); + public override ImmutableArray SupportedDiagnostics => [Descriptor]; public override void Initialize(AnalysisContext context) { diff --git a/src/EFCore.Analyzers/UninitializedDbSetDiagnosticSuppressor.cs b/src/EFCore.Analyzers/UninitializedDbSetDiagnosticSuppressor.cs index 09c78dfce36..234ee5e3b69 100644 --- a/src/EFCore.Analyzers/UninitializedDbSetDiagnosticSuppressor.cs +++ b/src/EFCore.Analyzers/UninitializedDbSetDiagnosticSuppressor.cs @@ -17,8 +17,7 @@ public sealed class UninitializedDbSetDiagnosticSuppressor : DiagnosticSuppresso justification: AnalyzerStrings.UninitializedDbSetWarningSuppressionJustification); /// - public override ImmutableArray SupportedSuppressions { get; } - = ImmutableArray.Create(SuppressUninitializedDbSetRule); + public override ImmutableArray SupportedSuppressions { get; } = [SuppressUninitializedDbSetRule]; /// public override void ReportSuppressions(SuppressionAnalysisContext context) @@ -28,7 +27,7 @@ public override void ReportSuppressions(SuppressionAnalysisContext context) foreach (var diagnostic in context.ReportedDiagnostics) { - // We have an warning about an uninitialized non-nullable property. + // We have a warning about an uninitialized non-nullable property. // CS8618 contains the location of the uninitialized property in AdditionalLocations; note that if the class has a constructor, // the diagnostic main Location points to the constructor rather than to the uninitialized property. diff --git a/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs index fd42271c82b..2990718e5c0 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs @@ -97,14 +97,14 @@ private static void ProcessEntityType(IConventionEntityTypeBuilder entityTypeBui /// public override void ProcessDiscriminatorPropertySet( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder structuralTypeBuilder, string? name, IConventionContext context) { - var entityType = entityTypeBuilder.Metadata; - if (entityType.IsDocumentRoot()) + if (structuralTypeBuilder.Metadata is not IConventionEntityType entityType + || entityType.IsDocumentRoot()) { - base.ProcessDiscriminatorPropertySet(entityTypeBuilder, name, context); + base.ProcessDiscriminatorPropertySet(structuralTypeBuilder, name, context); } } diff --git a/src/EFCore.Cosmos/Metadata/Conventions/CosmosJsonIdConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/CosmosJsonIdConvention.cs index 45d69cae227..c3748e1974c 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/CosmosJsonIdConvention.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/CosmosJsonIdConvention.cs @@ -174,16 +174,10 @@ private void ProcessEntityType(IConventionEntityType entityType, IConventionCont } // Don't chain, because each of these could return null if the property has been explicitly configured with some other value. - computedIdPropertyBuilder = computedIdPropertyBuilder.ToJsonProperty(IdPropertyJsonName) - ?? computedIdPropertyBuilder; - - computedIdPropertyBuilder = computedIdPropertyBuilder.IsRequired(true) - ?? computedIdPropertyBuilder; - - computedIdPropertyBuilder = computedIdPropertyBuilder.HasValueGeneratorFactory(typeof(IdValueGeneratorFactory)) - ?? computedIdPropertyBuilder; - + computedIdPropertyBuilder.ToJsonProperty(IdPropertyJsonName); + computedIdPropertyBuilder.HasValueGeneratorFactory(typeof(IdValueGeneratorFactory)); computedIdPropertyBuilder.AfterSave(PropertySaveBehavior.Throw); + computedIdPropertyBuilder.IsRequired(true); } /// @@ -327,8 +321,13 @@ public virtual void ProcessModelAnnotationChanged( /// public virtual void ProcessDiscriminatorPropertySet( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder structuralTypeBuilder, string? name, IConventionContext context) - => ProcessEntityType(entityTypeBuilder.Metadata, context); + { + if (structuralTypeBuilder is IConventionEntityTypeBuilder entityTypeBuilder) + { + ProcessEntityType(entityTypeBuilder.Metadata, context); + } + } } diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs index d349d73e609..6e434aee428 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs @@ -691,7 +691,11 @@ private Expression CreateGetValueExpression( && !property.IsShadowProperty()) { var readExpression = CreateGetValueExpression( - jTokenExpression, storeName, type.MakeNullable(), property.GetTypeMapping()); + jTokenExpression, + storeName, + type.MakeNullable(), + property.GetTypeMapping(), + isNonNullableScalar: false); var nonNullReadExpression = readExpression; if (nonNullReadExpression.Type != type) @@ -712,7 +716,14 @@ private Expression CreateGetValueExpression( } return Convert( - CreateGetValueExpression(jTokenExpression, storeName, type.MakeNullable(), property.GetTypeMapping()), + CreateGetValueExpression( + jTokenExpression, + storeName, + type.MakeNullable(), + property.GetTypeMapping(), + // special case keys - we check them for null to see if the entity needs to be materialized, so we want to keep the null, rather than non-nullable default + // returning defaults is supposed to help with evolving the schema - so this doesn't concern keys anyway (they shouldn't evolve) + isNonNullableScalar: !property.IsNullable && !property.IsKey()), type); } @@ -720,7 +731,8 @@ private Expression CreateGetValueExpression( Expression jTokenExpression, string storeName, Type type, - CoreTypeMapping typeMapping = null) + CoreTypeMapping typeMapping = null, + bool isNonNullableScalar = false) { Check.DebugAssert(type.IsNullableType(), "Must read nullable type from JObject."); @@ -763,6 +775,7 @@ var body Constant(CosmosClientWrapper.Serializer)), converter.ConvertFromProviderExpression.Body); + var originalBodyType = body.Type; if (body.Type != type) { body = Convert(body, type); @@ -783,7 +796,11 @@ var body } else { - replaceExpression = Default(type); + replaceExpression = isNonNullableScalar + ? Expression.Convert( + Default(originalBodyType), + type) + : Default(type); } body = Condition( @@ -799,7 +816,11 @@ var body } else { - valueExpression = ConvertJTokenToType(jTokenExpression, typeMapping?.ClrType.MakeNullable() ?? type); + valueExpression = ConvertJTokenToType( + jTokenExpression, + (isNonNullableScalar + ? typeMapping?.ClrType + : typeMapping?.ClrType.MakeNullable()) ?? type); if (valueExpression.Type != type) { diff --git a/src/EFCore.Cosmos/Query/Internal/Translators/CosmosStringMethodTranslator.cs b/src/EFCore.Cosmos/Query/Internal/Translators/CosmosStringMethodTranslator.cs index 845409fdc99..a9d0b80b5f8 100644 --- a/src/EFCore.Cosmos/Query/Internal/Translators/CosmosStringMethodTranslator.cs +++ b/src/EFCore.Cosmos/Query/Internal/Translators/CosmosStringMethodTranslator.cs @@ -13,31 +13,52 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal; /// public class CosmosStringMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) : IMethodCallTranslator { - private static readonly MethodInfo IndexOfMethodInfo + private static readonly MethodInfo IndexOfMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.IndexOf), [typeof(string)])!; - private static readonly MethodInfo IndexOfMethodInfoWithStartingPosition + private static readonly MethodInfo IndexOfMethodInfoChar + = typeof(string).GetRuntimeMethod(nameof(string.IndexOf), [typeof(char)])!; + + private static readonly MethodInfo IndexOfMethodInfoWithStartingPositionString = typeof(string).GetRuntimeMethod(nameof(string.IndexOf), [typeof(string), typeof(int)])!; - private static readonly MethodInfo ReplaceMethodInfo + private static readonly MethodInfo IndexOfMethodInfoWithStartingPositionChar + = typeof(string).GetRuntimeMethod(nameof(string.IndexOf), [typeof(char), typeof(int)])!; + + private static readonly MethodInfo ReplaceMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.Replace), [typeof(string), typeof(string)])!; - private static readonly MethodInfo ContainsMethodInfo + private static readonly MethodInfo ReplaceMethodInfoChar + = typeof(string).GetRuntimeMethod(nameof(string.Replace), [typeof(char), typeof(char)])!; + + private static readonly MethodInfo ContainsMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.Contains), [typeof(string)])!; - private static readonly MethodInfo ContainsWithStringComparisonMethodInfo + private static readonly MethodInfo ContainsMethodInfoChar + = typeof(string).GetRuntimeMethod(nameof(string.Contains), [typeof(char)])!; + + private static readonly MethodInfo ContainsWithStringComparisonMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.Contains), [typeof(string), typeof(StringComparison)])!; - private static readonly MethodInfo StartsWithMethodInfo + private static readonly MethodInfo ContainsWithStringComparisonMethodInfoChar + = typeof(string).GetRuntimeMethod(nameof(string.Contains), [typeof(char), typeof(StringComparison)])!; + + private static readonly MethodInfo StartsWithMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.StartsWith), [typeof(string)])!; - private static readonly MethodInfo StartsWithWithStringComparisonMethodInfo + private static readonly MethodInfo StartsWithMethodInfoChar + = typeof(string).GetRuntimeMethod(nameof(string.StartsWith), [typeof(char)])!; + + private static readonly MethodInfo StartsWithWithStringComparisonMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.StartsWith), [typeof(string), typeof(StringComparison)])!; - private static readonly MethodInfo EndsWithMethodInfo + private static readonly MethodInfo EndsWithMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.EndsWith), [typeof(string)])!; - private static readonly MethodInfo EndsWithWithStringComparisonMethodInfo + private static readonly MethodInfo EndsWithMethodInfoChar + = typeof(string).GetRuntimeMethod(nameof(string.EndsWith), [typeof(char)])!; + + private static readonly MethodInfo EndsWithWithStringComparisonMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.EndsWith), [typeof(string), typeof(StringComparison)])!; private static readonly MethodInfo ToLowerMethodInfo @@ -109,27 +130,27 @@ private static readonly MethodInfo StringComparisonWithComparisonTypeArgumentSta { if (instance != null) { - if (IndexOfMethodInfo.Equals(method)) + if (IndexOfMethodInfoString.Equals(method) || IndexOfMethodInfoChar.Equals(method)) { return TranslateSystemFunction("INDEX_OF", typeof(int), instance, arguments[0]); } - if (IndexOfMethodInfoWithStartingPosition.Equals(method)) + if (IndexOfMethodInfoWithStartingPositionString.Equals(method) || IndexOfMethodInfoWithStartingPositionChar.Equals(method)) { return TranslateSystemFunction("INDEX_OF", typeof(int), instance, arguments[0], arguments[1]); } - if (ReplaceMethodInfo.Equals(method)) + if (ReplaceMethodInfoString.Equals(method) || ReplaceMethodInfoChar.Equals(method)) { return TranslateSystemFunction("REPLACE", method.ReturnType, instance, arguments[0], arguments[1]); } - if (ContainsMethodInfo.Equals(method)) + if (ContainsMethodInfoString.Equals(method) || ContainsMethodInfoChar.Equals(method)) { return TranslateSystemFunction("CONTAINS", typeof(bool), instance, arguments[0]); } - if (ContainsWithStringComparisonMethodInfo.Equals(method)) + if (ContainsWithStringComparisonMethodInfoString.Equals(method) || ContainsWithStringComparisonMethodInfoChar.Equals(method)) { if (arguments[1] is SqlConstantExpression { Value: StringComparison comparisonType }) { @@ -150,12 +171,12 @@ private static readonly MethodInfo StringComparisonWithComparisonTypeArgumentSta return null; } - if (StartsWithMethodInfo.Equals(method)) + if (StartsWithMethodInfoString.Equals(method) || StartsWithMethodInfoChar.Equals(method)) { return TranslateSystemFunction("STARTSWITH", typeof(bool), instance, arguments[0]); } - if (StartsWithWithStringComparisonMethodInfo.Equals(method)) + if (StartsWithWithStringComparisonMethodInfoString.Equals(method)) { if (arguments[1] is SqlConstantExpression { Value: StringComparison comparisonType }) { @@ -176,12 +197,12 @@ private static readonly MethodInfo StringComparisonWithComparisonTypeArgumentSta return null; } - if (EndsWithMethodInfo.Equals(method)) + if (EndsWithMethodInfoString.Equals(method) || EndsWithMethodInfoChar.Equals(method)) { return TranslateSystemFunction("ENDSWITH", typeof(bool), instance, arguments[0]); } - if (EndsWithWithStringComparisonMethodInfo.Equals(method)) + if (EndsWithWithStringComparisonMethodInfoString.Equals(method)) { if (arguments[1] is SqlConstantExpression { Value: StringComparison comparisonType }) { diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs index 9f2793deeaa..910a9a293de 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs @@ -8,7 +8,6 @@ using System.Text; using Microsoft.EntityFrameworkCore.Cosmos.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal; -using Microsoft.EntityFrameworkCore.Cosmos.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; using Microsoft.EntityFrameworkCore.Internal; using Newtonsoft.Json; diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index d5efd7f6a81..6eaa03404eb 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -628,9 +628,60 @@ protected virtual void GenerateComplexPropertyAnnotations( IComplexProperty property, IndentedStringBuilder stringBuilder) { + var discriminatorProperty = property.ComplexType.FindDiscriminatorProperty(); + if (discriminatorProperty != null) + { + stringBuilder + .AppendLine() + .Append(propertyBuilderName) + .Append('.') + .Append("HasDiscriminator"); + + if (discriminatorProperty.DeclaringType == property.ComplexType + && discriminatorProperty.Name != "Discriminator") + { + var propertyClrType = FindValueConverter(discriminatorProperty)?.ProviderClrType + .MakeNullable(discriminatorProperty.IsNullable) + ?? discriminatorProperty.ClrType; + stringBuilder + .Append('<') + .Append(Code.Reference(propertyClrType)) + .Append(">(") + .Append(Code.Literal(discriminatorProperty.Name)) + .Append(')'); + } + else + { + stringBuilder + .Append("()"); + } + + var discriminatorValue = property.ComplexType.GetDiscriminatorValue(); + if (discriminatorValue != null) + { + if (discriminatorProperty != null) + { + var valueConverter = FindValueConverter(discriminatorProperty); + if (valueConverter != null) + { + discriminatorValue = valueConverter.ConvertToProvider(discriminatorValue); + } + } + + stringBuilder + .Append('.') + .Append("HasValue") + .Append('(') + .Append(Code.UnknownLiteral(discriminatorValue)) + .Append(')'); + } + + stringBuilder.AppendLine(";"); + } + var propertyAnnotations = Dependencies.AnnotationCodeGenerator - .FilterIgnoredAnnotations(property.GetAnnotations()) - .ToDictionary(a => a.Name, a => a); + .FilterIgnoredAnnotations(property.GetAnnotations()) + .ToDictionary(a => a.Name, a => a); var typeAnnotations = Dependencies.AnnotationCodeGenerator .FilterIgnoredAnnotations(property.ComplexType.GetAnnotations()) diff --git a/src/EFCore.Design/Properties/DesignStrings.Designer.cs b/src/EFCore.Design/Properties/DesignStrings.Designer.cs index f213b989dd2..e245a42bcc8 100644 --- a/src/EFCore.Design/Properties/DesignStrings.Designer.cs +++ b/src/EFCore.Design/Properties/DesignStrings.Designer.cs @@ -129,14 +129,6 @@ public static string CompiledModelCustomCacheKeyFactory(object? factoryType) GetString("CompiledModelCustomCacheKeyFactory", nameof(factoryType)), factoryType); - /// - /// The entity type '{entityType}' has a defining query configured. Compiled model can't be generated, because defining queries are not supported. - /// - public static string CompiledModelDefiningQuery(object? entityType) - => string.Format( - GetString("CompiledModelDefiningQuery", nameof(entityType)), - entityType); - /// /// Successfully generated a compiled model, it will be discovered automatically, but you can also call '{optionsCall}'. Run this command again when the model is modified. /// diff --git a/src/EFCore.Design/Properties/DesignStrings.resx b/src/EFCore.Design/Properties/DesignStrings.resx index 1303c7e4816..d0506266612 100644 --- a/src/EFCore.Design/Properties/DesignStrings.resx +++ b/src/EFCore.Design/Properties/DesignStrings.resx @@ -162,9 +162,6 @@ The context is configured to use a custom model cache key factory '{factoryType}', this usually indicates that the produced model can change between context instances. To preserve this behavior manually modify the generated compiled model source code. - - The entity type '{entityType}' has a defining query configured. Compiled model can't be generated, because defining queries are not supported. - Successfully generated a compiled model, it will be discovered automatically, but you can also call '{optionsCall}'. Run this command again when the model is modified. diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs index 78b54d3c672..e18a43f317d 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs @@ -898,14 +898,6 @@ private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGenerator throw new InvalidOperationException(DesignStrings.CompiledModelQueryFilter(entityType.ShortName())); } -#pragma warning disable CS0618 // Type or member is obsolete - if (entityType.GetDefiningQuery() != null) - { - // TODO: Move to InMemoryCSharpRuntimeAnnotationCodeGenerator, see #21624 - throw new InvalidOperationException(DesignStrings.CompiledModelDefiningQuery(entityType.ShortName())); - } -#pragma warning restore CS0618 // Type or member is obsolete - AddNamespace(entityType.ClrType, parameters.Namespaces); var mainBuilder = parameters.MainBuilder; @@ -925,14 +917,6 @@ private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGenerator .Append(_code.Literal(entityType.HasSharedClrType)); } - var discriminatorProperty = entityType.GetDiscriminatorPropertyName(); - if (discriminatorProperty != null) - { - mainBuilder.AppendLine(",") - .Append("discriminatorProperty: ") - .Append(_code.Literal(discriminatorProperty)); - } - var changeTrackingStrategy = entityType.GetChangeTrackingStrategy(); if (changeTrackingStrategy != ChangeTrackingStrategy.Snapshot) { @@ -959,6 +943,14 @@ private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGenerator .Append(_code.Literal(true)); } + var discriminatorProperty = entityType.GetDiscriminatorPropertyName(); + if (discriminatorProperty != null) + { + mainBuilder.AppendLine(",") + .Append("discriminatorProperty: ") + .Append(_code.Literal(discriminatorProperty)); + } + var discriminatorValue = entityType.GetDiscriminatorValue(); if (discriminatorValue != null) { @@ -2182,6 +2174,24 @@ private void CreateComplexProperty( .Append(_code.Literal(true)); } + var discriminatorPropertyName = complexType.GetDiscriminatorPropertyName(); + if (discriminatorPropertyName != null) + { + mainBuilder.AppendLine(",") + .Append("discriminatorProperty: ") + .Append(_code.Literal(discriminatorPropertyName)); + } + + var discriminatorValue = complexType.GetDiscriminatorValue(); + if (discriminatorValue != null) + { + AddNamespace(discriminatorValue.GetType(), parameters.Namespaces); + + mainBuilder.AppendLine(",") + .Append("discriminatorValue: ") + .Append(_code.UnknownLiteral(discriminatorValue)); + } + mainBuilder.AppendLine(",") .Append("propertyCount: ") .Append(_code.Literal(complexType.GetDeclaredProperties().Count())); diff --git a/src/EFCore.InMemory/Design/Internal/InMemoryCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.InMemory/Design/Internal/InMemoryCSharpRuntimeAnnotationCodeGenerator.cs index a9c7e56e256..0ea2c29c5ff 100644 --- a/src/EFCore.InMemory/Design/Internal/InMemoryCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.InMemory/Design/Internal/InMemoryCSharpRuntimeAnnotationCodeGenerator.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Design.Internal; +using Microsoft.EntityFrameworkCore.InMemory.Internal; namespace Microsoft.EntityFrameworkCore.InMemory.Design.Internal; @@ -25,4 +26,20 @@ public InMemoryCSharpRuntimeAnnotationCodeGenerator( : base(dependencies) { } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override void Generate(IEntityType entityType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + if (entityType.GetInMemoryQuery() != null) + { + throw new InvalidOperationException(InMemoryStrings.CompiledModelDefiningQuery(entityType.DisplayName())); + } + + base.Generate(entityType, parameters); + } } diff --git a/src/EFCore.InMemory/EFCore.InMemory.csproj b/src/EFCore.InMemory/EFCore.InMemory.csproj index bf88275bc8f..17c05bf18d1 100644 --- a/src/EFCore.InMemory/EFCore.InMemory.csproj +++ b/src/EFCore.InMemory/EFCore.InMemory.csproj @@ -5,7 +5,7 @@ $(DefaultNetCoreTargetFramework) 3.6 Microsoft.EntityFrameworkCore.InMemory - Microsoft.EntityFrameworkCore.InMemory + Microsoft.EntityFrameworkCore true $(PackageTags);In-Memory true diff --git a/src/EFCore.InMemory/Extensions/InMemoryEntityTypeBuilderExtensions.cs b/src/EFCore.InMemory/Extensions/InMemoryEntityTypeBuilderExtensions.cs index e0bc4d28569..9d7c8d9a93b 100644 --- a/src/EFCore.InMemory/Extensions/InMemoryEntityTypeBuilderExtensions.cs +++ b/src/EFCore.InMemory/Extensions/InMemoryEntityTypeBuilderExtensions.cs @@ -101,9 +101,5 @@ public static bool CanSetInMemoryQuery( this IConventionEntityTypeBuilder entityTypeBuilder, LambdaExpression? query, bool fromDataAnnotation = false) -#pragma warning disable EF1001 // Internal EF Core API usage. -#pragma warning disable CS0612 // Type or member is obsolete - => entityTypeBuilder.CanSetAnnotation(CoreAnnotationNames.DefiningQuery, query, fromDataAnnotation); -#pragma warning restore CS0612 // Type or member is obsolete -#pragma warning restore EF1001 // Internal EF Core API usage. + => entityTypeBuilder.CanSetAnnotation(InMemoryAnnotationNames.DefiningQuery, query, fromDataAnnotation); } diff --git a/src/EFCore.InMemory/Extensions/InMemoryEntityTypeExtensions.cs b/src/EFCore.InMemory/Extensions/InMemoryEntityTypeExtensions.cs index 5045a664b85..0c42dc1ffa2 100644 --- a/src/EFCore.InMemory/Extensions/InMemoryEntityTypeExtensions.cs +++ b/src/EFCore.InMemory/Extensions/InMemoryEntityTypeExtensions.cs @@ -21,11 +21,7 @@ public static class InMemoryEntityTypeExtensions /// The entity type to get the in-memory query for. /// The LINQ query used as the default source. public static LambdaExpression? GetInMemoryQuery(this IReadOnlyEntityType entityType) -#pragma warning disable EF1001 // Internal EF Core API usage. -#pragma warning disable CS0612 // Type or member is obsolete - => (LambdaExpression?)entityType[CoreAnnotationNames.DefiningQuery]; -#pragma warning restore CS0612 // Type or member is obsolete -#pragma warning restore EF1001 // Internal EF Core API usage. + => (LambdaExpression?)entityType[InMemoryAnnotationNames.DefiningQuery]; /// /// Sets the LINQ query used as the default source for queries of this type. @@ -36,11 +32,7 @@ public static void SetInMemoryQuery( this IMutableEntityType entityType, LambdaExpression? inMemoryQuery) => entityType -#pragma warning disable EF1001 // Internal EF Core API usage. -#pragma warning disable CS0612 // Type or member is obsolete - .SetOrRemoveAnnotation(CoreAnnotationNames.DefiningQuery, inMemoryQuery); -#pragma warning restore CS0612 // Type or member is obsolete -#pragma warning restore EF1001 // Internal EF Core API usage. + .SetOrRemoveAnnotation(InMemoryAnnotationNames.DefiningQuery, inMemoryQuery); /// /// Sets the LINQ query used as the default source for queries of this type. @@ -54,11 +46,7 @@ public static void SetInMemoryQuery( LambdaExpression? inMemoryQuery, bool fromDataAnnotation = false) => (LambdaExpression?)entityType -#pragma warning disable EF1001 // Internal EF Core API usage. -#pragma warning disable CS0612 // Type or member is obsolete - .SetOrRemoveAnnotation(CoreAnnotationNames.DefiningQuery, inMemoryQuery, fromDataAnnotation) -#pragma warning restore CS0612 // Type or member is obsolete -#pragma warning restore EF1001 // Internal EF Core API usage. + .SetOrRemoveAnnotation(InMemoryAnnotationNames.DefiningQuery, inMemoryQuery, fromDataAnnotation) ?.Value; /// @@ -66,10 +54,6 @@ public static void SetInMemoryQuery( /// /// The entity type. /// The configuration source for . - public static ConfigurationSource? GetDefiningQueryConfigurationSource(this IConventionEntityType entityType) -#pragma warning disable EF1001 // Internal EF Core API usage. -#pragma warning disable CS0612 // Type or member is obsolete - => entityType.FindAnnotation(CoreAnnotationNames.DefiningQuery)?.GetConfigurationSource(); -#pragma warning restore CS0612 // Type or member is obsolete -#pragma warning restore EF1001 // Internal EF Core API usage. + public static ConfigurationSource? GetInMemoryQueryConfigurationSource(this IConventionEntityType entityType) + => entityType.FindAnnotation(InMemoryAnnotationNames.DefiningQuery)?.GetConfigurationSource(); } diff --git a/src/EFCore.InMemory/Extensions/InMemoryServiceCollectionExtensions.cs b/src/EFCore.InMemory/Extensions/InMemoryServiceCollectionExtensions.cs index 6b8d1c64e40..d3d50a93eb0 100644 --- a/src/EFCore.InMemory/Extensions/InMemoryServiceCollectionExtensions.cs +++ b/src/EFCore.InMemory/Extensions/InMemoryServiceCollectionExtensions.cs @@ -4,7 +4,6 @@ using System.ComponentModel; using Microsoft.EntityFrameworkCore.InMemory.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; -using Microsoft.EntityFrameworkCore.InMemory.Metadata.Conventions; using Microsoft.EntityFrameworkCore.InMemory.Query.Internal; using Microsoft.EntityFrameworkCore.InMemory.Storage.Internal; using Microsoft.EntityFrameworkCore.InMemory.ValueGeneration.Internal; diff --git a/src/EFCore.InMemory/Metadata/Conventions/InMemoryConventionSetBuilder.cs b/src/EFCore.InMemory/Metadata/Conventions/InMemoryConventionSetBuilder.cs index 0f13f38176f..7bfad928af8 100644 --- a/src/EFCore.InMemory/Metadata/Conventions/InMemoryConventionSetBuilder.cs +++ b/src/EFCore.InMemory/Metadata/Conventions/InMemoryConventionSetBuilder.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.InMemory.Metadata.Conventions; +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// A builder for building conventions for th in-memory provider. diff --git a/src/EFCore.InMemory/Metadata/Internal/CosmosAnnotationNames.cs b/src/EFCore.InMemory/Metadata/Internal/CosmosAnnotationNames.cs new file mode 100644 index 00000000000..ce1a599dfdc --- /dev/null +++ b/src/EFCore.InMemory/Metadata/Internal/CosmosAnnotationNames.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public static class InMemoryAnnotationNames +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public const string Prefix = "InMemory:"; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public const string DefiningQuery = Prefix + "DefiningQuery"; +} diff --git a/src/EFCore.InMemory/Properties/InMemoryStrings.Designer.cs b/src/EFCore.InMemory/Properties/InMemoryStrings.Designer.cs index 83f0de02464..4076751e735 100644 --- a/src/EFCore.InMemory/Properties/InMemoryStrings.Designer.cs +++ b/src/EFCore.InMemory/Properties/InMemoryStrings.Designer.cs @@ -21,7 +21,15 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Internal public static class InMemoryStrings { private static readonly ResourceManager _resourceManager - = new ResourceManager("Microsoft.EntityFrameworkCore.InMemory.Properties.InMemoryStrings", typeof(InMemoryStrings).Assembly); + = new ResourceManager("Microsoft.EntityFrameworkCore.Properties.InMemoryStrings", typeof(InMemoryStrings).Assembly); + + /// + /// The entity type '{entityType}' has a defining query configured. Compiled model can't be generated, because defining queries are not supported. + /// + public static string CompiledModelDefiningQuery(object? entityType) + => string.Format( + GetString("CompiledModelDefiningQuery", nameof(entityType)), + entityType); /// /// Cannot apply 'DefaultIfEmpty' after a client-evaluated projection. Consider applying 'DefaultIfEmpty' before last 'Select' or use 'AsEnumerable' before 'DefaultIfEmpty' to apply it on client-side. @@ -131,7 +139,7 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Internal public static class InMemoryResources { private static readonly ResourceManager _resourceManager - = new ResourceManager("Microsoft.EntityFrameworkCore.InMemory.Properties.InMemoryStrings", typeof(InMemoryResources).Assembly); + = new ResourceManager("Microsoft.EntityFrameworkCore.Properties.InMemoryStrings", typeof(InMemoryResources).Assembly); /// /// Saved {count} entities to in-memory store. diff --git a/src/EFCore.InMemory/Properties/InMemoryStrings.Designer.tt b/src/EFCore.InMemory/Properties/InMemoryStrings.Designer.tt index 8f9f997c866..349c4ed04e9 100644 --- a/src/EFCore.InMemory/Properties/InMemoryStrings.Designer.tt +++ b/src/EFCore.InMemory/Properties/InMemoryStrings.Designer.tt @@ -1,7 +1,7 @@ <# Session = new System.Collections.Generic.Dictionary(); Session["ResourceFile"] = "InMemoryStrings.resx"; - Session["ResourceNamespace"] = "Microsoft.EntityFrameworkCore.InMemory.Properties"; + Session["ResourceNamespace"] = "Microsoft.EntityFrameworkCore.Properties"; Session["LoggingDefinitionsClass"] = "Diagnostics.Internal.InMemoryLoggingDefinitions"; #> <#@ include file="..\..\..\tools\Resources.tt" #> \ No newline at end of file diff --git a/src/EFCore.InMemory/Properties/InMemoryStrings.resx b/src/EFCore.InMemory/Properties/InMemoryStrings.resx index 93e3f5cfd5d..ed8494a3500 100644 --- a/src/EFCore.InMemory/Properties/InMemoryStrings.resx +++ b/src/EFCore.InMemory/Properties/InMemoryStrings.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + The entity type '{entityType}' has a defining query configured. Compiled model can't be generated, because defining queries are not supported. + Cannot apply 'DefaultIfEmpty' after a client-evaluated projection. Consider applying 'DefaultIfEmpty' before last 'Select' or use 'AsEnumerable' before 'DefaultIfEmpty' to apply it on client-side. diff --git a/src/EFCore.Relational/Diagnostics/CommandEndEventData.cs b/src/EFCore.Relational/Diagnostics/CommandEndEventData.cs index 52ab9d5c36c..4e61f915c4a 100644 --- a/src/EFCore.Relational/Diagnostics/CommandEndEventData.cs +++ b/src/EFCore.Relational/Diagnostics/CommandEndEventData.cs @@ -19,6 +19,7 @@ public class CommandEndEventData : CommandEventData /// A delegate that generates a log message for this event. /// The being used. /// The . + /// The command text that can be logged. /// The currently being used, to null if not known. /// The method. /// A correlation ID that identifies the instance being used. @@ -33,6 +34,7 @@ public CommandEndEventData( Func messageGenerator, DbConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -47,6 +49,7 @@ public CommandEndEventData( messageGenerator, connection, command, + logCommandText, context, executeMethod, commandId, diff --git a/src/EFCore.Relational/Diagnostics/CommandErrorEventData.cs b/src/EFCore.Relational/Diagnostics/CommandErrorEventData.cs index 9e9f6007b18..1f8cd114380 100644 --- a/src/EFCore.Relational/Diagnostics/CommandErrorEventData.cs +++ b/src/EFCore.Relational/Diagnostics/CommandErrorEventData.cs @@ -18,6 +18,7 @@ public class CommandErrorEventData : CommandEndEventData, IErrorEventData /// A delegate that generates a log message for this event. /// The being used. /// The that was executing when it failed. + /// The command text that can be logged. /// The currently being used, to null if not known. /// The method that was used to execute the command. /// A correlation ID that identifies the instance being used. @@ -33,6 +34,7 @@ public CommandErrorEventData( Func messageGenerator, DbConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -48,6 +50,7 @@ public CommandErrorEventData( messageGenerator, connection, command, + logCommandText, context, executeMethod, commandId, diff --git a/src/EFCore.Relational/Diagnostics/CommandEventData.cs b/src/EFCore.Relational/Diagnostics/CommandEventData.cs index 0562c1961c1..03e06c7d49a 100644 --- a/src/EFCore.Relational/Diagnostics/CommandEventData.cs +++ b/src/EFCore.Relational/Diagnostics/CommandEventData.cs @@ -19,6 +19,7 @@ public class CommandEventData : CommandCorrelatedEventData /// A delegate that generates a log message for this event. /// The being used. /// The . + /// The command text that can be logged. /// The currently being used, to null if not known. /// The method. /// A correlation ID that identifies the instance being used. @@ -32,6 +33,7 @@ public CommandEventData( Func messageGenerator, DbConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -53,6 +55,7 @@ public CommandEventData( commandSource) { Command = command; + LogCommandText = logCommandText; LogParameterValues = logParameterValues; } @@ -61,6 +64,11 @@ public CommandEventData( /// public virtual DbCommand Command { get; } + /// + /// The command text that can be logged. + /// + public virtual string LogCommandText { get; } + /// /// Indicates whether or not the application allows logging of parameter values. /// diff --git a/src/EFCore.Relational/Diagnostics/CommandExecutedEventData.cs b/src/EFCore.Relational/Diagnostics/CommandExecutedEventData.cs index 78fb02b4e5c..e7cfb0c7c8b 100644 --- a/src/EFCore.Relational/Diagnostics/CommandExecutedEventData.cs +++ b/src/EFCore.Relational/Diagnostics/CommandExecutedEventData.cs @@ -18,6 +18,7 @@ public class CommandExecutedEventData : CommandEndEventData /// A delegate that generates a log message for this event. /// The being used. /// The that was executing when it failed. + /// The command text that can be logged. /// The currently being used, to null if not known. /// The method that was used to execute the command. /// A correlation ID that identifies the instance being used. @@ -33,6 +34,7 @@ public CommandExecutedEventData( Func messageGenerator, DbConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -48,6 +50,7 @@ public CommandExecutedEventData( messageGenerator, connection, command, + logCommandText, context, executeMethod, commandId, diff --git a/src/EFCore.Relational/Diagnostics/DbConnectionInterceptor.cs b/src/EFCore.Relational/Diagnostics/DbConnectionInterceptor.cs index a320c514b1f..464b5e428e0 100644 --- a/src/EFCore.Relational/Diagnostics/DbConnectionInterceptor.cs +++ b/src/EFCore.Relational/Diagnostics/DbConnectionInterceptor.cs @@ -97,4 +97,16 @@ public virtual Task ConnectionFailedAsync( ConnectionErrorEventData eventData, CancellationToken cancellationToken = default) => Task.CompletedTask; + + /// + public virtual void ConnectionCanceled(DbConnection connection, ConnectionEndEventData eventData) + { + } + + /// + public virtual Task ConnectionCanceledAsync( + DbConnection connection, + ConnectionEndEventData eventData, + CancellationToken cancellationToken = default) + => Task.CompletedTask; } diff --git a/src/EFCore.Relational/Diagnostics/IDbConnectionInterceptor.cs b/src/EFCore.Relational/Diagnostics/IDbConnectionInterceptor.cs index b8b95043837..d5d982ab73b 100644 --- a/src/EFCore.Relational/Diagnostics/IDbConnectionInterceptor.cs +++ b/src/EFCore.Relational/Diagnostics/IDbConnectionInterceptor.cs @@ -261,7 +261,7 @@ Task ConnectionDisposedAsync(DbConnection connection, ConnectionEndEventData eve => Task.CompletedTask; /// - /// Called when closing of a connection has failed with an exception. + /// Called when a connection operation (e.g. open) has failed with an exception. /// /// The connection. /// Contextual information about the connection. @@ -270,7 +270,7 @@ void ConnectionFailed(DbConnection connection, ConnectionErrorEventData eventDat } /// - /// Called when closing of a connection has failed with an exception. + /// Called when a connection operation (e.g. open) has failed with an exception. /// /// The connection. /// Contextual information about the connection. @@ -279,4 +279,24 @@ void ConnectionFailed(DbConnection connection, ConnectionErrorEventData eventDat /// If the is canceled. Task ConnectionFailedAsync(DbConnection connection, ConnectionErrorEventData eventData, CancellationToken cancellationToken = default) => Task.CompletedTask; + + /// + /// Called when the opening of a connection was canceled. + /// + /// The connection. + /// Contextual information about the connection. + void ConnectionCanceled(DbConnection connection, ConnectionEndEventData eventData) + { + } + + /// + /// Called when the opening of a connection was canceled. + /// + /// The connection. + /// Contextual information about the connection. + /// A to observe while waiting for the task to complete. + /// A representing the asynchronous operation. + /// If the is canceled. + Task ConnectionCanceledAsync(DbConnection connection, ConnectionEndEventData eventData, CancellationToken cancellationToken = default) + => Task.CompletedTask; } diff --git a/src/EFCore.Relational/Diagnostics/IRelationalCommandDiagnosticsLogger.cs b/src/EFCore.Relational/Diagnostics/IRelationalCommandDiagnosticsLogger.cs index 8e1381dcbf2..1737a698620 100644 --- a/src/EFCore.Relational/Diagnostics/IRelationalCommandDiagnosticsLogger.cs +++ b/src/EFCore.Relational/Diagnostics/IRelationalCommandDiagnosticsLogger.cs @@ -93,6 +93,7 @@ DbCommand CommandInitialized( /// /// The connection. /// The database command object. + /// The command text that can be logged. /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. @@ -102,6 +103,7 @@ DbCommand CommandInitialized( InterceptionResult CommandReaderExecuting( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -113,6 +115,7 @@ InterceptionResult CommandReaderExecuting( /// /// The connection. /// The database command object. + /// The command text that can be logged. /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. @@ -122,6 +125,7 @@ InterceptionResult CommandReaderExecuting( InterceptionResult CommandScalarExecuting( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -133,6 +137,7 @@ InterceptionResult CommandScalarExecuting( /// /// The connection. /// The database command object. + /// The command text that can be logged. /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. @@ -142,6 +147,7 @@ InterceptionResult CommandScalarExecuting( InterceptionResult CommandNonQueryExecuting( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -153,6 +159,7 @@ InterceptionResult CommandNonQueryExecuting( /// /// The connection. /// The database command object. + /// The command text that can be logged. /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. @@ -164,6 +171,7 @@ InterceptionResult CommandNonQueryExecuting( ValueTask> CommandReaderExecutingAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -176,6 +184,7 @@ ValueTask> CommandReaderExecutingAsync( /// /// The connection. /// The database command object. + /// The command text that can be logged. /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. @@ -187,6 +196,7 @@ ValueTask> CommandReaderExecutingAsync( ValueTask> CommandScalarExecutingAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -199,6 +209,7 @@ ValueTask> CommandScalarExecutingAsync( /// /// The connection. /// The database command object. + /// The command text that can be logged. /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. @@ -210,6 +221,7 @@ ValueTask> CommandScalarExecutingAsync( ValueTask> CommandNonQueryExecutingAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -222,6 +234,7 @@ ValueTask> CommandNonQueryExecutingAsync( /// /// The connection. /// The database command object. + /// The command text that can be logged. /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. @@ -233,6 +246,7 @@ ValueTask> CommandNonQueryExecutingAsync( DbDataReader CommandReaderExecuted( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -246,6 +260,7 @@ DbDataReader CommandReaderExecuted( /// /// The connection. /// The database command object. + /// The command text that can be logged. /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. @@ -257,6 +272,7 @@ DbDataReader CommandReaderExecuted( object? CommandScalarExecuted( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -270,6 +286,7 @@ DbDataReader CommandReaderExecuted( /// /// The connection. /// The database command object. + /// The command text that can be logged. /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. @@ -281,6 +298,7 @@ DbDataReader CommandReaderExecuted( int CommandNonQueryExecuted( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -294,6 +312,7 @@ int CommandNonQueryExecuted( /// /// The connection. /// The database command object. + /// The command text that can be logged. /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. @@ -307,6 +326,7 @@ int CommandNonQueryExecuted( ValueTask CommandReaderExecutedAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -321,6 +341,7 @@ ValueTask CommandReaderExecutedAsync( /// /// The connection. /// The database command object. + /// The command text that can be logged. /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. @@ -334,6 +355,7 @@ ValueTask CommandReaderExecutedAsync( ValueTask CommandScalarExecutedAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -348,6 +370,7 @@ ValueTask CommandReaderExecutedAsync( /// /// The connection. /// The database command object. + /// The command text that can be logged. /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. @@ -361,6 +384,7 @@ ValueTask CommandReaderExecutedAsync( ValueTask CommandNonQueryExecutedAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -375,6 +399,7 @@ ValueTask CommandNonQueryExecutedAsync( /// /// The connection. /// The database command object. + /// The command text that can be logged. /// The currently being used, to null if not known. /// Represents the method that will be called to execute the command. /// The correlation ID associated with the given . @@ -386,6 +411,7 @@ ValueTask CommandNonQueryExecutedAsync( void CommandError( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -400,6 +426,7 @@ void CommandError( /// /// The connection. /// The database command object. + /// The command text that can be logged. /// The currently being used, to null if not known. /// Represents the method that will be called to execute the command. /// The correlation ID associated with the given . @@ -414,6 +441,7 @@ void CommandError( Task CommandErrorAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -429,16 +457,18 @@ Task CommandErrorAsync( /// /// The connection. /// The database command object. + /// The command text that can be logged. /// The currently being used, to null if not known. /// Represents the method that will be called to execute the command. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. /// The time that execution began. - /// The amount of time that passed until the exception was raised. + /// The amount of time that passed until the command was canceled. /// Source of the command. void CommandCanceled( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -452,12 +482,13 @@ void CommandCanceled( /// /// The connection. /// The database command object. + /// The command text that can be logged. /// The currently being used, to null if not known. /// Represents the method that will be called to execute the command. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. /// The time that execution began. - /// The amount of time that passed until the exception was raised. + /// The amount of time that passed until the command was canceled. /// Source of the command. /// A to observe while waiting for the task to complete. /// A representing the async operation. @@ -465,6 +496,7 @@ void CommandCanceled( Task CommandCanceledAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, diff --git a/src/EFCore.Relational/Diagnostics/IRelationalConnectionDiagnosticsLogger.cs b/src/EFCore.Relational/Diagnostics/IRelationalConnectionDiagnosticsLogger.cs index a30d6370b17..18a4d111d3e 100644 --- a/src/EFCore.Relational/Diagnostics/IRelationalConnectionDiagnosticsLogger.cs +++ b/src/EFCore.Relational/Diagnostics/IRelationalConnectionDiagnosticsLogger.cs @@ -210,6 +210,32 @@ Task ConnectionErrorAsync( bool logErrorAsDebug, CancellationToken cancellationToken = default); + /// + /// Logs for the event. + /// + /// The connection. + /// The time that the operation was started. + /// The amount of time that passed until the command was canceled. + void ConnectionCanceled( + IRelationalConnection connection, + DateTimeOffset startTime, + TimeSpan duration); + + /// + /// Logs for the event. + /// + /// The connection. + /// The time that the operation was started. + /// The amount of time that passed until the command was canceled. + /// A to observe while waiting for the task to complete. + /// A representing the async operation. + /// If the is canceled. + Task ConnectionCanceledAsync( + IRelationalConnection connection, + DateTimeOffset startTime, + TimeSpan duration, + CancellationToken cancellationToken = default); + /// /// Whether or need /// to be logged. diff --git a/src/EFCore.Relational/Diagnostics/Internal/RelationalCommandDiagnosticsLogger.cs b/src/EFCore.Relational/Diagnostics/Internal/RelationalCommandDiagnosticsLogger.cs index 67c00efb825..b6648cfbe76 100644 --- a/src/EFCore.Relational/Diagnostics/Internal/RelationalCommandDiagnosticsLogger.cs +++ b/src/EFCore.Relational/Diagnostics/Internal/RelationalCommandDiagnosticsLogger.cs @@ -219,6 +219,7 @@ private CommandEndEventData BroadcastCommandCreated( CommandCreated, connection, command, + command.CommandText, context, executeMethod, commandId, @@ -320,6 +321,7 @@ private CommandEndEventData BroadcastCommandInitialized( CommandInitialized, connection, command, + command.CommandText, context, executeMethod, commandId, @@ -355,6 +357,7 @@ static string CommandInitialized(EventDefinitionBase definition, EventData paylo public virtual InterceptionResult CommandReaderExecuting( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -371,11 +374,11 @@ public virtual InterceptionResult CommandReaderExecuting( definition.Log( this, - command.Parameters.FormatParameters(ShouldLogParameterValues(command)), + command.Parameters.FormatParameters(ShouldLogSensitiveData()), command.CommandType, command.CommandTimeout, Environment.NewLine, - command.CommandText.TrimEnd()); + logCommandText.TrimEnd()); } if (NeedsEventData( @@ -386,6 +389,7 @@ public virtual InterceptionResult CommandReaderExecuting( var eventData = BroadcastCommandExecuting( connection.DbConnection, command, + logCommandText, context, DbCommandMethod.ExecuteReader, commandId, @@ -415,6 +419,7 @@ public virtual InterceptionResult CommandReaderExecuting( public virtual InterceptionResult CommandScalarExecuting( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -431,11 +436,11 @@ public virtual InterceptionResult CommandScalarExecuting( definition.Log( this, - command.Parameters.FormatParameters(ShouldLogParameterValues(command)), + command.Parameters.FormatParameters(ShouldLogSensitiveData()), command.CommandType, command.CommandTimeout, Environment.NewLine, - command.CommandText.TrimEnd()); + logCommandText.TrimEnd()); } if (NeedsEventData( @@ -446,6 +451,7 @@ public virtual InterceptionResult CommandScalarExecuting( var eventData = BroadcastCommandExecuting( connection.DbConnection, command, + logCommandText, context, DbCommandMethod.ExecuteScalar, commandId, @@ -475,6 +481,7 @@ public virtual InterceptionResult CommandScalarExecuting( public virtual InterceptionResult CommandNonQueryExecuting( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -491,11 +498,11 @@ public virtual InterceptionResult CommandNonQueryExecuting( definition.Log( this, - command.Parameters.FormatParameters(ShouldLogParameterValues(command)), + command.Parameters.FormatParameters(ShouldLogSensitiveData()), command.CommandType, command.CommandTimeout, Environment.NewLine, - command.CommandText.TrimEnd()); + logCommandText.TrimEnd()); } if (NeedsEventData( @@ -506,6 +513,7 @@ public virtual InterceptionResult CommandNonQueryExecuting( var eventData = BroadcastCommandExecuting( connection.DbConnection, command, + logCommandText, context, DbCommandMethod.ExecuteNonQuery, commandId, @@ -535,6 +543,7 @@ public virtual InterceptionResult CommandNonQueryExecuting( public virtual ValueTask> CommandReaderExecutingAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -552,11 +561,11 @@ public virtual ValueTask> CommandReaderExecutin definition.Log( this, - command.Parameters.FormatParameters(ShouldLogParameterValues(command)), + command.Parameters.FormatParameters(ShouldLogSensitiveData()), command.CommandType, command.CommandTimeout, Environment.NewLine, - command.CommandText.TrimEnd()); + logCommandText.TrimEnd()); } if (NeedsEventData( @@ -567,6 +576,7 @@ public virtual ValueTask> CommandReaderExecutin var eventData = BroadcastCommandExecuting( connection.DbConnection, command, + logCommandText, context, DbCommandMethod.ExecuteReader, commandId, @@ -596,6 +606,7 @@ public virtual ValueTask> CommandReaderExecutin public virtual ValueTask> CommandScalarExecutingAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -613,11 +624,11 @@ public virtual ValueTask> CommandScalarExecutingAsync definition.Log( this, - command.Parameters.FormatParameters(ShouldLogParameterValues(command)), + command.Parameters.FormatParameters(ShouldLogSensitiveData()), command.CommandType, command.CommandTimeout, Environment.NewLine, - command.CommandText.TrimEnd()); + logCommandText.TrimEnd()); } if (NeedsEventData( @@ -628,6 +639,7 @@ public virtual ValueTask> CommandScalarExecutingAsync var eventData = BroadcastCommandExecuting( connection.DbConnection, command, + logCommandText, context, DbCommandMethod.ExecuteScalar, commandId, @@ -657,6 +669,7 @@ public virtual ValueTask> CommandScalarExecutingAsync public virtual ValueTask> CommandNonQueryExecutingAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -674,11 +687,11 @@ public virtual ValueTask> CommandNonQueryExecutingAsync( definition.Log( this, - command.Parameters.FormatParameters(ShouldLogParameterValues(command)), + command.Parameters.FormatParameters(ShouldLogSensitiveData()), command.CommandType, command.CommandTimeout, Environment.NewLine, - command.CommandText.TrimEnd()); + logCommandText.TrimEnd()); } if (NeedsEventData( @@ -689,6 +702,7 @@ public virtual ValueTask> CommandNonQueryExecutingAsync( var eventData = BroadcastCommandExecuting( connection.DbConnection, command, + logCommandText, context, DbCommandMethod.ExecuteNonQuery, commandId, @@ -712,6 +726,7 @@ public virtual ValueTask> CommandNonQueryExecutingAsync( private CommandEventData BroadcastCommandExecuting( DbConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -728,12 +743,13 @@ private CommandEventData BroadcastCommandExecuting( CommandExecuting, connection, command, + logCommandText, context, executeMethod, commandId, connectionId, async, - ShouldLogParameterValues(command), + ShouldLogSensitiveData(), startTime, commandSource); @@ -750,7 +766,7 @@ static string CommandExecuting(EventDefinitionBase definition, EventData payload p.Command.CommandType, p.Command.CommandTimeout, Environment.NewLine, - p.Command.CommandText.TrimEnd()); + p.LogCommandText.TrimEnd()); } } @@ -767,6 +783,7 @@ static string CommandExecuting(EventDefinitionBase definition, EventData payload public virtual DbDataReader CommandReaderExecuted( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -784,11 +801,11 @@ public virtual DbDataReader CommandReaderExecuted( definition.Log( this, string.Format(CultureInfo.InvariantCulture, "{0:N0}", duration.TotalMilliseconds), - command.Parameters.FormatParameters(ShouldLogParameterValues(command)), + command.Parameters.FormatParameters(ShouldLogSensitiveData()), command.CommandType, command.CommandTimeout, Environment.NewLine, - command.CommandText.TrimEnd()); + logCommandText.TrimEnd()); } if (NeedsEventData( @@ -799,6 +816,7 @@ public virtual DbDataReader CommandReaderExecuted( var eventData = BroadcastCommandExecuted( connection.DbConnection, command, + logCommandText, context, DbCommandMethod.ExecuteReader, commandId, @@ -830,6 +848,7 @@ public virtual DbDataReader CommandReaderExecuted( public virtual object? CommandScalarExecuted( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -847,11 +866,11 @@ public virtual DbDataReader CommandReaderExecuted( definition.Log( this, string.Format(CultureInfo.InvariantCulture, "{0:N0}", duration.TotalMilliseconds), - command.Parameters.FormatParameters(ShouldLogParameterValues(command)), + command.Parameters.FormatParameters(ShouldLogSensitiveData()), command.CommandType, command.CommandTimeout, Environment.NewLine, - command.CommandText.TrimEnd()); + logCommandText.TrimEnd()); } if (NeedsEventData( @@ -862,6 +881,7 @@ public virtual DbDataReader CommandReaderExecuted( var eventData = BroadcastCommandExecuted( connection.DbConnection, command, + logCommandText, context, DbCommandMethod.ExecuteScalar, commandId, @@ -893,6 +913,7 @@ public virtual DbDataReader CommandReaderExecuted( public virtual int CommandNonQueryExecuted( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -910,11 +931,11 @@ public virtual int CommandNonQueryExecuted( definition.Log( this, string.Format(CultureInfo.InvariantCulture, "{0:N0}", duration.TotalMilliseconds), - command.Parameters.FormatParameters(ShouldLogParameterValues(command)), + command.Parameters.FormatParameters(ShouldLogSensitiveData()), command.CommandType, command.CommandTimeout, Environment.NewLine, - command.CommandText.TrimEnd()); + logCommandText.TrimEnd()); } if (NeedsEventData( @@ -925,6 +946,7 @@ public virtual int CommandNonQueryExecuted( var eventData = BroadcastCommandExecuted( connection.DbConnection, command, + logCommandText, context, DbCommandMethod.ExecuteNonQuery, commandId, @@ -956,6 +978,7 @@ public virtual int CommandNonQueryExecuted( public virtual ValueTask CommandReaderExecutedAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -974,11 +997,11 @@ public virtual ValueTask CommandReaderExecutedAsync( definition.Log( this, string.Format(CultureInfo.InvariantCulture, "{0:N0}", duration.TotalMilliseconds), - command.Parameters.FormatParameters(ShouldLogParameterValues(command)), + command.Parameters.FormatParameters(ShouldLogSensitiveData()), command.CommandType, command.CommandTimeout, Environment.NewLine, - command.CommandText.TrimEnd()); + logCommandText.TrimEnd()); } if (NeedsEventData( @@ -989,6 +1012,7 @@ public virtual ValueTask CommandReaderExecutedAsync( var eventData = BroadcastCommandExecuted( connection.DbConnection, command, + logCommandText, context, DbCommandMethod.ExecuteReader, commandId, @@ -1020,6 +1044,7 @@ public virtual ValueTask CommandReaderExecutedAsync( public virtual ValueTask CommandScalarExecutedAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -1038,11 +1063,11 @@ public virtual ValueTask CommandReaderExecutedAsync( definition.Log( this, string.Format(CultureInfo.InvariantCulture, "{0:N0}", duration.TotalMilliseconds), - command.Parameters.FormatParameters(ShouldLogParameterValues(command)), + command.Parameters.FormatParameters(ShouldLogSensitiveData()), command.CommandType, command.CommandTimeout, Environment.NewLine, - command.CommandText.TrimEnd()); + logCommandText.TrimEnd()); } if (NeedsEventData( @@ -1053,6 +1078,7 @@ public virtual ValueTask CommandReaderExecutedAsync( var eventData = BroadcastCommandExecuted( connection.DbConnection, command, + logCommandText, context, DbCommandMethod.ExecuteScalar, commandId, @@ -1084,6 +1110,7 @@ public virtual ValueTask CommandReaderExecutedAsync( public virtual ValueTask CommandNonQueryExecutedAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -1102,11 +1129,11 @@ public virtual ValueTask CommandNonQueryExecutedAsync( definition.Log( this, string.Format(CultureInfo.InvariantCulture, "{0:N0}", duration.TotalMilliseconds), - command.Parameters.FormatParameters(ShouldLogParameterValues(command)), + command.Parameters.FormatParameters(ShouldLogSensitiveData()), command.CommandType, command.CommandTimeout, Environment.NewLine, - command.CommandText.TrimEnd()); + logCommandText.TrimEnd()); } if (NeedsEventData( @@ -1117,6 +1144,7 @@ public virtual ValueTask CommandNonQueryExecutedAsync( var eventData = BroadcastCommandExecuted( connection.DbConnection, command, + logCommandText, context, DbCommandMethod.ExecuteNonQuery, commandId, @@ -1142,6 +1170,7 @@ public virtual ValueTask CommandNonQueryExecutedAsync( private CommandExecutedEventData BroadcastCommandExecuted( DbConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -1160,13 +1189,14 @@ private CommandExecutedEventData BroadcastCommandExecuted( CommandExecuted, connection, command, + logCommandText, context, executeMethod, commandId, connectionId, methodResult, async, - ShouldLogParameterValues(command), + ShouldLogSensitiveData(), startTime, duration, commandSource); @@ -1185,7 +1215,7 @@ static string CommandExecuted(EventDefinitionBase definition, EventData payload) p.Command.CommandType, p.Command.CommandTimeout, Environment.NewLine, - p.Command.CommandText.TrimEnd()); + p.LogCommandText.TrimEnd()); } } @@ -1202,6 +1232,7 @@ static string CommandExecuted(EventDefinitionBase definition, EventData payload) public virtual void CommandError( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -1213,7 +1244,7 @@ public virtual void CommandError( { var definition = RelationalResources.LogCommandFailed(this); - LogCommandError(command, duration, definition); + LogCommandError(command, logCommandText, duration, definition); if (NeedsEventData( definition, out var interceptor, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -1221,6 +1252,7 @@ public virtual void CommandError( var eventData = BroadcastCommandError( connection.DbConnection, command, + logCommandText, context, executeMethod, commandId, @@ -1240,6 +1272,7 @@ public virtual void CommandError( private void LogCommandError( DbCommand command, + string logCommandText, TimeSpan duration, EventDefinition definition) { @@ -1248,11 +1281,11 @@ private void LogCommandError( definition.Log( this, string.Format(CultureInfo.InvariantCulture, "{0:N0}", duration.TotalMilliseconds), - command.Parameters.FormatParameters(ShouldLogParameterValues(command)), + command.Parameters.FormatParameters(ShouldLogSensitiveData()), command.CommandType, command.CommandTimeout, Environment.NewLine, - command.CommandText.TrimEnd()); + logCommandText.TrimEnd()); } } @@ -1265,6 +1298,7 @@ private void LogCommandError( public virtual Task CommandErrorAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -1277,7 +1311,7 @@ public virtual Task CommandErrorAsync( { var definition = RelationalResources.LogCommandFailed(this); - LogCommandError(command, duration, definition); + LogCommandError(command, logCommandText, duration, definition); if (NeedsEventData( definition, out var interceptor, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -1285,6 +1319,7 @@ public virtual Task CommandErrorAsync( var eventData = BroadcastCommandError( connection.DbConnection, command, + logCommandText, context, executeMethod, commandId, @@ -1310,6 +1345,7 @@ public virtual Task CommandErrorAsync( private CommandErrorEventData BroadcastCommandError( DbConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -1328,13 +1364,14 @@ private CommandErrorEventData BroadcastCommandError( CommandError, connection, command, + logCommandText, context, executeMethod, commandId, connectionId, exception, async, - ShouldLogParameterValues(command), + ShouldLogSensitiveData(), startTime, duration, commandSource); @@ -1353,7 +1390,7 @@ static string CommandError(EventDefinitionBase definition, EventData payload) p.Command.CommandType, p.Command.CommandTimeout, Environment.NewLine, - p.Command.CommandText.TrimEnd()); + p.LogCommandText.TrimEnd()); } } @@ -1365,6 +1402,7 @@ static string CommandError(EventDefinitionBase definition, EventData payload) public virtual void CommandCanceled( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -1375,7 +1413,7 @@ public virtual void CommandCanceled( { var definition = RelationalResources.LogCommandCanceled(this); - LogCommandCanceled(command, duration, definition); + LogCommandCanceled(command, logCommandText, duration, definition); if (NeedsEventData( definition, out var interceptor, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -1383,6 +1421,7 @@ public virtual void CommandCanceled( var eventData = BroadcastCommandCanceled( connection.DbConnection, command, + logCommandText, context, executeMethod, commandId, @@ -1403,6 +1442,7 @@ public virtual void CommandCanceled( public virtual Task CommandCanceledAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -1414,7 +1454,7 @@ public virtual Task CommandCanceledAsync( { var definition = RelationalResources.LogCommandCanceled(this); - LogCommandCanceled(command, duration, definition); + LogCommandCanceled(command, logCommandText, duration, definition); if (NeedsEventData( definition, out var interceptor, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -1422,6 +1462,7 @@ public virtual Task CommandCanceledAsync( var eventData = BroadcastCommandCanceled( connection.DbConnection, command, + logCommandText, context, executeMethod, commandId, @@ -1445,6 +1486,7 @@ public virtual Task CommandCanceledAsync( private void LogCommandCanceled( DbCommand command, + string logCommandText, TimeSpan duration, EventDefinition definition) { @@ -1453,17 +1495,18 @@ private void LogCommandCanceled( definition.Log( this, string.Format(CultureInfo.InvariantCulture, "{0:N0}", duration.TotalMilliseconds), - command.Parameters.FormatParameters(ShouldLogParameterValues(command)), + command.Parameters.FormatParameters(ShouldLogSensitiveData()), command.CommandType, command.CommandTimeout, Environment.NewLine, - command.CommandText.TrimEnd()); + logCommandText.TrimEnd()); } } private CommandEndEventData BroadcastCommandCanceled( DbConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -1481,12 +1524,13 @@ private CommandEndEventData BroadcastCommandCanceled( CommandCanceled, connection, command, + logCommandText, context, executeMethod, commandId, connectionId, async, - ShouldLogParameterValues(command), + ShouldLogSensitiveData(), startTime, duration, commandSource); @@ -1505,7 +1549,7 @@ static string CommandCanceled(EventDefinitionBase definition, EventData payload) p.Command.CommandType, p.Command.CommandTimeout, Environment.NewLine, - p.Command.CommandText.TrimEnd()); + p.LogCommandText.TrimEnd()); } } @@ -1738,8 +1782,5 @@ public virtual bool ShouldLogDataReaderClose(DateTimeOffset now) public virtual bool ShouldLogDataReaderDispose(DateTimeOffset now) => now > _suppressDataReaderDisposingExpiration; - private bool ShouldLogParameterValues(DbCommand command) - => command.Parameters.Count > 0 && ShouldLogSensitiveData(); - #endregion ShouldLog checks } diff --git a/src/EFCore.Relational/Diagnostics/Internal/RelationalConnectionDiagnosticsLogger.cs b/src/EFCore.Relational/Diagnostics/Internal/RelationalConnectionDiagnosticsLogger.cs index 5777768e8a0..0418bdc277f 100644 --- a/src/EFCore.Relational/Diagnostics/Internal/RelationalConnectionDiagnosticsLogger.cs +++ b/src/EFCore.Relational/Diagnostics/Internal/RelationalConnectionDiagnosticsLogger.cs @@ -892,6 +892,121 @@ private void LogConnectionError( #endregion ConnectionError + #region ConnectionCanceled + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void ConnectionCanceled( + IRelationalConnection connection, + DateTimeOffset startTime, + TimeSpan duration) + { + var definition = RelationalResources.LogConnectionCanceled(this); + + LogConnectionCanceled(connection, definition); + + if (NeedsEventData( + definition, out var interceptor, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = BroadcastConnectionCanceled( + connection, + startTime, + duration, + async: false, + definition, + diagnosticSourceEnabled, + simpleLogEnabled); + + interceptor?.ConnectionCanceled(connection.DbConnection, eventData); + } + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Task ConnectionCanceledAsync( + IRelationalConnection connection, + DateTimeOffset startTime, + TimeSpan duration, + CancellationToken cancellationToken = default) + { + var definition = RelationalResources.LogConnectionCanceled(this); + + LogConnectionCanceled(connection, definition); + + if (NeedsEventData( + definition, out var interceptor, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = BroadcastConnectionCanceled( + connection, + startTime, + duration, + async: true, + definition, + diagnosticSourceEnabled, + simpleLogEnabled); + + interceptor?.ConnectionCanceled(connection.DbConnection, eventData); + } + + return Task.CompletedTask; + } + + private ConnectionEndEventData BroadcastConnectionCanceled( + IRelationalConnection connection, + DateTimeOffset startTime, + TimeSpan duration, + bool async, + EventDefinition definition, + bool diagnosticSourceEnabled, + bool simpleLogEnabled) + { + var eventData = new ConnectionEndEventData( + definition, + ConnectionCanceled, + connection.DbConnection, + connection.Context, + connection.ConnectionId, + async, + startTime, + duration); + + DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); + + return eventData; + + static string ConnectionCanceled(EventDefinitionBase definition, EventData payload) + { + var d = (EventDefinition)definition; + var p = (ConnectionEndEventData)payload; + return d.GenerateMessage( + p.Connection.Database, + p.Connection.DataSource); + } + } + + private void LogConnectionCanceled( + IRelationalConnection connection, + EventDefinition definition) + { + if (ShouldLog(definition)) + { + definition.Log( + this, + connection.DbConnection.Database, + connection.DbConnection.DataSource); + } + } + + #endregion ConnectionCanceled + #region ConnectionCreating /// diff --git a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs index 0b1f48c5275..e4bb8529c3f 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs @@ -33,6 +33,7 @@ private enum Id ConnectionCreated, ConnectionDisposing, ConnectionDisposed, + ConnectionCanceled, // Command events CommandExecuting = CoreEventId.RelationalBaseId + 100, @@ -217,6 +218,19 @@ private static EventId MakeConnectionId(Id id) /// public static readonly EventId ConnectionError = MakeConnectionId(Id.ConnectionError); + /// + /// A open operation has been canceled. + /// + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + public static readonly EventId ConnectionCanceled = MakeConnectionId(Id.ConnectionCanceled); + /// /// A is about to be created by EF. /// diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs index 9634f027bf7..a4c49f8b04f 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs @@ -133,6 +133,15 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions [EntityFrameworkInternal] public EventDefinitionBase? LogConnectionError; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public EventDefinitionBase? LogConnectionCanceled; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs index 517205fb735..2e0a92fb071 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs @@ -39,9 +39,6 @@ public static class RelationalEntityTypeExtensions return ((entityType as IConventionEntityType)?.GetViewNameConfigurationSource() == null) && (entityType as IConventionEntityType)?.GetFunctionNameConfigurationSource() == null -#pragma warning disable CS0618 // Type or member is obsolete - && (entityType as IConventionEntityType)?.GetDefiningQueryConfigurationSource() == null -#pragma warning restore CS0618 // Type or member is obsolete && (entityType as IConventionEntityType)?.GetSqlQueryConfigurationSource() == null ? GetDefaultTableName(entityType) : null; @@ -267,9 +264,6 @@ public static void SetSchema(this IMutableEntityType entityType, string? value) } return ((entityType as IConventionEntityType)?.GetFunctionNameConfigurationSource() == null) -#pragma warning disable CS0618 // Type or member is obsolete - && ((entityType as IConventionEntityType)?.GetDefiningQueryConfigurationSource() == null) -#pragma warning restore CS0618 // Type or member is obsolete && ((entityType as IConventionEntityType)?.GetSqlQueryConfigurationSource() == null) ? GetDefaultViewName(entityType) : null; diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index a2bd60aaecc..84bf260d1ec 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -1957,6 +1957,11 @@ protected override void ValidateInheritanceMapping( var discriminatorValues = new Dictionary(); foreach (var derivedType in derivedTypes) { + foreach (var complexProperty in derivedType.GetDeclaredComplexProperties()) + { + ValidateDiscriminatorValues(complexProperty.ComplexType); + } + var discriminatorValue = derivedType.GetDiscriminatorValue(); if (!derivedType.ClrType.IsInstantiable() || discriminatorValue is null) diff --git a/src/EFCore.Relational/Metadata/Conventions/DiscriminatorLengthConvention.cs b/src/EFCore.Relational/Metadata/Conventions/DiscriminatorLengthConvention.cs index cb149ff89f4..5c9fee094a2 100644 --- a/src/EFCore.Relational/Metadata/Conventions/DiscriminatorLengthConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/DiscriminatorLengthConvention.cs @@ -54,7 +54,7 @@ public virtual void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, && !discriminatorProperty.IsForeignKey()) { var maxDiscriminatorValueLength = - entityType.GetDerivedTypesInclusive().Select(e => ((string)e.GetDiscriminatorValue()!).Length).Max(); + entityType.GetDerivedTypesInclusive().Select(e => (e.GetDiscriminatorValue() as string)?.Length ?? 0).Max(); var previous = 1; var current = 1; diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalQueryFilterRewritingConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalQueryFilterRewritingConvention.cs index 8453e441377..1cb87baf3c2 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalQueryFilterRewritingConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalQueryFilterRewritingConvention.cs @@ -39,14 +39,6 @@ public override void ProcessModelFinalizing( { entityType.SetQueryFilter((LambdaExpression)DbSetAccessRewriter.Rewrite(modelBuilder.Metadata, queryFilter)); } - -#pragma warning disable CS0618 // Type or member is obsolete - var definingQuery = ((IEntityType)entityType).GetDefiningQuery(); - if (definingQuery != null) - { - entityType.SetDefiningQuery((LambdaExpression)DbSetAccessRewriter.Rewrite(modelBuilder.Metadata, definingQuery)); - } -#pragma warning restore CS0618 // Type or member is obsolete } } diff --git a/src/EFCore.Relational/Metadata/Internal/JsonColumn.cs b/src/EFCore.Relational/Metadata/Internal/JsonColumn.cs index 3ad9900707c..10354167307 100644 --- a/src/EFCore.Relational/Metadata/Internal/JsonColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/JsonColumn.cs @@ -36,7 +36,7 @@ public JsonColumn( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override RelationalTypeMapping GetDefaultStoreTypeMapping() - => (RelationalTypeMapping)Table.Model.Model.GetModelDependencies().TypeMappingSource.FindMapping(typeof(JsonElement))!; + => (RelationalTypeMapping)Table.Model.Model.GetModelDependencies().TypeMappingSource.FindMapping(typeof(JsonTypePlaceholder))!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/JsonColumnBase.cs b/src/EFCore.Relational/Metadata/Internal/JsonColumnBase.cs index 3209bf64e74..acc1bf09fe9 100644 --- a/src/EFCore.Relational/Metadata/Internal/JsonColumnBase.cs +++ b/src/EFCore.Relational/Metadata/Internal/JsonColumnBase.cs @@ -36,5 +36,5 @@ public JsonColumnBase( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override RelationalTypeMapping GetDefaultStoreTypeMapping() - => (RelationalTypeMapping)Table.Model.Model.GetModelDependencies().TypeMappingSource.FindMapping(typeof(JsonElement))!; + => (RelationalTypeMapping)Table.Model.Model.GetModelDependencies().TypeMappingSource.FindMapping(typeof(JsonTypePlaceholder))!; } diff --git a/src/EFCore.Relational/Metadata/Internal/JsonViewColumn.cs b/src/EFCore.Relational/Metadata/Internal/JsonViewColumn.cs index 77c327332e3..4b57ad45b70 100644 --- a/src/EFCore.Relational/Metadata/Internal/JsonViewColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/JsonViewColumn.cs @@ -36,5 +36,5 @@ public JsonViewColumn( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override RelationalTypeMapping GetDefaultStoreTypeMapping() - => (RelationalTypeMapping)Table.Model.Model.GetModelDependencies().TypeMappingSource.FindMapping(typeof(JsonElement))!; + => (RelationalTypeMapping)Table.Model.Model.GetModelDependencies().TypeMappingSource.FindMapping(typeof(JsonTypePlaceholder))!; } diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 8e2294fe404..27709772f4c 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -582,7 +582,7 @@ private static void CreateContainerColumn( { Check.DebugAssert(tableBase.FindColumn(containerColumnName) == null, $"Table does not have column '{containerColumnName}'."); - var jsonColumnTypeMapping = relationalTypeMappingSource.FindMapping(typeof(JsonElement), storeTypeName: containerColumnType)!; + var jsonColumnTypeMapping = relationalTypeMappingSource.FindMapping(typeof(JsonTypePlaceholder), storeTypeName: containerColumnType)!; var jsonColumn = createColumn(containerColumnName, containerColumnType, tableBase, jsonColumnTypeMapping); tableBase.Columns.Add(containerColumnName, jsonColumn); jsonColumn.IsNullable = !ownership.IsRequiredDependent || !ownership.IsUnique; diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 5220ac5bddc..13c8cbcf2a3 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -2146,7 +2146,7 @@ public static string UnsupportedOperatorForSqlExpression(object? nodeType, objec nodeType, expressionType); /// - /// No relational type mapping can be found for property '{entity}.{property}' and the current provider doesn't specify a default store type for the properties of type '{clrType}'. + /// No relational type mapping can be found for property '{entity}.{property}' and the current provider doesn't specify a default store type for the properties of type '{clrType}'. /// public static string UnsupportedPropertyType(object? entity, object? property, object? clrType) => string.Format( @@ -2971,6 +2971,31 @@ public static EventDefinition LogConnectionErrorAsDebug(IDiagnos return (EventDefinition)definition; } + /// + /// A connection open was canceled to database '{database}' on server '{server}'. + /// + public static EventDefinition LogConnectionCanceled(IDiagnosticsLogger logger) + { + var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogConnectionCanceled; + if (definition == null) + { + definition = NonCapturingLazyInitializer.EnsureInitialized( + ref ((RelationalLoggingDefinitions)logger.Definitions).LogConnectionCanceled, + logger, + static logger => new EventDefinition( + logger.Options, + RelationalEventId.ConnectionCanceled, + LogLevel.Debug, + "RelationalEventId.ConnectionCanceled", + level => LoggerMessage.Define( + level, + RelationalEventId.ConnectionCanceled, + _resourceManager.GetString("LogConnectionCanceled")!))); + } + + return (EventDefinition)definition; + } + /// /// Created transaction savepoint. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 2641f26d37e..c5604c77a73 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -716,6 +716,10 @@ An error occurred using the connection to database '{database}' on server '{server}'. Debug RelationalEventId.ConnectionError string string + + A connection open was canceled to database '{database}' on server '{server}'. + Debug RelationalEventId.ConnectionCanceled string string string + Created transaction savepoint. Debug RelationalEventId.CreatedTransactionSavepoint diff --git a/src/EFCore.Relational/Query/ISqlExpressionFactory.cs b/src/EFCore.Relational/Query/ISqlExpressionFactory.cs index 2712bec47ed..d60a38c5a02 100644 --- a/src/EFCore.Relational/Query/ISqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/ISqlExpressionFactory.cs @@ -431,6 +431,25 @@ SqlExpression NiladicFunction( /// An expression representing a constant in a SQL tree. SqlExpression Constant(object? value, Type type, RelationalTypeMapping? typeMapping = null); + /// + /// Creates a new which represents a constant in a SQL tree. + /// + /// A value. + /// if the expression contains sensitive values; otherwise, . + /// The associated with the expression. + /// An expression representing a constant in a SQL tree. + SqlExpression Constant(object value, bool sensitive, RelationalTypeMapping? typeMapping = null); + + /// + /// Creates a new which represents a constant in a SQL tree. + /// + /// A value. + /// The type for the constant. Useful when value is null. + /// if the expression contains sensitive values; otherwise, . + /// The associated with the expression. + /// An expression representing a constant in a SQL tree. + SqlExpression Constant(object? value, Type type, bool sensitive, RelationalTypeMapping? typeMapping = null); + /// /// Creates a new which represents a SQL token. /// diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs index b0a7ad8fb7c..8abac687511 100644 --- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs @@ -628,7 +628,7 @@ protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpres protected override Expression VisitSqlConstant(SqlConstantExpression sqlConstantExpression) { _relationalCommandBuilder - .Append(sqlConstantExpression.TypeMapping!.GenerateSqlLiteral(sqlConstantExpression.Value)); + .Append(sqlConstantExpression.TypeMapping!.GenerateSqlLiteral(sqlConstantExpression.Value), sqlConstantExpression.IsSensitive); return sqlConstantExpression; } diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs index ea11f3b5dd8..fa5f9d7323a 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs @@ -678,6 +678,7 @@ Expression GenerateRelationalCommandExpression(IReadOnlyDictionary) ])!, Property( @@ -685,6 +686,7 @@ Expression GenerateRelationalCommandExpression(IReadOnlyDictionary().Select( diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index 362bba301d1..ff0ec799f8a 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -976,4 +976,12 @@ public virtual SqlExpression Constant(object value, RelationalTypeMapping? typeM /// public virtual SqlExpression Constant(object? value, Type type, RelationalTypeMapping? typeMapping = null) => new SqlConstantExpression(value, type, typeMapping); + + /// + public virtual SqlExpression Constant(object value, bool sensitive, RelationalTypeMapping? typeMapping = null) + => new SqlConstantExpression(value, sensitive, typeMapping); + + /// + public virtual SqlExpression Constant(object? value, Type type, bool sensitive, RelationalTypeMapping? typeMapping = null) + => new SqlConstantExpression(value, type, sensitive, typeMapping); } diff --git a/src/EFCore.Relational/Query/SqlExpressions/SqlConstantExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SqlConstantExpression.cs index 84a4480caf0..dd7279f0927 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SqlConstantExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SqlConstantExpression.cs @@ -25,8 +25,9 @@ public class SqlConstantExpression : SqlExpression /// The of the expression. /// The associated with the expression. public SqlConstantExpression(object? value, Type type, RelationalTypeMapping? typeMapping) - : base(type.UnwrapNullableType(), typeMapping) - => Value = value; + : this(value, type, false, typeMapping) + { + } /// /// Creates a new instance of the class. @@ -34,7 +35,32 @@ public SqlConstantExpression(object? value, Type type, RelationalTypeMapping? ty /// An to set the property equal to. /// The associated with the expression. public SqlConstantExpression(object value, RelationalTypeMapping? typeMapping) - : this(value, value.GetType(), typeMapping) + : this(value, false, typeMapping) + { + } + + /// + /// Creates a new instance of the class. + /// + /// An to set the property equal to. + /// The of the expression. + /// if the expression contains sensitive values; otherwise, . + /// The associated with the expression. + public SqlConstantExpression(object? value, Type type, bool sensitive, RelationalTypeMapping? typeMapping) + : base(type.UnwrapNullableType(), typeMapping) + { + Value = value; + IsSensitive = sensitive; + } + + /// + /// Creates a new instance of the class. + /// + /// An to set the property equal to. + /// if the expression contains sensitive values; otherwise, . + /// The associated with the expression. + public SqlConstantExpression(object value, bool sensitive, RelationalTypeMapping? typeMapping) + : this(value, value.GetType(), sensitive, typeMapping) { } @@ -45,7 +71,7 @@ public SqlConstantExpression(object value, RelationalTypeMapping? typeMapping) /// The associated with the expression. [Obsolete("Call the constructor accepting a value (and possibly a Type) instead")] public SqlConstantExpression(ConstantExpression constantExpression, RelationalTypeMapping? typeMapping) - : this(constantExpression.Value, constantExpression.Type, typeMapping) + : this(constantExpression.Value, constantExpression.Type, sensitive: false, typeMapping) { } @@ -54,13 +80,18 @@ public SqlConstantExpression(ConstantExpression constantExpression, RelationalTy /// public virtual object? Value { get; } + /// + /// Whether the expression contains sensitive values. + /// + public virtual bool IsSensitive { get; } + /// /// Applies supplied type mapping to this expression. /// /// A relational type mapping to apply. /// A new expression which has supplied type mapping. public virtual SqlExpression ApplyTypeMapping(RelationalTypeMapping? typeMapping) - => new SqlConstantExpression(Value, Type, typeMapping); + => new SqlConstantExpression(Value, Type, IsSensitive, typeMapping); /// protected override Expression VisitChildren(ExpressionVisitor visitor) @@ -70,12 +101,13 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) public override Expression Quote() => New( _quotingConstructor ??= typeof(SqlConstantExpression) - .GetConstructor([typeof(object), typeof(Type), typeof(RelationalTypeMapping)])!, + .GetConstructor([typeof(object), typeof(Type), typeof(bool), typeof(RelationalTypeMapping)])!, Type.IsValueType ? Convert( Constant(Value, Type), typeof(object)) : Constant(Value, Type), Constant(Type), + Constant(IsSensitive), RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); /// diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index ef0e6280cb8..a26ca0af260 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -129,7 +129,7 @@ protected override Expression VisitExtension(Expression node) processedValues.Add( new RowValueExpression( [ - _sqlExpressionFactory.Constant(value, value?.GetType() ?? typeof(object), typeMapping) + _sqlExpressionFactory.Constant(value, value?.GetType() ?? typeof(object), sensitive: true, typeMapping) ])); } @@ -765,7 +765,7 @@ InExpression ProcessInExpressionValues( continue; } - processedValues.Add(_sqlExpressionFactory.Constant(value, value?.GetType() ?? typeof(object), typeMapping)); + processedValues.Add(_sqlExpressionFactory.Constant(value, value?.GetType() ?? typeof(object), sensitive: true, typeMapping)); } } else @@ -1329,6 +1329,7 @@ protected virtual SqlExpression VisitSqlParameter( return _sqlExpressionFactory.Constant( parameterValue, sqlParameterExpression.Type, + sensitive: true, sqlParameterExpression.TypeMapping); } diff --git a/src/EFCore.Relational/Storage/IRelationalCommandBuilder.cs b/src/EFCore.Relational/Storage/IRelationalCommandBuilder.cs index c147558f126..002fc9ea641 100644 --- a/src/EFCore.Relational/Storage/IRelationalCommandBuilder.cs +++ b/src/EFCore.Relational/Storage/IRelationalCommandBuilder.cs @@ -53,15 +53,17 @@ public interface IRelationalCommandBuilder /// Appends an object to the command text. /// /// The object to be written. + /// Whether the value should be redacted (i.e. in log). /// The same builder instance so that multiple calls can be chained. - IRelationalCommandBuilder Append(string value); + IRelationalCommandBuilder Append(string value, bool redact = false); /// /// Appends an object to the command text. /// /// The object to be written. + /// Whether the value should be redacted (i.e. in log). /// The same builder instance so that multiple calls can be chained. - IRelationalCommandBuilder Append(FormattableString value); + IRelationalCommandBuilder Append(FormattableString value, bool redact = false); /// /// Appends a blank line to the command text. diff --git a/src/EFCore.Relational/Storage/IRelationalCommandTemplate.cs b/src/EFCore.Relational/Storage/IRelationalCommandTemplate.cs index 3f184eee826..22d83c04afd 100644 --- a/src/EFCore.Relational/Storage/IRelationalCommandTemplate.cs +++ b/src/EFCore.Relational/Storage/IRelationalCommandTemplate.cs @@ -23,6 +23,11 @@ public interface IRelationalCommandTemplate /// string CommandText { get; } + /// + /// Gets the command text to be logged. + /// + string LogCommandText { get; } + /// /// Gets the parameters to be copied to the destination command. /// diff --git a/src/EFCore.Relational/Storage/JsonTypeMapping.cs b/src/EFCore.Relational/Storage/JsonTypeMapping.cs index 289bd85144c..9a14f340bae 100644 --- a/src/EFCore.Relational/Storage/JsonTypeMapping.cs +++ b/src/EFCore.Relational/Storage/JsonTypeMapping.cs @@ -2,13 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Data; -using System.Text.Json; namespace Microsoft.EntityFrameworkCore.Storage; /// /// -/// Represents the mapping between a type and a database type. +/// Represents the mapping between a JSON object and a database type. /// /// /// This type is typically used by database providers (and other extensions). It is generally diff --git a/src/EFCore.Relational/Storage/JsonTypePlaceholder.cs b/src/EFCore.Relational/Storage/JsonTypePlaceholder.cs new file mode 100644 index 00000000000..027ee3465d7 --- /dev/null +++ b/src/EFCore.Relational/Storage/JsonTypePlaceholder.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Storage; + +/// +/// A type representing CLR type of the JsonTypeMapping. +/// +public sealed class JsonTypePlaceholder +{ + private JsonTypePlaceholder() + { + } +} diff --git a/src/EFCore.Relational/Storage/RelationalCommand.cs b/src/EFCore.Relational/Storage/RelationalCommand.cs index 3d271f5cd73..20e81d269ed 100644 --- a/src/EFCore.Relational/Storage/RelationalCommand.cs +++ b/src/EFCore.Relational/Storage/RelationalCommand.cs @@ -33,14 +33,17 @@ public class RelationalCommand : IRelationalCommand /// /// Service dependencies. /// The text of the command to be executed. + /// Text to be logged for the command. /// Parameters for the command. public RelationalCommand( RelationalCommandBuilderDependencies dependencies, string commandText, + string logCommandText, IReadOnlyList parameters) { Dependencies = dependencies; CommandText = commandText; + LogCommandText = logCommandText; Parameters = parameters; } @@ -54,6 +57,11 @@ public RelationalCommand( /// public virtual string CommandText { get; private set; } + /// + /// Gets the command text to be logged. + /// + public virtual string LogCommandText { get; private set; } + /// /// Gets the parameters for the command. /// @@ -89,6 +97,7 @@ public virtual int ExecuteNonQuery(RelationalCommandParameterObject parameterObj var interceptionResult = logger?.CommandNonQueryExecuting( connection, command, + LogCommandText, context, commandId, connection.ConnectionId, @@ -103,6 +112,7 @@ public virtual int ExecuteNonQuery(RelationalCommandParameterObject parameterObj return logger?.CommandNonQueryExecuted( connection, command, + LogCommandText, context, commandId, connection.ConnectionId, @@ -124,6 +134,7 @@ public virtual int ExecuteNonQuery(RelationalCommandParameterObject parameterObj logger?.CommandCanceled( connection, command, + LogCommandText, context, DbCommandMethod.ExecuteNonQuery, commandId, @@ -137,6 +148,7 @@ public virtual int ExecuteNonQuery(RelationalCommandParameterObject parameterObj logger?.CommandError( connection, command, + LogCommandText, context, DbCommandMethod.ExecuteNonQuery, commandId, @@ -193,6 +205,7 @@ public virtual async Task ExecuteNonQueryAsync( : await logger.CommandNonQueryExecutingAsync( connection, command, + LogCommandText, context, commandId, connection.ConnectionId, @@ -210,6 +223,7 @@ public virtual async Task ExecuteNonQueryAsync( result = await logger.CommandNonQueryExecutedAsync( connection, command, + LogCommandText, context, commandId, connection.ConnectionId, @@ -237,6 +251,7 @@ public virtual async Task ExecuteNonQueryAsync( await logger.CommandCanceledAsync( connection, command, + LogCommandText, context, DbCommandMethod.ExecuteNonQuery, commandId, @@ -252,6 +267,7 @@ await logger.CommandCanceledAsync( await logger.CommandErrorAsync( connection, command, + LogCommandText, context, DbCommandMethod.ExecuteNonQuery, commandId, @@ -303,6 +319,7 @@ await logger.CommandErrorAsync( var interceptionResult = logger?.CommandScalarExecuting( connection, command, + LogCommandText, context, commandId, connection.ConnectionId, @@ -317,6 +334,7 @@ await logger.CommandErrorAsync( return logger?.CommandScalarExecuted( connection, command, + LogCommandText, context, commandId, connection.ConnectionId, @@ -338,6 +356,7 @@ await logger.CommandErrorAsync( logger?.CommandCanceled( connection, command, + LogCommandText, context, DbCommandMethod.ExecuteScalar, commandId, @@ -351,6 +370,7 @@ await logger.CommandErrorAsync( logger?.CommandError( connection, command, + LogCommandText, context, DbCommandMethod.ExecuteScalar, commandId, @@ -407,6 +427,7 @@ await logger.CommandErrorAsync( : await logger.CommandScalarExecutingAsync( connection, command, + LogCommandText, context, commandId, connection.ConnectionId, @@ -424,6 +445,7 @@ await logger.CommandErrorAsync( result = await logger.CommandScalarExecutedAsync( connection, command, + LogCommandText, context, commandId, connection.ConnectionId, @@ -450,6 +472,7 @@ await logger.CommandErrorAsync( await logger.CommandCanceledAsync( connection, command, + LogCommandText, context, DbCommandMethod.ExecuteScalar, commandId, @@ -465,6 +488,7 @@ await logger.CommandCanceledAsync( await logger.CommandErrorAsync( connection, command, + LogCommandText, context, DbCommandMethod.ExecuteScalar, commandId, @@ -521,6 +545,7 @@ public virtual RelationalDataReader ExecuteReader(RelationalCommandParameterObje var interceptionResult = logger!.CommandReaderExecuting( connection, command, + LogCommandText, context, commandId, connection.ConnectionId, @@ -534,6 +559,7 @@ public virtual RelationalDataReader ExecuteReader(RelationalCommandParameterObje reader = logger.CommandReaderExecuted( connection, command, + LogCommandText, context, commandId, connection.ConnectionId, @@ -554,6 +580,7 @@ public virtual RelationalDataReader ExecuteReader(RelationalCommandParameterObje logger?.CommandCanceled( connection, command, + LogCommandText, context, DbCommandMethod.ExecuteReader, commandId, @@ -567,6 +594,7 @@ public virtual RelationalDataReader ExecuteReader(RelationalCommandParameterObje logger?.CommandError( connection, command, + LogCommandText, context, DbCommandMethod.ExecuteReader, commandId, @@ -647,6 +675,7 @@ public virtual async Task ExecuteReaderAsync( var interceptionResult = await logger!.CommandReaderExecutingAsync( connection, command, + LogCommandText, context, commandId, connection.ConnectionId, @@ -662,6 +691,7 @@ public virtual async Task ExecuteReaderAsync( reader = await logger.CommandReaderExecutedAsync( connection, command, + LogCommandText, context, commandId, connection.ConnectionId, @@ -686,6 +716,7 @@ public virtual async Task ExecuteReaderAsync( await logger.CommandCanceledAsync( connection, command, + LogCommandText, context, DbCommandMethod.ExecuteReader, commandId, @@ -701,6 +732,7 @@ await logger.CommandCanceledAsync( await logger.CommandErrorAsync( connection, command, + LogCommandText, context, DbCommandMethod.ExecuteReader, commandId, @@ -874,6 +906,7 @@ protected virtual RelationalDataReader CreateRelationalDataReader() public virtual void PopulateFrom(IRelationalCommandTemplate commandTemplate) { CommandText = commandTemplate.CommandText; + LogCommandText = commandTemplate.LogCommandText; Parameters = commandTemplate.Parameters; } } diff --git a/src/EFCore.Relational/Storage/RelationalCommandBuilder.cs b/src/EFCore.Relational/Storage/RelationalCommandBuilder.cs index 079cf521883..6a05c14b430 100644 --- a/src/EFCore.Relational/Storage/RelationalCommandBuilder.cs +++ b/src/EFCore.Relational/Storage/RelationalCommandBuilder.cs @@ -10,6 +10,7 @@ public class RelationalCommandBuilder : IRelationalCommandBuilder { private readonly List _parameters = []; private readonly IndentedStringBuilder _commandTextBuilder = new(); + private IndentedStringBuilder? _logCommandTextBuilder; /// /// @@ -37,7 +38,11 @@ public virtual IRelationalTypeMappingSource TypeMappingSource /// public virtual IRelationalCommand Build() - => new RelationalCommand(Dependencies, _commandTextBuilder.ToString(), Parameters); + { + var commandText = _commandTextBuilder.ToString(); + var logCommandText = _logCommandTextBuilder?.ToString() ?? commandText; + return new RelationalCommand(Dependencies, commandText, logCommandText, Parameters); + } /// /// Gets the command text. @@ -66,17 +71,21 @@ public virtual IRelationalCommandBuilder RemoveParameterAt(int index) } /// - public virtual IRelationalCommandBuilder Append(string value) + public virtual IRelationalCommandBuilder Append(string value, bool redact = false) { + InitializeLogCommandTextBuilderIfNeeded(redact); _commandTextBuilder.Append(value); + _logCommandTextBuilder?.Append(redact ? "?" : value); return this; } /// - public virtual IRelationalCommandBuilder Append(FormattableString value) + public virtual IRelationalCommandBuilder Append(FormattableString value, bool redact = false) { + InitializeLogCommandTextBuilderIfNeeded(redact); _commandTextBuilder.Append(value); + _logCommandTextBuilder?.Append(redact ? $"?" : value); return this; } @@ -85,6 +94,7 @@ public virtual IRelationalCommandBuilder Append(FormattableString value) public virtual IRelationalCommandBuilder AppendLine() { _commandTextBuilder.AppendLine(); + _logCommandTextBuilder?.AppendLine(); return this; } @@ -93,6 +103,7 @@ public virtual IRelationalCommandBuilder AppendLine() public virtual IRelationalCommandBuilder IncrementIndent() { _commandTextBuilder.IncrementIndent(); + _logCommandTextBuilder?.IncrementIndent(); return this; } @@ -101,6 +112,7 @@ public virtual IRelationalCommandBuilder IncrementIndent() public virtual IRelationalCommandBuilder DecrementIndent() { _commandTextBuilder.DecrementIndent(); + _logCommandTextBuilder?.DecrementIndent(); return this; } @@ -108,4 +120,14 @@ public virtual IRelationalCommandBuilder DecrementIndent() /// public virtual int CommandTextLength => _commandTextBuilder.Length; + + private void InitializeLogCommandTextBuilderIfNeeded(bool redact) + { + if (redact + && _logCommandTextBuilder is null + && !Dependencies.LoggingOptions.IsSensitiveDataLoggingEnabled) + { + _logCommandTextBuilder = _commandTextBuilder.Clone(); + } + } } diff --git a/src/EFCore.Relational/Storage/RelationalCommandBuilderDependencies.cs b/src/EFCore.Relational/Storage/RelationalCommandBuilderDependencies.cs index 79bbd85c859..3db84e30c0c 100644 --- a/src/EFCore.Relational/Storage/RelationalCommandBuilderDependencies.cs +++ b/src/EFCore.Relational/Storage/RelationalCommandBuilderDependencies.cs @@ -46,9 +46,11 @@ public sealed record RelationalCommandBuilderDependencies [EntityFrameworkInternal] public RelationalCommandBuilderDependencies( IRelationalTypeMappingSource typeMappingSource, - IExceptionDetector exceptionDetector) + IExceptionDetector exceptionDetector, + ILoggingOptions loggingOptions) { ExceptionDetector = exceptionDetector; + LoggingOptions = loggingOptions; #pragma warning disable CS0618 // Type or member is obsolete TypeMappingSource = typeMappingSource; @@ -60,6 +62,11 @@ public RelationalCommandBuilderDependencies( /// public IExceptionDetector ExceptionDetector { get; init; } + /// + /// The logging options. + /// + public ILoggingOptions LoggingOptions { get; init; } + /// /// The source for s to use. /// diff --git a/src/EFCore.Relational/Storage/RelationalConnection.cs b/src/EFCore.Relational/Storage/RelationalConnection.cs index 8b1169d3641..f5507648334 100644 --- a/src/EFCore.Relational/Storage/RelationalConnection.cs +++ b/src/EFCore.Relational/Storage/RelationalConnection.cs @@ -42,6 +42,7 @@ public abstract class RelationalConnection : IRelationalConnection, ITransaction private readonly ConcurrentStack _ambientTransactions = new(); private DbConnection? _connection; private readonly IRelationalCommandBuilder _relationalCommandBuilder; + private readonly IExceptionDetector _exceptionDetector; private IRelationalCommand? _cachedRelationalCommand; /// @@ -74,6 +75,8 @@ protected RelationalConnection(RelationalConnectionDependencies dependencies) { _connectionOwned = true; } + + _exceptionDetector = dependencies.ExceptionDetector; } /// @@ -726,7 +729,14 @@ private void OpenInternal(bool errorsExpected) } catch (Exception e) { - logger.ConnectionError(this, e, startTime, stopwatch.Elapsed, errorsExpected); + if (_exceptionDetector.IsCancellation(e, CancellationToken.None)) + { + logger.ConnectionCanceled(this, startTime, stopwatch.Elapsed); + } + else + { + logger.ConnectionError(this, e, startTime, stopwatch.Elapsed, errorsExpected); + } throw; } @@ -773,14 +783,16 @@ await logger.ConnectionOpenedAsync(this, startTime, stopwatch.Elapsed, cancellat } catch (Exception e) { - await logger.ConnectionErrorAsync( - this, - e, - startTime, - stopwatch.Elapsed, - errorsExpected, - cancellationToken) - .ConfigureAwait(false); + if (_exceptionDetector.IsCancellation(e, CancellationToken.None)) + { + await logger.ConnectionCanceledAsync(this, startTime, stopwatch.Elapsed, cancellationToken) + .ConfigureAwait(false); + } + else + { + await logger.ConnectionErrorAsync(this, e, startTime, stopwatch.Elapsed, errorsExpected, cancellationToken) + .ConfigureAwait(false); + } throw; } diff --git a/src/EFCore.Relational/Storage/RelationalConnectionDependencies.cs b/src/EFCore.Relational/Storage/RelationalConnectionDependencies.cs index c6797be24f1..714b30e0a18 100644 --- a/src/EFCore.Relational/Storage/RelationalConnectionDependencies.cs +++ b/src/EFCore.Relational/Storage/RelationalConnectionDependencies.cs @@ -54,7 +54,8 @@ public RelationalConnectionDependencies( INamedConnectionStringResolver connectionStringResolver, IRelationalTransactionFactory relationalTransactionFactory, ICurrentDbContext currentContext, - IRelationalCommandBuilderFactory relationalCommandBuilderFactory) + IRelationalCommandBuilderFactory relationalCommandBuilderFactory, + IExceptionDetector exceptionDetector) { ContextOptions = contextOptions; TransactionLogger = transactionLogger; @@ -63,6 +64,7 @@ public RelationalConnectionDependencies( RelationalTransactionFactory = relationalTransactionFactory; CurrentContext = currentContext; RelationalCommandBuilderFactory = relationalCommandBuilderFactory; + ExceptionDetector = exceptionDetector; } /// @@ -100,4 +102,9 @@ public RelationalConnectionDependencies( /// Contains the instance currently in use. /// public IRelationalCommandBuilderFactory RelationalCommandBuilderFactory { get; init; } + + /// + /// The exception detector. + /// + public IExceptionDetector ExceptionDetector { get; init; } } diff --git a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs index 9cf220045a6..10ad36fcdb4 100644 --- a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs +++ b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs @@ -2577,10 +2577,16 @@ private IReadOnlyList RewriteOperations( var newRawSchema = renameTableOperation.NewSchema; var newSchema = newRawSchema ?? model?.GetDefaultSchema(); + var temporalTableInformation = BuildTemporalInformationFromMigrationOperation(schema, renameTableOperation); if (!temporalTableInformationMap.ContainsKey((tableName, rawSchema))) { - var temporalTableInformation = BuildTemporalInformationFromMigrationOperation(schema, renameTableOperation); temporalTableInformationMap[(tableName, rawSchema)] = temporalTableInformation; + } + + // we still need to check here - table with the new name could have existed before and have been deleted + // we want to preserve the original temporal info of that deleted table + if (!temporalTableInformationMap.ContainsKey((newTableName, newRawSchema))) + { temporalTableInformationMap[(newTableName, newRawSchema)] = temporalTableInformation; } @@ -2675,10 +2681,19 @@ private IReadOnlyList RewriteOperations( var schema = rawSchema ?? model?.GetDefaultSchema(); - // we are guaranteed to find entry here - we looped through all the operations earlier, - // info missing from operations we got from the model - // and in case of no/incomplete model we created dummy (non-temporal) entries - var temporalInformation = temporalTableInformationMap[(tableName, rawSchema)]; + TemporalOperationInformation temporalInformation; + if (operation is CreateTableOperation) + { + // for create table we always generate new temporal information from the operation itself + // just in case there was a table with that name before that got deleted/renamed + // also, temporal state (disabled versioning etc.) should always reset when creating a table + temporalInformation = BuildTemporalInformationFromMigrationOperation(schema, operation); + temporalTableInformationMap[(tableName, rawSchema)] = temporalInformation; + } + else + { + temporalInformation = temporalTableInformationMap[(tableName, rawSchema)]; + } switch (operation) { diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs index c0a7d72d43c..8e4edd47f06 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs @@ -53,15 +53,24 @@ private static readonly HashSet ArithmeticOperatorTypes ExpressionType.Modulo ]; - private static readonly MethodInfo StringStartsWithMethodInfo + private static readonly MethodInfo StringStartsWithMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.StartsWith), [typeof(string)])!; - private static readonly MethodInfo StringEndsWithMethodInfo + private static readonly MethodInfo StringStartsWithMethodInfoChar + = typeof(string).GetRuntimeMethod(nameof(string.StartsWith), [typeof(char)])!; + + private static readonly MethodInfo StringEndsWithMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.EndsWith), [typeof(string)])!; - private static readonly MethodInfo StringContainsMethodInfo + private static readonly MethodInfo StringEndsWithMethodInfoChar + = typeof(string).GetRuntimeMethod(nameof(string.EndsWith), [typeof(char)])!; + + private static readonly MethodInfo StringContainsMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.Contains), [typeof(string)])!; + private static readonly MethodInfo StringContainsMethodInfoChar + = typeof(string).GetRuntimeMethod(nameof(string.Contains), [typeof(char)])!; + private static readonly MethodInfo StringJoinMethodInfo = typeof(string).GetRuntimeMethod(nameof(string.Join), [typeof(string), typeof(string[])])!; @@ -187,21 +196,21 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp methodCallExpression.Type); } - if (method == StringStartsWithMethodInfo + if ((method == StringStartsWithMethodInfoString || method == StringStartsWithMethodInfoChar) && TryTranslateStartsEndsWithContains( methodCallExpression.Object!, methodCallExpression.Arguments[0], StartsEndsWithContains.StartsWith, out var translation1)) { return translation1; } - if (method == StringEndsWithMethodInfo + if ((method == StringEndsWithMethodInfoString || method == StringEndsWithMethodInfoChar) && TryTranslateStartsEndsWithContains( methodCallExpression.Object!, methodCallExpression.Arguments[0], StartsEndsWithContains.EndsWith, out var translation2)) { return translation2; } - if (method == StringContainsMethodInfo + if ((method == StringContainsMethodInfoString || method == StringContainsMethodInfoChar) && TryTranslateStartsEndsWithContains( methodCallExpression.Object!, methodCallExpression.Arguments[0], StartsEndsWithContains.Contains, out var translation3)) { @@ -328,6 +337,32 @@ string when _sqlServerSingletonOptions.EngineType is SqlServerEngineType.AzureSy }), _sqlExpressionFactory.Constant(LikeEscapeString)), + char s when !IsLikeWildChar(s) + => _sqlExpressionFactory.Like( + translatedInstance, + _sqlExpressionFactory.Constant( + methodType switch + { + StartsEndsWithContains.StartsWith => s + "%", + StartsEndsWithContains.EndsWith => "%" + s, + StartsEndsWithContains.Contains => $"%{s}%", + + _ => throw new ArgumentOutOfRangeException(nameof(methodType), methodType, null) + })), + + char s => _sqlExpressionFactory.Like( + translatedInstance, + _sqlExpressionFactory.Constant( + methodType switch + { + StartsEndsWithContains.StartsWith => LikeEscapeChar + s + "%", + StartsEndsWithContains.EndsWith => "%" + LikeEscapeChar + s, + StartsEndsWithContains.Contains => $"%{LikeEscapeChar}{s}%", + + _ => throw new ArgumentOutOfRangeException(nameof(methodType), methodType, null) + }), + _sqlExpressionFactory.Constant(LikeEscapeString)), + _ => throw new UnreachableException() }; @@ -463,6 +498,22 @@ SqlExpression CharIndexGreaterThanZero() _ => throw new ArgumentOutOfRangeException(nameof(methodType), methodType, null) }, + char s when !IsLikeWildChar(s) => methodType switch + { + StartsEndsWithContains.StartsWith => s + "%", + StartsEndsWithContains.EndsWith => "%" + s, + StartsEndsWithContains.Contains => $"%{s}%", + _ => throw new ArgumentOutOfRangeException(nameof(methodType), methodType, null) + }, + + char s => methodType switch + { + StartsEndsWithContains.StartsWith => LikeEscapeChar + s + "%", + StartsEndsWithContains.EndsWith => "%" + LikeEscapeChar + s, + StartsEndsWithContains.Contains => $"%{LikeEscapeChar}{s}%", + _ => throw new ArgumentOutOfRangeException(nameof(methodType), methodType, null) + }, + _ => throw new UnreachableException() }; diff --git a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerStringMethodTranslator.cs b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerStringMethodTranslator.cs index 6d0a410bb73..db7c88a1e02 100644 --- a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerStringMethodTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerStringMethodTranslator.cs @@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; +using CharTypeMapping = Microsoft.EntityFrameworkCore.Storage.CharTypeMapping; using ExpressionExtensions = Microsoft.EntityFrameworkCore.Query.ExpressionExtensions; // ReSharper disable once CheckNamespace @@ -16,15 +17,24 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; /// public class SqlServerStringMethodTranslator : IMethodCallTranslator { - private static readonly MethodInfo IndexOfMethodInfo + private static readonly MethodInfo IndexOfMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.IndexOf), [typeof(string)])!; - private static readonly MethodInfo IndexOfMethodInfoWithStartingPosition + private static readonly MethodInfo IndexOfMethodInfoChar + = typeof(string).GetRuntimeMethod(nameof(string.IndexOf), [typeof(char)])!; + + private static readonly MethodInfo IndexOfMethodInfoWithStartingPositionString = typeof(string).GetRuntimeMethod(nameof(string.IndexOf), [typeof(string), typeof(int)])!; - private static readonly MethodInfo ReplaceMethodInfo + private static readonly MethodInfo IndexOfMethodInfoWithStartingPositionChar + = typeof(string).GetRuntimeMethod(nameof(string.IndexOf), [typeof(char), typeof(int)])!; + + private static readonly MethodInfo ReplaceMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.Replace), [typeof(string), typeof(string)])!; + private static readonly MethodInfo ReplaceMethodInfoChar + = typeof(string).GetRuntimeMethod(nameof(string.Replace), [typeof(char), typeof(char)])!; + private static readonly MethodInfo ToLowerMethodInfo = typeof(string).GetRuntimeMethod(nameof(string.ToLower), Type.EmptyTypes)!; @@ -115,25 +125,25 @@ public SqlServerStringMethodTranslator(ISqlExpressionFactory sqlExpressionFactor { if (instance != null) { - if (IndexOfMethodInfo.Equals(method)) + if (IndexOfMethodInfoString.Equals(method) || IndexOfMethodInfoChar.Equals(method)) { return TranslateIndexOf(instance, method, arguments[0], null); } - if (IndexOfMethodInfoWithStartingPosition.Equals(method)) + if (IndexOfMethodInfoWithStartingPositionString.Equals(method) || IndexOfMethodInfoWithStartingPositionChar.Equals(method)) { return TranslateIndexOf(instance, method, arguments[0], arguments[1]); } - if (ReplaceMethodInfo.Equals(method)) + if (ReplaceMethodInfoString.Equals(method) || ReplaceMethodInfoChar.Equals(method)) { var firstArgument = arguments[0]; var secondArgument = arguments[1]; var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, firstArgument, secondArgument); instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping); - firstArgument = _sqlExpressionFactory.ApplyTypeMapping(firstArgument, stringTypeMapping); - secondArgument = _sqlExpressionFactory.ApplyTypeMapping(secondArgument, stringTypeMapping); + firstArgument = _sqlExpressionFactory.ApplyTypeMapping(firstArgument, firstArgument.Type == typeof(char) ? CharTypeMapping.Default : stringTypeMapping); + secondArgument = _sqlExpressionFactory.ApplyTypeMapping(secondArgument, secondArgument.Type == typeof(char) ? CharTypeMapping.Default : stringTypeMapping); return _sqlExpressionFactory.Function( "REPLACE", @@ -323,7 +333,8 @@ private SqlExpression TranslateIndexOf( SqlExpression? startIndex) { var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, searchExpression)!; - searchExpression = _sqlExpressionFactory.ApplyTypeMapping(searchExpression, stringTypeMapping); + searchExpression = _sqlExpressionFactory.ApplyTypeMapping(searchExpression, searchExpression.Type == typeof(char) ? CharTypeMapping.Default : stringTypeMapping); + instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping); var charIndexArguments = new List { searchExpression, instance }; diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerOwnedJsonTypeMapping.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerOwnedJsonTypeMapping.cs index 6bd4cfeffb0..801ac044755 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerOwnedJsonTypeMapping.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerOwnedJsonTypeMapping.cs @@ -45,7 +45,7 @@ private static readonly MethodInfo GetStringMethod /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public SqlServerOwnedJsonTypeMapping(string storeType) - : base(storeType, typeof(JsonElement), System.Data.DbType.String) + : base(storeType, typeof(JsonTypePlaceholder), System.Data.DbType.String) { } diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs index 50d50413c28..b89cfbc9f60 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Data; -using System.Text.Json; namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; @@ -239,7 +238,7 @@ public SqlServerTypeMappingSource( var clrType = mappingInfo.ClrType; var storeTypeName = mappingInfo.StoreTypeName; - if (clrType == typeof(JsonElement)) + if (clrType == typeof(JsonTypePlaceholder)) { return storeTypeName == "json" ? SqlServerOwnedJsonTypeMapping.OwnedJsonTypeDefault diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index 48aa49e1c2c..0d04114979f 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -19,12 +19,18 @@ public class SqliteSqlTranslatingExpressionVisitor : RelationalSqlTranslatingExp private readonly QueryCompilationContext _queryCompilationContext; private readonly ISqlExpressionFactory _sqlExpressionFactory; - private static readonly MethodInfo StringStartsWithMethodInfo + private static readonly MethodInfo StringStartsWithMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.StartsWith), [typeof(string)])!; - private static readonly MethodInfo StringEndsWithMethodInfo + private static readonly MethodInfo StringStartsWithMethodInfoChar + = typeof(string).GetRuntimeMethod(nameof(string.StartsWith), [typeof(char)])!; + + private static readonly MethodInfo StringEndsWithMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.EndsWith), [typeof(string)])!; + private static readonly MethodInfo StringEndsWithMethodInfoChar + = typeof(string).GetRuntimeMethod(nameof(string.EndsWith), [typeof(char)])!; + private static readonly MethodInfo EscapeLikePatternParameterMethod = typeof(SqliteSqlTranslatingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(ConstructLikePatternParameter))!; @@ -255,14 +261,14 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp { var method = methodCallExpression.Method; - if (method == StringStartsWithMethodInfo + if ((method == StringStartsWithMethodInfoString || method == StringStartsWithMethodInfoChar) && TryTranslateStartsEndsWith( methodCallExpression.Object!, methodCallExpression.Arguments[0], startsWith: true, out var translation1)) { return translation1; } - if (method == StringEndsWithMethodInfo + if ((method == StringEndsWithMethodInfoString || method == StringEndsWithMethodInfoChar) && TryTranslateStartsEndsWith( methodCallExpression.Object!, methodCallExpression.Arguments[0], startsWith: false, out var translation2)) { @@ -316,6 +322,15 @@ bool TryTranslateStartsEndsWith( translatedInstance, _sqlExpressionFactory.Constant(startsWith ? s + '%' : '%' + s)), + char s => IsLikeWildChar(s) + ? _sqlExpressionFactory.Like( + translatedInstance, + _sqlExpressionFactory.Constant(startsWith ? LikeEscapeString + s + "%" : '%' + LikeEscapeString + s), + _sqlExpressionFactory.Constant(LikeEscapeString)) + : _sqlExpressionFactory.Like( + translatedInstance, + _sqlExpressionFactory.Constant(startsWith ? s + "%" : "%" + s)), + _ => throw new UnreachableException() }; @@ -443,6 +458,10 @@ bool TryTranslateStartsEndsWith( string s => startsWith ? EscapeLikePattern(s) + '%' : '%' + EscapeLikePattern(s), + char s when IsLikeWildChar(s )=> startsWith ? LikeEscapeString + s + '%' : '%' + LikeEscapeString + s, + + char s => startsWith ? s + "%" : "%" + s, + _ => throw new UnreachableException() }; diff --git a/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteStringMethodTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteStringMethodTranslator.cs index bc38e4af196..7297982a0e6 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteStringMethodTranslator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteStringMethodTranslator.cs @@ -15,12 +15,24 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; /// public class SqliteStringMethodTranslator : IMethodCallTranslator { - private static readonly MethodInfo IndexOfMethodInfo + private static readonly MethodInfo IndexOfMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.IndexOf), [typeof(string)])!; - private static readonly MethodInfo ReplaceMethodInfo + private static readonly MethodInfo IndexOfMethodInfoChar + = typeof(string).GetRuntimeMethod(nameof(string.IndexOf), [typeof(char)])!; + + private static readonly MethodInfo IndexOfMethodInfoWithStartingPositionString + = typeof(string).GetRuntimeMethod(nameof(string.IndexOf), [typeof(string), typeof(int)])!; + + private static readonly MethodInfo IndexOfMethodInfoWithStartingPositionChar + = typeof(string).GetRuntimeMethod(nameof(string.IndexOf), [typeof(char), typeof(int)])!; + + private static readonly MethodInfo ReplaceMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.Replace), [typeof(string), typeof(string)])!; + private static readonly MethodInfo ReplaceMethodInfoChar + = typeof(string).GetRuntimeMethod(nameof(string.Replace), [typeof(char), typeof(char)])!; + private static readonly MethodInfo ToLowerMethodInfo = typeof(string).GetRuntimeMethod(nameof(string.ToLower), Type.EmptyTypes)!; @@ -65,8 +77,10 @@ private static readonly MethodInfo TrimEndMethodInfoWithCharArrayArg private static readonly MethodInfo TrimMethodInfoWithCharArrayArg = typeof(string).GetRuntimeMethod(nameof(string.Trim), [typeof(char[])])!; - private static readonly MethodInfo ContainsMethodInfo + private static readonly MethodInfo ContainsMethodInfoString = typeof(string).GetRuntimeMethod(nameof(string.Contains), [typeof(string)])!; + private static readonly MethodInfo ContainsMethodInfoChar + = typeof(string).GetRuntimeMethod(nameof(string.Contains), [typeof(char)])!; private static readonly MethodInfo FirstOrDefaultMethodInfoWithoutArgs = typeof(Enumerable).GetRuntimeMethods().Single( @@ -103,7 +117,7 @@ public SqliteStringMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) { if (instance != null) { - if (IndexOfMethodInfo.Equals(method)) + if (IndexOfMethodInfoString.Equals(method) || IndexOfMethodInfoChar.Equals(method)) { var argument = arguments[0]; var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, argument); @@ -114,7 +128,7 @@ public SqliteStringMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) new[] { _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping), - _sqlExpressionFactory.ApplyTypeMapping(argument, stringTypeMapping) + _sqlExpressionFactory.ApplyTypeMapping(argument, argument.Type == typeof(char) ? CharTypeMapping.Default : stringTypeMapping) }, nullable: true, argumentsPropagateNullability: Statics.TrueArrays[2], @@ -122,7 +136,35 @@ public SqliteStringMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) _sqlExpressionFactory.Constant(1)); } - if (ReplaceMethodInfo.Equals(method)) + if (IndexOfMethodInfoWithStartingPositionString.Equals(method) || IndexOfMethodInfoWithStartingPositionChar.Equals(method)) + { + var argument = arguments[0]; + var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, argument); + instance = _sqlExpressionFactory.Function( + "substr", + new[] { instance, _sqlExpressionFactory.Add(arguments[1], _sqlExpressionFactory.Constant(1)) }, + nullable: true, + argumentsPropagateNullability: Statics.TrueArrays[2], + method.ReturnType, + instance.TypeMapping); + + return _sqlExpressionFactory.Add( + _sqlExpressionFactory.Subtract( + _sqlExpressionFactory.Function( + "instr", + new[] + { + _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping), + _sqlExpressionFactory.ApplyTypeMapping(argument, argument.Type == typeof(char) ? CharTypeMapping.Default : stringTypeMapping) + }, + nullable: true, + argumentsPropagateNullability: Statics.TrueArrays[2], + method.ReturnType), + _sqlExpressionFactory.Constant(1)), + arguments[1]); + } + + if (ReplaceMethodInfoString.Equals(method) || ReplaceMethodInfoChar.Equals(method)) { var firstArgument = arguments[0]; var secondArgument = arguments[1]; @@ -133,8 +175,8 @@ public SqliteStringMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) new[] { _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping), - _sqlExpressionFactory.ApplyTypeMapping(firstArgument, stringTypeMapping), - _sqlExpressionFactory.ApplyTypeMapping(secondArgument, stringTypeMapping) + _sqlExpressionFactory.ApplyTypeMapping(firstArgument, firstArgument.Type == typeof(char) ? CharTypeMapping.Default : stringTypeMapping), + _sqlExpressionFactory.ApplyTypeMapping(secondArgument, secondArgument.Type == typeof(char) ? CharTypeMapping.Default : stringTypeMapping) }, nullable: true, argumentsPropagateNullability: Statics.TrueArrays[3], @@ -197,13 +239,13 @@ public SqliteStringMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) return ProcessTrimMethod(instance, arguments, "trim"); } - if (ContainsMethodInfo.Equals(method)) + if (ContainsMethodInfoString.Equals(method) || ContainsMethodInfoChar.Equals(method)) { var pattern = arguments[0]; var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern); instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping); - pattern = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping); + pattern = _sqlExpressionFactory.ApplyTypeMapping(pattern, pattern.Type == typeof(char) ? CharTypeMapping.Default : stringTypeMapping); return _sqlExpressionFactory.GreaterThan( diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs index 2b40d34a556..51925eb4d9e 100644 --- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs +++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs @@ -39,7 +39,7 @@ private static readonly ConstructorInfo MemoryStreamConstructor /// /// The name of the database type. public SqliteJsonTypeMapping(string storeType) - : base(storeType, typeof(JsonElement), System.Data.DbType.String) + : base(storeType, typeof(JsonTypePlaceholder), System.Data.DbType.String) { } diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs index 8e9e80978f0..2cdc07fefe9 100644 --- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs +++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs @@ -83,7 +83,7 @@ private static readonly HashSet SpatialiteTypes { typeof(double), Real }, { typeof(float), new FloatTypeMapping(RealTypeName) }, { typeof(Guid), SqliteGuidTypeMapping.Default }, - { typeof(JsonElement), SqliteJsonTypeMapping.Default } + { typeof(JsonTypePlaceholder), SqliteJsonTypeMapping.Default } }; private readonly Dictionary _storeTypeMappings = new(StringComparer.OrdinalIgnoreCase) diff --git a/src/EFCore.Sqlite/PACKAGE.md b/src/EFCore.Sqlite/PACKAGE.md index 27712755653..c861db6ff34 100644 --- a/src/EFCore.Sqlite/PACKAGE.md +++ b/src/EFCore.Sqlite/PACKAGE.md @@ -7,7 +7,7 @@ Call the `UseSqlite` method to choose the SQLite database provider for your `DbC ```csharp protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - optionsBuilder.UseSqlite("Data Source=databse.dat"); + optionsBuilder.UseSqlite("Data Source=database.db"); } ``` diff --git a/src/EFCore.Tasks/EFCore.Tasks.csproj b/src/EFCore.Tasks/EFCore.Tasks.csproj index eb72b2ae76a..6e4d77a6802 100644 --- a/src/EFCore.Tasks/EFCore.Tasks.csproj +++ b/src/EFCore.Tasks/EFCore.Tasks.csproj @@ -49,7 +49,6 @@ - diff --git a/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs b/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs deleted file mode 100644 index e6b66ad06b9..00000000000 --- a/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.EntityFrameworkCore.Metadata.Internal; - -// ReSharper disable once CheckNamespace -namespace Microsoft.EntityFrameworkCore; - -/// -/// Extension methods for . -/// -[Obsolete("Use IConventionEntityType")] // Delete with defining query -public static class ConventionEntityTypeExtensions -{ - /// - /// Sets the LINQ query used as the default source for queries of this type. - /// - /// The entity type. - /// The LINQ query used as the default source. - /// Indicates whether the configuration was specified using a data annotation. - [Obsolete("Use InMemoryEntityTypeExtensions.SetInMemoryQuery")] - public static void SetDefiningQuery( - this IConventionEntityType entityType, - LambdaExpression? definingQuery, - bool fromDataAnnotation = false) - => ((EntityType)entityType).SetDefiningQuery( - definingQuery, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// Returns the configuration source for . - /// - /// The entity type. - /// The configuration source for . - [Obsolete("Use InMemoryEntityTypeExtensions.GetInMemoryQueryConfigurationSource")] - public static ConfigurationSource? GetDefiningQueryConfigurationSource(this IConventionEntityType entityType) - => entityType.FindAnnotation(CoreAnnotationNames.DefiningQuery)?.GetConfigurationSource(); -} diff --git a/src/EFCore/Extensions/EntityTypeExtensions.cs b/src/EFCore/Extensions/EntityTypeExtensions.cs deleted file mode 100644 index b99fbb127ce..00000000000 --- a/src/EFCore/Extensions/EntityTypeExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.EntityFrameworkCore.Metadata.Internal; - -// ReSharper disable once CheckNamespace -namespace Microsoft.EntityFrameworkCore; - -/// -/// Entity type extension methods for . -/// -[Obsolete("Use IReadOnlyEntityType")] // Delete with defining query -public static class EntityTypeExtensions -{ - /// - /// Gets the LINQ query used as the default source for queries of this type. - /// - /// The entity type to get the defining query for. - /// The LINQ query used as the default source. - [Obsolete("Use InMemoryEntityTypeExtensions.GetInMemoryQuery")] - public static LambdaExpression? GetDefiningQuery(this IEntityType entityType) - => (LambdaExpression?)entityType[CoreAnnotationNames.DefiningQuery]; -} diff --git a/src/EFCore/Extensions/MutableEntityTypeExtensions.cs b/src/EFCore/Extensions/MutableEntityTypeExtensions.cs deleted file mode 100644 index 395b1aa72eb..00000000000 --- a/src/EFCore/Extensions/MutableEntityTypeExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.EntityFrameworkCore.Metadata.Internal; - -// ReSharper disable once CheckNamespace -namespace Microsoft.EntityFrameworkCore; - -/// -/// Extension methods for . -/// -[Obsolete("Use IMutableEntityType")] // Delete with defining query -public static class MutableEntityTypeExtensions -{ - /// - /// Sets the LINQ query used as the default source for queries of this type. - /// - /// The entity type. - /// The LINQ query used as the default source. - [Obsolete("Use InMemoryEntityTypeExtensions.SetInMemoryQuery")] - public static void SetDefiningQuery( - this IMutableEntityType entityType, - LambdaExpression? definingQuery) - => ((EntityType)entityType).SetDefiningQuery(definingQuery, ConfigurationSource.Explicit); -} diff --git a/src/EFCore/Infrastructure/IndentedStringBuilder.cs b/src/EFCore/Infrastructure/IndentedStringBuilder.cs index c2dd4523ed9..c77d21c3954 100644 --- a/src/EFCore/Infrastructure/IndentedStringBuilder.cs +++ b/src/EFCore/Infrastructure/IndentedStringBuilder.cs @@ -290,6 +290,19 @@ public virtual IDisposable Indent() public virtual IDisposable SuspendIndent() => new IndentSuspender(this); + /// + /// Clones this , copying the built string and current indent level. + /// + /// New instance of . + public virtual IndentedStringBuilder Clone() + { + var result = new IndentedStringBuilder(); + result._stringBuilder.Append(_stringBuilder); + result._indent = _indent; + result._indentPending = _indentPending; + return result; + } + /// /// Returns the built string. /// diff --git a/src/EFCore/Infrastructure/Internal/LazyLoader.cs b/src/EFCore/Infrastructure/Internal/LazyLoader.cs index 8215392c8d8..c575cd4f957 100644 --- a/src/EFCore/Infrastructure/Internal/LazyLoader.cs +++ b/src/EFCore/Infrastructure/Internal/LazyLoader.cs @@ -23,7 +23,8 @@ public class LazyLoader : ILazyLoader, IInjectableService private bool _detached; private IDictionary? _loadedStates; private readonly Lock _isLoadingLock = new Lock(); - private readonly Dictionary<(object Entity, string NavigationName), (TaskCompletionSource TaskCompletionSource, AsyncLocal Depth)> _isLoading = new(NavEntryEqualityComparer.Instance); + private readonly Dictionary<(object Entity, string NavigationName), TaskCompletionSource> _isLoading = new(NavEntryEqualityComparer.Instance); + private static readonly AsyncLocal _isLoadingCallDepth = new(); private HashSet? _nonLazyNavigations; /// @@ -112,26 +113,28 @@ public virtual void Load(object entity, [CallerMemberName] string navigationName var navEntry = (entity, navigationName); bool exists; - (TaskCompletionSource TaskCompletionSource, AsyncLocal Depth) isLoadingValue; + TaskCompletionSource isLoadingValue; lock (_isLoadingLock) { ref var refIsLoadingValue = ref CollectionsMarshal.GetValueRefOrAddDefault(_isLoading, navEntry, out exists); if (!exists) { - refIsLoadingValue = (new(), new()); + refIsLoadingValue = new(); } + _isLoadingCallDepth.Value++; isLoadingValue = refIsLoadingValue!; - isLoadingValue.Depth.Value++; } if (exists) { - // Only waits for the outermost call on the call stack. See #35528. - if (isLoadingValue.Depth.Value == 1) + // Only waits for the outermost call on the call stack. See #35528. + // if _isLoadingCallDepth.Value > 1 the call is recursive, waiting probably generates a deadlock See #35832. + if (_isLoadingCallDepth.Value == 1) { - isLoadingValue.TaskCompletionSource.Task.Wait(); + isLoadingValue.Task.Wait(); } + _isLoadingCallDepth.Value--; return; } @@ -156,7 +159,8 @@ public virtual void Load(object entity, [CallerMemberName] string navigationName } finally { - isLoadingValue.TaskCompletionSource.TrySetResult(); + isLoadingValue.TrySetResult(); + _isLoadingCallDepth.Value--; lock (_isLoadingLock) { _isLoading.Remove(navEntry); @@ -181,26 +185,28 @@ public virtual async Task LoadAsync( var navEntry = (entity, navigationName); bool exists; - (TaskCompletionSource TaskCompletionSource, AsyncLocal Depth) isLoadingValue; + TaskCompletionSource isLoadingValue; lock (_isLoadingLock) { ref var refIsLoadingValue = ref CollectionsMarshal.GetValueRefOrAddDefault(_isLoading, navEntry, out exists); if (!exists) { - refIsLoadingValue = (new(), new()); + refIsLoadingValue = new(); } + _isLoadingCallDepth.Value++; isLoadingValue = refIsLoadingValue!; - isLoadingValue.Depth.Value++; } if (exists) { - // Only waits for the outermost call on the call stack. See #35528. - if (isLoadingValue.Depth.Value == 1) + // Only waits for the outermost call on the call stack. See #35528. + // if _isLoadingCallDepth.Value > 1 the call is recursive, waiting probably generates a deadlock See #35832. + if (_isLoadingCallDepth.Value == 1) { - await isLoadingValue.TaskCompletionSource.Task.WaitAsync(cancellationToken).ConfigureAwait(false); + await isLoadingValue.Task.WaitAsync(cancellationToken).ConfigureAwait(false); } + _isLoadingCallDepth.Value--; return; } @@ -226,7 +232,8 @@ await entry.LoadAsync( } finally { - isLoadingValue.TaskCompletionSource.TrySetResult(); + isLoadingValue.TrySetResult(); + _isLoadingCallDepth.Value--; lock (_isLoadingLock) { _isLoading.Remove(navEntry); diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 9012bc3a4bc..a6b08037638 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -640,12 +640,16 @@ protected virtual void ValidateInheritanceMapping( /// The entity type to validate. protected virtual void ValidateDiscriminatorValues(IEntityType rootEntityType) { - var derivedTypes = rootEntityType.GetDerivedTypesInclusive().ToList(); + var derivedTypes = rootEntityType.GetDerivedTypesInclusive(); var discriminatorProperty = rootEntityType.FindDiscriminatorProperty(); if (discriminatorProperty == null) { - if (derivedTypes.Count == 1) + if (!derivedTypes.Skip(1).Any()) { + foreach (var complexProperty in rootEntityType.GetDeclaredComplexProperties()) + { + ValidateDiscriminatorValues(complexProperty.ComplexType); + } return; } @@ -654,6 +658,68 @@ protected virtual void ValidateDiscriminatorValues(IEntityType rootEntityType) } var discriminatorValues = new Dictionary(discriminatorProperty.GetKeyValueComparer()); + foreach (var derivedType in derivedTypes) + { + foreach (var complexProperty in derivedType.GetDeclaredComplexProperties()) + { + ValidateDiscriminatorValues(complexProperty.ComplexType); + } + + if (!derivedType.ClrType.IsInstantiable()) + { + continue; + } + + var discriminatorValue = derivedType[CoreAnnotationNames.DiscriminatorValue]; + if (discriminatorValue == null) + { + throw new InvalidOperationException( + CoreStrings.NoDiscriminatorValue(derivedType.DisplayName())); + } + + if (!discriminatorProperty.ClrType.IsInstanceOfType(discriminatorValue)) + { + throw new InvalidOperationException( + CoreStrings.DiscriminatorValueIncompatible( + discriminatorValue, derivedType.DisplayName(), discriminatorProperty.ClrType.DisplayName())); + } + + if (discriminatorValues.TryGetValue(discriminatorValue, out var duplicateEntityType)) + { + throw new InvalidOperationException( + CoreStrings.DuplicateDiscriminatorValue( + derivedType.DisplayName(), discriminatorValue, duplicateEntityType.DisplayName())); + } + + discriminatorValues[discriminatorValue] = derivedType; + } + } + + /// + /// Validates the discriminator and values for the given complex type and nested ones. + /// + /// The entity type to validate. + protected virtual void ValidateDiscriminatorValues(IComplexType complexType) + { + foreach (var complexProperty in complexType.GetComplexProperties()) + { + ValidateDiscriminatorValues(complexProperty.ComplexType); + } + + var derivedTypes = complexType.GetDerivedTypesInclusive(); + var discriminatorProperty = complexType.FindDiscriminatorProperty(); + if (discriminatorProperty == null) + { + if (!derivedTypes.Skip(1).Any()) + { + return; + } + + throw new InvalidOperationException( + CoreStrings.NoDiscriminatorProperty(complexType.DisplayName())); + } + + var discriminatorValues = new Dictionary(discriminatorProperty.GetKeyValueComparer()); foreach (var derivedType in derivedTypes) { diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs index 5bc54a973d2..aa8be6da059 100644 --- a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs @@ -168,11 +168,11 @@ public virtual ComplexTypePropertyBuilder Property(string propertyName) /// If no property with the given name exists, then a new property will be added. /// /// - /// When adding a new property, if a property with the same name exists in the entity class - /// then it will be added to the model. If no property exists in the entity class, then + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then /// a new shadow state property will be added. A shadow state property is one that does not have a - /// corresponding property in the entity class. The current value for the property is stored in - /// the rather than being stored in instances of the entity class. + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. /// /// The type of the property to be configured. /// The name of the property to be configured. @@ -188,11 +188,11 @@ public virtual ComplexTypePropertyBuilder Property(string /// If no property with the given name exists, then a new property will be added. /// /// - /// When adding a new property, if a property with the same name exists in the entity class - /// then it will be added to the model. If no property exists in the entity class, then + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then /// a new shadow state property will be added. A shadow state property is one that does not have a - /// corresponding property in the entity class. The current value for the property is stored in - /// the rather than being stored in instances of the entity class. + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. /// /// The type of the property to be configured. /// The name of the property to be configured. @@ -225,11 +225,11 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(string /// If no property with the given name exists, then a new property will be added. /// /// - /// When adding a new property, if a property with the same name exists in the entity class - /// then it will be added to the model. If no property exists in the entity class, then + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then /// a new shadow state property will be added. A shadow state property is one that does not have a - /// corresponding property in the entity class. The current value for the property is stored in - /// the rather than being stored in instances of the entity class. + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. /// /// The type of the property to be configured. /// The name of the property to be configured. @@ -245,11 +245,11 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollect /// If no property with the given name exists, then a new property will be added. /// /// - /// When adding a new property, if a property with the same name exists in the entity class - /// then it will be added to the model. If no property exists in the entity class, then + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then /// a new shadow state property will be added. A shadow state property is one that does not have a - /// corresponding property in the entity class. The current value for the property is stored in - /// the rather than being stored in instances of the entity class. + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. /// /// The type of the property to be configured. /// The name of the property to be configured. @@ -265,7 +265,7 @@ public virtual ComplexTypePrimitiveCollectionBuilder PrimitiveCollection(Type pr /// If no property with the given name exists, then a new property will be added. /// /// - /// Indexer properties are stored in the entity using + /// Indexer properties are stored in the complex type using /// an indexer /// supplying the provided property name. /// @@ -284,7 +284,7 @@ public virtual ComplexTypePropertyBuilder IndexerProperty /// If no property with the given name exists, then a new property will be added. /// /// - /// Indexer properties are stored in the entity using + /// Indexer properties are stored in the complex type using /// an indexer /// supplying the provided property name. /// @@ -563,8 +563,8 @@ public virtual ComplexPropertyBuilder Ignore(string propertyName) } /// - /// Configures the to be used for this entity type. - /// This strategy indicates how the context detects changes to properties for an instance of the entity type. + /// Configures the to be used for this complex type. + /// This strategy indicates how the context detects changes to properties for an instance of the complex type. /// /// The change tracking strategy to be used. /// The same builder instance so that multiple configuration calls can be chained. @@ -623,6 +623,53 @@ public virtual ComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAcces return this; } + /// + /// Configures the discriminator property used to identify the complex type in the store. + /// + /// A builder that allows the discriminator property to be configured. + public virtual ComplexTypeDiscriminatorBuilder HasDiscriminator() + => TypeBuilder.HasDiscriminator(ConfigurationSource.Explicit)!; + + /// + /// Configures the discriminator property used to identify the complex type in the store. + /// + /// The name of the discriminator property. + /// The type of values stored in the discriminator property. + /// A builder that allows the discriminator property to be configured. + public virtual ComplexTypeDiscriminatorBuilder HasDiscriminator( + string name, + Type type) + { + Check.NotEmpty(name, nameof(name)); + Check.NotNull(type, nameof(type)); + + return TypeBuilder.HasDiscriminator(name, type, ConfigurationSource.Explicit)!; + } + + /// + /// Configures the discriminator property used to identify the complex type in the store. + /// + /// The type of values stored in the discriminator property. + /// The name of the discriminator property. + /// A builder that allows the discriminator property to be configured. + public virtual ComplexTypeDiscriminatorBuilder HasDiscriminator(string name) + { + Check.NotEmpty(name, nameof(name)); + + return new ComplexTypeDiscriminatorBuilder( + TypeBuilder.HasDiscriminator(name, typeof(TDiscriminator), ConfigurationSource.Explicit)!); + } + + /// + /// Configures the complex type as having no discriminator property. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder HasNoDiscriminator() + { + TypeBuilder.HasNoDiscriminator(ConfigurationSource.Explicit); + return this; + } + #region Hidden System.Object members /// diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs index 7997d990656..9a08411918b 100644 --- a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs +++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs @@ -32,7 +32,7 @@ public ComplexPropertyBuilder(IMutableComplexProperty complexProperty) } /// - /// Adds or updates an annotation on the entity type. If an annotation with the key specified in + /// Adds or updates an annotation on the complex property. If an annotation with the key specified in /// already exists its value will be updated. /// /// The key of the annotation to be added or updated. @@ -42,7 +42,7 @@ public ComplexPropertyBuilder(IMutableComplexProperty complexProperty) => (ComplexPropertyBuilder)base.HasPropertyAnnotation(annotation, value); /// - /// Adds or updates an annotation on the entity type. If an annotation with the key specified in + /// Adds or updates an annotation on the complex type. If an annotation with the key specified in /// already exists its value will be updated. /// /// The key of the annotation to be added or updated. @@ -86,7 +86,7 @@ public ComplexPropertyBuilder(IMutableComplexProperty complexProperty) => (ComplexPropertyBuilder)base.HasField(fieldName); /// - /// Returns an object that can be used to configure a property of the entity type. + /// Returns an object that can be used to configure a property of the complex type. /// If the specified property is not already part of the model, it will be added. /// /// @@ -101,7 +101,7 @@ public virtual ComplexTypePropertyBuilder Property(Express .Metadata); /// - /// Returns an object that can be used to configure a primitive collection property of the entity type. + /// Returns an object that can be used to configure a primitive collection property of the complex type. /// If the specified property is not already part of the model, it will be added. /// /// @@ -332,8 +332,8 @@ public virtual ComplexPropertyBuilder ComplexProperty( } /// - /// Excludes the given property from the entity type. This method is typically used to remove properties - /// or navigations from the entity type that were added by convention. + /// Excludes the given property from the complex type. This method is typically used to remove properties + /// or navigations from the complex type that were added by convention. /// /// /// A lambda expression representing the property to be ignored @@ -344,16 +344,16 @@ public virtual ComplexPropertyBuilder Ignore(Expression - /// Excludes the given property from the entity type. This method is typically used to remove properties - /// or navigations from the entity type that were added by convention. + /// Excludes the given property from the complex type. This method is typically used to remove properties + /// or navigations from the complex type that were added by convention. /// - /// The name of the property to be removed from the entity type. + /// The name of the property to be removed from the complex type. public new virtual ComplexPropertyBuilder Ignore(string propertyName) => (ComplexPropertyBuilder)base.Ignore(propertyName); /// - /// Configures the to be used for this entity type. - /// This strategy indicates how the context detects changes to properties for an instance of the entity type. + /// Configures the to be used for this complex type. + /// This strategy indicates how the context detects changes to properties for an instance of the complex type. /// /// The change tracking strategy to be used. /// The same builder instance so that multiple configuration calls can be chained. @@ -381,22 +381,47 @@ public virtual ComplexPropertyBuilder Ignore(Expression (ComplexPropertyBuilder)base.UsePropertyAccessMode(propertyAccessMode); /// - /// Sets the to use for all properties of this entity type. + /// Sets the to use for all properties of this complex type. /// /// /// /// By default, the backing field, if one is found by convention or has been specified, is used when /// new objects are constructed, typically when entities are queried from the database. /// Properties are used for all other accesses. Calling this method will change that behavior - /// for all properties of this entity type as described in the enum. + /// for all properties of this complex type as described in the enum. /// /// - /// Calling this method overrides for all properties of this entity type any access mode that was + /// Calling this method overrides for all properties of this complex type any access mode that was /// set on the model. /// /// - /// The to use for properties of this entity type. + /// The to use for properties of this complex type. /// The same builder instance so that multiple configuration calls can be chained. public new virtual ComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode) => (ComplexPropertyBuilder)base.UseDefaultPropertyAccessMode(propertyAccessMode); + + /// + /// Configures the discriminator property used to identify the complex type in the store. + /// + /// The type of values stored in the discriminator property. + /// + /// A lambda expression representing the property to be used as the discriminator ( + /// blog => blog.Discriminator). + /// + /// A builder that allows the discriminator property to be configured. + public virtual ComplexTypeDiscriminatorBuilder HasDiscriminator( + Expression> propertyExpression) + { + Check.NotNull(propertyExpression, nameof(propertyExpression)); + + return new ComplexTypeDiscriminatorBuilder( + TypeBuilder.HasDiscriminator(propertyExpression.GetMemberAccess(), ConfigurationSource.Explicit)!); + } + + /// + /// Configures the entity type as having no discriminator property. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder HasNoDiscriminator() + => (ComplexPropertyBuilder)base.HasNoDiscriminator(); } diff --git a/src/EFCore/Metadata/Builders/ComplexTypeDiscriminatorBuilder.cs b/src/EFCore/Metadata/Builders/ComplexTypeDiscriminatorBuilder.cs new file mode 100644 index 00000000000..3f352a33375 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexTypeDiscriminatorBuilder.cs @@ -0,0 +1,133 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API surface for setting discriminator values. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public class ComplexTypeDiscriminatorBuilder : IConventionComplexTypeDiscriminatorBuilder +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ComplexTypeDiscriminatorBuilder(IMutableComplexType complexType) + => ComplexTypeBuilder = ((ComplexType)complexType).Builder; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual InternalComplexTypeBuilder ComplexTypeBuilder { get; } + + /// + /// Configures the default discriminator value to use. + /// + /// The discriminator value. + /// The same builder so that multiple calls can be chained. + public virtual ComplexTypeDiscriminatorBuilder HasValue(object? value) + => HasValue(ComplexTypeBuilder, value, ConfigurationSource.Explicit)!; + + private ComplexTypeDiscriminatorBuilder? HasValue( + InternalComplexTypeBuilder? complexTypeBuilder, + object? value, + ConfigurationSource configurationSource) + { + if (complexTypeBuilder == null) + { + return null; + } + + var baseComplexTypeBuilder = ComplexTypeBuilder; + if (!baseComplexTypeBuilder.Metadata.IsAssignableFrom(complexTypeBuilder.Metadata) + && (!baseComplexTypeBuilder.Metadata.ClrType.IsAssignableFrom(complexTypeBuilder.Metadata.ClrType) + || complexTypeBuilder.HasBaseType(baseComplexTypeBuilder.Metadata, configurationSource) == null)) + { + throw new InvalidOperationException( + CoreStrings.DiscriminatorEntityTypeNotDerived( + complexTypeBuilder.Metadata.DisplayName(), + baseComplexTypeBuilder.Metadata.DisplayName())); + } + + if (configurationSource == ConfigurationSource.Explicit) + { + ((IMutableComplexType)complexTypeBuilder.Metadata).SetDiscriminatorValue(value); + } + else + { + if (!((IConventionComplexTypeDiscriminatorBuilder)this).CanSetValue( + value, configurationSource == ConfigurationSource.DataAnnotation)) + { + return null; + } + + ((IConventionComplexType)complexTypeBuilder.Metadata) + .SetDiscriminatorValue(value, configurationSource == ConfigurationSource.DataAnnotation); + } + + return this; + } + + /// + IConventionComplexType IConventionComplexTypeDiscriminatorBuilder.ComplexType + { + [DebuggerStepThrough] + get => ComplexTypeBuilder.Metadata; + } + + /// + [DebuggerStepThrough] + IConventionComplexTypeDiscriminatorBuilder? IConventionComplexTypeDiscriminatorBuilder.HasValue(object? value, bool fromDataAnnotation) + => HasValue( + ComplexTypeBuilder, value, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + bool IConventionComplexTypeDiscriminatorBuilder.CanSetValue(object? value, bool fromDataAnnotation) + => ((IConventionComplexTypeBuilder)ComplexTypeBuilder).CanSetAnnotation(CoreAnnotationNames.DiscriminatorValue, value, fromDataAnnotation); + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectEqualsIsObjectEquals + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore/Metadata/Builders/ComplexTypeDiscriminatorBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexTypeDiscriminatorBuilder`.cs new file mode 100644 index 00000000000..f5d1fd95ac3 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexTypeDiscriminatorBuilder`.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API surface for setting discriminator values. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +/// The type of the discriminator property. +public class ComplexTypeDiscriminatorBuilder +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ComplexTypeDiscriminatorBuilder(ComplexTypeDiscriminatorBuilder builder) + => Builder = builder; + + private ComplexTypeDiscriminatorBuilder Builder { get; } + + /// + /// Configures the default discriminator value to use. + /// + /// The discriminator value. + /// The same builder so that multiple calls can be chained. + public virtual ComplexTypeDiscriminatorBuilder HasValue(TDiscriminator value) + => new(Builder.HasValue(value)); +} diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs index 3cee16b13e4..b84fb98a562 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs @@ -425,21 +425,6 @@ public virtual EntityTypeBuilder Ignore(Expression HasQueryFilter(Expression>? filter) => (EntityTypeBuilder)base.HasQueryFilter(filter); - /// - /// Configures a query used to provide data for a keyless entity type. - /// - /// The query that will provide the underlying data for the keyless entity type. - /// The same builder instance so that multiple calls can be chained. - [Obsolete("Use InMemoryEntityTypeBuilderExtensions.ToInMemoryQuery")] - public virtual EntityTypeBuilder ToQuery(Expression>> query) - { - Check.NotNull(query, nameof(query)); - - Builder.HasDefiningQuery(query, ConfigurationSource.Explicit); - - return this; - } - /// /// Configures an unnamed index on the specified properties. /// If there is an existing index on the given list of properties, diff --git a/src/EFCore/Metadata/Builders/IConventionComplexTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionComplexTypeBuilder.cs index f2558141ad4..eebef69dc3e 100644 --- a/src/EFCore/Metadata/Builders/IConventionComplexTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionComplexTypeBuilder.cs @@ -134,4 +134,61 @@ public interface IConventionComplexTypeBuilder : IConventionTypeBaseBuilder new IConventionComplexTypeBuilder? UsePropertyAccessMode( PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); + + /// + /// Configures the discriminator property used to identify which complex type each row in a table represents + /// when an inheritance hierarchy is mapped to a single table in a relational database. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// A builder that allows the discriminator property to be configured. + IConventionComplexTypeDiscriminatorBuilder? HasDiscriminator(bool fromDataAnnotation = false); + + /// + /// Configures the discriminator property used to identify which complex type each row in a table represents + /// when an inheritance hierarchy is mapped to a single table in a relational database. + /// + /// The type of values stored in the discriminator property. + /// Indicates whether the configuration was specified using a data annotation. + /// A builder that allows the discriminator property to be configured. + IConventionComplexTypeDiscriminatorBuilder? HasDiscriminator(Type type, bool fromDataAnnotation = false); + + /// + /// Configures the discriminator property used to identify which complex type each row in a table represents + /// when an inheritance hierarchy is mapped to a single table in a relational database. + /// + /// The name of the discriminator property. + /// Indicates whether the configuration was specified using a data annotation. + /// A builder that allows the discriminator property to be configured. + IConventionComplexTypeDiscriminatorBuilder? HasDiscriminator(string name, bool fromDataAnnotation = false); + + /// + /// Configures the discriminator property used to identify which complex type each row in a table represents + /// when an inheritance hierarchy is mapped to a single table in a relational database. + /// + /// The name of the discriminator property. + /// The type of values stored in the discriminator property. + /// Indicates whether the configuration was specified using a data annotation. + /// A builder that allows the discriminator property to be configured. + IConventionComplexTypeDiscriminatorBuilder? HasDiscriminator(string name, Type type, bool fromDataAnnotation = false); + + /// + /// Configures the discriminator property used to identify which complex type each row in a table represents + /// when an inheritance hierarchy is mapped to a single table in a relational database. + /// + /// The property mapped to the discriminator property. + /// Indicates whether the configuration was specified using a data annotation. + /// A builder that allows the discriminator property to be configured. + IConventionComplexTypeDiscriminatorBuilder? HasDiscriminator(MemberInfo memberInfo, bool fromDataAnnotation = false); + + /// + /// Removes the discriminator property from this complex type. + /// This method is usually called when the complex type is no longer mapped to the same table as any other type in + /// the hierarchy or when this complex type is no longer the root type. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the discriminator was configured, + /// otherwise. + /// + IConventionComplexTypeBuilder? HasNoDiscriminator(bool fromDataAnnotation = false); } diff --git a/src/EFCore/Metadata/Builders/IConventionComplexTypeDiscriminatorBuilder.cs b/src/EFCore/Metadata/Builders/IConventionComplexTypeDiscriminatorBuilder.cs new file mode 100644 index 00000000000..84f3309c4a5 --- /dev/null +++ b/src/EFCore/Metadata/Builders/IConventionComplexTypeDiscriminatorBuilder.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API surface for setting discriminator values from conventions. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IConventionComplexTypeDiscriminatorBuilder +{ + /// + /// Gets the complex type on which the discriminator is being configured. + /// + IConventionComplexType ComplexType { get; } + + /// + /// Configures the discriminator value to use. + /// + /// The discriminator value. + /// Indicates whether the configuration was specified using a data annotation. + /// The same builder so that multiple calls can be chained. + IConventionComplexTypeDiscriminatorBuilder? HasValue(object? value, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the discriminator value can be set from this configuration source. + /// + /// The discriminator value. + /// Indicates whether the configuration was specified using a data annotation. + /// if the discriminator value can be set from this configuration source. + bool CanSetValue(object? value, bool fromDataAnnotation = false); +} diff --git a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs index 97310be44a7..e7014919485 100644 --- a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs @@ -878,26 +878,6 @@ bool CanHaveTrigger( /// if the given query filter can be set. bool CanSetQueryFilter(LambdaExpression? filter, bool fromDataAnnotation = false); - /// - /// Configures a query used to provide data for a keyless entity type. - /// - /// The query that will provide the underlying data for the keyless entity type. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the query was set, otherwise. - /// - [Obsolete("Use InMemoryEntityTypeBuilderExtensions.ToInMemoryQuery")] - IConventionEntityTypeBuilder? HasDefiningQuery(LambdaExpression? query, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the given defining query can be set from the current configuration source. - /// - /// The query that will provide the underlying data for the keyless entity type. - /// Indicates whether the configuration was specified using a data annotation. - /// if the given defining query can be set. - [Obsolete("Use InMemoryEntityTypeBuilderExtensions.CanSetInMemoryQuery")] - bool CanSetDefiningQuery(LambdaExpression? query, bool fromDataAnnotation = false); - /// /// Configures the to be used for this entity type. /// This strategy indicates how the context detects changes to properties for an instance of the entity type. @@ -989,46 +969,6 @@ bool CanHaveTrigger( /// IConventionEntityTypeBuilder? HasNoDiscriminator(bool fromDataAnnotation = false); - /// - /// Returns a value indicating whether the discriminator property can be configured. - /// - /// The name of the discriminator property. - /// Indicates whether the configuration was specified using a data annotation. - /// if the configuration can be applied. - bool CanSetDiscriminator(string name, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the discriminator property can be configured. - /// - /// The type of values stored in the discriminator property. - /// Indicates whether the configuration was specified using a data annotation. - /// if the configuration can be applied. - bool CanSetDiscriminator(Type type, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the discriminator property can be configured. - /// - /// The type of values stored in the discriminator property. - /// The name of the discriminator property. - /// Indicates whether the configuration was specified using a data annotation. - /// if the configuration can be applied. - bool CanSetDiscriminator(string name, Type type, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the discriminator property can be configured. - /// - /// The property mapped to the discriminator property. - /// Indicates whether the configuration was specified using a data annotation. - /// if the configuration can be applied. - bool CanSetDiscriminator(MemberInfo memberInfo, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the discriminator property can be removed. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// if the discriminator property can be removed. - bool CanRemoveDiscriminator(bool fromDataAnnotation = false); - /// /// Gets or creates a builder for the target of a potential navigation. /// diff --git a/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs b/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs index 26ce5a70949..93975fbc427 100644 --- a/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs @@ -399,4 +399,44 @@ bool CanHaveComplexIndexerProperty( /// Indicates whether the configuration was specified using a data annotation. /// if the given can be set. bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the discriminator property can be configured. + /// + /// The name of the discriminator property. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + bool CanSetDiscriminator(string name, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the discriminator property can be configured. + /// + /// The type of values stored in the discriminator property. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + bool CanSetDiscriminator(Type type, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the discriminator property can be configured. + /// + /// The type of values stored in the discriminator property. + /// The name of the discriminator property. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + bool CanSetDiscriminator(string name, Type type, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the discriminator property can be configured. + /// + /// The property mapped to the discriminator property. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + bool CanSetDiscriminator(MemberInfo memberInfo, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the discriminator property can be removed. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// if the discriminator property can be removed. + bool CanRemoveDiscriminator(bool fromDataAnnotation = false); } diff --git a/src/EFCore/Metadata/Conventions/DiscriminatorConvention.cs b/src/EFCore/Metadata/Conventions/DiscriminatorConvention.cs index 67b68bbfdb0..4d93d6cfe7f 100644 --- a/src/EFCore/Metadata/Conventions/DiscriminatorConvention.cs +++ b/src/EFCore/Metadata/Conventions/DiscriminatorConvention.cs @@ -75,7 +75,7 @@ public virtual void ProcessEntityTypeBaseTypeChanged( /// public virtual void ProcessDiscriminatorPropertySet( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder structuralTypeBuilder, string? name, IConventionContext context) { @@ -84,10 +84,22 @@ public virtual void ProcessDiscriminatorPropertySet( return; } - var discriminator = entityTypeBuilder.HasDiscriminator(name, typeof(string)); - if (discriminator != null) + if (structuralTypeBuilder is IConventionEntityTypeBuilder entityTypeBuilder) { - SetDefaultDiscriminatorValues(entityTypeBuilder.Metadata.GetDerivedTypesInclusive(), discriminator); + var discriminator = entityTypeBuilder.HasDiscriminator(name, typeof(string)); + if (discriminator != null) + { + SetDefaultDiscriminatorValues(entityTypeBuilder.Metadata.GetDerivedTypesInclusive(), discriminator); + } + } + else + { + var complexTypeBuilder = (IConventionComplexTypeBuilder)structuralTypeBuilder; + var discriminator = complexTypeBuilder.HasDiscriminator(name, typeof(string)); + if (discriminator != null) + { + SetDefaultDiscriminatorValue(complexTypeBuilder.Metadata, discriminator); + } } } @@ -121,4 +133,16 @@ protected virtual void SetDefaultDiscriminatorValues( discriminatorBuilder.HasValue(entityType, entityType.GetDefaultDiscriminatorValue()); } } + + /// + /// Configures the discriminator value for the given complex type. + /// + /// The complex type to configure. + /// The discriminator builder. + protected virtual void SetDefaultDiscriminatorValue( + IConventionComplexType complexType, + IConventionComplexTypeDiscriminatorBuilder discriminatorBuilder) + { + discriminatorBuilder.HasValue(complexType.GetDefaultDiscriminatorValue()); + } } diff --git a/src/EFCore/Metadata/Conventions/IDiscriminatorPropertySetConvention.cs b/src/EFCore/Metadata/Conventions/IDiscriminatorPropertySetConvention.cs index 85e3bf295d1..63ca3b4847b 100644 --- a/src/EFCore/Metadata/Conventions/IDiscriminatorPropertySetConvention.cs +++ b/src/EFCore/Metadata/Conventions/IDiscriminatorPropertySetConvention.cs @@ -4,7 +4,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// -/// Represents an operation that should be performed when a discriminator property is set for an entity type. +/// Represents an operation that should be performed when a discriminator property is set for a type. /// /// /// See Model building conventions for more information and examples. @@ -14,11 +14,11 @@ public interface IDiscriminatorPropertySetConvention : IConvention /// /// Called after a discriminator property is set. /// - /// The builder for the entity type. + /// The builder for the type. /// The name of the discriminator property. /// Additional information associated with convention execution. void ProcessDiscriminatorPropertySet( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder structuralTypeBuilder, string? name, IConventionContext context); } diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs index 6ccb4600a06..5a179129c29 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs @@ -70,7 +70,7 @@ public int GetLeafCount() string name); public abstract string? OnDiscriminatorPropertySet( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder structuralTypeBuilder, string? name); public abstract IConventionKey? OnEntityTypePrimaryKeyChanged( diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs index e5195facd77..1a91165384b 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs @@ -80,9 +80,9 @@ public override string OnEntityTypeMemberIgnored(IConventionEntityTypeBuilder en return name; } - public override string? OnDiscriminatorPropertySet(IConventionEntityTypeBuilder entityTypeBuilder, string? name) + public override string? OnDiscriminatorPropertySet(IConventionTypeBaseBuilder structuralTypeBuilder, string? name) { - Add(new OnDiscriminatorPropertySetNode(entityTypeBuilder, name)); + Add(new OnDiscriminatorPropertySetNode(structuralTypeBuilder, name)); return name; } @@ -523,13 +523,13 @@ public override void Run(ConventionDispatcher dispatcher) => dispatcher._immediateConventionScope.OnEntityTypeMemberIgnored(EntityTypeBuilder, Name); } - private sealed class OnDiscriminatorPropertySetNode(IConventionEntityTypeBuilder entityTypeBuilder, string? name) : ConventionNode + private sealed class OnDiscriminatorPropertySetNode(IConventionTypeBaseBuilder structuralTypeBuilder, string? name) : ConventionNode { - public IConventionEntityTypeBuilder EntityTypeBuilder { get; } = entityTypeBuilder; + public IConventionTypeBaseBuilder StructuralTypeBuilder { get; } = structuralTypeBuilder; public string? Name { get; } = name; public override void Run(ConventionDispatcher dispatcher) - => dispatcher._immediateConventionScope.OnDiscriminatorPropertySet(EntityTypeBuilder, Name); + => dispatcher._immediateConventionScope.OnDiscriminatorPropertySet(StructuralTypeBuilder, Name); } private sealed class OnEntityTypeBaseTypeChangedNode( diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs index ca81bc4da76..aefed1db3d9 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs @@ -256,15 +256,15 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return !entityTypeBuilder.Metadata.IsIgnored(name) ? null : name; } - public override string? OnDiscriminatorPropertySet(IConventionEntityTypeBuilder entityTypeBuilder, string? name) + public override string? OnDiscriminatorPropertySet(IConventionTypeBaseBuilder structuralTypeBuilder, string? name) { - if (!entityTypeBuilder.Metadata.IsInModel) + if (!structuralTypeBuilder.Metadata.IsInModel) { return null; } #if DEBUG - var initialValue = entityTypeBuilder.Metadata.GetDiscriminatorPropertyName(); + var initialValue = structuralTypeBuilder.Metadata.GetDiscriminatorPropertyName(); #endif using (dispatcher.DelayConventions()) { @@ -273,20 +273,20 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB foreach (var entityTypeConvention in conventionSet.DiscriminatorPropertySetConventions) { entityTypeConvention.ProcessDiscriminatorPropertySet( - entityTypeBuilder, name, _nullableStringConventionContext); + structuralTypeBuilder, name, _nullableStringConventionContext); if (_nullableStringConventionContext.ShouldStopProcessing()) { return _nullableStringConventionContext.Result; } #if DEBUG Check.DebugAssert( - initialValue == entityTypeBuilder.Metadata.GetDiscriminatorPropertyName(), + initialValue == structuralTypeBuilder.Metadata.GetDiscriminatorPropertyName(), $"Convention {entityTypeConvention.GetType().Name} changed value without terminating"); #endif } } - return entityTypeBuilder.Metadata.GetDiscriminatorPropertyName(); + return structuralTypeBuilder.Metadata.GetDiscriminatorPropertyName(); } public override IConventionEntityType? OnEntityTypeBaseTypeChanged( diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs index a223d479e2c..7fad1e7d49c 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs @@ -136,9 +136,9 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual string? OnDiscriminatorPropertySet( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder structuralTypeBuilder, string? name) - => _scope.OnDiscriminatorPropertySet(entityTypeBuilder, name); + => _scope.OnDiscriminatorPropertySet(structuralTypeBuilder, name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs index 3e4c032235b..e3684e0b68f 100644 --- a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs +++ b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs @@ -245,10 +245,10 @@ private static RuntimeEntityType Create(IEntityType entityType, RuntimeModel mod entityType.ClrType, entityType.BaseType == null ? null : model.FindEntityType(entityType.BaseType.Name)!, entityType.HasSharedClrType, - entityType.GetDiscriminatorPropertyName(), entityType.GetChangeTrackingStrategy(), entityType.FindIndexerPropertyInfo(), entityType.IsPropertyBag, + entityType.GetDiscriminatorPropertyName(), entityType.GetDiscriminatorValue(), derivedTypesCount: entityType.GetDirectlyDerivedTypes().Count(), propertyCount: entityType.GetDeclaredProperties().Count(), @@ -293,9 +293,6 @@ protected virtual void ProcessEntityTypeAnnotations( { if (CoreAnnotationNames.AllNames.Contains(key) && key != CoreAnnotationNames.QueryFilter -#pragma warning disable CS0612 // Type or member is obsolete - && key != CoreAnnotationNames.DefiningQuery -#pragma warning restore CS0612 // Type or member is obsolete && key != CoreAnnotationNames.DiscriminatorMappingComplete) { annotations.Remove(key); @@ -307,14 +304,6 @@ protected virtual void ProcessEntityTypeAnnotations( annotations[CoreAnnotationNames.QueryFilter] = new QueryRootRewritingExpressionVisitor(runtimeEntityType.Model).Rewrite((Expression)queryFilter!); } - -#pragma warning disable CS0612 // Type or member is obsolete - if (annotations.TryGetValue(CoreAnnotationNames.DefiningQuery, out var definingQuery)) - { - annotations[CoreAnnotationNames.DefiningQuery] = -#pragma warning restore CS0612 // Type or member is obsolete - new QueryRootRewritingExpressionVisitor(runtimeEntityType.Model).Rewrite((Expression)definingQuery!); - } } } @@ -510,23 +499,25 @@ protected virtual void ProcessServicePropertyAnnotations( private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeTypeBase runtimeStructuralType) { + var complexType = complexProperty.ComplexType; var runtimeComplexProperty = runtimeStructuralType.AddComplexProperty( complexProperty.Name, complexProperty.ClrType, - complexProperty.ComplexType.Name, - complexProperty.ComplexType.ClrType, + complexType.Name, + complexType.ClrType, complexProperty.PropertyInfo, complexProperty.FieldInfo, complexProperty.GetPropertyAccessMode(), complexProperty.IsNullable, complexProperty.IsCollection, - complexProperty.ComplexType.GetChangeTrackingStrategy(), - complexProperty.ComplexType.FindIndexerPropertyInfo(), - complexProperty.ComplexType.IsPropertyBag, - propertyCount: complexProperty.ComplexType.GetDeclaredProperties().Count(), - complexPropertyCount: complexProperty.ComplexType.GetDeclaredComplexProperties().Count()); + complexType.GetChangeTrackingStrategy(), + complexType.FindIndexerPropertyInfo(), + complexType.IsPropertyBag, + complexType.GetDiscriminatorPropertyName(), + complexType.GetDiscriminatorValue(), + propertyCount: complexType.GetDeclaredProperties().Count(), + complexPropertyCount: complexType.GetDeclaredComplexProperties().Count()); - var complexType = complexProperty.ComplexType; var runtimeComplexType = runtimeComplexProperty.ComplexType; foreach (var property in complexType.GetProperties()) diff --git a/src/EFCore/Metadata/IComplexType.cs b/src/EFCore/Metadata/IComplexType.cs index cc26dde9b0e..2afdce621e1 100644 --- a/src/EFCore/Metadata/IComplexType.cs +++ b/src/EFCore/Metadata/IComplexType.cs @@ -16,6 +16,32 @@ public interface IComplexType : IReadOnlyComplexType, ITypeBase /// new IComplexProperty ComplexProperty { get; } + /// + /// Gets the base type of this complex type. Returns if this is not a derived type in an inheritance + /// hierarchy. + /// + new IComplexType? BaseType { get; } + + /// + /// Gets all types in the model that derive from this complex type. + /// + /// The derived types. + new IEnumerable GetDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); + + /// + /// Returns all derived types of this complex type, including the type itself. + /// + /// Derived types. + new IEnumerable GetDerivedTypesInclusive() + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); + + /// + /// Gets all types in the model that directly derive from this complex type. + /// + /// The derived types. + new IEnumerable GetDirectlyDerivedTypes(); + /// /// Gets the entity type on which the complex property chain is declared. /// diff --git a/src/EFCore/Metadata/IConventionComplexType.cs b/src/EFCore/Metadata/IConventionComplexType.cs index bd2dcb09a38..7745994f11a 100644 --- a/src/EFCore/Metadata/IConventionComplexType.cs +++ b/src/EFCore/Metadata/IConventionComplexType.cs @@ -27,4 +27,39 @@ public interface IConventionComplexType : IReadOnlyComplexType, IConventionTypeB /// Gets the associated property. /// new IConventionComplexProperty ComplexProperty { get; } + + /// + /// Gets the base type of this type. Returns if this is not a derived type in an inheritance hierarchy. + /// + new IConventionComplexType? BaseType { get; } + + /// + /// Gets the root base type for a given type. + /// + /// + /// The root base type. If the given type is not a derived type, then the same type is returned. + /// + new IConventionComplexType GetRootType() + => (IConventionComplexType)((IReadOnlyTypeBase)this).GetRootType(); + + /// + /// Gets all types in the model that derive from this type. + /// + /// The derived types. + new IEnumerable GetDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); + + /// + /// Returns all derived types of this type, including the type itself. + /// + /// Derived types. + new IEnumerable GetDerivedTypesInclusive() + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); + + /// + /// Gets all types in the model that directly derive from this type. + /// + /// The derived types. + new IEnumerable GetDirectlyDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDirectlyDerivedTypes().Cast(); } diff --git a/src/EFCore/Metadata/IConventionEntityType.cs b/src/EFCore/Metadata/IConventionEntityType.cs index 0e56d116b9a..254311839f9 100644 --- a/src/EFCore/Metadata/IConventionEntityType.cs +++ b/src/EFCore/Metadata/IConventionEntityType.cs @@ -56,27 +56,6 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// The configuration source for . ConfigurationSource? GetQueryFilterConfigurationSource(); - /// - /// Returns the property that will be used for storing a discriminator value. - /// - /// The property that will be used for storing a discriminator value. - new IConventionProperty? FindDiscriminatorProperty() - => (IConventionProperty?)((IReadOnlyEntityType)this).FindDiscriminatorProperty(); - - /// - /// Sets the that will be used for storing a discriminator value. - /// - /// The property to set. - /// Indicates whether the configuration was specified using a data annotation. - /// The discriminator property. - IConventionProperty? SetDiscriminatorProperty(IReadOnlyProperty? property, bool fromDataAnnotation = false); - - /// - /// Gets the for the discriminator property. - /// - /// The or if no discriminator property has been set. - ConfigurationSource? GetDiscriminatorPropertyConfigurationSource(); - /// /// Sets the value indicating whether the discriminator mapping is complete. /// @@ -93,31 +72,6 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas ConfigurationSource? GetDiscriminatorMappingCompleteConfigurationSource() => FindAnnotation(CoreAnnotationNames.DiscriminatorMappingComplete)?.GetConfigurationSource(); - /// - /// Sets the discriminator value for this entity type. - /// - /// The value to set. - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - object? SetDiscriminatorValue(object? value, bool fromDataAnnotation = false) - => SetAnnotation(CoreAnnotationNames.DiscriminatorValue, value, fromDataAnnotation) - ?.Value; - - /// - /// Removes the discriminator value for this entity type. - /// - /// The removed discriminator value. - object? RemoveDiscriminatorValue() - => RemoveAnnotation(CoreAnnotationNames.DiscriminatorValue)?.Value; - - /// - /// Gets the for the discriminator value. - /// - /// The or if no discriminator value has been set. - ConfigurationSource? GetDiscriminatorValueConfigurationSource() - => FindAnnotation(CoreAnnotationNames.DiscriminatorValue) - ?.GetConfigurationSource(); - /// /// Sets the base type of this entity type. Returns if this is not a derived type in an inheritance hierarchy. /// @@ -126,12 +80,6 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// The new base type. IConventionEntityType? SetBaseType(IConventionEntityType? entityType, bool fromDataAnnotation = false); - /// - /// Returns the configuration source for the property. - /// - /// The configuration source for the property. - ConfigurationSource? GetBaseTypeConfigurationSource(); - /// /// Gets all types in the model from which this entity type derives, starting with the root. /// diff --git a/src/EFCore/Metadata/IConventionModel.cs b/src/EFCore/Metadata/IConventionModel.cs index c31023226d8..6a149ee47c3 100644 --- a/src/EFCore/Metadata/IConventionModel.cs +++ b/src/EFCore/Metadata/IConventionModel.cs @@ -133,6 +133,7 @@ public interface IConventionModel : IReadOnlyModel, IConventionAnnotatable /// The defining entity type. /// Indicates whether the configuration was specified using a data annotation. /// The new entity type. + [Obsolete] IConventionEntityType? AddEntityType( string name, string definingNavigationName, @@ -147,6 +148,7 @@ public interface IConventionModel : IReadOnlyModel, IConventionAnnotatable /// The defining entity type. /// Indicates whether the configuration was specified using a data annotation. /// The new entity type. + [Obsolete] IConventionEntityType? AddEntityType( [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, string definingNavigationName, diff --git a/src/EFCore/Metadata/IConventionTypeBase.cs b/src/EFCore/Metadata/IConventionTypeBase.cs index 01904e20875..2e20cb235ee 100644 --- a/src/EFCore/Metadata/IConventionTypeBase.cs +++ b/src/EFCore/Metadata/IConventionTypeBase.cs @@ -37,6 +37,101 @@ public interface IConventionTypeBase : IReadOnlyTypeBase, IConventionAnnotatable new IConventionEntityType ContainingEntityType => (IConventionEntityType)this; + /// + /// Gets the base type of this type. Returns if this is not a derived type in an inheritance hierarchy. + /// + new IConventionTypeBase? BaseType { get; } + + /// + /// Gets the root base type for a given type. + /// + /// + /// The root base type. If the given type is not a derived type, then the same type is returned. + /// + new IConventionTypeBase GetRootType() + => (IConventionTypeBase)((IReadOnlyTypeBase)this).GetRootType(); + + /// + /// Gets all types in the model that derive from this type. + /// + /// The derived types. + new IEnumerable GetDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); + + /// + /// Returns all derived types of this type, including the type itself. + /// + /// Derived types. + new IEnumerable GetDerivedTypesInclusive() + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); + + /// + /// Gets all types in the model that directly derive from this type. + /// + /// The derived types. + new IEnumerable GetDirectlyDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDirectlyDerivedTypes().Cast(); + + /// + /// Returns the property that will be used for storing a discriminator value. + /// + /// The property that will be used for storing a discriminator value. + new IConventionProperty? FindDiscriminatorProperty() + => (IConventionProperty?)((IReadOnlyEntityType)this).FindDiscriminatorProperty(); + + /// + /// Sets the that will be used for storing a discriminator value. + /// + /// The property to set. + /// Indicates whether the configuration was specified using a data annotation. + /// The discriminator property. + IConventionProperty? SetDiscriminatorProperty(IReadOnlyProperty? property, bool fromDataAnnotation = false); + + /// + /// Gets the for the discriminator property. + /// + /// The or if no discriminator property has been set. + ConfigurationSource? GetDiscriminatorPropertyConfigurationSource(); + + /// + /// Sets the discriminator value for this type. + /// + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + object? SetDiscriminatorValue(object? value, bool fromDataAnnotation = false) + => SetAnnotation(CoreAnnotationNames.DiscriminatorValue, value, fromDataAnnotation) + ?.Value; + + /// + /// Removes the discriminator value for this type. + /// + /// The removed discriminator value. + object? RemoveDiscriminatorValue() + => RemoveAnnotation(CoreAnnotationNames.DiscriminatorValue)?.Value; + + /// + /// Gets the for the discriminator value. + /// + /// The or if no discriminator value has been set. + ConfigurationSource? GetDiscriminatorValueConfigurationSource() + => FindAnnotation(CoreAnnotationNames.DiscriminatorValue) + ?.GetConfigurationSource(); + + /// + /// Sets the base type of this type. Returns if this is not a derived type in an inheritance hierarchy. + /// + /// The base type. + /// Indicates whether the configuration was specified using a data annotation. + /// The new base type. + IConventionTypeBase? SetBaseType(IConventionTypeBase? structuralType, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for the property. + /// + /// The configuration source for the property. + ConfigurationSource? GetBaseTypeConfigurationSource(); + /// /// Marks the given member name as ignored, preventing conventions from adding a matching property /// or navigation to the type. diff --git a/src/EFCore/Metadata/IEntityType.cs b/src/EFCore/Metadata/IEntityType.cs index 9bd3499963f..53a3bb83c9c 100644 --- a/src/EFCore/Metadata/IEntityType.cs +++ b/src/EFCore/Metadata/IEntityType.cs @@ -25,12 +25,6 @@ public interface IEntityType : IReadOnlyEntityType, ITypeBase /// InstantiationBinding? ServiceOnlyConstructorBinding { get; } - /// - /// Returns the that will be used for storing a discriminator value. - /// - new IProperty? FindDiscriminatorProperty() - => (IProperty?)((IReadOnlyEntityType)this).FindDiscriminatorProperty(); - /// /// Gets the root base type for a given entity type. /// @@ -73,14 +67,14 @@ public interface IEntityType : IReadOnlyEntityType, ITypeBase /// /// The derived types. new IEnumerable GetDerivedTypes() - => ((IReadOnlyEntityType)this).GetDerivedTypes().Cast(); + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); /// /// Returns all derived types of this entity type, including the type itself. /// /// Derived types. new IEnumerable GetDerivedTypesInclusive() - => ((IReadOnlyEntityType)this).GetDerivedTypesInclusive().Cast(); + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); /// /// Gets all types in the model that directly derive from this entity type. diff --git a/src/EFCore/Metadata/IMutableComplexType.cs b/src/EFCore/Metadata/IMutableComplexType.cs index 4e9056ba685..0053729426b 100644 --- a/src/EFCore/Metadata/IMutableComplexType.cs +++ b/src/EFCore/Metadata/IMutableComplexType.cs @@ -22,4 +22,40 @@ public interface IMutableComplexType : IReadOnlyComplexType, IMutableTypeBase /// Gets the associated property. /// new IMutableComplexProperty ComplexProperty { get; } + + /// + /// Gets or sets the base type of this complex type. Returns if this is not a derived type in an inheritance + /// hierarchy. + /// + new IMutableComplexType? BaseType { get; } + + /// + /// Gets the root base type for a given complex type. + /// + /// + /// The root base type. If the given complex type is not a derived type, then the same complex type is returned. + /// + new IMutableComplexType GetRootType() + => (IMutableComplexType)((IReadOnlyTypeBase)this).GetRootType(); + + /// + /// Gets all types in the model that derive from this complex type. + /// + /// The derived types. + new IEnumerable GetDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); + + /// + /// Returns all derived types of this complex type, including the type itself. + /// + /// Derived types. + new IEnumerable GetDerivedTypesInclusive() + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); + + /// + /// Gets all types in the model that directly derive from this complex type. + /// + /// The derived types. + new IEnumerable GetDirectlyDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDirectlyDerivedTypes().Cast(); } diff --git a/src/EFCore/Metadata/IMutableEntityType.cs b/src/EFCore/Metadata/IMutableEntityType.cs index d2321aa2b9a..53d118fd707 100644 --- a/src/EFCore/Metadata/IMutableEntityType.cs +++ b/src/EFCore/Metadata/IMutableEntityType.cs @@ -43,19 +43,6 @@ public interface IMutableEntityType : IReadOnlyEntityType, IMutableTypeBase /// The LINQ expression filter. void SetQueryFilter(LambdaExpression? queryFilter); - /// - /// Returns the property that will be used for storing a discriminator value. - /// - /// The property that will be used for storing a discriminator value. - new IMutableProperty? FindDiscriminatorProperty() - => (IMutableProperty?)((IReadOnlyEntityType)this).FindDiscriminatorProperty(); - - /// - /// Sets the that will be used for storing a discriminator value. - /// - /// The property to set. - void SetDiscriminatorProperty(IReadOnlyProperty? property); - /// /// Sets the value indicating whether the discriminator mapping is complete. /// @@ -63,19 +50,6 @@ public interface IMutableEntityType : IReadOnlyEntityType, IMutableTypeBase void SetDiscriminatorMappingComplete(bool? complete) => SetOrRemoveAnnotation(CoreAnnotationNames.DiscriminatorMappingComplete, complete); - /// - /// Sets the discriminator value for this entity type. - /// - /// The value to set. - void SetDiscriminatorValue(object? value) - => SetAnnotation(CoreAnnotationNames.DiscriminatorValue, value); - - /// - /// Removes the discriminator value for this entity type. - /// - void RemoveDiscriminatorValue() - => RemoveAnnotation(CoreAnnotationNames.DiscriminatorValue); - /// /// Gets all types in the model from which this entity type derives, starting with the root. /// @@ -113,21 +87,21 @@ void RemoveDiscriminatorValue() /// /// The derived types. new IEnumerable GetDerivedTypes() - => ((IReadOnlyEntityType)this).GetDerivedTypes().Cast(); + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); /// /// Returns all derived types of this entity type, including the type itself. /// /// Derived types. new IEnumerable GetDerivedTypesInclusive() - => ((IReadOnlyEntityType)this).GetDerivedTypesInclusive().Cast(); + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); /// /// Gets all types in the model that directly derive from this entity type. /// /// The derived types. new IEnumerable GetDirectlyDerivedTypes() - => ((IReadOnlyEntityType)this).GetDirectlyDerivedTypes().Cast(); + => ((IReadOnlyTypeBase)this).GetDirectlyDerivedTypes().Cast(); /// /// Gets the root base type for a given entity type. diff --git a/src/EFCore/Metadata/IMutableTypeBase.cs b/src/EFCore/Metadata/IMutableTypeBase.cs index 3c5080be740..69efecea0e5 100644 --- a/src/EFCore/Metadata/IMutableTypeBase.cs +++ b/src/EFCore/Metadata/IMutableTypeBase.cs @@ -32,6 +32,42 @@ public interface IMutableTypeBase : IReadOnlyTypeBase, IMutableAnnotatable new IMutableEntityType ContainingEntityType => (IMutableEntityType)this; + /// + /// Gets or sets the base type of this type. Returns if this is not a derived type in an inheritance + /// hierarchy. + /// + new IMutableTypeBase? BaseType { get; set; } + + /// + /// Gets the root base type for a given type. + /// + /// + /// The root base type. If the given type is not a derived type, then the same type is returned. + /// + new IMutableTypeBase GetRootType() + => (IMutableTypeBase)((IReadOnlyTypeBase)this).GetRootType(); + + /// + /// Gets all types in the model that derive from this type. + /// + /// The derived types. + new IEnumerable GetDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); + + /// + /// Returns all derived types of this type, including the type itself. + /// + /// Derived types. + new IEnumerable GetDerivedTypesInclusive() + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); + + /// + /// Gets all types in the model that directly derive from this type. + /// + /// The derived types. + new IEnumerable GetDirectlyDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDirectlyDerivedTypes().Cast(); + /// /// Marks the given member name as ignored, preventing conventions from adding a matching property /// or navigation to the type. @@ -60,6 +96,32 @@ public interface IMutableTypeBase : IReadOnlyTypeBase, IMutableAnnotatable /// The list of ignored member names. IEnumerable GetIgnoredMembers(); + /// + /// Returns the property that will be used for storing a discriminator value. + /// + /// The property that will be used for storing a discriminator value. + new IMutableProperty? FindDiscriminatorProperty() + => (IMutableProperty?)((IReadOnlyTypeBase)this).FindDiscriminatorProperty(); + + /// + /// Sets the that will be used for storing a discriminator value. + /// + /// The property to set. + void SetDiscriminatorProperty(IReadOnlyProperty? property); + + /// + /// Sets the discriminator value for this type. + /// + /// The value to set. + void SetDiscriminatorValue(object? value) + => SetAnnotation(CoreAnnotationNames.DiscriminatorValue, value); + + /// + /// Removes the discriminator value for this type. + /// + void RemoveDiscriminatorValue() + => RemoveAnnotation(CoreAnnotationNames.DiscriminatorValue); + /// /// Adds a property to this type. /// diff --git a/src/EFCore/Metadata/IReadOnlyComplexType.cs b/src/EFCore/Metadata/IReadOnlyComplexType.cs index 6d6633a641c..e23f8c684fd 100644 --- a/src/EFCore/Metadata/IReadOnlyComplexType.cs +++ b/src/EFCore/Metadata/IReadOnlyComplexType.cs @@ -19,6 +19,12 @@ public interface IReadOnlyComplexType : IReadOnlyTypeBase /// IReadOnlyComplexProperty ComplexProperty { get; } + /// + /// Gets the base type of this complex type. Returns if this is not a + /// derived type in an inheritance hierarchy. + /// + new IReadOnlyComplexType? BaseType { get; } + /// /// Gets a value indicating whether given type is one of the containing types for this complex type. /// @@ -44,6 +50,34 @@ bool IsContainedBy(Type type) return false; } + /// + /// Gets all types in the model that derive from this complex type. + /// + /// The derived types. + new IEnumerable GetDerivedTypes(); + + /// + /// Returns all derived types of this complex type, including the type itself. + /// + /// Derived types. + new IEnumerable GetDerivedTypesInclusive() + => new[] { this }.Concat(GetDerivedTypes()); + + /// + /// Gets all types in the model that directly derive from this complex type. + /// + /// The derived types. + new IEnumerable GetDirectlyDerivedTypes(); + + /// + /// Gets the root base type for a given entity type. + /// + /// + /// The root base type. If the given entity type is not a derived type, then the same entity type is returned. + /// + new IReadOnlyComplexType GetRootType() + => BaseType?.GetRootType() ?? this; + /// /// /// Creates a human-readable representation of the given metadata. diff --git a/src/EFCore/Metadata/IReadOnlyEntityType.cs b/src/EFCore/Metadata/IReadOnlyEntityType.cs index b5ee5ba7083..0b04f92494c 100644 --- a/src/EFCore/Metadata/IReadOnlyEntityType.cs +++ b/src/EFCore/Metadata/IReadOnlyEntityType.cs @@ -18,7 +18,7 @@ public interface IReadOnlyEntityType : IReadOnlyTypeBase /// Gets the base type of this entity type. Returns if this is not a /// derived type in an inheritance hierarchy. /// - IReadOnlyEntityType? BaseType { get; } + new IReadOnlyEntityType? BaseType { get; } /// /// Gets the data stored in the model for the given entity type. @@ -35,22 +35,6 @@ public interface IReadOnlyEntityType : IReadOnlyTypeBase /// The LINQ expression filter. LambdaExpression? GetQueryFilter(); - /// - /// Returns the property that will be used for storing a discriminator value. - /// - /// The property that will be used for storing a discriminator value. - IReadOnlyProperty? FindDiscriminatorProperty() - { - var propertyName = GetDiscriminatorPropertyName(); - return propertyName == null ? null : FindProperty(propertyName); - } - - /// - /// Returns the name of the property that will be used for storing a discriminator value. - /// - /// The name of the property that will be used for storing a discriminator value. - string? GetDiscriminatorPropertyName(); - /// /// Returns the value indicating whether the discriminator mapping is complete for this entity type. /// @@ -58,28 +42,6 @@ bool GetIsDiscriminatorMappingComplete() => (bool?)this[CoreAnnotationNames.DiscriminatorMappingComplete] ?? true; - /// - /// Returns the discriminator value for this entity type. - /// - /// The discriminator value for this entity type. - object? GetDiscriminatorValue() - { - var annotation = FindAnnotation(CoreAnnotationNames.DiscriminatorValue); - return annotation != null - ? annotation.Value - : !ClrType.IsInstantiable() - || (BaseType == null && GetDirectlyDerivedTypes().Count() == 0) - ? null - : (object?)GetDefaultDiscriminatorValue(); - } - - /// - /// Returns the default discriminator value that would be used for this entity type. - /// - /// The default discriminator value for this entity type. - string GetDefaultDiscriminatorValue() - => !HasSharedClrType ? ClrType.ShortDisplayName() : ShortName(); - /// /// Gets all types in the model from which this entity type derives, starting with the root. /// @@ -123,20 +85,22 @@ IEnumerable GetAllBaseTypesInclusiveAscending() /// Gets all types in the model that derive from this entity type. /// /// The derived types. - IEnumerable GetDerivedTypes(); + new IEnumerable GetDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); /// /// Returns all derived types of this entity type, including the type itself. /// /// Derived types. - IEnumerable GetDerivedTypesInclusive() - => new[] { this }.Concat(GetDerivedTypes()); + new IEnumerable GetDerivedTypesInclusive() + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); /// /// Gets all types in the model that directly derive from this entity type. /// /// The derived types. - IEnumerable GetDirectlyDerivedTypes(); + new IEnumerable GetDirectlyDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDirectlyDerivedTypes().Cast(); /// /// Returns all the derived types of this entity type, including the type itself, @@ -152,7 +116,7 @@ IEnumerable GetConcreteDerivedTypesInclusive() /// /// The root base type. If the given entity type is not a derived type, then the same entity type is returned. /// - IReadOnlyEntityType GetRootType() + new IReadOnlyEntityType GetRootType() => BaseType?.GetRootType() ?? this; /// diff --git a/src/EFCore/Metadata/IReadOnlyTypeBase.cs b/src/EFCore/Metadata/IReadOnlyTypeBase.cs index 5da87dc25aa..b08a3e3eb71 100644 --- a/src/EFCore/Metadata/IReadOnlyTypeBase.cs +++ b/src/EFCore/Metadata/IReadOnlyTypeBase.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata; @@ -24,6 +25,12 @@ public interface IReadOnlyTypeBase : IReadOnlyAnnotatable IReadOnlyEntityType ContainingEntityType => (IReadOnlyEntityType)this; + /// + /// Gets the base type of this type. Returns if this is not a + /// derived type in an inheritance hierarchy. + /// + IReadOnlyTypeBase? BaseType { get; } + /// /// Gets the name of this type. /// @@ -177,6 +184,72 @@ bool IsAssignableFrom(IReadOnlyTypeBase derivedType) bool IsStrictlyDerivedFrom(IReadOnlyTypeBase baseType) => this != Check.NotNull(baseType, nameof(baseType)) && baseType.IsAssignableFrom(this); + /// + /// Gets all types in the model that derive from this type. + /// + /// The derived types. + IEnumerable GetDerivedTypes(); + + /// + /// Returns all derived types of this type, including the type itself. + /// + /// Derived types. + IEnumerable GetDerivedTypesInclusive() + => new[] { this }.Concat(GetDerivedTypes()); + + /// + /// Gets all types in the model that directly derive from this type. + /// + /// The derived types. + IEnumerable GetDirectlyDerivedTypes(); + + /// + /// Gets the root base type for a given entity type. + /// + /// + /// The root base type. If the given entity type is not a derived type, then the same entity type is returned. + /// + IReadOnlyTypeBase GetRootType() + => BaseType?.GetRootType() ?? this; + + /// + /// Returns the property that will be used for storing a discriminator value. + /// + /// The property that will be used for storing a discriminator value. + IReadOnlyProperty? FindDiscriminatorProperty() + { + var propertyName = GetDiscriminatorPropertyName(); + return propertyName == null ? null : FindProperty(propertyName); + } + + /// + /// Returns the name of the property that will be used for storing a discriminator value. + /// + /// The name of the property that will be used for storing a discriminator value. + string? GetDiscriminatorPropertyName(); + + /// + /// Returns the discriminator value for this type. + /// + /// The discriminator value for this type. + object? GetDiscriminatorValue() + { + var annotation = FindAnnotation(CoreAnnotationNames.DiscriminatorValue); + return annotation != null + ? annotation.Value + : !ClrType.IsInstantiable() + || (BaseType == null && GetDirectlyDerivedTypes().Count() == 0) + ? null + : (object?)GetDefaultDiscriminatorValue(); + } + + /// + /// Returns the default discriminator value that would be used for this type. + /// + /// The default discriminator value for this type. + string GetDefaultDiscriminatorValue() + => !HasSharedClrType ? ClrType.ShortDisplayName() : ShortName(); + /// /// Gets the property with the given name. Returns if no property with the given name is defined. /// diff --git a/src/EFCore/Metadata/ITypeBase.cs b/src/EFCore/Metadata/ITypeBase.cs index 45aa87e5bc3..e0d92258e51 100644 --- a/src/EFCore/Metadata/ITypeBase.cs +++ b/src/EFCore/Metadata/ITypeBase.cs @@ -22,11 +22,43 @@ public interface ITypeBase : IReadOnlyTypeBase, IAnnotatable new IEntityType ContainingEntityType => (IEntityType)this; + /// + /// Gets the base type of this type. Returns if this is not a derived type in an inheritance + /// hierarchy. + /// + new ITypeBase? BaseType { get; } + /// /// Gets the for the preferred constructor. /// InstantiationBinding? ConstructorBinding { get; } + /// + /// Gets all types in the model that derive from this type. + /// + /// The derived types. + new IEnumerable GetDerivedTypes() + => ((IReadOnlyTypeBase)this).GetDerivedTypes().Cast(); + + /// + /// Returns all derived types of this type, including the type itself. + /// + /// Derived types. + new IEnumerable GetDerivedTypesInclusive() + => ((IReadOnlyTypeBase)this).GetDerivedTypesInclusive().Cast(); + + /// + /// Gets all types in the model that directly derive from this type. + /// + /// The derived types. + new IEnumerable GetDirectlyDerivedTypes(); + + /// + /// Returns the that will be used for storing a discriminator value. + /// + new IProperty? FindDiscriminatorProperty() + => (IProperty?)((IReadOnlyTypeBase)this).FindDiscriminatorProperty(); + /// /// Gets a property on the given type. Returns if no property is found. /// diff --git a/src/EFCore/Metadata/Internal/ComplexType.cs b/src/EFCore/Metadata/Internal/ComplexType.cs index b906a8a1c5b..35e15e5726f 100644 --- a/src/EFCore/Metadata/Internal/ComplexType.cs +++ b/src/EFCore/Metadata/Internal/ComplexType.cs @@ -137,6 +137,15 @@ public virtual void SetRemovedFromModel() public new virtual ComplexType? BaseType => (ComplexType?)base.BaseType; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override TypeBase? SetBaseType(TypeBase? newBaseType, ConfigurationSource configurationSource) + => SetBaseType((ComplexType?)newBaseType, configurationSource); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -216,7 +225,7 @@ public virtual void SetRemovedFromModel() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - public virtual ConfigurationSource? GetBaseTypeConfigurationSource() + public override ConfigurationSource? GetBaseTypeConfigurationSource() => _baseTypeConfigurationSource; [DebuggerStepThrough] @@ -290,7 +299,7 @@ public virtual bool IsAssignableFrom(ComplexType derivedType) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - public virtual ComplexType GetRootType() + public new virtual ComplexType GetRootType() => BaseType?.GetRootType() ?? this; /// @@ -529,6 +538,53 @@ public override string ToString() #region Explicit interface implementations + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IReadOnlyComplexType? IReadOnlyComplexType.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IMutableComplexType? IMutableComplexType.BaseType + { + get => BaseType; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionComplexType? IConventionComplexType.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IComplexType? IComplexType.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -685,5 +741,35 @@ IEntityType ITypeBase.ContainingEntityType get => ContainingEntityType; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyComplexType.GetDerivedTypes() + => GetDerivedTypes(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyComplexType.GetDirectlyDerivedTypes() + => GetDirectlyDerivedTypes(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IComplexType.GetDirectlyDerivedTypes() + => GetDirectlyDerivedTypes(); + #endregion } diff --git a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs index cb6e7a32c61..1246a351809 100644 --- a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs +++ b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs @@ -195,15 +195,6 @@ public static class CoreAnnotationNames /// public const string QueryFilter = "QueryFilter"; - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [Obsolete] // Remove with defining query - public const string DefiningQuery = "DefiningQuery"; - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -411,9 +402,6 @@ public static class CoreAnnotationNames AfterSaveBehavior, BeforeSaveBehavior, QueryFilter, -#pragma warning disable CS0612 // Type or member is obsolete - DefiningQuery, -#pragma warning restore CS0612 // Type or member is obsolete EagerLoaded, LazyLoadingEnabled, ProviderClrType, diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index 7cadc45f845..79cf4767af7 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -330,6 +330,15 @@ private string DisplayName() private void UpdateIsKeylessConfigurationSource(ConfigurationSource configurationSource) => _isKeylessConfigurationSource = configurationSource.Max(_isKeylessConfigurationSource); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override TypeBase? SetBaseType(TypeBase? newBaseType, ConfigurationSource configurationSource) + => SetBaseType((EntityType?)newBaseType, configurationSource); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -423,9 +432,15 @@ private void UpdateIsKeylessConfigurationSource(ConfigurationSource configuratio /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - public virtual ConfigurationSource? GetBaseTypeConfigurationSource() + public override ConfigurationSource? GetBaseTypeConfigurationSource() => _baseTypeConfigurationSource; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// [DebuggerStepThrough] private void UpdateBaseTypeConfigurationSource(ConfigurationSource configurationSource) => _baseTypeConfigurationSource = configurationSource.Max(_baseTypeConfigurationSource); @@ -475,8 +490,8 @@ private bool InheritsFrom(EntityType entityType) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - public virtual EntityType GetRootType() - => (EntityType)((IReadOnlyEntityType)this).GetRootType(); + new public virtual EntityType GetRootType() + => (EntityType)((IReadOnlyTypeBase)this).GetRootType(); /// /// Runs the conventions when an annotation was set or removed. @@ -2903,75 +2918,6 @@ public virtual PropertyAccessMode GetNavigationAccessMode() public virtual ConfigurationSource? GetQueryFilterConfigurationSource() => FindAnnotation(CoreAnnotationNames.QueryFilter)?.GetConfigurationSource(); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [Obsolete] - public virtual LambdaExpression? SetDefiningQuery(LambdaExpression? definingQuery, ConfigurationSource configurationSource) - => (LambdaExpression?)SetOrRemoveAnnotation(CoreAnnotationNames.DefiningQuery, definingQuery, configurationSource)?.Value; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Property? SetDiscriminatorProperty(Property? property, ConfigurationSource configurationSource) - { - if ((string?)this[CoreAnnotationNames.DiscriminatorProperty] == property?.Name) - { - return property; - } - - CheckDiscriminatorProperty(property); - - SetAnnotation(CoreAnnotationNames.DiscriminatorProperty, property?.Name, configurationSource); - - return Model.ConventionDispatcher.OnDiscriminatorPropertySet(Builder, property?.Name) == property?.Name - ? property - : (Property?)((IReadOnlyEntityType)this).FindDiscriminatorProperty(); - } - - private void CheckDiscriminatorProperty(Property? property) - { - if (property != null) - { - if (BaseType != null) - { - throw new InvalidOperationException( - CoreStrings.DiscriminatorPropertyMustBeOnRoot(DisplayName())); - } - - if (property.DeclaringType != this) - { - throw new InvalidOperationException( - CoreStrings.DiscriminatorPropertyNotFound(property.Name, DisplayName())); - } - } - } - - /// - /// Returns the name of the property that will be used for storing a discriminator value. - /// - /// The name of the property that will be used for storing a discriminator value. - public virtual string? GetDiscriminatorPropertyName() - => BaseType is null - ? (string?)this[CoreAnnotationNames.DiscriminatorProperty] - : ((IReadOnlyEntityType)this).GetRootType().GetDiscriminatorPropertyName(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - public virtual ConfigurationSource? GetDiscriminatorPropertyConfigurationSource() - => FindAnnotation(CoreAnnotationNames.DiscriminatorProperty)?.GetConfigurationSource(); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -3225,30 +3171,6 @@ IModel ITypeBase.Model get => BaseType; } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableEntityType.SetDiscriminatorProperty(IReadOnlyProperty? property) - => SetDiscriminatorProperty((Property?)property, ConfigurationSource.Explicit); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IConventionProperty? IConventionEntityType.SetDiscriminatorProperty( - IReadOnlyProperty? property, - bool fromDataAnnotation) - => SetDiscriminatorProperty( - (Property?)property, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/InternalComplexTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalComplexTypeBuilder.cs index c32a85e7df1..6c150269c17 100644 --- a/src/EFCore/Metadata/Internal/InternalComplexTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalComplexTypeBuilder.cs @@ -503,6 +503,60 @@ public virtual bool CanSetServiceOnlyConstructorBinding( => configurationSource.Overrides(Metadata.GetServiceOnlyConstructorBindingConfigurationSource()) || Metadata.ServiceOnlyConstructorBinding == constructorBinding; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexTypeDiscriminatorBuilder? HasDiscriminator(ConfigurationSource configurationSource) + => DiscriminatorBuilder( + GetOrCreateDiscriminatorProperty(type: null, name: null, memberInfo: null, configurationSource)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexTypeDiscriminatorBuilder? HasDiscriminator( + string? name, + Type? type, + ConfigurationSource configurationSource) + { + Check.DebugAssert(name != null || type != null, $"Either {nameof(name)} or {nameof(type)} should be non-null"); + + return CanSetDiscriminator(name, type, configurationSource) + ? DiscriminatorBuilder( + GetOrCreateDiscriminatorProperty(type, name, memberInfo: null, configurationSource)) + : null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexTypeDiscriminatorBuilder? HasDiscriminator(MemberInfo memberInfo, ConfigurationSource configurationSource) + => CanSetDiscriminator( + Check.NotNull(memberInfo, nameof(memberInfo)).GetSimpleMemberName(), memberInfo.GetMemberType(), configurationSource) + ? DiscriminatorBuilder( + GetOrCreateDiscriminatorProperty(type: null, name: null, memberInfo, configurationSource)) + : null; + + private ComplexTypeDiscriminatorBuilder? DiscriminatorBuilder(InternalPropertyBuilder? discriminatorPropertyBuilder) + => discriminatorPropertyBuilder == null ? null : new ComplexTypeDiscriminatorBuilder(Metadata); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public new virtual InternalComplexTypeBuilder? HasNoDiscriminator(ConfigurationSource configurationSource) + => (InternalComplexTypeBuilder?)base.HasNoDiscriminator(configurationSource); + IConventionComplexType IConventionComplexTypeBuilder.Metadata { [DebuggerStepThrough] @@ -617,4 +671,72 @@ IConventionComplexTypeBuilder IConventionComplexTypeBuilder.RemoveUnusedImplicit bool fromDataAnnotation) => (IConventionComplexTypeBuilder?)UsePropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeDiscriminatorBuilder? IConventionComplexTypeBuilder.HasDiscriminator(bool fromDataAnnotation) + => HasDiscriminator( + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeDiscriminatorBuilder? IConventionComplexTypeBuilder.HasDiscriminator(Type type, bool fromDataAnnotation) + => HasDiscriminator( + name: null, Check.NotNull(type, nameof(type)), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeDiscriminatorBuilder? IConventionComplexTypeBuilder.HasDiscriminator(string name, bool fromDataAnnotation) + => HasDiscriminator( + Check.NotEmpty(name, nameof(name)), type: null, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeDiscriminatorBuilder? IConventionComplexTypeBuilder.HasDiscriminator(string name, Type type, bool fromDataAnnotation) + => HasDiscriminator( + Check.NotEmpty(name, nameof(name)), Check.NotNull(type, nameof(type)), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeDiscriminatorBuilder? IConventionComplexTypeBuilder.HasDiscriminator(MemberInfo memberInfo, bool fromDataAnnotation) + => HasDiscriminator( + memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + public IConventionComplexTypeBuilder? HasNoDiscriminator(bool fromDataAnnotation = false) + => HasNoDiscriminator(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 58c607bc23e..39f503cab8e 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -1322,38 +1322,6 @@ public virtual bool CanSetQueryFilter(LambdaExpression? filter, ConfigurationSou => configurationSource.Overrides(Metadata.GetQueryFilterConfigurationSource()) || Metadata.GetQueryFilter() == filter; - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [Obsolete] - public virtual InternalEntityTypeBuilder? HasDefiningQuery( - LambdaExpression? query, - ConfigurationSource configurationSource) - { - if (CanSetDefiningQuery(query, configurationSource)) - { - Metadata.SetDefiningQuery(query, configurationSource); - - return this; - } - - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [Obsolete] - public virtual bool CanSetDefiningQuery(LambdaExpression? query, ConfigurationSource configurationSource) - => configurationSource.Overrides(Metadata.GetDefiningQueryConfigurationSource()) - || Metadata.GetDefiningQuery() == query; - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -4352,8 +4320,7 @@ public virtual bool CanSetServiceOnlyConstructorBinding( /// public virtual DiscriminatorBuilder? HasDiscriminator(ConfigurationSource configurationSource) => DiscriminatorBuilder( - GetOrCreateDiscriminatorProperty(type: null, name: null, ConfigurationSource.Convention), - configurationSource); + GetOrCreateDiscriminatorProperty(type: null, name: null, memberInfo: null, configurationSource)); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4370,8 +4337,7 @@ public virtual bool CanSetServiceOnlyConstructorBinding( return CanSetDiscriminator(name, type, configurationSource) ? DiscriminatorBuilder( - GetOrCreateDiscriminatorProperty(type, name, configurationSource), - configurationSource) + GetOrCreateDiscriminatorProperty(type, name, memberInfo: null, configurationSource)) : null; } @@ -4385,87 +4351,11 @@ public virtual bool CanSetServiceOnlyConstructorBinding( => CanSetDiscriminator( Check.NotNull(memberInfo, nameof(memberInfo)).GetSimpleMemberName(), memberInfo.GetMemberType(), configurationSource) ? DiscriminatorBuilder( - Metadata.GetRootType().Builder.Property( - memberInfo, configurationSource), - configurationSource) + GetOrCreateDiscriminatorProperty(type: null, name: null, memberInfo, configurationSource)) : null; - private const string DefaultDiscriminatorName = "Discriminator"; - - private static readonly Type DefaultDiscriminatorType = typeof(string); - - private InternalPropertyBuilder? GetOrCreateDiscriminatorProperty(Type? type, string? name, ConfigurationSource configurationSource) - { - var discriminatorProperty = ((IReadOnlyEntityType)Metadata).FindDiscriminatorProperty(); - if ((name != null && discriminatorProperty?.Name != name) - || (type != null && discriminatorProperty?.ClrType != type)) - { - discriminatorProperty = null; - } - - return Metadata.GetRootType().Builder.Property( - type ?? discriminatorProperty?.ClrType ?? DefaultDiscriminatorType, - name ?? discriminatorProperty?.Name ?? DefaultDiscriminatorName, - typeConfigurationSource: type != null ? configurationSource : null, - configurationSource)?.AfterSave(PropertySaveBehavior.Throw, ConfigurationSource.Convention); - } - - private DiscriminatorBuilder? DiscriminatorBuilder( - InternalPropertyBuilder? discriminatorPropertyBuilder, - ConfigurationSource configurationSource) - { - if (discriminatorPropertyBuilder == null) - { - return null; - } - - var rootTypeBuilder = Metadata.GetRootType().Builder; - var discriminatorProperty = discriminatorPropertyBuilder.Metadata; - // Make sure the property is on the root type - discriminatorPropertyBuilder = rootTypeBuilder.Property( - discriminatorProperty.ClrType, discriminatorProperty.Name, null, ConfigurationSource.Convention)!; - - RemoveUnusedDiscriminatorProperty(discriminatorProperty, configurationSource); - - rootTypeBuilder.Metadata.SetDiscriminatorProperty(discriminatorProperty, configurationSource); - - RemoveIncompatibleDiscriminatorValues(Metadata, discriminatorProperty, configurationSource); - - discriminatorPropertyBuilder.IsRequired(true, ConfigurationSource.Convention); - discriminatorPropertyBuilder.HasValueGeneratorFactory( - typeof(DiscriminatorValueGeneratorFactory), ConfigurationSource.Convention); - - return new DiscriminatorBuilder(Metadata); - } - - private void RemoveIncompatibleDiscriminatorValues( - EntityType entityType, - Property? newDiscriminatorProperty, - ConfigurationSource configurationSource) - { - if ((newDiscriminatorProperty != null || entityType.BaseType != null) - && (newDiscriminatorProperty == null - || newDiscriminatorProperty.ClrType.IsInstanceOfType(((IReadOnlyEntityType)entityType).GetDiscriminatorValue()))) - { - return; - } - - if (configurationSource.Overrides(((IConventionEntityType)entityType).GetDiscriminatorValueConfigurationSource())) - { - ((IMutableEntityType)entityType).RemoveDiscriminatorValue(); - } - - if (entityType.BaseType == null) - { - foreach (var derivedType in entityType.GetDerivedTypes()) - { - if (configurationSource.Overrides(((IConventionEntityType)derivedType).GetDiscriminatorValueConfigurationSource())) - { - ((IMutableEntityType)derivedType).RemoveDiscriminatorValue(); - } - } - } - } + private DiscriminatorBuilder? DiscriminatorBuilder(InternalPropertyBuilder? discriminatorPropertyBuilder) + => discriminatorPropertyBuilder == null ? null : new DiscriminatorBuilder(Metadata); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4473,27 +4363,14 @@ private void RemoveIncompatibleDiscriminatorValues( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual InternalEntityTypeBuilder? HasNoDiscriminator(ConfigurationSource configurationSource) + public override InternalEntityTypeBuilder? HasNoDiscriminator(ConfigurationSource configurationSource) { - if (Metadata[CoreAnnotationNames.DiscriminatorProperty] == null) - { - return this; - } - - if (!configurationSource.Overrides(Metadata.GetDiscriminatorPropertyConfigurationSource())) + var builder = base.HasNoDiscriminator(configurationSource); + if (builder == null) { return null; } - if (((IReadOnlyEntityType)Metadata).FindDiscriminatorProperty()?.DeclaringType == Metadata) - { - RemoveUnusedDiscriminatorProperty(null, configurationSource); - } - - Metadata.SetDiscriminatorProperty(null, configurationSource); - - RemoveIncompatibleDiscriminatorValues(Metadata, null, configurationSource); - if (configurationSource == ConfigurationSource.Explicit) { ((IMutableEntityType)Metadata).SetDiscriminatorMappingComplete(null); @@ -4508,78 +4385,6 @@ private void RemoveIncompatibleDiscriminatorValues( return this; } - private void RemoveUnusedDiscriminatorProperty(Property? newDiscriminatorProperty, ConfigurationSource configurationSource) - { - var oldDiscriminatorProperty = ((IReadOnlyEntityType)Metadata).FindDiscriminatorProperty() as Property; - if (oldDiscriminatorProperty?.IsInModel == true - && oldDiscriminatorProperty != newDiscriminatorProperty) - { - oldDiscriminatorProperty.DeclaringType.Builder.RemoveUnusedImplicitProperties( - new[] { oldDiscriminatorProperty }); - - if (oldDiscriminatorProperty.IsInModel) - { - oldDiscriminatorProperty.Builder.IsRequired(null, configurationSource); - oldDiscriminatorProperty.Builder.HasValueGenerator((Type?)null, configurationSource); - } - } - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanSetDiscriminator(string? name, Type? type, ConfigurationSource configurationSource) - => name == null && type == null - ? CanRemoveDiscriminator(configurationSource) - : CanSetDiscriminator(((IReadOnlyEntityType)Metadata).FindDiscriminatorProperty(), name, type, configurationSource); - - private bool CanSetDiscriminator( - IReadOnlyProperty? discriminatorProperty, - string? name, - Type? discriminatorType, - ConfigurationSource configurationSource) - => ((name == null && discriminatorType == null) - || ((name == null || discriminatorProperty?.Name == name) - && (discriminatorType == null || discriminatorProperty?.ClrType == discriminatorType)) - || configurationSource.Overrides(Metadata.GetRootType().GetDiscriminatorPropertyConfigurationSource())) - && (discriminatorProperty != null - || Metadata.GetRootType().Builder.CanAddDiscriminatorProperty( - discriminatorType ?? DefaultDiscriminatorType, - name ?? DefaultDiscriminatorName, - typeConfigurationSource: discriminatorType != null - ? configurationSource - : null)); - - private bool CanRemoveDiscriminator(ConfigurationSource configurationSource) - => CanSetAnnotation(CoreAnnotationNames.DiscriminatorProperty, null, configurationSource); - - private bool CanAddDiscriminatorProperty( - Type propertyType, - string name, - ConfigurationSource? typeConfigurationSource) - { - var conflictingProperty = Metadata.FindPropertiesInHierarchy(name).FirstOrDefault(); - if (conflictingProperty != null - && (conflictingProperty.IsShadowProperty() || conflictingProperty.IsIndexerProperty()) - && conflictingProperty.ClrType != propertyType - && typeConfigurationSource != null - && !typeConfigurationSource.Overrides(conflictingProperty.GetTypeConfigurationSource())) - { - return false; - } - - var memberInfo = Metadata.IsPropertyBag - ? null - : Metadata.ClrType.GetMembersInHierarchy(name).FirstOrDefault(); - - return memberInfo == null - || propertyType == memberInfo.GetMemberType() - || typeConfigurationSource == null; - } - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -5481,28 +5286,6 @@ bool IConventionEntityTypeBuilder.CanHaveTrigger(string modelName, bool fromData bool IConventionEntityTypeBuilder.CanSetQueryFilter(LambdaExpression? filter, bool fromDataAnnotation) => CanSetQueryFilter(filter, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - [Obsolete] - IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasDefiningQuery(LambdaExpression? query, bool fromDataAnnotation) - => HasDefiningQuery(query, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - [Obsolete] - bool IConventionEntityTypeBuilder.CanSetDefiningQuery(LambdaExpression? query, bool fromDataAnnotation) - => CanSetDefiningQuery(query, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -5597,64 +5380,6 @@ bool IConventionEntityTypeBuilder.CanSetDefiningQuery(LambdaExpression? query, b IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasNoDiscriminator(bool fromDataAnnotation) => HasNoDiscriminator(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanSetDiscriminator(string name, bool fromDataAnnotation) - => CanSetDiscriminator( - name, type: null, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanSetDiscriminator(Type type, bool fromDataAnnotation) - => CanSetDiscriminator( - name: null, type, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanSetDiscriminator(string name, Type type, bool fromDataAnnotation) - => CanSetDiscriminator( - name, type, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanSetDiscriminator(MemberInfo memberInfo, bool fromDataAnnotation) - => CanSetDiscriminator( - Check.NotNull(memberInfo, nameof(memberInfo)).GetSimpleMemberName(), memberInfo.GetMemberType(), - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanRemoveDiscriminator(bool fromDataAnnotation) - => CanRemoveDiscriminator(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs index 0e889e243f4..213ba106bdb 100644 --- a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs @@ -13,7 +13,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public abstract class InternalTypeBaseBuilder : AnnotatableBuilder, +public abstract class InternalTypeBaseBuilder : + AnnotatableBuilder, IConventionTypeBaseBuilder { /// @@ -169,9 +170,9 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing ConfigurationSource? configurationSource, bool skipTypeCheck = false) { - var entityType = Metadata; + var structuralType = Metadata; List? propertiesToDetach = null; - var existingProperty = entityType.FindProperty(propertyName); + var existingProperty = structuralType.FindProperty(propertyName); if (existingProperty != null) { if (existingProperty.DeclaringType != Metadata) @@ -181,7 +182,7 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing Metadata.RemoveIgnored(propertyName); } - entityType = (EntityType)existingProperty.DeclaringType; + structuralType = existingProperty.DeclaringType; } if (IsCompatible(memberInfo, existingProperty) @@ -274,7 +275,7 @@ public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existing RemoveMembersInHierarchy(propertyName, configurationSource.Value); } - builder = entityType.AddProperty( + builder = structuralType.AddProperty( propertyName, propertyType, memberInfo, typeConfigurationSource, configurationSource.Value)!.Builder; detachedProperties?.Attach(this); @@ -1022,7 +1023,7 @@ protected virtual void RemovePropertyIfUnused(Property property, ConfigurationSo Metadata.RemoveIgnored(propertyName); } - typeBase = (EntityType)existingComplexProperty.DeclaringType; + typeBase = existingComplexProperty.DeclaringType; } var existingComplexType = existingComplexProperty.ComplexType; @@ -1336,6 +1337,224 @@ public virtual bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessM => configurationSource.Overrides(((IConventionTypeBase)Metadata).GetPropertyAccessModeConfigurationSource()) || ((IConventionTypeBase)Metadata).GetPropertyAccessMode() == propertyAccessMode; + private const string DefaultDiscriminatorName = "Discriminator"; + + private static readonly Type DefaultDiscriminatorType = typeof(string); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalTypeBaseBuilder? HasNoDiscriminator(ConfigurationSource configurationSource) + { + if (Metadata[CoreAnnotationNames.DiscriminatorProperty] == null) + { + return this; + } + + if (!configurationSource.Overrides(Metadata.GetDiscriminatorPropertyConfigurationSource())) + { + return null; + } + + if (((IReadOnlyTypeBase)Metadata).FindDiscriminatorProperty()?.DeclaringType == Metadata) + { + RemoveUnusedDiscriminatorProperty(null, configurationSource); + } + + Metadata.SetDiscriminatorProperty(null, configurationSource); + + RemoveIncompatibleDiscriminatorValues(Metadata, null, configurationSource); + + return this; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected virtual InternalPropertyBuilder? GetOrCreateDiscriminatorProperty( + Type? type, string? name, MemberInfo? memberInfo, ConfigurationSource configurationSource) + { + if (memberInfo != null) + { + type ??= memberInfo.GetMemberType(); + name ??= memberInfo.Name; + } + else + { + var existingDiscriminatorProperty = ((IReadOnlyTypeBase)Metadata).FindDiscriminatorProperty(); + if ((name == null || (existingDiscriminatorProperty?.Name) == name) + && (type == null || (existingDiscriminatorProperty?.ClrType) == type)) + { + type ??= existingDiscriminatorProperty?.ClrType; + name ??= existingDiscriminatorProperty?.Name; + } + + type ??= DefaultDiscriminatorType; + name ??= DefaultDiscriminatorName; + } + + var rootType = Metadata.GetRootType(); + var discriminatorPropertyBuilder = rootType.Builder.Property( + type, + name, + memberInfo, + typeConfigurationSource: type != null ? ConfigurationSource.Convention : null, + ConfigurationSource.Convention); + + if (discriminatorPropertyBuilder == null) + { + if (configurationSource != ConfigurationSource.Convention) + { + rootType.Builder.RemoveProperty(rootType.FindProperty(name)!, configurationSource); + discriminatorPropertyBuilder = rootType.Builder.Property( + type, + name, + memberInfo, + typeConfigurationSource: type != null ? ConfigurationSource.Convention : null, + ConfigurationSource.Convention); + } + + if (discriminatorPropertyBuilder == null) + { + return null; + } + } + + var discriminatorProperty = discriminatorPropertyBuilder.Metadata; + RemoveUnusedDiscriminatorProperty(discriminatorProperty, configurationSource); + + rootType.SetDiscriminatorProperty(discriminatorProperty, configurationSource); + + RemoveIncompatibleDiscriminatorValues(Metadata, discriminatorProperty, configurationSource); + + discriminatorPropertyBuilder.AfterSave(PropertySaveBehavior.Throw, ConfigurationSource.Convention); + discriminatorPropertyBuilder.IsRequired(true, ConfigurationSource.Convention); + discriminatorPropertyBuilder.HasValueGeneratorFactory( + typeof(DiscriminatorValueGeneratorFactory), ConfigurationSource.Convention); + + return discriminatorPropertyBuilder; + } + + private void RemoveUnusedDiscriminatorProperty(Property? newDiscriminatorProperty, ConfigurationSource configurationSource) + { + var oldDiscriminatorProperty = ((IReadOnlyTypeBase)Metadata).FindDiscriminatorProperty() as Property; + if (oldDiscriminatorProperty?.IsInModel == true + && oldDiscriminatorProperty != newDiscriminatorProperty) + { + oldDiscriminatorProperty.DeclaringType.Builder.RemoveUnusedImplicitProperties([oldDiscriminatorProperty]); + + if (oldDiscriminatorProperty.IsInModel) + { + // TODO: Remove this once layering is implemented, #15898 + oldDiscriminatorProperty.Builder.AfterSave(null, ConfigurationSource.Convention); + oldDiscriminatorProperty.Builder.IsRequired(null, ConfigurationSource.Convention); + oldDiscriminatorProperty.Builder.HasValueGenerator((Type?)null, ConfigurationSource.Convention); + } + } + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetDiscriminator(string? name, Type? type, ConfigurationSource configurationSource) + => name == null && type == null + ? CanRemoveDiscriminator(configurationSource) + : CanSetDiscriminator(((IReadOnlyTypeBase)Metadata).FindDiscriminatorProperty(), name, type, configurationSource); + + private bool CanSetDiscriminator( + IReadOnlyProperty? discriminatorProperty, + string? name, + Type? discriminatorType, + ConfigurationSource configurationSource) + => ((name == null && discriminatorType == null) + || ((name == null || discriminatorProperty?.Name == name) + && (discriminatorType == null || discriminatorProperty?.ClrType == discriminatorType)) + || configurationSource.Overrides(Metadata.GetRootType().GetDiscriminatorPropertyConfigurationSource())) + && (discriminatorProperty != null + || Metadata.GetRootType().Builder.CanAddDiscriminatorProperty( + discriminatorType ?? DefaultDiscriminatorType, + name ?? DefaultDiscriminatorName, + typeConfigurationSource: discriminatorType != null + ? configurationSource + : null)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanRemoveDiscriminator(ConfigurationSource configurationSource) + => CanSetAnnotation(CoreAnnotationNames.DiscriminatorProperty, null, configurationSource); + + private bool CanAddDiscriminatorProperty( + Type propertyType, + string name, + ConfigurationSource? typeConfigurationSource) + { + var conflictingProperty = Metadata.FindPropertiesInHierarchy(name).FirstOrDefault(); + if (conflictingProperty != null + && (conflictingProperty.IsShadowProperty() || conflictingProperty.IsIndexerProperty()) + && conflictingProperty.ClrType != propertyType + && typeConfigurationSource != null + && !typeConfigurationSource.Overrides(conflictingProperty.GetTypeConfigurationSource())) + { + return false; + } + + var memberInfo = Metadata.IsPropertyBag + ? null + : Metadata.ClrType.GetMembersInHierarchy(name).FirstOrDefault(); + + return memberInfo == null + || propertyType == memberInfo.GetMemberType() + || typeConfigurationSource == null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected virtual void RemoveIncompatibleDiscriminatorValues( + TypeBase structuralType, + Property? newDiscriminatorProperty, + ConfigurationSource configurationSource) + { + if ((newDiscriminatorProperty != null || structuralType.BaseType != null) + && (newDiscriminatorProperty == null + || newDiscriminatorProperty.ClrType.IsInstanceOfType(((IReadOnlyTypeBase)structuralType).GetDiscriminatorValue()))) + { + return; + } + + if (configurationSource.Overrides(((IConventionTypeBase)structuralType).GetDiscriminatorValueConfigurationSource())) + { + ((IMutableTypeBase)structuralType).RemoveDiscriminatorValue(); + } + + if (structuralType.BaseType == null) + { + foreach (var derivedType in structuralType.GetDerivedTypes()) + { + if (configurationSource.Overrides(((IConventionTypeBase)derivedType).GetDiscriminatorValueConfigurationSource())) + { + ((IMutableTypeBase)derivedType).RemoveDiscriminatorValue(); + } + } + } + } + IConventionTypeBase IConventionTypeBaseBuilder.Metadata { [DebuggerStepThrough] @@ -1375,6 +1594,16 @@ IConventionTypeBase IConventionTypeBaseBuilder.Metadata => (IConventionTypeBaseBuilder?)base.HasNoAnnotation( name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanRemoveDiscriminator(bool fromDataAnnotation) + => CanRemoveDiscriminator(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -1768,4 +1997,52 @@ bool IConventionTypeBaseBuilder.CanSetChangeTrackingStrategy(ChangeTrackingStrat bool IConventionTypeBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => CanSetPropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanSetDiscriminator(string name, bool fromDataAnnotation) + => CanSetDiscriminator( + name, type: null, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanSetDiscriminator(Type type, bool fromDataAnnotation) + => CanSetDiscriminator( + name: null, type, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanSetDiscriminator(string name, Type type, bool fromDataAnnotation) + => CanSetDiscriminator( + name, type, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanSetDiscriminator(MemberInfo memberInfo, bool fromDataAnnotation) + => CanSetDiscriminator( + Check.NotNull(memberInfo, nameof(memberInfo)).GetSimpleMemberName(), memberInfo.GetMemberType(), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } diff --git a/src/EFCore/Metadata/Internal/TypeBase.cs b/src/EFCore/Metadata/Internal/TypeBase.cs index 720db7259b9..e654a0e2f7e 100644 --- a/src/EFCore/Metadata/Internal/TypeBase.cs +++ b/src/EFCore/Metadata/Internal/TypeBase.cs @@ -155,7 +155,7 @@ public virtual InternalTypeBaseBuilder Builder /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - protected virtual TypeBase? BaseType + public virtual TypeBase? BaseType { get => _baseType; set => _baseType = value; @@ -167,9 +167,25 @@ protected virtual TypeBase? BaseType /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - protected virtual SortedSet DirectlyDerivedTypes + public virtual SortedSet DirectlyDerivedTypes => _directlyDerivedTypes; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public abstract TypeBase? SetBaseType(TypeBase? newBaseType, ConfigurationSource configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public abstract ConfigurationSource? GetBaseTypeConfigurationSource(); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -190,7 +206,7 @@ protected virtual IEnumerable GetDerivedTypes() { if (DirectlyDerivedTypes.Count == 0) { - return Enumerable.Empty(); + return []; } var derivedTypes = new List(); @@ -217,7 +233,7 @@ protected virtual IEnumerable GetDerivedTypes() [DebuggerStepThrough] public virtual IEnumerable GetDerivedTypesInclusive() => DirectlyDerivedTypes.Count == 0 - ? new[] { this } + ? [this] : new[] { this }.Concat(GetDerivedTypes()); /// @@ -254,6 +270,16 @@ public virtual bool IsAssignableFrom(TypeBase derivedType) return false; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + public virtual TypeBase GetRootType() + => (TypeBase)((IReadOnlyTypeBase)this).GetRootType(); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -344,6 +370,65 @@ public virtual void UpdateConfigurationSource(ConfigurationSource configurationS return null; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Property? SetDiscriminatorProperty(Property? property, ConfigurationSource configurationSource) + { + if ((string?)this[CoreAnnotationNames.DiscriminatorProperty] == property?.Name) + { + return property; + } + + CheckDiscriminatorProperty(property); + + SetAnnotation(CoreAnnotationNames.DiscriminatorProperty, property?.Name, configurationSource); + + return Model.ConventionDispatcher.OnDiscriminatorPropertySet(Builder, property?.Name) == property?.Name + ? property + : (Property?)((IReadOnlyEntityType)this).FindDiscriminatorProperty(); + } + + private void CheckDiscriminatorProperty(Property? property) + { + if (property != null) + { + if (BaseType != null) + { + throw new InvalidOperationException( + CoreStrings.DiscriminatorPropertyMustBeOnRoot(DisplayName())); + } + + if (property.DeclaringType != this) + { + throw new InvalidOperationException( + CoreStrings.DiscriminatorPropertyNotFound(property.Name, DisplayName())); + } + } + } + + /// + /// Returns the name of the property that will be used for storing a discriminator value. + /// + /// The name of the property that will be used for storing a discriminator value. + public virtual string? GetDiscriminatorPropertyName() + => BaseType is null + ? (string?)this[CoreAnnotationNames.DiscriminatorProperty] + : ((IReadOnlyEntityType)this).GetRootType().GetDiscriminatorPropertyName(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + public virtual ConfigurationSource? GetDiscriminatorPropertyConfigurationSource() + => FindAnnotation(CoreAnnotationNames.DiscriminatorProperty)?.GetConfigurationSource(); + #region Properties /// @@ -1447,6 +1532,54 @@ Type IReadOnlyTypeBase.ClrType get => ClrType; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IReadOnlyTypeBase? IReadOnlyTypeBase.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IMutableTypeBase? IMutableTypeBase.BaseType + { + get => BaseType; + set => SetBaseType((TypeBase?)value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionTypeBase? IConventionTypeBase.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + ITypeBase? ITypeBase.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -1479,6 +1612,71 @@ IConventionTypeBaseBuilder IConventionTypeBase.Builder string? IConventionTypeBase.AddIgnored(string name, bool fromDataAnnotation) => AddIgnored(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBase? IConventionTypeBase.SetBaseType(IConventionTypeBase? structuralType, bool fromDataAnnotation) + => SetBaseType( + (TypeBase?)structuralType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetDerivedTypes() + => GetDerivedTypes(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetDirectlyDerivedTypes() + => DirectlyDerivedTypes; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable ITypeBase.GetDirectlyDerivedTypes() + => DirectlyDerivedTypes; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutableTypeBase.SetDiscriminatorProperty(IReadOnlyProperty? property) + => SetDiscriminatorProperty((Property?)property, ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionProperty? IConventionTypeBase.SetDiscriminatorProperty( + IReadOnlyProperty? property, + bool fromDataAnnotation) + => SetDiscriminatorProperty( + (Property?)property, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/RuntimeComplexProperty.cs b/src/EFCore/Metadata/RuntimeComplexProperty.cs index e8b95bbeafb..d00da612c07 100644 --- a/src/EFCore/Metadata/RuntimeComplexProperty.cs +++ b/src/EFCore/Metadata/RuntimeComplexProperty.cs @@ -37,6 +37,8 @@ public RuntimeComplexProperty( ChangeTrackingStrategy changeTrackingStrategy, PropertyInfo? indexerPropertyInfo, bool propertyBag, + string? discriminatorProperty, + object? discriminatorValue, int propertyCount, int complexPropertyCount) : base(name, propertyInfo, fieldInfo, propertyAccessMode) @@ -47,6 +49,7 @@ public RuntimeComplexProperty( _isCollection = collection; ComplexType = new RuntimeComplexType( targetTypeName, targetType, this, changeTrackingStrategy, indexerPropertyInfo, propertyBag, + discriminatorProperty, discriminatorValue, propertyCount: propertyCount, complexPropertyCount: complexPropertyCount); } diff --git a/src/EFCore/Metadata/RuntimeComplexType.cs b/src/EFCore/Metadata/RuntimeComplexType.cs index 0fd2c553b67..0c769ff8ede 100644 --- a/src/EFCore/Metadata/RuntimeComplexType.cs +++ b/src/EFCore/Metadata/RuntimeComplexType.cs @@ -34,10 +34,13 @@ public RuntimeComplexType( ChangeTrackingStrategy changeTrackingStrategy, PropertyInfo? indexerPropertyInfo, bool propertyBag, + string? discriminatorProperty, + object? discriminatorValue, int propertyCount, int complexPropertyCount) : base( - name, type, complexProperty.DeclaringType.Model, null, changeTrackingStrategy, indexerPropertyInfo, propertyBag, + name, type, complexProperty.DeclaringType.Model, baseType: null, changeTrackingStrategy, indexerPropertyInfo, propertyBag, + discriminatorProperty, discriminatorValue, derivedTypesCount: 0, propertyCount: propertyCount, complexPropertyCount: complexPropertyCount) @@ -51,6 +54,9 @@ public RuntimeComplexType( }; } + private new RuntimeComplexType? BaseType + => (RuntimeComplexType?)base.BaseType; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -241,4 +247,39 @@ IEntityType ITypeBase.ContainingEntityType [DebuggerStepThrough] get => ContainingEntityType; } + + /// + IReadOnlyComplexType? IReadOnlyComplexType.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + + /// + IComplexType? IComplexType.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyComplexType.GetDerivedTypes() + => GetDerivedTypes(); + + /// + IEnumerable IReadOnlyComplexType.GetDerivedTypesInclusive() + => !HasDirectlyDerivedTypes + ? [this] + : new[] { this }.Concat(GetDerivedTypes()); + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyComplexType.GetDirectlyDerivedTypes() + => DirectlyDerivedTypes.Cast(); + + /// + [DebuggerStepThrough] + IEnumerable IComplexType.GetDirectlyDerivedTypes() + => DirectlyDerivedTypes.Cast(); } diff --git a/src/EFCore/Metadata/RuntimeEntityType.cs b/src/EFCore/Metadata/RuntimeEntityType.cs index e9516734cea..d6d9c18e7e4 100644 --- a/src/EFCore/Metadata/RuntimeEntityType.cs +++ b/src/EFCore/Metadata/RuntimeEntityType.cs @@ -25,7 +25,6 @@ public class RuntimeEntityType : RuntimeTypeBase, IRuntimeEntityType private Utilities.OrderedDictionary? _namedIndexes; private readonly Utilities.OrderedDictionary, RuntimeKey> _keys; private Utilities.OrderedDictionary? _triggers; - private readonly object? _discriminatorValue; private readonly bool _hasSharedClrType; private RuntimeKey? _primaryKey; private InstantiationBinding? _constructorBinding; @@ -58,10 +57,10 @@ public RuntimeEntityType( bool sharedClrType, RuntimeModel model, RuntimeEntityType? baseType, - string? discriminatorProperty, ChangeTrackingStrategy changeTrackingStrategy, PropertyInfo? indexerPropertyInfo, bool propertyBag, + string? discriminatorProperty, object? discriminatorValue, int derivedTypesCount, int propertyCount, @@ -76,14 +75,12 @@ public RuntimeEntityType( int triggerCount) : base( name, type, model, baseType, changeTrackingStrategy, indexerPropertyInfo, propertyBag, + discriminatorProperty, discriminatorValue, derivedTypesCount: derivedTypesCount, propertyCount: propertyCount, complexPropertyCount: complexPropertyCount) { _hasSharedClrType = sharedClrType; - - SetAnnotation(CoreAnnotationNames.DiscriminatorProperty, discriminatorProperty); - _discriminatorValue = discriminatorValue; _foreignKeys = new List(foreignKeyCount); _navigations = new Utilities.OrderedDictionary(navigationCount, StringComparer.Ordinal); if (skipNavigationCount > 0) @@ -327,7 +324,7 @@ public virtual IEnumerable FindDeclaredForeignKeys(IReadOnlyL private RuntimeForeignKey? FindDeclaredForeignKey( IReadOnlyList properties, IReadOnlyKey principalKey, - IReadOnlyEntityType principalEntityType) + IReadOnlyTypeBase principalEntityType) { if (_foreignKeys.Count == 0) { @@ -949,23 +946,6 @@ public virtual DebugView DebugView LambdaExpression? IReadOnlyEntityType.GetQueryFilter() => (LambdaExpression?)this[CoreAnnotationNames.QueryFilter]; - /// - [DebuggerStepThrough] - string? IReadOnlyEntityType.GetDiscriminatorPropertyName() - { - if (BaseType != null) - { - return ((IReadOnlyEntityType)this).GetRootType().GetDiscriminatorPropertyName(); - } - - return (string?)this[CoreAnnotationNames.DiscriminatorProperty]; - } - - /// - [DebuggerStepThrough] - object? IReadOnlyEntityType.GetDiscriminatorValue() - => _discriminatorValue; - /// bool IReadOnlyTypeBase.HasSharedClrType { @@ -1009,7 +989,7 @@ IEnumerable IReadOnlyEntityType.GetDerivedTypes() /// IEnumerable IReadOnlyEntityType.GetDerivedTypesInclusive() => !HasDirectlyDerivedTypes - ? new[] { this } + ? [this] : new[] { this }.Concat(GetDerivedTypes()); /// diff --git a/src/EFCore/Metadata/RuntimeModel.cs b/src/EFCore/Metadata/RuntimeModel.cs index 3b52e587521..bab0de72d9f 100644 --- a/src/EFCore/Metadata/RuntimeModel.cs +++ b/src/EFCore/Metadata/RuntimeModel.cs @@ -98,13 +98,13 @@ public virtual void SetSkipDetectChanges(bool skipDetectChanges) /// The CLR class that is used to represent instances of this type. /// Whether this entity type can share its ClrType with other entities. /// The base type of this entity type. - /// The name of the property that will be used for storing a discriminator value. /// The change tracking strategy for this entity type. /// The for the indexer on the associated CLR type if one exists. /// /// A value indicating whether this entity type has an indexer which is able to contain arbitrary properties /// and a method that can be used to determine whether a given indexer property contains a value. /// + /// The name of the property that will be used for storing a discriminator value. /// The discriminator value for this entity type. /// The expected number of directly derived entity types. /// The expected number of declared properties for this entity type. @@ -123,10 +123,10 @@ public virtual RuntimeEntityType AddEntityType( [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, RuntimeEntityType? baseType = null, bool sharedClrType = false, - string? discriminatorProperty = null, ChangeTrackingStrategy changeTrackingStrategy = ChangeTrackingStrategy.Snapshot, PropertyInfo? indexerPropertyInfo = null, bool propertyBag = false, + string? discriminatorProperty = null, object? discriminatorValue = null, int derivedTypesCount = 0, int propertyCount = 0, @@ -146,10 +146,10 @@ public virtual RuntimeEntityType AddEntityType( sharedClrType, this, baseType, - discriminatorProperty, changeTrackingStrategy, indexerPropertyInfo, propertyBag, + discriminatorProperty, discriminatorValue, derivedTypesCount: derivedTypesCount, propertyCount: propertyCount, diff --git a/src/EFCore/Metadata/RuntimeTypeBase.cs b/src/EFCore/Metadata/RuntimeTypeBase.cs index c3c6676699a..64fbb264fb6 100644 --- a/src/EFCore/Metadata/RuntimeTypeBase.cs +++ b/src/EFCore/Metadata/RuntimeTypeBase.cs @@ -19,6 +19,7 @@ public abstract class RuntimeTypeBase : RuntimeAnnotatableBase, IRuntimeTypeBase private RuntimeModel _model; private readonly RuntimeTypeBase? _baseType; private SortedSet? _directlyDerivedTypes; + private readonly object? _discriminatorValue; private readonly Utilities.OrderedDictionary _properties; private Utilities.OrderedDictionary? _complexProperties; private readonly PropertyInfo? _indexerPropertyInfo; @@ -45,6 +46,8 @@ protected RuntimeTypeBase( ChangeTrackingStrategy changeTrackingStrategy, PropertyInfo? indexerPropertyInfo, bool propertyBag, + string? discriminatorProperty, + object? discriminatorValue, int derivedTypesCount, int propertyCount, int complexPropertyCount) @@ -60,7 +63,11 @@ protected RuntimeTypeBase( _changeTrackingStrategy = changeTrackingStrategy; _indexerPropertyInfo = indexerPropertyInfo; + _discriminatorValue = discriminatorValue; _isPropertyBag = propertyBag; + if(discriminatorProperty != null) { + SetAnnotation(CoreAnnotationNames.DiscriminatorProperty, discriminatorProperty); + } _properties = new Utilities.OrderedDictionary(propertyCount, new PropertyNameComparer(this)); if (complexPropertyCount > 0) { @@ -129,7 +136,7 @@ protected virtual IEnumerable GetDerivedTypes() { if (!HasDirectlyDerivedTypes) { - return Enumerable.Empty(); + return []; } var derivedTypes = new List(); @@ -363,6 +370,8 @@ protected virtual IEnumerable GetProperties() /// A value indicating whether this entity type has an indexer which is able to contain arbitrary properties /// and a method that can be used to determine whether a given indexer property contains a value. /// + /// The name of the property that will be used for storing a discriminator value. + /// The discriminator value for this complex type. /// The expected number of declared properties for this complex type. /// The expected number of declared complex properties for this complex type. /// The newly created property. @@ -379,6 +388,8 @@ public virtual RuntimeComplexProperty AddComplexProperty( ChangeTrackingStrategy changeTrackingStrategy = ChangeTrackingStrategy.Snapshot, PropertyInfo? indexerPropertyInfo = null, bool propertyBag = false, + string? discriminatorProperty = null, + object? discriminatorValue = null, int propertyCount = 0, int complexPropertyCount = 0) { @@ -396,6 +407,8 @@ public virtual RuntimeComplexProperty AddComplexProperty( changeTrackingStrategy, indexerPropertyInfo, propertyBag, + discriminatorProperty, + discriminatorValue, propertyCount: propertyCount, complexPropertyCount: complexPropertyCount); @@ -428,8 +441,8 @@ public virtual IEnumerable GetDeclaredComplexProperties( private IEnumerable GetDerivedComplexProperties() => !HasDirectlyDerivedTypes - ? Enumerable.Empty() - : GetDerivedTypes().Cast().SelectMany(et => et.GetDeclaredComplexProperties()); + ? [] + : GetDerivedTypes().SelectMany(et => et.GetDeclaredComplexProperties()); /// /// Gets the complex properties defined on this type. @@ -459,7 +472,7 @@ private IEnumerable FindDerivedComplexProperties(string Check.NotNull(propertyName, nameof(propertyName)); return !HasDirectlyDerivedTypes - ? Enumerable.Empty() + ? [] : (IEnumerable)GetDerivedTypes() .Select(et => et.FindDeclaredComplexProperty(propertyName)).Where(p => p != null); } @@ -607,8 +620,43 @@ public virtual void FinalizeType() protected static IEnumerable ToEnumerable(T? element) where T : class => element == null - ? Enumerable.Empty() - : new[] { element }; + ? [] + : [element]; + + /// + IReadOnlyTypeBase? IReadOnlyTypeBase.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + + /// + ITypeBase? ITypeBase.BaseType + { + [DebuggerStepThrough] + get => BaseType; + } + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetDerivedTypes() + => GetDerivedTypes(); + + /// + IEnumerable IReadOnlyTypeBase.GetDerivedTypesInclusive() + => !HasDirectlyDerivedTypes + ? [this] + : new[] { this }.Concat(GetDerivedTypes()); + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetDirectlyDerivedTypes() + => DirectlyDerivedTypes.Cast(); + + /// + [DebuggerStepThrough] + IEnumerable ITypeBase.GetDirectlyDerivedTypes() + => DirectlyDerivedTypes.Cast(); /// bool IReadOnlyTypeBase.HasSharedClrType @@ -638,6 +686,23 @@ IModel ITypeBase.Model get => Model; } + /// + [DebuggerStepThrough] + string? IReadOnlyTypeBase.GetDiscriminatorPropertyName() + { + if (BaseType != null) + { + return ((IReadOnlyTypeBase)this).GetRootType().GetDiscriminatorPropertyName(); + } + + return (string?)this[CoreAnnotationNames.DiscriminatorProperty]; + } + + /// + [DebuggerStepThrough] + object? IReadOnlyTypeBase.GetDiscriminatorValue() + => _discriminatorValue; + /// [DebuggerStepThrough] IReadOnlyProperty? IReadOnlyTypeBase.FindDeclaredProperty(string name) diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs index 0c4d3874bfe..5752032680e 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs @@ -200,9 +200,8 @@ protected override Expression VisitExtension(Expression extensionExpression) { case EntityQueryRootExpression entityQueryRootExpression: var entityType = entityQueryRootExpression.EntityType; -#pragma warning disable CS0618 // Type or member is obsolete - var definingQuery = entityType.GetDefiningQuery(); -#pragma warning restore CS0618 // Type or member is obsolete + // TODO: Move to InMemory #21624 + var definingQuery = (LambdaExpression?)entityType["InMemory:DefiningQuery"]; NavigationExpansionExpression navigationExpansionExpression; if (definingQuery != null // Apply defining query only when it is not custom query root @@ -1120,15 +1119,14 @@ private NavigationExpansionExpression ProcessInclude( { if (source.PendingSelector is NavigationTreeExpression { Value: EntityReference entityReference }) { -#pragma warning disable CS0618 // Type or member is obsolete - if (entityReference.EntityType.GetDefiningQuery() != null) + // TODO: Move to InMemory #21624 + if (entityReference.EntityType["InMemory:DefiningQuery"] != null) { throw new InvalidOperationException( #pragma warning disable CS0612 // Type or member is obsolete CoreStrings.IncludeOnEntityWithDefiningQueryNotSupported(expression, entityReference.EntityType.DisplayName())); #pragma warning restore CS0612 // Type or member is obsolete } -#pragma warning restore CS0618 // Type or member is obsolete if (expression is ConstantExpression { Value: string navigationChain }) { diff --git a/src/EFCore/Query/StructuralTypeShaperExpression.cs b/src/EFCore/Query/StructuralTypeShaperExpression.cs index 5a41c40663e..10c73ba14c0 100644 --- a/src/EFCore/Query/StructuralTypeShaperExpression.cs +++ b/src/EFCore/Query/StructuralTypeShaperExpression.cs @@ -21,10 +21,10 @@ namespace Microsoft.EntityFrameworkCore.Query; public class StructuralTypeShaperExpression : Expression, IPrintableExpression { private static readonly MethodInfo CreateUnableToDiscriminateExceptionMethod - = typeof(StructuralTypeShaperExpression).GetTypeInfo().GetDeclaredMethod(nameof(CreateUnableToDiscriminateException))!; + = typeof(StructuralTypeShaperExpression).GetMethod(nameof(CreateUnableToDiscriminateException))!; private static readonly MethodInfo GetDiscriminatorValueMethod - = typeof(IReadOnlyEntityType).GetTypeInfo().GetDeclaredMethod(nameof(IReadOnlyEntityType.GetDiscriminatorValue))!; + = typeof(IReadOnlyTypeBase).GetMethod(nameof(IReadOnlyTypeBase.GetDiscriminatorValue))!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -176,7 +176,7 @@ protected virtual LambdaExpression GenerateMaterializationCondition(ITypeBase ty expressions.Add(conditions); } - body = Block(new[] { discriminatorValueVariable }, expressions); + body = Block([discriminatorValueVariable], expressions); } else { diff --git a/test/Directory.Packages.props b/test/Directory.Packages.props index de8a6dc1cf3..59f7d4a7b5c 100644 --- a/test/Directory.Packages.props +++ b/test/Directory.Packages.props @@ -1,11 +1,13 @@ + + - - - + + + + - @@ -14,19 +16,13 @@ - + - - - - - - diff --git a/test/EFCore.Analyzers.Tests/EFCore.Analyzers.Tests.csproj b/test/EFCore.Analyzers.Tests/EFCore.Analyzers.Tests.csproj index 084a382f3ab..c763d148fe9 100644 --- a/test/EFCore.Analyzers.Tests/EFCore.Analyzers.Tests.csproj +++ b/test/EFCore.Analyzers.Tests/EFCore.Analyzers.Tests.csproj @@ -6,6 +6,8 @@ Microsoft.EntityFrameworkCore true true + + $(NoWarn);NU1608 @@ -45,10 +47,10 @@ - - - - + + + + diff --git a/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs index 1daeec31d64..fbca3930f57 100644 --- a/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs @@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore; #nullable disable -public class EndToEndCosmosTest : NonSharedModelTestBase +public class EndToEndCosmosTest(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { [ConditionalFact] public async Task Can_add_update_delete_end_to_end() @@ -27,7 +27,6 @@ public async Task Can_add_update_delete_end_to_end() using (var context = contextFactory.CreateContext()) { ListLoggerFactory.Clear(); - context.Database.EnsureCreated(); context.Add(customer); @@ -37,7 +36,7 @@ public async Task Can_add_update_delete_end_to_end() Assert.Equal(LogLevel.Information, logEntry.Level); Assert.Contains("CreateItem", logEntry.Message); - Assert.Equal(3, ListLoggerFactory.Log.Count(l => l.Id == CosmosEventId.SyncNotSupported)); + Assert.Equal(1, ListLoggerFactory.Log.Count(l => l.Id == CosmosEventId.SyncNotSupported)); } using (var context = contextFactory.CreateContext()) @@ -111,8 +110,6 @@ public async Task Can_add_update_delete_end_to_end_async() using (var context = contextFactory.CreateContext()) { - await context.Database.EnsureCreatedAsync(); - await context.AddAsync(customer); await context.SaveChangesAsync(); @@ -189,8 +186,6 @@ public async Task Can_add_update_delete_detached_entity_end_to_end_async() string storeId = null; using (var context = contextFactory.CreateContext()) { - await context.Database.EnsureCreatedAsync(); - var entry = await context.AddAsync(customer); await context.SaveChangesAsync(); @@ -257,102 +252,6 @@ public async Task Can_add_update_delete_detached_entity_end_to_end_async() } } - [ConditionalFact] - public async Task Can_add_update_untracked_properties() - { - var contextFactory = await InitializeAsync( - b => b.Entity(), - shouldLogCategory: _ => true, - onConfiguring: o => o.ConfigureWarnings(w => w.Log(CosmosEventId.SyncNotSupported, CosmosEventId.NoPartitionKeyDefined))); - - var customer = new Customer { Id = 42, Name = "Theon" }; - - using (var context = contextFactory.CreateContext()) - { - context.Database.EnsureCreated(); - - var entry = context.Add(customer); - - context.SaveChanges(); - - var document = entry.Property("__jObject").CurrentValue; - Assert.NotNull(document); - Assert.Equal("Theon", document["Name"]); - - context.Remove(customer); - - context.SaveChanges(); - - Assert.Equal(4, ListLoggerFactory.Log.Count(l => l.Id == CosmosEventId.SyncNotSupported)); - } - - using (var context = contextFactory.CreateContext()) - { - Assert.Empty(context.Set().ToList()); - - var entry = context.Add(customer); - - entry.Property("__jObject").CurrentValue = new JObject { ["key1"] = "value1" }; - - context.SaveChanges(); - - var document = entry.Property("__jObject").CurrentValue; - Assert.NotNull(document); - Assert.Equal("Theon", document["Name"]); - Assert.Equal("value1", document["key1"]); - - document["key2"] = "value2"; - entry.State = EntityState.Modified; - context.SaveChanges(); - - Assert.Equal(7, ListLoggerFactory.Log.Count(l => l.Id == CosmosEventId.SyncNotSupported)); - } - - using (var context = contextFactory.CreateContext()) - { - var customerFromStore = context.Set().Single(); - - Assert.Equal(42, customerFromStore.Id); - Assert.Equal("Theon", customerFromStore.Name); - - var entry = context.Entry(customerFromStore); - var document = entry.Property("__jObject").CurrentValue; - Assert.Equal("value1", document["key1"]); - Assert.Equal("value2", document["key2"]); - - document["key1"] = "value1.1"; - customerFromStore.Name = "Theon Greyjoy"; - - context.SaveChanges(); - - Assert.Equal(9, ListLoggerFactory.Log.Count(l => l.Id == CosmosEventId.SyncNotSupported)); - } - - using (var context = contextFactory.CreateContext()) - { - var customerFromStore = context.Set().Single(); - - Assert.Equal("Theon Greyjoy", customerFromStore.Name); - - var entry = context.Entry(customerFromStore); - var document = entry.Property("__jObject").CurrentValue; - Assert.Equal("value1.1", document["key1"]); - Assert.Equal("value2", document["key2"]); - - context.Remove(customerFromStore); - - context.SaveChanges(); - - Assert.Equal(11, ListLoggerFactory.Log.Count(l => l.Id == CosmosEventId.SyncNotSupported)); - } - - using (var context = contextFactory.CreateContext()) - { - Assert.Empty(context.Set().ToList()); - Assert.Equal(12, ListLoggerFactory.Log.Count(l => l.Id == CosmosEventId.SyncNotSupported)); - } - } - [ConditionalFact] public async Task Can_add_update_untracked_properties_async() { @@ -365,8 +264,6 @@ public async Task Can_add_update_untracked_properties_async() using (var context = contextFactory.CreateContext()) { - await context.Database.EnsureCreatedAsync(); - var entry = await context.AddAsync(customer); await context.SaveChangesAsync(); @@ -472,8 +369,6 @@ public async Task Can_add_update_delete_end_to_end_with_Guid_async() using (var context = contextFactory.CreateContext()) { - await context.Database.EnsureCreatedAsync(); - await context.AddAsync(customer); await context.SaveChangesAsync(); @@ -540,8 +435,6 @@ public async Task Can_add_update_delete_end_to_end_with_DateTime_async() using (var context = contextFactory.CreateContext()) { - await context.Database.EnsureCreatedAsync(); - var entry = await context.AddAsync(customer); Assert.Equal("0001-01-01T00:00:00.0000000|Theon^2F^5C^23^5C^5C^3F", entry.CurrentValues["__id"]); @@ -636,8 +529,6 @@ public async Task Can_add_update_delete_with_dateTime_string_end_to_end_async() using (var context = contextFactory.CreateContext()) { - await context.Database.EnsureCreatedAsync(); - await context.AddAsync(customer); await context.SaveChangesAsync(); @@ -983,8 +874,6 @@ private async Task Can_add_update_delete_with_collection( using (var context = contextFactory.CreateContext()) { - await context.Database.EnsureCreatedAsync(); - await context.AddAsync(customer); await context.SaveChangesAsync(); @@ -1060,8 +949,6 @@ public async Task Can_read_with_find_with_resource_id_async() await using (var context = contextFactory.CreateContext()) { - await context.Database.EnsureCreatedAsync(); - Assert.Null( context.Model.FindEntityType(typeof(CustomerWithResourceId))! .FindProperty(CosmosJsonIdConvention.DefaultIdPropertyName)); @@ -1108,70 +995,6 @@ await context.AddAsync( } } - [ConditionalFact] - public async Task Can_read_with_find_with_resource_id() - { - var contextFactory = await InitializeAsync( - shouldLogCategory: _ => true, - onConfiguring: o => o.ConfigureWarnings(w => w.Log(CosmosEventId.SyncNotSupported))); - - const int pk1 = 1; - const int pk2 = 2; - - var customer = new CustomerWithResourceId - { - id = "42", - Name = "Theon", - PartitionKey1 = pk1, - PartitionKey2 = 3.15m - }; - - using (var context = contextFactory.CreateContext()) - { - context.Database.EnsureCreated(); - - context.Add(customer); - context.Add( - new CustomerWithResourceId - { - id = "42", - Name = "Theon Twin", - PartitionKey1 = pk2, - PartitionKey2 = 3.15m - }); - - context.SaveChanges(); - } - - using (var context = contextFactory.CreateContext()) - { - var customerFromStore = context.Set() - .Find(pk1, 3.15m, "42"); - - Assert.Equal("42", customerFromStore.id); - Assert.Equal("Theon", customerFromStore.Name); - Assert.Equal(pk1, customerFromStore.PartitionKey1); - Assert.Equal(3.15m, customerFromStore.PartitionKey2); - AssertSql(context, """ReadItem([1.0,3.15], 42)"""); - - customerFromStore.Name = "Theon Greyjoy"; - - context.SaveChanges(); - } - - using (var context = contextFactory.CreateContext()) - { - var customerFromStore = context.Set() - .WithPartitionKey(pk1, 3.15m) - .Single(); - - Assert.Equal("42", customerFromStore.id); - Assert.Equal("Theon Greyjoy", customerFromStore.Name); - Assert.Equal(pk1, customerFromStore.PartitionKey1); - Assert.Equal(3.15m, customerFromStore.PartitionKey2); - } - } - [ConditionalFact] public async Task Find_with_empty_resource_id_throws() { @@ -1211,8 +1034,6 @@ public async Task Can_read_with_find_with_partition_key_and_value_generator_asyn await using (var context = contextFactory.CreateContext()) { - await context.Database.EnsureCreatedAsync(); - await context.AddAsync(customer); await context.AddAsync( new Customer @@ -1257,75 +1078,6 @@ await context.AddAsync( } } - [ConditionalFact] - public async Task Can_read_with_find_with_partition_key_and_value_generator() - { - var contextFactory = await InitializeAsync( - shouldLogCategory: _ => true, - onConfiguring: o => o.ConfigureWarnings(w => w.Log(CosmosEventId.SyncNotSupported)), - addServices: s => s.AddSingleton()); - - const int pk1 = 1; - const int pk2 = 2; - - var customer = new Customer - { - Id = 42, - Name = "Theon", - PartitionKey1 = pk1, - PartitionKey2 = "One", - PartitionKey3 = true - }; - - using (var context = contextFactory.CreateContext()) - { - context.Database.EnsureCreated(); - - context.Add(customer); - context.Add( - new Customer - { - Id = 42, - Name = "Theon Twin", - PartitionKey1 = pk2, - PartitionKey2 = "Two", - PartitionKey3 = false - }); - - context.SaveChanges(); - } - - using (var context = contextFactory.CreateContext()) - { - var customerFromStore = context.Set() - .Find(pk1, 42, "One", true); - - Assert.Equal(42, customerFromStore.Id); - Assert.Equal("Theon", customerFromStore.Name); - Assert.Equal(pk1, customerFromStore.PartitionKey1); - Assert.Equal("One", customerFromStore.PartitionKey2); - Assert.True(customerFromStore.PartitionKey3); - AssertSql(context, """ReadItem([1.0,"One",true], Customer-42)"""); - - customerFromStore.Name = "Theon Greyjoy"; - - context.SaveChanges(); - } - - using (var context = contextFactory.CreateContext()) - { - var customerFromStore = context.Set() - .WithPartitionKey(pk1, "One", true) - .Single(); - - Assert.Equal(42, customerFromStore.Id); - Assert.Equal("Theon Greyjoy", customerFromStore.Name); - Assert.Equal(pk1, customerFromStore.PartitionKey1); - Assert.Equal("One", customerFromStore.PartitionKey2); - Assert.True(customerFromStore.PartitionKey3); - } - } - [ConditionalFact] public async Task Can_read_with_find_with_partition_key_without_value_generator() { @@ -1346,8 +1098,6 @@ public async Task Can_read_with_find_with_partition_key_without_value_generator( using (var context = contextFactory.CreateContext()) { - context.Database.EnsureCreated(); - var customerEntry = context.Entry(customer); customerEntry.Property(CosmosJsonIdConvention.DefaultIdPropertyName).CurrentValue = "42"; customerEntry.State = EntityState.Added; @@ -1436,8 +1186,6 @@ public async Task Can_read_with_find_without_partition_key() await using (var context = contextFactory.CreateContext()) { - await context.Database.EnsureCreatedAsync(); - await context.AddAsync(customer); await context.SaveChangesAsync(); @@ -1464,8 +1212,6 @@ public async Task Can_read_with_find_with_PK_partition_key() await using (var context = contextFactory.CreateContext()) { - await context.Database.EnsureCreatedAsync(); - await context.AddAsync(customer); await context.SaveChangesAsync(); @@ -1492,8 +1238,6 @@ public async Task Can_read_with_find_with_PK_resource_id() await using (var context = contextFactory.CreateContext()) { - await context.Database.EnsureCreatedAsync(); - await context.AddAsync(customer); await context.SaveChangesAsync(); @@ -1659,8 +1403,6 @@ public async Task Can_use_detached_entities_without_discriminators() using (var context = contextFactory.CreateContext()) { - await context.Database.EnsureCreatedAsync(); - await context.AddAsync(customer); await context.SaveChangesAsync(); @@ -1710,8 +1452,6 @@ public async Task Can_update_unmapped_properties() using (var context = contextFactory.CreateContext()) { - context.Database.EnsureCreated(); - var entry = context.Add(customer); entry.Property("EMail").CurrentValue = "theon.g@winterfell.com"; @@ -1771,8 +1511,6 @@ public async Task Can_use_non_persisted_properties() using (var context = contextFactory.CreateContext()) { - await context.Database.EnsureCreatedAsync(); - await context.AddAsync(customer); await context.SaveChangesAsync(); @@ -1861,8 +1599,6 @@ public async Task Using_a_conflicting_incompatible_id_throws() await Assert.ThrowsAnyAsync( async () => { - await context.Database.EnsureCreatedAsync(); - await context.AddAsync(new ConflictingIncompatibleId { id = 42 }); await context.SaveChangesAsync(); @@ -1893,8 +1629,6 @@ public async Task Can_add_update_delete_end_to_end_with_conflicting_id() using (var context = contextFactory.CreateContext()) { - await context.Database.EnsureCreatedAsync(); - await context.AddAsync(entity); await context.SaveChangesAsync(); @@ -1950,7 +1684,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity(); } - [ConditionalTheory(Skip = "Issue #33600 - flaky test")] + [ConditionalTheory] [InlineData(true)] [InlineData(false)] public async Task Can_have_non_string_property_named_Discriminator(bool useDiscriminator) @@ -1973,7 +1707,6 @@ public async Task Can_have_non_string_property_named_Discriminator(bool useDiscr onConfiguring: o => o.ConfigureWarnings(w => w.Log(CosmosEventId.SyncNotSupported, CosmosEventId.NoPartitionKeyDefined))); using var context = contextFactory.CreateContext(); - context.Database.EnsureCreated(); var entry = await context.AddAsync(new NonStringDiscriminator { Id = 1 }); await context.SaveChangesAsync(); @@ -1985,117 +1718,57 @@ public async Task Can_have_non_string_property_named_Discriminator(bool useDiscr var baseEntity = await context.Set().OrderBy(e => e.Id).FirstOrDefaultAsync(); Assert.NotNull(baseEntity); - if (useDiscriminator) - { - AssertSql( - context, - """ -SELECT c -FROM root c -WHERE (c["Discriminator"] = 0) -ORDER BY c["Id"] -OFFSET 0 LIMIT 1 -"""); - } - else - { - AssertSql( - context, - """ -SELECT c + AssertSql( + context, + """ +SELECT VALUE c FROM root c ORDER BY c["Id"] OFFSET 0 LIMIT 1 """); - } ListLoggerFactory.Clear(); Assert.Equal( baseEntity, await context.Set() .Where(e => e.Discriminator == Discriminator.Base).OrderBy(e => e.Id).FirstOrDefaultAsync()); - if (useDiscriminator) - { - AssertSql( - context, - """ -SELECT c -FROM root c -WHERE ((c["Discriminator"] = 0) AND (c["Discriminator"] = 0)) -ORDER BY c["Id"] -OFFSET 0 LIMIT 1 -"""); - } - else - { AssertSql( context, """ -SELECT c +SELECT VALUE c FROM root c WHERE (c["Discriminator"] = 0) ORDER BY c["Id"] OFFSET 0 LIMIT 1 """); - } ListLoggerFactory.Clear(); Assert.Equal( baseEntity, await context.Set() .Where(e => e.GetType() == typeof(NonStringDiscriminator)).OrderBy(e => e.Id).FirstOrDefaultAsync()); - if (useDiscriminator) - { - AssertSql( - context, - """ -SELECT c -FROM root c -WHERE (c["Discriminator"] = 0) -ORDER BY c["Id"] -OFFSET 0 LIMIT 1 -"""); - } - else - { AssertSql( context, """ -SELECT c +SELECT VALUE c FROM root c ORDER BY c["Id"] OFFSET 0 LIMIT 1 """); - } ListLoggerFactory.Clear(); Assert.Equal( baseEntity, await context.Set() .Where(e => e is NonStringDiscriminator).OrderBy(e => e.Id).FirstOrDefaultAsync()); - if (useDiscriminator) - { - AssertSql( - context, - """ -SELECT c -FROM root c -WHERE (c["Discriminator"] = 0) -ORDER BY c["Id"] -OFFSET 0 LIMIT 1 -"""); - } - else - { - AssertSql( - context, - """ -SELECT c + AssertSql( + context, + """ +SELECT VALUE c FROM root c ORDER BY c["Id"] OFFSET 0 LIMIT 1 """); - } } private class NonStringDiscriminator diff --git a/test/EFCore.Cosmos.FunctionalTests/JsonTypesCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/JsonTypesCosmosTest.cs index 49754ed19a5..40661f7433d 100644 --- a/test/EFCore.Cosmos.FunctionalTests/JsonTypesCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/JsonTypesCosmosTest.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore; -public class JsonTypesCosmosTest : JsonTypesTestBase +public class JsonTypesCosmosTest(NonSharedFixture fixture) : JsonTypesTestBase(fixture) { public override Task Can_read_write_collection_of_Guid_converted_to_bytes_JSON_values(string expected) // Cosmos provider cannot map collections of elements with converters. See Issue #34026. diff --git a/test/EFCore.Cosmos.FunctionalTests/MaterializationInterceptionCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/MaterializationInterceptionCosmosTest.cs index 41e04f0587a..aaa80d3118c 100644 --- a/test/EFCore.Cosmos.FunctionalTests/MaterializationInterceptionCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/MaterializationInterceptionCosmosTest.cs @@ -5,8 +5,8 @@ namespace Microsoft.EntityFrameworkCore; #nullable disable -public class MaterializationInterceptionCosmosTest : - MaterializationInterceptionTestBase +public class MaterializationInterceptionCosmosTest(NonSharedFixture fixture) : + MaterializationInterceptionTestBase(fixture) { public override Task Intercept_query_materialization_with_owned_types_projecting_collection(bool async, bool usePooling) => Task.CompletedTask; diff --git a/test/EFCore.Cosmos.FunctionalTests/ModelBuilding/CosmosModelBuilderGenericTest.cs b/test/EFCore.Cosmos.FunctionalTests/ModelBuilding/CosmosModelBuilderGenericTest.cs index cb55586cfb1..4b4f38d82ff 100644 --- a/test/EFCore.Cosmos.FunctionalTests/ModelBuilding/CosmosModelBuilderGenericTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/ModelBuilding/CosmosModelBuilderGenericTest.cs @@ -776,7 +776,7 @@ public override void Can_set_complex_property_annotation() Assert.Equal("bar2", complexProperty["foo2"]); Assert.Equal(typeof(Customer).Name, complexProperty.Name); Assert.Equal( - @"Customer (Customer) Required + @"Customer (Customer) ComplexType: ComplexProperties.Customer#Customer Properties: " + @" diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/AdHocCosmosTestHelpers.cs b/test/EFCore.Cosmos.FunctionalTests/Query/AdHocCosmosTestHelpers.cs new file mode 100644 index 00000000000..19a5b39eccc --- /dev/null +++ b/test/EFCore.Cosmos.FunctionalTests/Query/AdHocCosmosTestHelpers.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net; +using Microsoft.Azure.Cosmos; +using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.EntityFrameworkCore.Query; + +public class AdHocCosmosTestHelpers +{ + public static async Task CreateCustomEntityHelperAsync( + Container container, + string json, + CancellationToken cancellationToken) + { + var document = JObject.Parse(json); + + var stream = new MemoryStream(); + await using var __ = stream.ConfigureAwait(false); + var writer = new StreamWriter(stream, new UTF8Encoding(), bufferSize: 1024, leaveOpen: false); + await using var ___ = writer.ConfigureAwait(false); + using var jsonWriter = new JsonTextWriter(writer); + + CosmosClientWrapper.Serializer.Serialize(jsonWriter, document); + await jsonWriter.FlushAsync(cancellationToken).ConfigureAwait(false); + + var response = await container.CreateItemStreamAsync( + stream, + PartitionKey.None, + requestOptions: null, + cancellationToken) + .ConfigureAwait(false); + + + if (response.StatusCode != HttpStatusCode.Created) + { + throw new InvalidOperationException($"Failed to create entity (status code: {response.StatusCode}) for json: {json}"); + } + } +} diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/AdHocJsonQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/AdHocJsonQueryCosmosTest.cs new file mode 100644 index 00000000000..6ad9b089102 --- /dev/null +++ b/test/EFCore.Cosmos.FunctionalTests/Query/AdHocJsonQueryCosmosTest.cs @@ -0,0 +1,1649 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.EntityFrameworkCore.Query; + +public class AdHocJsonQueryCosmosTest(NonSharedFixture fixture) : AdHocJsonQueryTestBase(fixture) +{ + #region 21006 + + public override async Task Project_root_with_missing_scalars(bool async) + { + if (async) + { + await base.Project_root_with_missing_scalars(async); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["Id"] < 4) +"""); + } + } + + [ConditionalTheory(Skip = "issue #35702")] + public override async Task Project_top_level_json_entity_with_missing_scalars(bool async) + { + if (async) + { + await base.Project_top_level_json_entity_with_missing_scalars(async); + + AssertSql(); + } + } + + public override async Task Project_nested_json_entity_with_missing_scalars(bool async) + { + if (async) + { + await AssertTranslationFailed( + () => base.Project_nested_json_entity_with_missing_scalars(async)); + + AssertSql(); + } + } + + [ConditionalTheory(Skip = "issue #34067")] + public override async Task Project_top_level_entity_with_null_value_required_scalars(bool async) + { + if (async) + { + await base.Project_top_level_entity_with_null_value_required_scalars(async); + + AssertSql( + """ +SELECT c["Id"], c +FROM root c +WHERE (c["Id"] = 4) +"""); + } + } + + public override async Task Project_root_entity_with_missing_required_navigation(bool async) + { + if (async) + { + await base.Project_root_entity_with_missing_required_navigation(async); + + AssertSql( + """ +ReadItem(?, ?) +"""); + } + } + + public override async Task Project_missing_required_navigation(bool async) + { + if (async) + { + await base.Project_missing_required_navigation(async); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["Id"] = 5) +"""); + } + } + + public override async Task Project_root_entity_with_null_required_navigation(bool async) + { + if (async) + { + await base.Project_root_entity_with_null_required_navigation(async); + + AssertSql( + """ +ReadItem(?, ?) +"""); + } + } + + public override async Task Project_null_required_navigation(bool async) + { + if (async) + { + await base.Project_null_required_navigation(async); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["Id"] = 6) +"""); + } + } + + public override async Task Project_missing_required_scalar(bool async) + { + if (async) + { + await base.Project_missing_required_scalar(async); + + AssertSql( + """ +SELECT c["Id"], c["RequiredReference"]["Number"] +FROM root c +WHERE (c["Id"] = 2) +"""); + } + } + + public override async Task Project_null_required_scalar(bool async) + { + if (async) + { + await base.Project_null_required_scalar(async); + + AssertSql( + """ +SELECT c["Id"], c["RequiredReference"]["Number"] +FROM root c +WHERE (c["Id"] = 4) +"""); + } + } + + protected override void OnModelCreating21006(ModelBuilder modelBuilder) + { + base.OnModelCreating21006(modelBuilder); + + modelBuilder.Entity().ToContainer("Entities"); + } + + protected override async Task Seed21006(Context21006 context) + { + await base.Seed21006(context); + + var wrapper = (CosmosClientWrapper)context.GetService(); + var singletonWrapper = context.GetService(); + var entitiesContainer = singletonWrapper.Client.GetContainer(context.Database.GetCosmosDatabaseId(), containerId: "Entities"); + + var missingTopLevel = +$$""" +{ + "Id": 2, + "$type": "Entity", + "Name": "e2", + "id": "2", + "Collection": + [ + { + "Text": "e2 c1", + "NestedCollection": [ + { + "DoB": "2000-01-01T00:00:00", + "Text": "e2 c1 c1" + }, + { + "DoB": "2000-01-01T00:00:00", + "Text": "e2 c1 c2" + } + ], + "NestedOptionalReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e2 c1 nor" + }, + "NestedRequiredReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e2 c1 nrr" + } + }, + { + "Text": "e2 c2", + "NestedCollection": [ + { + "DoB": "2000-01-01T00:00:00", + "Text": "e2 c2 c1" + }, + { + "DoB": "2000-01-01T00:00:00", + "Text": "e2 c2 c2" + } + ], + "NestedOptionalReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e2 c2 nor" + }, + "NestedRequiredReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e2 c2 nrr" + } + } + ], + "OptionalReference": { + "Text": "e2 or", + "NestedCollection": + [ + { + "DoB": "2000-01-01T00:00:00", + "Text": "e2 or c1" + }, + { + "DoB": "2000-01-01T00:00:00", + "Text": "e2 or c2" + } + ], + "NestedOptionalReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e2 or nor" + }, + "NestedRequiredReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e2 or nrr" + } + }, + "RequiredReference": { + "Text": "e2 rr", + "NestedCollection": + [ + { + "DoB": "2000-01-01T00:00:00", + "Text": "e2 rr c1" + }, + { + "DoB": "2000-01-01T00:00:00", + "Text": "e2 rr c2" + } + ], + "NestedOptionalReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e2 rr nor" + }, + "NestedRequiredReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e2 rr nrr" + } + } +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + missingTopLevel, + CancellationToken.None); + + var missingNested = +$$""" +{ + "Id": 3, + "$type": "Entity", + "Name": "e3", + "id": "3", + "Collection": + [ + { + "Number": 7.0, + "Text": "e3 c1", + "NestedCollection": + [ + { + "Text": "e3 c1 c1" + }, + { + "Text": "e3 c1 c2" + } + ], + "NestedOptionalReference": { + "Text": "e3 c1 nor" + }, + "NestedRequiredReference": { + "Text": "e3 c1 nrr" + } + }, + { + "Number": 7.0, + "Text": "e3 c2", + "NestedCollection": + [ + { + "Text": "e3 c2 c1" + }, + { + "Text": "e3 c2 c2" + } + ], + "NestedOptionalReference": { + "Text": "e3 c2 nor" + }, + "NestedRequiredReference": { + "Text": "e3 c2 nrr" + } + } + ], + "OptionalReference": { + "Number": 7.0, + "Text": "e3 or", + "NestedCollection": + [ + { + "Text": "e3 or c1" + }, + { + "Text": "e3 or c2" + } + ], + "NestedOptionalReference": { + "Text": "e3 or nor" + }, + "NestedRequiredReference": { + "Text": "e3 or nrr" + } + }, + "RequiredReference": { + "Number": 7.0, + "Text": "e3 rr", + "NestedCollection": + [ + { + "Text": "e3 rr c1" + }, + { + "Text": "e3 rr c2" + } + ], + "NestedOptionalReference": { + "Text": "e3 rr nor" + }, + "NestedRequiredReference": { + "Text": "e3 rr nrr" + } + } +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + missingNested, + CancellationToken.None); + + var nullTopLevel = +$$""" +{ + "Id": 4, + "$type": "Entity", + "Name": "e4", + "id": "4", + "Collection": + [ + { + "Number": null, + "Text": "e4 c1", + "NestedCollection": + [ + { + "DoB": "2000-01-01T00:00:00", + "Text": "e4 c1 c1" + }, + { + "DoB": "2000-01-01T00:00:00", + "Text": "e4 c1 c2" + } + ], + "NestedOptionalReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e4 c1 nor" + }, + "NestedRequiredReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e4 c1 nrr" + } + }, + { + "Number": null, + "Text": "e4 c2", + "NestedCollection": + [ + { + "DoB": "2000-01-01T00:00:00", + "Text": "e4 c2 c1" + }, + { + "DoB": "2000-01-01T00:00:00", + "Text": "e4 c2 c2" + } + ], + "NestedOptionalReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e4 c2 nor" + }, + "NestedRequiredReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e4 c2 nrr" + } + } + ], + "OptionalReference": { + "Number": null, + "Text": "e4 or", + "NestedCollection": + [ + { + "DoB": "2000-01-01T00:00:00", + "Text": "e4 or c1" + }, + { + "DoB": "2000-01-01T00:00:00", + "Text": "e4 or c2" + } + ], + "NestedOptionalReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e4 or nor" + }, + "NestedRequiredReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e4 or nrr" + } + }, + "RequiredReference": { + "Number": null, + "Text": "e4 rr", + "NestedCollection": + [ + { + "DoB": "2000-01-01T00:00:00", + "Text": "e4 rr c1" + }, + { + "DoB": "2000-01-01T00:00:00", + "Text": "e4 rr c2" + } + ], + "NestedOptionalReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e4 rr nor" + }, + "NestedRequiredReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e4 rr nrr" + } + } +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + nullTopLevel, + CancellationToken.None); + + var missingRequiredNav = +$$""" +{ + "Id": 5, + "$type": "Entity", + "Name": "e5", + "id": "5", + "Collection": + [ + { + "Number": 7.0, + "Text": "e5 c1", + "NestedCollection": + [ + { + "DoB": "2000-01-01T00:00:00", + "Text": "e5 c1 c1" + }, + { + "DoB": "2000-01-01T00:00:00", + "Text": "e5 c1 c2" + } + ], + "NestedOptionalReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e5 c1 nor" + }, + }, + { + "Number": 7.0, + "Text": "e5 c2", + "NestedCollection": + [ + { + "DoB": "2000-01-01T00:00:00", + "Text": "e5 c2 c1" + }, + { + "DoB": "2000-01-01T00:00:00", + "Text": "e5 c2 c2" + } + ], + "NestedOptionalReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e5 c2 nor" + }, + } + ], + "OptionalReference": { + "Number": 7.0, + "Text": "e5 or", + "NestedCollection": + [ + { + "DoB": "2000-01-01T00:00:00", + "Text": "e5 or c1" + }, + { + "DoB": "2000-01-01T00:00:00", + "Text": "e5 or c2" + } + ], + "NestedOptionalReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e5 or nor" + }, + }, + "RequiredReference": { + "Number": 7.0, + "Text": "e5 rr", + "NestedCollection": + [ + { + "DoB": "2000-01-01T00:00:00", + "Text": "e5 rr c1" + }, + { + "DoB": "2000-01-01T00:00:00", + "Text": "e5 rr c2" + } + ], + "NestedOptionalReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e5 rr nor" + }, + } +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + missingRequiredNav, + CancellationToken.None); + + var nullRequiredNav = +$$""" +{ + "Id": 6, + "$type": "Entity", + "Name": "e6", + "id": "6", + "Collection": + [ + { + "Number": 7.0, + "Text": "e6 c1", + "NestedCollection": + [ + { + "DoB": "2000-01-01T00:00:00", + "Text": "e6 c1 c1" + }, + { + "DoB": "2000-01-01T00:00:00", + "Text": "e6 c1 c2" + } + ], + "NestedOptionalReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e6 c1 nor" + }, + "NestedRequiredReference": null + }, + { + "Number": 7.0, + "Text": "e6 c2", + "NestedCollection": + [ + { + "DoB": "2000-01-01T00:00:00", + "Text": "e6 c2 c1" + }, + { + "DoB": "2000-01-01T00:00:00", + "Text": "e6 c2 c2" + } + ], + "NestedOptionalReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e6 c2 nor" + }, + "NestedRequiredReference": null + } + ], + "OptionalReference": { + "Number": 7.0, + "Text": "e6 or", + "NestedCollection": + [ + { + "DoB": "2000-01-01T00:00:00", + "Text": "e6 or c1" + }, + { + "DoB": "2000-01-01T00:00:00", + "Text": "e6 or c2" + } + ], + "NestedOptionalReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e6 or nor" + }, + "NestedRequiredReference": null + }, + "RequiredReference": { + "Number": 7.0, + "Text": "e6 rr", + "NestedCollection": + [ + { + "DoB": "2000-01-01T00:00:00", + "Text": "e6 rr c1" + }, + { + "DoB": "2000-01-01T00:00:00", + "Text": "e6 rr c2" + } + ], + "NestedOptionalReference": { + "DoB": "2000-01-01T00:00:00", + "Text": "e6 rr nor" + }, + "NestedRequiredReference": null + } +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + nullRequiredNav, + CancellationToken.None); + } + + #endregion + + #region 29219 + + // Cosmos returns unexpected number of results (i.e. not returning row with non-existent NullableScalar + // this is by design behavior in Cosmos, so we just skip the test to avoid validation error + public override Task Can_project_nullable_json_property_when_the_element_in_json_is_not_present() + => Task.CompletedTask; + + protected override void OnModelCreating29219(ModelBuilder modelBuilder) + { + base.OnModelCreating29219(modelBuilder); + + modelBuilder.Entity().ToContainer("Entities"); + } + + protected override async Task Seed29219(DbContext context) + { + await base.Seed29219(context); + + var wrapper = (CosmosClientWrapper)context.GetService(); + var singletonWrapper = context.GetService(); + var entitiesContainer = singletonWrapper.Client.GetContainer(context.Database.GetCosmosDatabaseId(), containerId: "Entities"); + + var missingNullableScalars = +$$""" +{ + "Id": 3, + "$type": "MyEntity", + "id": "3", + "Collection": + [ + { + "NonNullableScalar" : 10001 + } + ], + "Reference": { + "NonNullableScalar" : 30 + } +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + missingNullableScalars, + CancellationToken.None); + } + + #endregion + + #region 30028 + + [ConditionalTheory(Skip = "issue #35702")] + public override Task Missing_navigation_works_with_deduplication(bool async) + => base.Missing_navigation_works_with_deduplication(async); + + // missing array comes out as empty on Cosmos + public override Task Accessing_missing_navigation_works() + => Task.CompletedTask; + + protected override void OnModelCreating30028(ModelBuilder modelBuilder) + { + base.OnModelCreating30028(modelBuilder); + + modelBuilder.Entity().ToContainer("Entities"); + } + + protected override async Task Seed30028(DbContext context) + { + var wrapper = (CosmosClientWrapper)context.GetService(); + var singletonWrapper = context.GetService(); + var entitiesContainer = singletonWrapper.Client.GetContainer(context.Database.GetCosmosDatabaseId(), containerId: "Entities"); + + var complete = +$$$$""" +{ + "Id": 1, + "$type": "MyEntity", + "id": "1", + "Json": { + "RootName":"e1", + "Collection": + [ + { + "BranchName":"e1 c1", + "Nested":{ + "LeafName":"e1 c1 l" + } + }, + { + "BranchName":"e1 c2", + "Nested":{ + "LeafName":"e1 c2 l" + } + } + ], + "OptionalReference":{ + "BranchName":"e1 or", + "Nested":{ + "LeafName":"e1 or l" + } + }, + "RequiredReference":{ + "BranchName":"e1 rr", + "Nested":{ + "LeafName":"e1 rr l" + } + } + } +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + complete, + CancellationToken.None); + + var missingCollection = +$$$$""" +{ + "Id": 2, + "$type": "MyEntity", + "id": "2", + "Json": { + "RootName":"e2", + "OptionalReference":{ + "BranchName":"e2 or", + "Nested":{ + "LeafName":"e2 or l" + } + }, + "RequiredReference":{ + "BranchName":"e2 rr", + "Nested":{ + "LeafName":"e2 rr l" + } + } + } +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + missingCollection, + CancellationToken.None); + + var missingOptionalReference = +$$$$""" +{ + "Id": 3, + "$type": "MyEntity", + "id": "3", + "Json": { + "RootName":"e3", + "Collection": + [ + { + "BranchName":"e3 c1", + "Nested":{ + "LeafName":"e3 c1 l" + } + }, + { + "BranchName":"e3 c2", + "Nested":{ + "LeafName":"e3 c2 l" + } + } + ], + "RequiredReference":{ + "BranchName":"e3 rr", + "Nested":{ + "LeafName":"e3 rr l" + } + } + } +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + missingOptionalReference, + CancellationToken.None); + + var missingRequiredReference = +$$$$""" +{ + "Id": 4, + "$type": "MyEntity", + "id": "4", + "Json": { + "RootName":"e4", + "Collection": + [ + { + "BranchName":"e4 c1", + "Nested":{ + "LeafName":"e4 c1 l" + } + }, + { + "BranchName":"e4 c2", + "Nested":{ + "LeafName":"e4 c2 l" + } + } + ], + "OptionalReference":{ + "BranchName":"e4 or", + "Nested":{ + "LeafName":"e4 or l" + } + } + } +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + missingRequiredReference, + CancellationToken.None); + } + + #endregion + + #region 33046 + + protected override void OnModelCreating33046(ModelBuilder modelBuilder) + { + base.OnModelCreating33046(modelBuilder); + + modelBuilder.Entity().ToContainer("Reviews"); + } + + protected override async Task Seed33046(DbContext context) + { + var wrapper = (CosmosClientWrapper)context.GetService(); + var singletonWrapper = context.GetService(); + var entitiesContainer = singletonWrapper.Client.GetContainer(context.Database.GetCosmosDatabaseId(), containerId: "Reviews"); + + var json = +$$$$""" +{ + "Id": 1, + "$type": "Review", + "id": "1", + "Rounds": + [ + { + "RoundNumber":11, + "SubRounds": + [ + { + "SubRoundNumber":111 + }, + { + "SubRoundNumber":112 + } + ] + } + ] +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + json, + CancellationToken.None); + } + + #endregion + + #region 34960 + + + public override async Task Try_project_collection_but_JSON_is_entity() + { + var message = (await Assert.ThrowsAsync( + () => base.Try_project_collection_but_JSON_is_entity())).Message; + + Assert.Equal( + $"Deserialized JSON type '{typeof(JObject).FullName}' is not compatible with expected type '{typeof(JArray).FullName}'. Path 'Collection'.", + message); + } + + public override async Task Try_project_reference_but_JSON_is_collection() + { + var message = (await Assert.ThrowsAsync( + () => base.Try_project_reference_but_JSON_is_collection())).Message; + + Assert.Equal( + $"Deserialized JSON type '{typeof(JArray).FullName}' is not compatible with expected type '{typeof(JObject).FullName}'. Path 'Reference'.", + message); + } + + protected override void OnModelCreating34960(ModelBuilder modelBuilder) + { + base.OnModelCreating34960(modelBuilder); + modelBuilder.Entity().ToContainer("Entities"); + modelBuilder.Entity().ToContainer("Junk"); + } + + protected async override Task Seed34960(Context34960 context) + { + await base.Seed34960(context); + + var wrapper = (CosmosClientWrapper)context.GetService(); + var singletonWrapper = context.GetService(); + var entitiesContainer = singletonWrapper.Client.GetContainer(context.Database.GetCosmosDatabaseId(), containerId: "Entities"); + + var json = +$$$""" +{ + "Id": 4, + "$type": "Entity", + "id": "4", + "Collection": null, + "Reference": null +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + json, + CancellationToken.None); + + var junkContainer = singletonWrapper.Client.GetContainer(context.Database.GetCosmosDatabaseId(), containerId: "Junk"); + + var objectWhereCollectionShouldBe = +$$$""" +{ + "Id": 1, + "$type": "JunkEntity", + "id": "1", + "Collection": { "DoB":"2000-01-01T00:00:00","Text":"junk" }, + "Reference": null +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + junkContainer, + objectWhereCollectionShouldBe, + CancellationToken.None); + + var collectionWhereEntityShouldBe = +$$$""" +{ + "Id": 2, + "$type": "JunkEntity", + "id": "2", + "Collection": null, + "Reference": [{ "DoB":"2000-01-01T00:00:00","Text":"junk" }] +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + junkContainer, + collectionWhereEntityShouldBe, + CancellationToken.None); + } + + #endregion + + #region ArrayOfPrimitives + + protected override void OnModelCreatingArrayOfPrimitives(ModelBuilder modelBuilder) + { + base.OnModelCreatingArrayOfPrimitives(modelBuilder); + } + + #endregion + + #region JunkInJson + + protected override void OnModelCreatingJunkInJson(ModelBuilder modelBuilder) + { + base.OnModelCreatingJunkInJson(modelBuilder); + + modelBuilder.Entity().ToContainer("Entities"); + } + + protected override async Task SeedJunkInJson(DbContext context) + { + var wrapper = (CosmosClientWrapper)context.GetService(); + var singletonWrapper = context.GetService(); + var entitiesContainer = singletonWrapper.Client.GetContainer(context.Database.GetCosmosDatabaseId(), containerId: "Entities"); + + var json = +$$$""" +{ + "Id": 1, + "$type": "MyEntity", + "id": "1", + "Collection": + [ + { + "JunkReference": { + "Something":"SomeValue" + }, + "Name":"c11", + "JunkProperty1":50, + "Number":11.5, + "JunkCollection1":[], + "JunkCollection2": + [ + { + "Foo":"junk value" + } + ], + "NestedCollection": + [ + { + "DoB":"2002-04-01T00:00:00", + "DummyProp":"Dummy value" + }, + { + "DoB":"2002-04-02T00:00:00", + "DummyReference":{ + "Foo":5 + } + } + ], + "NestedReference":{ + "DoB":"2002-03-01T00:00:00" + } + }, + { + "Name":"c12", + "Number":12.5, + "NestedCollection": + [ + { + "DoB":"2002-06-01T00:00:00" + }, + { + "DoB":"2002-06-02T00:00:00" + } + ], + "NestedDummy":59, + "NestedReference":{ + "DoB":"2002-05-01T00:00:00" + } + } + ], + "CollectionWithCtor": + [ + { + "MyBool":true, + "Name":"c11 ctor", + "JunkReference":{ + "Something":"SomeValue", + "JunkCollection": + [ + { + "Foo":"junk value" + } + ] + }, + "NestedCollection": + [ + { + "DoB":"2002-08-01T00:00:00" + }, + { + "DoB":"2002-08-02T00:00:00" + } + ], + "NestedReference":{ + "DoB":"2002-07-01T00:00:00" + } + }, + { + "MyBool":false, + "Name":"c12 ctor", + "NestedCollection": + [ + { + "DoB":"2002-10-01T00:00:00" + }, + { + "DoB":"2002-10-02T00:00:00" + } + ], + "JunkCollection": + [ + { + "Foo":"junk value" + } + ], + "NestedReference":{ + "DoB":"2002-09-01T00:00:00" + } + } + ], + "Reference": { + "Name":"r1", + "JunkCollection": + [ + { + "Foo":"junk value" + } + ], + "JunkReference":{ + "Something":"SomeValue" + }, + "Number":1.5, + "NestedCollection": + [ + { + "DoB":"2000-02-01T00:00:00", + "JunkReference":{ + "Something":"SomeValue" + } + }, + { + "DoB":"2000-02-02T00:00:00" + } + ], + "NestedReference":{ + "DoB":"2000-01-01T00:00:00" + } + }, + "ReferenceWithCtor":{ + "MyBool":true, + "JunkCollection": + [ + { + "Foo":"junk value" + } + ], + "Name":"r1 ctor", + "JunkReference":{ + "Something":"SomeValue" + }, + "NestedCollection": + [ + { + "DoB":"2001-02-01T00:00:00" + }, + { + "DoB":"2001-02-02T00:00:00" + } + ], + "NestedReference":{ + "JunkCollection": + [ + { + "Foo":"junk value" + } + ], + "DoB":"2001-01-01T00:00:00" + } + } +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + json, + CancellationToken.None); + } + + #endregion + + #region TrickyBuffering + + protected override void OnModelCreatingTrickyBuffering(ModelBuilder modelBuilder) + { + base.OnModelCreatingTrickyBuffering(modelBuilder); + + modelBuilder.Entity().ToContainer("Entities"); + } + + protected override async Task SeedTrickyBuffering(DbContext context) + { + var wrapper = (CosmosClientWrapper)context.GetService(); + var singletonWrapper = context.GetService(); + var entitiesContainer = singletonWrapper.Client.GetContainer(context.Database.GetCosmosDatabaseId(), containerId: "Entities"); + + var json = +$$$""" +{ + "Id": 1, + "$type": "MyEntity", + "id": "1", + "Reference": { + "Name": "r1", + "Number": 7, + "JunkReference": { + "Something": "SomeValue" + }, + "JunkCollection": + [ + { + "Foo": "junk value" + } + ], + "NestedReference": { + "DoB": "2000-01-01T00:00:00" + }, + "NestedCollection": + [ + { + "DoB": "2000-02-01T00:00:00", + "JunkReference": { + "Something": "SomeValue" + } + }, + { + "DoB": "2000-02-02T00:00:00" + } + ] + } +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + json, + CancellationToken.None); + } + + #endregion + + #region ShadowProperties + + protected override void OnModelCreatingShadowProperties(ModelBuilder modelBuilder) + { + base.OnModelCreatingShadowProperties(modelBuilder); + + modelBuilder.Entity(b => + { + b.ToContainer("Entities"); + + //b.OwnsOne(x => x.Reference, b => + //{ + // // b.ToJson().HasColumnType(JsonColumnType); + // b.Property("ShadowString"); + //}); + + b.OwnsOne(x => x.ReferenceWithCtor, b => + { + // b.ToJson().HasColumnType(JsonColumnType); + b.Property("Shadow_Int").ToJsonProperty("ShadowInt"); + }); + + //b.OwnsMany( + // x => x.Collection, b => + // { + // // b.ToJson().HasColumnType(JsonColumnType); + // b.Property("ShadowDouble"); + // }); + + //b.OwnsMany( + // x => x.CollectionWithCtor, b => + // { + // //b.ToJson().HasColumnType(JsonColumnType); + // b.Property("ShadowNullableByte"); + // }); + }); + } + + protected override async Task SeedShadowProperties(DbContext context) + { + var wrapper = (CosmosClientWrapper)context.GetService(); + var singletonWrapper = context.GetService(); + var entitiesContainer = singletonWrapper.Client.GetContainer(context.Database.GetCosmosDatabaseId(), containerId: "Entities"); + + var json = +$$$""" +{ + "Id": 1, + "$type": "MyEntity", + "id": "1", + "Name": "e1", + "Collection": + [ + { + "Name":"e1_c1","ShadowDouble":5.5 + }, + { + "ShadowDouble":20.5,"Name":"e1_c2" + } + ], + "CollectionWithCtor": + [ + { + "Name":"e1_c1 ctor","ShadowNullableByte":6 + }, + { + "ShadowNullableByte":null,"Name":"e1_c2 ctor" + } + ], + "Reference": { "Name":"e1_r", "ShadowString":"Foo" }, + "ReferenceWithCtor": { "ShadowInt":143,"Name":"e1_r ctor" } +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + json, + CancellationToken.None); + } + + #endregion + + #region LazyLoadingProxies + + protected override void OnModelCreatingLazyLoadingProxies(ModelBuilder modelBuilder) + { + base.OnModelCreatingLazyLoadingProxies(modelBuilder); + } + + #endregion + + #region NotICollection + + protected override void OnModelCreatingNotICollection(ModelBuilder modelBuilder) + { + base.OnModelCreatingNotICollection(modelBuilder); + + modelBuilder.Entity().ToContainer("Entities"); + } + + protected override async Task SeedNotICollection(DbContext context) + { + var wrapper = (CosmosClientWrapper)context.GetService(); + var singletonWrapper = context.GetService(); + var entitiesContainer = singletonWrapper.Client.GetContainer(context.Database.GetCosmosDatabaseId(), containerId: "Entities"); + + var json1 = +$$$""" +{ + "Id": 1, + "$type": "MyEntity", + "id": "1", + "Json": + { + "Collection": + [ + { + "Bar":11,"Foo":"c11" + }, + { + "Bar":12,"Foo":"c12" + }, + { + "Bar":13,"Foo":"c13" + } + ] + } +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + json1, + CancellationToken.None); + + var json2 = +$$$""" +{ + "Id": 2, + "$type": "MyEntity", + "id": "2", + "Json": { + "Collection": + [ + { + "Bar":21,"Foo":"c21" + }, + { + "Bar":22,"Foo":"c22" + } + ] + } +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + json2, + CancellationToken.None); + } + + #endregion + + #region BadJsonProperties + + // missing collection comes back as empty on Cosmos + public override Task Bad_json_properties_empty_navigations(bool noTracking) + => Task.CompletedTask; + + protected override void OnModelCreatingBadJsonProperties(ModelBuilder modelBuilder) + { + base.OnModelCreatingBadJsonProperties(modelBuilder); + + modelBuilder.Entity().ToContainer("Entities"); + } + + protected override async Task SeedBadJsonProperties(ContextBadJsonProperties context) + { + var wrapper = (CosmosClientWrapper)context.GetService(); + var singletonWrapper = context.GetService(); + var entitiesContainer = singletonWrapper.Client.GetContainer(context.Database.GetCosmosDatabaseId(), containerId: "Entities"); + + var baseline = +$$$""" +{ + "Id": 1, + "$type": "Entity", + "id": "1", + "Scenario": "baseline", + "OptionalReference": {"NestedOptional": { "Text":"or no" }, "NestedRequired": { "Text":"or nr" }, "NestedCollection": [ { "Text":"or nc 1" }, { "Text":"or nc 2" } ] }, + "RequiredReference": {"NestedOptional": { "Text":"rr no" }, "NestedRequired": { "Text":"rr nr" }, "NestedCollection": [ { "Text":"rr nc 1" }, { "Text":"rr nc 2" } ] }, + "Collection": + [ + {"NestedOptional": { "Text":"c 1 no" }, "NestedRequired": { "Text":"c 1 nr" }, "NestedCollection": [ { "Text":"c 1 nc 1" }, { "Text":"c 1 nc 2" } ] }, + {"NestedOptional": { "Text":"c 2 no" }, "NestedRequired": { "Text":"c 2 nr" }, "NestedCollection": [ { "Text":"c 2 nc 1" }, { "Text":"c 2 nc 2" } ] } + ] +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + baseline, + CancellationToken.None); + + var duplicatedNavigations = +$$$""" +{ + "Id": 2, + "$type": "Entity", + "id": "2", + "Scenario": "duplicated navigations", + "OptionalReference": {"NestedOptional": { "Text":"or no" }, "NestedOptional": { "Text":"or no dupnav" }, "NestedRequired": { "Text":"or nr" }, "NestedCollection": [ { "Text":"or nc 1" }, { "Text":"or nc 2" } ], "NestedCollection": [ { "Text":"or nc 1 dupnav" }, { "Text":"or nc 2 dupnav" } ], "NestedRequired": { "Text":"or nr dupnav" } }, + "RequiredReference": {"NestedOptional": { "Text":"rr no" }, "NestedOptional": { "Text":"rr no dupnav" }, "NestedRequired": { "Text":"rr nr" }, "NestedCollection": [ { "Text":"rr nc 1" }, { "Text":"rr nc 2" } ], "NestedCollection": [ { "Text":"rr nc 1 dupnav" }, { "Text":"rr nc 2 dupnav" } ], "NestedRequired": { "Text":"rr nr dupnav" } }, + "Collection": + [ + {"NestedOptional": { "Text":"c 1 no" }, "NestedOptional": { "Text":"c 1 no dupnav" }, "NestedRequired": { "Text":"c 1 nr" }, "NestedCollection": [ { "Text":"c 1 nc 1" }, { "Text":"c 1 nc 2" } ], "NestedCollection": [ { "Text":"c 1 nc 1 dupnav" }, { "Text":"c 1 nc 2 dupnav" } ], "NestedRequired": { "Text":"c 1 nr dupnav" } }, + {"NestedOptional": { "Text":"c 2 no" }, "NestedOptional": { "Text":"c 2 no dupnav" }, "NestedRequired": { "Text":"c 2 nr" }, "NestedCollection": [ { "Text":"c 2 nc 1" }, { "Text":"c 2 nc 2" } ], "NestedCollection": [ { "Text":"c 2 nc 1 dupnav" }, { "Text":"c 2 nc 2 dupnav" } ], "NestedRequired": { "Text":"c 2 nr dupnav" } } + ] +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + duplicatedNavigations, + CancellationToken.None); + + var duplicatedScalars = +$$$""" +{ + "Id": 3, + "$type": "Entity", + "id": "3", + "Scenario": "duplicated scalars", + "OptionalReference": {"NestedOptional": { "Text":"or no", "Text":"or no dupprop" }, "NestedRequired": { "Text":"or nr", "Text":"or nr dupprop" }, "NestedCollection": [ { "Text":"or nc 1", "Text":"or nc 1 dupprop" }, { "Text":"or nc 2", "Text":"or nc 2 dupprop" } ] }, + "RequiredReference": {"NestedOptional": { "Text":"rr no", "Text":"rr no dupprop" }, "NestedRequired": { "Text":"rr nr", "Text":"rr nr dupprop" }, "NestedCollection": [ { "Text":"rr nc 1", "Text":"rr nc 1 dupprop" }, { "Text":"rr nc 2", "Text":"rr nc 2 dupprop" } ] }, + "Collection": + [ + {"NestedOptional": { "Text":"c 1 no", "Text":"c 1 no dupprop" }, "NestedRequired": { "Text":"c 1 nr", "Text":"c 1 nr dupprop" }, "NestedCollection": [ { "Text":"c 1 nc 1", "Text":"c 1 nc 1 dupprop" }, { "Text":"c 1 nc 2", "Text":"c 1 nc 2 dupprop" } ] }, + {"NestedOptional": { "Text":"c 2 no", "Text":"c 2 no dupprop" }, "NestedRequired": { "Text":"c 2 nr", "Text":"c 2 nr dupprop" }, "NestedCollection": [ { "Text":"c 2 nc 1", "Text":"c 2 nc 1 dupprop" }, { "Text":"c 2 nc 2", "Text":"c 2 nc 2 dupprop" } ] } + ] +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + duplicatedScalars, + CancellationToken.None); + + var emptyNavs = +$$$""" +{ + "Id": 4, + "$type": "Entity", + "id": "4", + "Scenario": "empty navigation property names", + "OptionalReference": {"": { "Text":"or no" }, "": { "Text":"or nr" }, "": [ { "Text":"or nc 1" }, { "Text":"or nc 2" } ] }, + "RequiredReference": {"": { "Text":"rr no" }, "": { "Text":"rr nr" }, "": [ { "Text":"rr nc 1" }, { "Text":"rr nc 2" } ] }, + "Collection": + [ + {"": { "Text":"c 1 no" }, "": { "Text":"c 1 nr" }, "": [ { "Text":"c 1 nc 1" }, { "Text":"c 1 nc 2" } ] }, + {"": { "Text":"c 2 no" }, "": { "Text":"c 2 nr" }, "": [ { "Text":"c 2 nc 1" }, { "Text":"c 2 nc 2" } ] } + ] +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + emptyNavs, + CancellationToken.None); + + var emptyScalars = +$$$""" +{ + "Id": 5, + "$type": "Entity", + "id": "5", + "Scenario": "empty scalar property names", + "OptionalReference": {"NestedOptional": { "":"or no" }, "NestedRequired": { "":"or nr" }, "NestedCollection": [ { "":"or nc 1" }, { "":"or nc 2" } ] }, + "RequiredReference": {"NestedOptional": { "":"rr no" }, "NestedRequired": { "":"rr nr" }, "NestedCollection": [ { "":"rr nc 1" }, { "":"rr nc 2" } ] }, + "Collection": + [ + {"NestedOptional": { "":"c 1 no" }, "NestedRequired": { "":"c 1 nr" }, "NestedCollection": [ { "":"c 1 nc 1" }, { "":"c 1 nc 2" } ] }, + {"NestedOptional": { "":"c 2 no" }, "NestedRequired": { "":"c 2 nr" }, "NestedCollection": [ { "":"c 2 nc 1" }, { "":"c 2 nc 2" } ] } + ] +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + emptyScalars, + CancellationToken.None); + + var nullNavs = +$$$""" +{ + "Id": 10, + "$type": "Entity", + "id": "10", + "Scenario": "null navigation property names", + "OptionalReference": {null: { "Text":"or no" }, null: { "Text":"or nr" }, null: [ { "Text":"or nc 1" }, { "Text":"or nc 2" } ] }, + "RequiredReference": {null: { "Text":"rr no" }, null: { "Text":"rr nr" }, null: [ { "Text":"rr nc 1" }, { "Text":"rr nc 2" } ] }, + "Collection": + [ + {null: { "Text":"c 1 no" }, null: { "Text":"c 1 nr" }, null: [ { "Text":"c 1 nc 1" }, { "Text":"c 1 nc 2" } ] }, + {null: { "Text":"c 2 no" }, null: { "Text":"c 2 nr" }, null: [ { "Text":"c 2 nc 1" }, { "Text":"c 2 nc 2" } ] } + ] +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + nullNavs, + CancellationToken.None); + + var nullScalars = +$$$""" +{ + "Id": 11, + "$type": "Entity", + "id": "11", + "Scenario": "null scalar property names", + "OptionalReference": {"NestedOptional": { null:"or no", "Text":"or no nonnull" }, "NestedRequired": { null:"or nr", "Text":"or nr nonnull" }, "NestedCollection": [ { null:"or nc 1", "Text":"or nc 1 nonnull" }, { null:"or nc 2", "Text":"or nc 2 nonnull" } ] }, + "RequiredReference": {"NestedOptional": { null:"rr no", "Text":"rr no nonnull" }, "NestedRequired": { null:"rr nr", "Text":"rr nr nonnull" }, "NestedCollection": [ { null:"rr nc 1", "Text":"rr nc 1 nonnull" }, { null:"rr nc 2", "Text":"rr nc 2 nonnull" } ] }, + "Collection": + [ + {"NestedOptional": { null:"c 1 no", "Text":"c 1 no nonnull" }, "NestedRequired": { null:"c 1 nr", "Text":"c 1 nr nonnull" }, "NestedCollection": [ { null:"c 1 nc 1", "Text":"c 1 nc 1 nonnull" }, { null:"c 1 nc 2", "Text":"c 1 nc 2 nonnull" } ] }, + {"NestedOptional": { null:"c 2 no", "Text":"c 2 no nonnull" }, "NestedRequired": { null:"c 2 nr", "Text":"c 2 nr nonnull" }, "NestedCollection": [ { null:"c 2 nc 1", "Text":"c 2 nc 1 nonnull" }, { null:"c 2 nc 2", "Text":"c 2 nc 2 nonnull" } ] } + ] +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + nullScalars, + CancellationToken.None); + } + + #endregion + + protected TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + private void AssertSql(params string[] expected) + => TestSqlLoggerFactory.AssertBaseline(expected); + + protected static async Task AssertTranslationFailed(Func query) + => Assert.Contains( + CoreStrings.TranslationFailed("")[48..], + (await Assert.ThrowsAsync(query)) + .Message); + + protected override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => builder.ConfigureWarnings(b => b.Ignore(CosmosEventId.NoPartitionKeyDefined)); + + protected override ITestStoreFactory TestStoreFactory + => CosmosTestStoreFactory.Instance; +} diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/AdHocMiscellaneousQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/AdHocMiscellaneousQueryCosmosTest.cs index e2dd2b90904..fdcff9e15e5 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/AdHocMiscellaneousQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/AdHocMiscellaneousQueryCosmosTest.cs @@ -2,13 +2,152 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal; namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class AdHocMiscellaneousQueryCosmosTest : NonSharedModelTestBase +public class AdHocMiscellaneousQueryCosmosTest(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { + #region 21006 + + [ConditionalFact] + public virtual async Task Project_all_types_entity_with_missing_scalars() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreating21006, + seed: Seed21006); + + await using var context = contextFactory.CreateContext(); + + var query = context.Set(); + + var result = await query.ToListAsync(); + } + + public void OnModelCreating21006(ModelBuilder modelBuilder) + { + modelBuilder.Entity(b => + { + b.Property(x => x.Id).ValueGeneratedNever(); + b.ToContainer("Entities"); + b.Property(x => x.TestDecimal).HasPrecision(18, 3); + b.OwnsOne(x => x.Reference, bb => + { + bb.Property(x => x.TestDecimal).HasPrecision(18, 3); + bb.Property(x => x.TestEnumWithIntConverter).HasConversion(); + }); + }); + } + + protected async Task Seed21006(JsonContext21006 context) + { + var wrapper = (CosmosClientWrapper)context.GetService(); + var singletonWrapper = context.GetService(); + var entitiesContainer = singletonWrapper.Client.GetContainer(context.Database.GetCosmosDatabaseId(), containerId: "Entities"); + + var missingTopLevel = +$$""" +{ + "Id": 1, + "$type": "Entity", + "id": "1", + "Reference": { + "Text": "e2 or" + } +} +"""; + + await AdHocCosmosTestHelpers.CreateCustomEntityHelperAsync( + entitiesContainer, + missingTopLevel, + CancellationToken.None); + } + + protected class JsonContext21006(DbContextOptions options) : DbContext(options) + { + public DbSet Entities { get; set; } + + public class Entity + { + public int Id { get; set; } + + public short TestInt16 { get; set; } + public int TestInt32 { get; set; } + public long TestInt64 { get; set; } + public double TestDouble { get; set; } + public decimal TestDecimal { get; set; } + public DateTime TestDateTime { get; set; } + public DateTimeOffset TestDateTimeOffset { get; set; } + public TimeSpan TestTimeSpan { get; set; } + public DateOnly TestDateOnly { get; set; } + public TimeOnly TestTimeOnly { get; set; } + public float TestSingle { get; set; } + public bool TestBoolean { get; set; } + public byte TestByte { get; set; } + + public byte[] TestByteArray { get; set; } + public Guid TestGuid { get; set; } + public ushort TestUnsignedInt16 { get; set; } + public uint TestUnsignedInt32 { get; set; } + public ulong TestUnsignedInt64 { get; set; } + public char TestCharacter { get; set; } + public sbyte TestSignedByte { get; set; } + public int? TestNullableInt32 { get; set; } + public JsonEnum TestEnum { get; set; } + public byte[] TestByteCollection { get; set; } + public IList TestUnsignedInt16Collection { get; set; } + public uint[] TestUnsignedInt32Collection { get; set; } + public sbyte[] TestSignedByteCollection { get; set; } + public JsonEntity Reference { get; set; } + } + + public class JsonEntity + { + public string Text { get; set; } + + public short TestInt16 { get; set; } + public int TestInt32 { get; set; } + public long TestInt64 { get; set; } + public double TestDouble { get; set; } + public decimal TestDecimal { get; set; } + public DateTime TestDateTime { get; set; } + public DateTimeOffset TestDateTimeOffset { get; set; } + public TimeSpan TestTimeSpan { get; set; } + public DateOnly TestDateOnly { get; set; } + public TimeOnly TestTimeOnly { get; set; } + public float TestSingle { get; set; } + public bool TestBoolean { get; set; } + public byte TestByte { get; set; } + public byte[] TestByteArray { get; set; } + public Guid TestGuid { get; set; } + public ushort TestUnsignedInt16 { get; set; } + public uint TestUnsignedInt32 { get; set; } + public ulong TestUnsignedInt64 { get; set; } + public char TestCharacter { get; set; } + public sbyte TestSignedByte { get; set; } + public int? TestNullableInt32 { get; set; } + public JsonEnum TestEnum { get; set; } + public JsonEnum TestEnumWithIntConverter { get; set; } + + public byte[] TestByteCollection { get; set; } + public IList TestUnsignedInt16Collection { get; set; } + public uint[] TestUnsignedInt32Collection { get; set; } + + public sbyte[] TestSignedByteCollection { get; set; } + } + + public enum JsonEnum + { + One = -1, + Two = 2, + Three = -3 + } + } + + #endregion + #region 34911 [ConditionalFact] diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryCosmosTest.cs index d74da8d9e11..0816f8e70c9 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryCosmosTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; -public class NonSharedPrimitiveCollectionsQueryCosmosTest : NonSharedPrimitiveCollectionsQueryTestBase +public class NonSharedPrimitiveCollectionsQueryCosmosTest(NonSharedFixture fixture) : NonSharedPrimitiveCollectionsQueryTestBase(fixture) { #region Support for specific element types diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/InProjection/JsonRelationshipsInProjectionNoTrackingQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/InProjection/JsonRelationshipsInProjectionNoTrackingQueryCosmosTest.cs deleted file mode 100644 index 469d6eb33c7..00000000000 --- a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/InProjection/JsonRelationshipsInProjectionNoTrackingQueryCosmosTest.cs +++ /dev/null @@ -1,317 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public class JsonRelationshipsInProjectionNoTrackingQueryCosmosTest : JsonRelationshipsInProjectionQueryTestBase -{ - private readonly NoTrackingRewriter _noTrackingRewriter = new(); - - public JsonRelationshipsInProjectionNoTrackingQueryCosmosTest(JsonRelationshipsQueryCosmosFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) - { - var rewritten = _noTrackingRewriter.Visit(serverQueryExpression); - - return rewritten; - } - - public override Task Project_root(bool async) - => Fixture.NoSyncTest( - async, async a => - { - await base.Project_root(a); - - AssertSql( - """ -SELECT VALUE c -FROM root c -"""); - }); - - public override Task Project_trunk_optional(bool async) - => Fixture.NoSyncTest( - async, async a => - { - await base.Project_trunk_optional(a); - - AssertSql( - """ -SELECT VALUE c -FROM root c -ORDER BY c["Id"] -"""); - }); - - public override Task Project_trunk_required(bool async) - => Fixture.NoSyncTest( - async, async a => - { - await base.Project_trunk_required(a); - - AssertSql( - """ -SELECT VALUE c -FROM root c -ORDER BY c["Id"] -"""); - }); - - public override Task Project_trunk_collection(bool async) - => Fixture.NoSyncTest( - async, async a => - { - await base.Project_trunk_collection(a); - - AssertSql( - """ -SELECT VALUE c -FROM root c -ORDER BY c["Id"] -"""); - }); - - public override Task Project_branch_required_required(bool async) - => Fixture.NoSyncTest( - async, async a => - { - await base.Project_branch_required_required(a); - - AssertSql( - """ -SELECT VALUE c -FROM root c -ORDER BY c["Id"] -"""); - }); - - public override Task Project_branch_required_optional(bool async) - => Fixture.NoSyncTest( - async, async a => - { - await base.Project_branch_required_optional(a); - - AssertSql( - """ -SELECT VALUE c -FROM root c -ORDER BY c["Id"] -"""); - }); - - public override async Task Project_branch_required_collection(bool async) - { - if (async) - { - //issue #31696 - await Assert.ThrowsAsync( - () => base.Project_branch_required_collection(async)); - - AssertSql( - """ -SELECT VALUE c -FROM root c -ORDER BY c["Id"] -"""); - } - } - - public override Task Project_branch_optional_required(bool async) - => Fixture.NoSyncTest( - async, async a => - { - await base.Project_branch_optional_required(a); - - AssertSql( - """ -SELECT VALUE c -FROM root c -ORDER BY c["Id"] -"""); - }); - - public override Task Project_branch_optional_optional(bool async) - => Fixture.NoSyncTest( - async, async a => - { - await base.Project_branch_optional_optional(a); - - AssertSql( - """ -SELECT VALUE c -FROM root c -ORDER BY c["Id"] -"""); - }); - - public override async Task Project_branch_optional_collection(bool async) - { - if (async) - { - //issue #31696 - await Assert.ThrowsAsync( - () => base.Project_branch_optional_collection(async)); - - AssertSql( - """ -SELECT VALUE c -FROM root c -ORDER BY c["Id"] -"""); - } - } - - public override async Task Project_branch_collection_element_using_indexer_constant(bool async) - { - if (async) - { - //issue #31696 - await Assert.ThrowsAsync( - () => base.Project_branch_collection_element_using_indexer_constant(async)); - - AssertSql( - """ -SELECT VALUE c -FROM root c -ORDER BY c["Id"] -"""); - } - } - - public override Task Project_root_duplicated(bool async) - => Fixture.NoSyncTest( - async, async a => - { - await base.Project_root_duplicated(a); - - AssertSql( - """ -SELECT VALUE c -FROM root c -"""); - }); - - public override async Task Project_trunk_and_branch_duplicated(bool async) - { - if (async) - { - //issue #31696 - await Assert.ThrowsAsync( - () => base.Project_trunk_and_branch_duplicated(async)); - - AssertSql( - """ -SELECT VALUE c -FROM root c -ORDER BY c["Id"] -"""); - } - } - - public override async Task Project_trunk_and_trunk_duplicated(bool async) - { - if (async) - { - //issue #31696 - await Assert.ThrowsAsync( - () => base.Project_trunk_and_trunk_duplicated(async)); - - AssertSql( - """ -SELECT VALUE c -FROM root c -ORDER BY c["Id"] -"""); - } - } - - public override async Task Project_multiple_branch_leaf(bool async) - { - if (async) - { - //issue #35702 - await Assert.ThrowsAsync( - () => base.Project_multiple_branch_leaf(async)); - - AssertSql(); - } - } - - public override Task Project_leaf_trunk_root(bool async) - => Fixture.NoSyncTest( - async, async a => - { - await base.Project_leaf_trunk_root(a); - - AssertSql( - """ -SELECT VALUE c -FROM root c -"""); - }); - - - public override Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) - => AssertTranslationFailed( - () => base.Project_subquery_root_set_required_trunk_FirstOrDefault_branch(async)); - - public override Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) - => AssertTranslationFailed( - () => base.Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(async)); - - public override Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - => AssertTranslationFailed( - () => base.Project_subquery_root_set_trunk_FirstOrDefault_collection(async)); - - public override Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - => AssertTranslationFailed( - () => base.Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async)); - - public override Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - => AssertTranslationFailed( - () => base.Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async)); - - public override async Task SelectMany_trunk_collection(bool async) - { - if (async) - { - //issue #34349 - await Assert.ThrowsAsync( - () => base.SelectMany_trunk_collection(async)); - - AssertSql(); - } - } - - public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) - { - if (async) - { - //issue #34349 - await Assert.ThrowsAsync( - () => base.SelectMany_required_trunk_reference_branch_collection(async)); - - AssertSql(); - } - } - - public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) - { - if (async) - { - //issue #34349 - await Assert.ThrowsAsync( - () => base.SelectMany_optional_trunk_reference_branch_collection(async)); - - AssertSql(); - } - } - - private void AssertSql(params string[] expected) - => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); -} diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/JsonRelationshipsQueryCosmosFixture.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedJsonRelationshipsCosmosFixture.cs similarity index 94% rename from test/EFCore.Cosmos.FunctionalTests/Query/Relationships/JsonRelationshipsQueryCosmosFixture.cs rename to test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedJsonRelationshipsCosmosFixture.cs index 0444901a127..4bd8b155917 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/JsonRelationshipsQueryCosmosFixture.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedJsonRelationshipsCosmosFixture.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public class JsonRelationshipsQueryCosmosFixture : JsonRelationshipsQueryFixtureBase +public class OwnedJsonRelationshipsCosmosFixture : OwnedJsonRelationshipsFixtureBase { public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/Projection/OwnedJsonNoTrackingProjectionCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/Projection/OwnedJsonNoTrackingProjectionCosmosTest.cs new file mode 100644 index 00000000000..863a7153f00 --- /dev/null +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/Projection/OwnedJsonNoTrackingProjectionCosmosTest.cs @@ -0,0 +1,151 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedJsonNoTrackingProjectionCosmosTest : OwnedJsonNoTrackingProjectionTestBase +{ + private readonly TrackingRewriter _trackingRewriter = new(); + + public OwnedJsonNoTrackingProjectionCosmosTest(OwnedJsonRelationshipsCosmosFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) + { + var rewritten = _trackingRewriter.Visit(serverQueryExpression); + + return rewritten; + } + + public override Task Select_trunk_collection(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Select_trunk_collection(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +ORDER BY c["Id"] +"""); + }); + + public override async Task Select_branch_required_collection(bool async) + { + if (async) + { + //issue #31696 + await Assert.ThrowsAsync( + () => base.Select_branch_required_collection(async)); + + AssertSql( + """ +SELECT VALUE c +FROM root c +ORDER BY c["Id"] +"""); + } + } + + public override async Task Select_branch_optional_collection(bool async) + { + if (async) + { + //issue #31696 + await Assert.ThrowsAsync( + () => base.Select_branch_optional_collection(async)); + + AssertSql( + """ +SELECT VALUE c +FROM root c +ORDER BY c["Id"] +"""); + } + } + + public override async Task Project_branch_collection_element_using_indexer_constant(bool async) + { + if (async) + { + //issue #31696 + await Assert.ThrowsAsync( + () => base.Project_branch_collection_element_using_indexer_constant(async)); + + AssertSql( + """ +SELECT VALUE c +FROM root c +ORDER BY c["Id"] +"""); + } + } + + public override async Task Select_multiple_branch_leaf(bool async) + { + if (async) + { + //issue #35702 + await Assert.ThrowsAsync( + () => base.Select_multiple_branch_leaf(async)); + + AssertSql(); + } + } + + public override Task Select_subquery_root_set_trunk_FirstOrDefault_collection(bool async) + => AssertTranslationFailed( + () => base.Select_subquery_root_set_trunk_FirstOrDefault_collection(async)); + + public override Task Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) + => AssertTranslationFailed( + () => base.Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async)); + + public override Task Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) + => AssertTranslationFailed( + () => base.Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async)); + + public override async Task SelectMany_trunk_collection(bool async) + { + if (async) + { + //issue #34349 + await Assert.ThrowsAsync( + () => base.SelectMany_trunk_collection(async)); + + AssertSql(); + } + } + + public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) + { + if (async) + { + //issue #34349 + await Assert.ThrowsAsync( + () => base.SelectMany_required_trunk_reference_branch_collection(async)); + + AssertSql(); + } + } + + public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) + { + if (async) + { + //issue #34349 + await Assert.ThrowsAsync( + () => base.SelectMany_optional_trunk_reference_branch_collection(async)); + + AssertSql(); + } + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/Projection/OwnedJsonReferenceNoTrackingProjectionCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/Projection/OwnedJsonReferenceNoTrackingProjectionCosmosTest.cs new file mode 100644 index 00000000000..a7e802b94f7 --- /dev/null +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/Projection/OwnedJsonReferenceNoTrackingProjectionCosmosTest.cs @@ -0,0 +1,192 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedJsonReferenceNoTrackingProjectionCosmosTest : OwnedJsonReferenceNoTrackingProjectionTestBase +{ + private readonly TrackingRewriter _trackingRewriter = new(); + + public OwnedJsonReferenceNoTrackingProjectionCosmosTest(OwnedJsonRelationshipsCosmosFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) + { + var rewritten = _trackingRewriter.Visit(serverQueryExpression); + + return rewritten; + } + + public override Task Select_root(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Select_root(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +"""); + }); + + public override Task Select_trunk_optional(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Select_trunk_optional(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +ORDER BY c["Id"] +"""); + }); + + public override Task Select_trunk_required(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Select_trunk_required(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +ORDER BY c["Id"] +"""); + }); + + public override Task Select_branch_required_required(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Select_branch_required_required(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +ORDER BY c["Id"] +"""); + }); + + public override Task Select_branch_required_optional(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Select_branch_required_optional(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +ORDER BY c["Id"] +"""); + }); + + public override Task Select_branch_optional_required(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Select_branch_optional_required(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +ORDER BY c["Id"] +"""); + }); + + public override Task Select_branch_optional_optional(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Select_branch_optional_optional(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +ORDER BY c["Id"] +"""); + }); + + public override Task Select_root_duplicated(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Select_root_duplicated(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +"""); + }); + + public override async Task Select_trunk_and_branch_duplicated(bool async) + { + if (async) + { + //issue #31696 + await Assert.ThrowsAsync( + () => base.Select_trunk_and_branch_duplicated(async)); + + AssertSql( + """ +SELECT VALUE c +FROM root c +ORDER BY c["Id"] +"""); + } + } + + public override async Task Select_trunk_and_trunk_duplicated(bool async) + { + if (async) + { + //issue #31696 + await Assert.ThrowsAsync( + () => base.Select_trunk_and_trunk_duplicated(async)); + + AssertSql( + """ +SELECT VALUE c +FROM root c +ORDER BY c["Id"] +"""); + } + } + + public override Task Select_leaf_trunk_root(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Select_leaf_trunk_root(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +"""); + }); + + + public override Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + => AssertTranslationFailed( + () => base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async)); + + public override Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + => AssertTranslationFailed( + () => base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async)); + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Translations/StringTranslationsCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Translations/StringTranslationsCosmosTest.cs index 3f9b73ecd4c..195b9e16703 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/Translations/StringTranslationsCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Translations/StringTranslationsCosmosTest.cs @@ -168,6 +168,20 @@ public override Task IndexOf(bool async) SELECT VALUE c FROM root c WHERE (INDEX_OF(c["String"], "eattl") != -1) +"""); + }); + + public override Task IndexOf_Char(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.IndexOf_Char(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (INDEX_OF(c["String"], "e") != -1) """); }); @@ -195,6 +209,22 @@ public override Task IndexOf_with_one_parameter_arg(bool async) """ @pattern=? +SELECT VALUE c +FROM root c +WHERE (INDEX_OF(c["String"], @pattern) = 1) +"""); + }); + + public override Task IndexOf_with_one_parameter_arg_char(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.IndexOf_with_one_parameter_arg_char(a); + + AssertSql( + """ +@pattern=? + SELECT VALUE c FROM root c WHERE (INDEX_OF(c["String"], @pattern) = 1) @@ -212,6 +242,20 @@ public override Task IndexOf_with_constant_starting_position(bool async) SELECT VALUE c FROM root c WHERE ((LENGTH(c["String"]) > 2) AND (INDEX_OF(c["String"], "e", 2) = 6)) +"""); + }); + + public override Task IndexOf_with_constant_starting_position_char(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.IndexOf_with_constant_starting_position_char(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE ((LENGTH(c["String"]) > 2) AND (INDEX_OF(c["String"], "e", 2) = 6)) """); }); @@ -225,6 +269,22 @@ public override Task IndexOf_with_parameter_starting_position(bool async) """ @start=? +SELECT VALUE c +FROM root c +WHERE ((LENGTH(c["String"]) > 2) AND (INDEX_OF(c["String"], "e", @start) = 6)) +"""); + }); + + public override Task IndexOf_with_parameter_starting_position_char(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.IndexOf_with_parameter_starting_position_char(a); + + AssertSql( + """ +@start=? + SELECT VALUE c FROM root c WHERE ((LENGTH(c["String"]) > 2) AND (INDEX_OF(c["String"], "e", @start) = 6)) @@ -252,6 +312,20 @@ public override Task Replace(bool async) SELECT VALUE c FROM root c WHERE (REPLACE(c["String"], "Sea", "Rea") = "Reattle") +"""); + }); + + public override Task Replace_Char(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Replace_Char(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (REPLACE(c["String"], "S", "R") = "Reattle") """); }); @@ -440,6 +514,20 @@ public override Task StartsWith_Literal(bool async) SELECT VALUE c FROM root c WHERE STARTSWITH(c["String"], "Se") +"""); + }); + + public override Task StartsWith_Literal_Char(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.StartsWith_Literal_Char(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE STARTSWITH(c["String"], "S") """); }); @@ -453,6 +541,22 @@ public override Task StartsWith_Parameter(bool async) """ @pattern=? +SELECT VALUE c +FROM root c +WHERE STARTSWITH(c["String"], @pattern) +"""); + }); + + public override Task StartsWith_Parameter_Char(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.StartsWith_Parameter_Char(a); + + AssertSql( + """ +@pattern=? + SELECT VALUE c FROM root c WHERE STARTSWITH(c["String"], @pattern) @@ -525,6 +629,20 @@ public override Task EndsWith_Literal(bool async) SELECT VALUE c FROM root c WHERE ENDSWITH(c["String"], "le") +"""); + }); + + public override Task EndsWith_Literal_Char(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.EndsWith_Literal_Char(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE ENDSWITH(c["String"], "e") """); }); @@ -538,6 +656,22 @@ public override Task EndsWith_Parameter(bool async) """ @pattern=? +SELECT VALUE c +FROM root c +WHERE ENDSWITH(c["String"], @pattern) +"""); + }); + + public override Task EndsWith_Parameter_Char(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.EndsWith_Parameter_Char(a); + + AssertSql( + """ +@pattern=? + SELECT VALUE c FROM root c WHERE ENDSWITH(c["String"], @pattern) @@ -613,6 +747,20 @@ WHERE CONTAINS(c["String"], "eattl") """); }); + public override Task Contains_Literal_Char(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Contains_Literal_Char(a); + + AssertSql( + """ + SELECT VALUE c + FROM root c + WHERE CONTAINS(c["String"], "e") + """); + }); + public override Task Contains_Column(bool async) => Fixture.NoSyncTest( async, async a => diff --git a/test/EFCore.Cosmos.FunctionalTests/ReloadTest.cs b/test/EFCore.Cosmos.FunctionalTests/ReloadTest.cs index f247fba55ae..b54ff771e0d 100644 --- a/test/EFCore.Cosmos.FunctionalTests/ReloadTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/ReloadTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore; public class ReloadTest : IClassFixture { - public static IEnumerable IsAsyncData = [[false], [true]]; + public static readonly IEnumerable IsAsyncData = [[false], [true]]; private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs index ee2cc478e48..1f1de4a2750 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs @@ -33,9 +33,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(Dictionary), baseEntityType, sharedClrType: true, - discriminatorProperty: "$type", indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(typeof(Dictionary)), propertyBag: true, + discriminatorProperty: "$type", discriminatorValue: "PrincipalBasePrincipalDerived>", propertyCount: 8, foreignKeyCount: 2, diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs index 00293d478e1..4d81b493b23 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs @@ -24,9 +24,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(Dictionary), baseEntityType, sharedClrType: true, - discriminatorProperty: "$type", indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(typeof(Dictionary)), propertyBag: true, + discriminatorProperty: "$type", discriminatorValue: "PrincipalBasePrincipalDerived>", propertyCount: 8, foreignKeyCount: 2, diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/CompiledModelCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/CompiledModelCosmosTest.cs index 77a0ebcbabc..bf32b55c841 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/CompiledModelCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/CompiledModelCosmosTest.cs @@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Scaffolding; -public class CompiledModelCosmosTest : CompiledModelTestBase +public class CompiledModelCosmosTest(NonSharedFixture fixture) : CompiledModelTestBase(fixture) { [ConditionalFact] public virtual Task Basic_cosmos_model() diff --git a/test/EFCore.Cosmos.FunctionalTests/Storage/CosmosDatabaseCreatorTest.cs b/test/EFCore.Cosmos.FunctionalTests/Storage/CosmosDatabaseCreatorTest.cs index dc683d4121d..a2f97236df2 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Storage/CosmosDatabaseCreatorTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Storage/CosmosDatabaseCreatorTest.cs @@ -8,40 +8,27 @@ namespace Microsoft.EntityFrameworkCore.Storage; [CosmosCondition(CosmosCondition.DoesNotUseTokenCredential)] public class CosmosDatabaseCreatorTest { - public static IEnumerable IsAsyncData = [[false], [true]]; + public static readonly IEnumerable IsAsyncData = [[true]]; [ConditionalFact] public async Task EnsureCreated_returns_true_when_database_does_not_exist() { - await using var testDatabase = CosmosTestStore.Create("NonExisting"); - try - { - using var context = new BloggingContext(testDatabase); - var creator = context.GetService(); - - Assert.True(await creator.EnsureCreatedAsync()); - } - finally - { - await testDatabase.InitializeAsync(testDatabase.ServiceProvider, () => new BloggingContext(testDatabase)); - } + await using var testDatabase = CosmosTestStore.Create("NonExistingDatabase"); + using var context = new BloggingContext(testDatabase); + var creator = context.GetService(); + await creator.EnsureDeletedAsync(); + Assert.True(await creator.EnsureCreatedAsync()); } [ConditionalFact] public async Task EnsureCreated_returns_true_when_database_exists_but_collections_do_not() { await using var testDatabase = CosmosTestStore.Create("EnsureCreatedTest"); - try - { - using var context = new BloggingContext(testDatabase); - var creator = context.GetService(); + await testDatabase.InitializeAsync(testDatabase.ServiceProvider, () => new BaseContext(testDatabase)); - Assert.True(await creator.EnsureCreatedAsync()); - } - finally - { - await testDatabase.InitializeAsync(testDatabase.ServiceProvider, () => new BloggingContext(testDatabase)); - } + using var context = new BloggingContext(testDatabase); + var creator = context.GetService(); + Assert.True(await creator.EnsureCreatedAsync()); } [ConditionalTheory] @@ -97,7 +84,7 @@ public async Task EnsureCreated_throws_for_missing_seed() (await Assert.ThrowsAsync(() => context.Database.EnsureCreatedAsync())).Message); } - private class BloggingContext(CosmosTestStore testStore, bool seed = false) : DbContext + private class BaseContext(CosmosTestStore testStore) : DbContext { private readonly string _connectionUri = testStore.ConnectionUri; private readonly string _authToken = testStore.AuthToken; @@ -111,6 +98,14 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) _authToken, _name, b => b.ApplyConfiguration()); + } + } + + private class BloggingContext(CosmosTestStore testStore, bool seed = false) : BaseContext(testStore) + { + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + base.OnConfiguring(optionsBuilder); if (seed) { @@ -118,10 +113,6 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) } } - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - } - public DbSet Blogs { get; set; } } diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestHelpers.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestHelpers.cs index 74ddb55fdca..56be8eb6343 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestHelpers.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestHelpers.cs @@ -3,8 +3,8 @@ using Microsoft.EntityFrameworkCore.Cosmos.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Internal; -// ReSharper disable once CheckNamespace +// ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore.TestUtilities; public class CosmosTestHelpers : TestHelpers diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs index c889a991c64..e9cccb6050f 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs @@ -84,6 +84,8 @@ private static string CreateName(string name) public TokenCredential TokenCredential { get; } public string ConnectionString { get; } + private static readonly SemaphoreSlim _connectionSemaphore = new(1, 1); + protected override DbContext CreateDefaultContext() => new TestStoreContext(this); @@ -96,7 +98,16 @@ public static async ValueTask IsConnectionAvailableAsync() { if (_connectionAvailable == null) { - _connectionAvailable = await TryConnectAsync().ConfigureAwait(false); + await _connectionSemaphore.WaitAsync(); + + try + { + _connectionAvailable ??= await TryConnectAsync().ConfigureAwait(false); + } + finally + { + _connectionSemaphore.Release(); + } } return _connectionAvailable.Value; @@ -271,19 +282,19 @@ public async Task EnsureCreatedAsync(DbContext context, CancellationToken var model = context.GetService().Model; - var modelThrouput = model.GetThroughput(); - if (modelThrouput == null + var modelThroughput = model.GetThroughput(); + if (modelThroughput == null && GetContainersToCreate(model).All(c => c.Throughput == null)) { - modelThrouput = ThroughputProperties.CreateManualThroughput(400); + modelThroughput = ThroughputProperties.CreateManualThroughput(400); } - if (modelThrouput != null) + if (modelThroughput != null) { sqlDatabaseCreateUpdateContent.Options = new CosmosDBCreateUpdateConfig { - Throughput = modelThrouput.Throughput, - AutoscaleMaxThroughput = modelThrouput.AutoscaleMaxThroughput + Throughput = modelThroughput.Throughput, + AutoscaleMaxThroughput = modelThroughput.AutoscaleMaxThroughput }; } @@ -478,44 +489,20 @@ private async Task DeleteContainers(DbContext context) { var cosmosClient = context.Database.GetCosmosClient(); var database = cosmosClient.GetDatabase(Name); + var containers = new List(); var containerIterator = database.GetContainerQueryIterator(); while (containerIterator.HasMoreResults) { foreach (var containerProperties in await containerIterator.ReadNextAsync().ConfigureAwait(false)) { - var container = database.GetContainer(containerProperties.Id); - var partitionKeys = containerProperties.PartitionKeyPaths.Select(p => p[1..]).ToList(); - var itemIterator = container.GetItemQueryIterator( - new QueryDefinition("SELECT * FROM c")); - - var items = new List<(string Id, PartitionKey PartitionKeyValue)>(); - while (itemIterator.HasMoreResults) - { - foreach (var item in await itemIterator.ReadNextAsync().ConfigureAwait(false)) - { - var partitionKeyValue = PartitionKey.None; - if (partitionKeys.Count >= 1 - && item[partitionKeys[0]] is not null) - { - var builder = new PartitionKeyBuilder(); - foreach (var partitionKey in partitionKeys) - { - builder.Add((string?)item[partitionKey]); - } - - partitionKeyValue = builder.Build(); - } - - items.Add((item["id"]!.ToString(), partitionKeyValue)); - } - } - - foreach (var item in items) - { - await container.DeleteItemAsync(item.Id, item.PartitionKeyValue).ConfigureAwait(false); - } + containers.Add(database.GetContainer(containerProperties.Id)); } } + + foreach(var container in containers) + { + await container.DeleteContainerAsync(); + } } else { @@ -676,6 +663,12 @@ public InstantiationBinding ServiceOnlyConstructorBinding IReadOnlyEntityType IReadOnlyEntityType.BaseType => null!; + ITypeBase? ITypeBase.BaseType + => BaseType; + + IReadOnlyTypeBase? IReadOnlyTypeBase.BaseType + => BaseType; + IReadOnlyModel IReadOnlyTypeBase.Model => throw new NotImplementedException(); @@ -1005,5 +998,14 @@ IReadOnlyPropertyBase IReadOnlyTypeBase.FindMember(string name) IEnumerable IReadOnlyTypeBase.FindMembersInHierarchy(string name) => throw new NotImplementedException(); + + IEnumerable ITypeBase.GetDirectlyDerivedTypes() + => GetDirectlyDerivedTypes(); + + IEnumerable IReadOnlyTypeBase.GetDerivedTypes() + => GetDerivedTypes(); + + IEnumerable IReadOnlyTypeBase.GetDirectlyDerivedTypes() + => GetDirectlyDerivedTypes(); } } diff --git a/test/EFCore.CrossStore.FunctionalTests/QueryTest.cs b/test/EFCore.CrossStore.FunctionalTests/QueryTest.cs index 29328c36caa..6089f533e48 100644 --- a/test/EFCore.CrossStore.FunctionalTests/QueryTest.cs +++ b/test/EFCore.CrossStore.FunctionalTests/QueryTest.cs @@ -10,7 +10,7 @@ namespace Microsoft.EntityFrameworkCore; public class QueryTest { - public static IEnumerable IsAsyncData = new object[][] { [false], [true] }; + public static readonly IEnumerable IsAsyncData = [[false], [true]]; [ConditionalTheory] [MemberData(nameof(IsAsyncData))] diff --git a/test/EFCore.Design.Tests/DesignApiConsistencyTest.cs b/test/EFCore.Design.Tests/DesignApiConsistencyTest.cs index 7a54b4ffc81..b3f37cbfe92 100644 --- a/test/EFCore.Design.Tests/DesignApiConsistencyTest.cs +++ b/test/EFCore.Design.Tests/DesignApiConsistencyTest.cs @@ -19,7 +19,7 @@ public class DesignApiConsistencyFixture : ApiConsistencyFixtureBase { public override HashSet FluentApiTypes { get; } = [typeof(DesignTimeServiceCollectionExtensions)]; - public override HashSet NonVirtualMethods { get; } = + public override HashSet VirtualMethodExceptions { get; } = [ typeof(CSharpEntityTypeGeneratorBase.ToStringInstanceHelper) .GetProperty(nameof(CSharpEntityTypeGeneratorBase.ToStringInstanceHelper.FormatProvider)).GetMethod, diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs index a9c0c44c61d..b55c4c17cfb 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs @@ -6028,9 +6028,12 @@ public virtual void Complex_properties_are_stored_in_snapshot() eb.PrimitiveCollection>("List") .HasColumnType("nvarchar(max)") .IsSparse(); - eb.ComplexProperty(e => e.EntityWithStringKey) - .Ignore(e => e.Properties) - .Property(e => e.Id).IsRequired(); + eb.ComplexProperty(e => e.EntityWithStringKey, cb => + { + cb.Ignore(e => e.Properties); + cb.Property(e => e.Id).IsRequired(); + cb.HasDiscriminator(); + }); eb.HasPropertyAnnotation("PropertyAnnotation", 1); eb.HasTypeAnnotation("TypeAnnotation", 2); }); @@ -6067,9 +6070,15 @@ public virtual void Complex_properties_are_stored_in_snapshot() b1.ComplexProperty>("EntityWithStringKey", "Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty.EntityWithTwoProperties#EntityWithTwoProperties.EntityWithStringKey#EntityWithStringKey", b2 => { + b2.Property("Discriminator") + .IsRequired() + .HasColumnType("nvarchar(max)"); + b2.Property("Id") .IsRequired() .HasColumnType("nvarchar(max)"); + + b2.HasDiscriminator().HasValue("EntityWithStringKey"); }); b1.HasPropertyAnnotation("PropertyAnnotation", 1); diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index 033af3a37ff..501d53b2301 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -150,12 +150,6 @@ public void Test_new_annotations_handled_for_entity_types() + " })") }, { -#pragma warning disable CS0612 // Type or member is obsolete - CoreAnnotationNames.DefiningQuery, -#pragma warning restore CS0612 // Type or member is obsolete - (Expression.Lambda(Expression.Constant(null)), _toNullTable) - }, - { RelationalAnnotationNames.ViewName, ("MyView", _toNullTable + ";" + _nl @@ -202,9 +196,6 @@ public void Test_new_annotations_handled_for_properties() CoreAnnotationNames.EagerLoaded, CoreAnnotationNames.LazyLoadingEnabled, CoreAnnotationNames.QueryFilter, -#pragma warning disable CS0612 // Type or member is obsolete - CoreAnnotationNames.DefiningQuery, -#pragma warning restore CS0612 // Type or member is obsolete CoreAnnotationNames.DiscriminatorProperty, CoreAnnotationNames.DiscriminatorValue, CoreAnnotationNames.InverseNavigations, diff --git a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs index a6707db2bce..fa8821d71ba 100644 --- a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.BulkUpdates; using Microsoft.EntityFrameworkCore.Query.Relationships.Include; -using Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; +using Microsoft.EntityFrameworkCore.Query.Relationships.Projection; namespace Microsoft.EntityFrameworkCore; @@ -27,19 +27,27 @@ public class InMemoryComplianceTest : ComplianceTestBase typeof(NonSharedModelBulkUpdatesTestBase), typeof(NorthwindBulkUpdatesTestBase<>), typeof(JsonQueryTestBase<>), + typeof(AdHocJsonQueryTestBase), // TODO: implement later once things are baked - typeof(ComplexRelationshipsInProjectionNoTrackingQueryTestBase<>), - typeof(ComplexRelationshipsInProjectionQueryTestBase<>), - typeof(EntityRelationshipsInProjectionNoTrackingQueryTestBase<>), - typeof(EntityRelationshipsInProjectionQueryTestBase<>), - typeof(JsonRelationshipsInProjectionNoTrackingQueryTestBase<>), - typeof(JsonRelationshipsInProjectionQueryTestBase<>), - typeof(OwnedRelationshipsInProjectionNoTrackingQueryTestBase<>), - typeof(OwnedRelationshipsInProjectionQueryTestBase<>), - typeof(RelationshipsInProjectionQueryTestBase<>), - typeof(EntityRelationshipsIncludeQueryTestBase<>), - typeof(RelationshipsIncludeQueryTestBase<>), + typeof(NavigationNoTrackingProjectionTestBase<>), + typeof(NavigationProjectionTestBase<>), + typeof(OwnedJsonNoTrackingProjectionTestBase<>), + typeof(OwnedJsonProjectionTestBase<>), + typeof(OwnedNoTrackingProjectionTestBase<>), + typeof(OwnedProjectionTestBase<>), + typeof(ProjectionTestBase<>), + typeof(NavigationIncludeTestBase<>), + + typeof(ComplexNoTrackingProjectionTestBase<>), + typeof(ComplexProjectionTestBase<>), + typeof(NavigationReferenceNoTrackingProjectionTestBase<>), + typeof(NavigationReferenceProjectionTestBase<>), + typeof(OwnedJsonReferenceNoTrackingProjectionTestBase<>), + typeof(OwnedJsonReferenceProjectionTestBase<>), + typeof(OwnedReferenceNoTrackingProjectionTestBase<>), + typeof(OwnedReferenceProjectionTestBase<>), + typeof(ReferenceProjectionTestBase<>), }; protected override Assembly TargetAssembly { get; } = typeof(InMemoryComplianceTest).Assembly; diff --git a/test/EFCore.InMemory.FunctionalTests/JsonTypesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/JsonTypesInMemoryTest.cs index 269f5d4fe47..e4852a7c5c7 100644 --- a/test/EFCore.InMemory.FunctionalTests/JsonTypesInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/JsonTypesInMemoryTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore; -public class JsonTypesInMemoryTest : JsonTypesTestBase +public class JsonTypesInMemoryTest(NonSharedFixture fixture) : JsonTypesTestBase(fixture) { public override Task Can_read_write_point() // No built-in JSON support for spatial types in the in-memory provider diff --git a/test/EFCore.InMemory.FunctionalTests/MaterializationInterceptionInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/MaterializationInterceptionInMemoryTest.cs index a37b21821f2..20a076add9f 100644 --- a/test/EFCore.InMemory.FunctionalTests/MaterializationInterceptionInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/MaterializationInterceptionInMemoryTest.cs @@ -5,8 +5,8 @@ namespace Microsoft.EntityFrameworkCore; -public class MaterializationInterceptionInMemoryTest : - MaterializationInterceptionTestBase +public class MaterializationInterceptionInMemoryTest(NonSharedFixture fixture) : + MaterializationInterceptionTestBase(fixture) { public class InMemoryLibraryContext(DbContextOptions options) : LibraryContext(options) { diff --git a/test/EFCore.InMemory.FunctionalTests/Query/AdHocAdvancedMappingsQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/AdHocAdvancedMappingsQueryInMemoryTest.cs index bcd988e7562..029068fc98d 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/AdHocAdvancedMappingsQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/AdHocAdvancedMappingsQueryInMemoryTest.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query; -public class AdHocAdvancedMappingsQueryInMemoryTest : AdHocAdvancedMappingsQueryTestBase +public class AdHocAdvancedMappingsQueryInMemoryTest(NonSharedFixture fixture) : AdHocAdvancedMappingsQueryTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; diff --git a/test/EFCore.InMemory.FunctionalTests/Query/AdHocManyToManyQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/AdHocManyToManyQueryInMemoryTest.cs index ee2fd520aa3..5c424febed8 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/AdHocManyToManyQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/AdHocManyToManyQueryInMemoryTest.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query; -public class AdHocManyToManyQueryInMemoryTest : AdHocManyToManyQueryTestBase +public class AdHocManyToManyQueryInMemoryTest(NonSharedFixture fixture) : AdHocManyToManyQueryTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; diff --git a/test/EFCore.InMemory.FunctionalTests/Query/AdHocMiscellaneousQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/AdHocMiscellaneousQueryInMemoryTest.cs index 943e6f28dd2..3bff6542a8d 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/AdHocMiscellaneousQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/AdHocMiscellaneousQueryInMemoryTest.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query; -public class AdHocMiscellaneousQueryInMemoryTest : AdHocMiscellaneousQueryTestBase +public class AdHocMiscellaneousQueryInMemoryTest(NonSharedFixture fixture) : AdHocMiscellaneousQueryTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; diff --git a/test/EFCore.InMemory.FunctionalTests/Query/AdHocNavigationsQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/AdHocNavigationsQueryInMemoryTest.cs index 5d991fed5f8..219305b81b7 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/AdHocNavigationsQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/AdHocNavigationsQueryInMemoryTest.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query; -public class AdHocNavigationsQueryInMemoryTest : AdHocNavigationsQueryTestBase +public class AdHocNavigationsQueryInMemoryTest(NonSharedFixture fixture) : AdHocNavigationsQueryTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; diff --git a/test/EFCore.InMemory.FunctionalTests/Query/AdHocQueryFiltersQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/AdHocQueryFiltersQueryInMemoryTest.cs index b065b774d56..538d96349b8 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/AdHocQueryFiltersQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/AdHocQueryFiltersQueryInMemoryTest.cs @@ -3,8 +3,132 @@ namespace Microsoft.EntityFrameworkCore.Query; -public class AdHocQueryFiltersQueryInMemoryTest : AdHocQueryFiltersQueryTestBase +public class AdHocQueryFiltersQueryInMemoryTest(NonSharedFixture fixture) : AdHocQueryFiltersQueryTestBase(fixture) { + #region 19708 + + [ConditionalFact] + public virtual async Task GroupJoin_SelectMany_gets_flattened() + { + var contextFactory = await InitializeAsync(seed: c => c.SeedAsync()); + using (var context = contextFactory.CreateContext()) + { + var query = context.CustomerFilters.ToList(); + } + + using (var context = contextFactory.CreateContext()) + { + var query = context.Set().ToList(); + + Assert.Collection( + query, + t => AssertCustomerView(t, 1, "First", 1, "FirstChild"), + t => AssertCustomerView(t, 2, "Second", 2, "SecondChild1"), + t => AssertCustomerView(t, 2, "Second", 3, "SecondChild2"), + t => AssertCustomerView(t, 3, "Third", null, "")); + + static void AssertCustomerView( + Context19708.CustomerView19708 actual, + int id, + string name, + int? customerMembershipId, + string customerMembershipName) + { + Assert.Equal(id, actual.Id); + Assert.Equal(name, actual.Name); + Assert.Equal(customerMembershipId, actual.CustomerMembershipId); + Assert.Equal(customerMembershipName, actual.CustomerMembershipName); + } + } + } + + protected class Context19708(DbContextOptions options) : DbContext(options) + { + public DbSet Customers { get; set; } + public DbSet CustomerMemberships { get; set; } + public DbSet CustomerFilters { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasQueryFilter( + e => (from a in (from c in Customers + join cm in CustomerMemberships on c.Id equals cm.CustomerId into g + from cm in g.DefaultIfEmpty() + select new { c.Id, CustomerMembershipId = (int?)cm.Id }) + where a.CustomerMembershipId != null && a.Id == e.CustomerId + select a).Count() + > 0) + .HasKey(e => e.CustomerId); + + modelBuilder.Entity().HasNoKey().ToInMemoryQuery(Build_Customers_Sql_View_InMemory()); + } + + public Task SeedAsync() + { + var customer1 = new Customer19708 { Name = "First" }; + var customer2 = new Customer19708 { Name = "Second" }; + var customer3 = new Customer19708 { Name = "Third" }; + + var customerMembership1 = new CustomerMembership19708 { Name = "FirstChild", Customer = customer1 }; + var customerMembership2 = new CustomerMembership19708 { Name = "SecondChild1", Customer = customer2 }; + var customerMembership3 = new CustomerMembership19708 { Name = "SecondChild2", Customer = customer2 }; + + AddRange(customer1, customer2, customer3); + AddRange(customerMembership1, customerMembership2, customerMembership3); + + return SaveChangesAsync(); + } + + private Expression>> Build_Customers_Sql_View_InMemory() + { + Expression>> query = () => + from customer in Customers + join customerMembership in CustomerMemberships on customer.Id equals customerMembership.CustomerId into + nullableCustomerMemberships + from customerMembership in nullableCustomerMemberships.DefaultIfEmpty() + select new CustomerView19708 + { + Id = customer.Id, + Name = customer.Name, + CustomerMembershipId = customerMembership != null ? customerMembership.Id : default(int?), + CustomerMembershipName = customerMembership != null ? customerMembership.Name : "" + }; + return query; + } + + public class Customer19708 + { + public int Id { get; set; } + public string Name { get; set; } + } + + public class CustomerMembership19708 + { + public int Id { get; set; } + public string Name { get; set; } + + public int CustomerId { get; set; } + public Customer19708 Customer { get; set; } + } + + public class CustomerFilter19708 + { + public int CustomerId { get; set; } + public int CustomerMembershipId { get; set; } + } + + public class CustomerView19708 + { + public int Id { get; set; } + public string Name { get; set; } + public int? CustomerMembershipId { get; set; } + public string CustomerMembershipName { get; set; } + } + } + + #endregion + protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/OwnedEntityQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/OwnedEntityQueryInMemoryTest.cs index 44bdf7bf0e5..1b97e8d8841 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/OwnedEntityQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/OwnedEntityQueryInMemoryTest.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query; -public class OwnedEntityQueryInMemoryTest : OwnedEntityQueryTestBase +public class OwnedEntityQueryInMemoryTest(NonSharedFixture fixture) : OwnedEntityQueryTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SharedTypeQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SharedTypeQueryInMemoryTest.cs index 00af8f46592..3cf2bee40ed 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SharedTypeQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SharedTypeQueryInMemoryTest.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query; -public class SharedTypeQueryInMemoryTest : SharedTypeQueryTestBase +public class SharedTypeQueryInMemoryTest(NonSharedFixture fixture) : SharedTypeQueryTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; diff --git a/test/EFCore.InMemory.FunctionalTests/Scaffolding/CompiledModelInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Scaffolding/CompiledModelInMemoryTest.cs index 425d4a13548..4c0e39444e7 100644 --- a/test/EFCore.InMemory.FunctionalTests/Scaffolding/CompiledModelInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Scaffolding/CompiledModelInMemoryTest.cs @@ -8,6 +8,7 @@ using System.Runtime.CompilerServices; using System.Text.Json; using Microsoft.EntityFrameworkCore.Design.Internal; +using Microsoft.EntityFrameworkCore.InMemory.Internal; using Microsoft.EntityFrameworkCore.InMemory.Storage.Internal; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -17,7 +18,7 @@ public class GlobalNamespaceContext(DbContextOptions opt namespace Microsoft.EntityFrameworkCore.Scaffolding { - public class CompiledModelInMemoryTest : CompiledModelTestBase + public class CompiledModelInMemoryTest(NonSharedFixture fixture) : CompiledModelTestBase(fixture) { [ConditionalFact] public virtual Task Empty_model() @@ -307,7 +308,7 @@ public virtual Task Throws_for_query_filter() [ConditionalFact] public virtual Task Throws_for_defining_query() => Test( - expectedExceptionMessage: DesignStrings.CompiledModelDefiningQuery("object")); + expectedExceptionMessage: InMemoryStrings.CompiledModelDefiningQuery("object")); public class DefiningQueryContext(DbContextOptions options) : DbContext(options) { diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesRelationalTestBase.cs index dd1d733fbb0..6f928377915 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesRelationalTestBase.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public abstract class NonSharedModelBulkUpdatesRelationalTestBase : NonSharedModelBulkUpdatesTestBase +public abstract class NonSharedModelBulkUpdatesRelationalTestBase(NonSharedFixture fixture) : NonSharedModelBulkUpdatesTestBase(fixture) { protected override string StoreName => "NonSharedModelBulkUpdatesTests"; diff --git a/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs index 353419c20ec..6aa464310b0 100644 --- a/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs @@ -7,9 +7,10 @@ namespace Microsoft.EntityFrameworkCore; #nullable disable -public abstract class EntitySplittingTestBase : NonSharedModelTestBase +public abstract class EntitySplittingTestBase : NonSharedModelTestBase, IClassFixture { - protected EntitySplittingTestBase(ITestOutputHelper testOutputHelper) + protected EntitySplittingTestBase(NonSharedFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) { // TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } diff --git a/test/EFCore.Relational.Specification.Tests/JsonTypesRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/JsonTypesRelationalTestBase.cs index 6a254f7fada..93ec9cc17cd 100644 --- a/test/EFCore.Relational.Specification.Tests/JsonTypesRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/JsonTypesRelationalTestBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore; -public abstract class JsonTypesRelationalTestBase : JsonTypesTestBase +public abstract class JsonTypesRelationalTestBase(NonSharedFixture fixture) : JsonTypesTestBase(fixture) { public override Task Can_read_write_array_of_array_of_array_of_int_JSON_values() => NoNestedCollections( diff --git a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs index 1557b610152..644e5616c88 100644 --- a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs @@ -24,7 +24,7 @@ protected MigrationsInfrastructureTestBase(TFixture fixture) protected string ActiveProvider { get; private set; } - public static IEnumerable IsAsyncData = [[false], [true]]; + public static readonly IEnumerable IsAsyncData = [[false], [true]]; // Database deletion can happen as async file operation and SQLClient // doesn't account for this, so give some time for it to happen on slow C.I. machines diff --git a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs index 7403fbb2c00..8241023fb2a 100644 --- a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs @@ -3256,6 +3256,118 @@ public virtual Task Add_required_primitve_collection_with_custom_converter_and_c Assert.Single(customersTable.PrimaryKey!.Columns)); }); + [ConditionalFact] + public virtual Task Multiop_drop_table_and_create_the_same_table_in_one_migration() + => TestComposite( + [ + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.HasKey("Id"); + e.ToTable("Customers"); + }), + builder => { }, + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.HasKey("Id"); + + e.ToTable("Customers"); + }) + ]); + + [ConditionalFact] + public virtual Task Multiop_create_table_and_drop_it_in_one_migration() + => TestComposite( + [ + builder => { }, + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.HasKey("Id"); + + e.ToTable("Customers"); + }), + builder => { }, + ]); + + [ConditionalFact] + public virtual Task Multiop_rename_table_and_drop() + => TestComposite( + [ + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.HasKey("Id"); + + e.ToTable("Customers"); + }), + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.HasKey("Id"); + + e.ToTable("NewCustomers"); + }), + builder => { }, + ]); + + [ConditionalFact] + public virtual Task Multiop_rename_table_and_create_new_table_with_the_old_name() + => TestComposite( + [ + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.HasKey("Id"); + + e.ToTable("Customers"); + }), + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.HasKey("Id"); + + e.ToTable("NewCustomers"); + }), + builder => + { + builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.HasKey("Id"); + + e.ToTable("NewCustomers"); + }); + + builder.Entity( + "AnotherCustomer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.HasKey("Id"); + + e.ToTable("Customers"); + }); + }, + ]); + protected class Person { public int Id { get; set; } @@ -3305,6 +3417,43 @@ protected virtual Task Test( MigrationsSqlGenerationOptions migrationsSqlGenerationOptions = MigrationsSqlGenerationOptions.Default) => Test(_ => { }, buildSourceAction, buildTargetAction, asserter, withConventions, migrationsSqlGenerationOptions); + protected virtual Task TestComposite( + List> buildActions, + bool withConventions = true, + MigrationsSqlGenerationOptions migrationsSqlGenerationOptions = MigrationsSqlGenerationOptions.Default) + { + if (buildActions.Count < 3) + { + throw new InvalidOperationException("You need at least 3 build actions for the composite case."); + } + + var context = CreateContext(); + var modelDiffer = context.GetService(); + var modelRuntimeInitializer = context.GetService(); + + var models = new List(); + for (var i = 0; i < buildActions.Count; i++) + { + var modelBuilder = CreateModelBuilder(withConventions); + buildActions[i](modelBuilder); + + var preSnapshotModel = modelRuntimeInitializer.Initialize( + (IModel)modelBuilder.Model, designTime: true, validationLogger: null); + + models.Add(preSnapshotModel); + } + + // build all migration operations going through each intermediate state of the model + var operations = new List(); + for (var i = 0; i < models.Count - 1; i++) + { + operations.AddRange( + modelDiffer.GetDifferences(models[i].GetRelationalModel(), models[i + 1].GetRelationalModel())); + } + + return Test(models.First(), models.Last(), operations, null, migrationsSqlGenerationOptions); + } + protected virtual Task Test( Action buildCommonAction, Action buildSourceAction, diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs index 5a5ea258faf..1c8aa47904a 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public abstract class AdHocAdvancedMappingsQueryRelationalTestBase : AdHocAdvancedMappingsQueryTestBase +public abstract class AdHocAdvancedMappingsQueryRelationalTestBase(NonSharedFixture fixture) : AdHocAdvancedMappingsQueryTestBase(fixture) { protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryRelationalTestBase.cs new file mode 100644 index 00000000000..c7dd7ebb70a --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryRelationalTestBase.cs @@ -0,0 +1,566 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json; + +namespace Microsoft.EntityFrameworkCore.Query; + +#nullable disable + +public abstract class AdHocJsonQueryRelationalTestBase(NonSharedFixture fixture) : AdHocJsonQueryTestBase(fixture) +{ + #region 21006 + + public override async Task Project_missing_required_navigation(bool async) + { + var message = (await Assert.ThrowsAsync( + () => base.Project_missing_required_navigation(async))).Message; + + Assert.Equal(RelationalStrings.JsonRequiredEntityWithNullJson(typeof(Context21006.JsonEntityNested).Name), message); + } + + public override async Task Project_null_required_navigation(bool async) + { + var message = (await Assert.ThrowsAsync( + () => base.Project_null_required_navigation(async))).Message; + + Assert.Equal(RelationalStrings.JsonRequiredEntityWithNullJson(typeof(Context21006.JsonEntityNested).Name), message); + } + + public override async Task Project_top_level_entity_with_null_value_required_scalars(bool async) + { + var message = (await Assert.ThrowsAsync( + () => base.Project_top_level_entity_with_null_value_required_scalars(async))).Message; + + Assert.Equal("Cannot get the value of a token type 'Null' as a number.", message); + } + + protected override void OnModelCreating21006(ModelBuilder modelBuilder) + { + base.OnModelCreating21006(modelBuilder); + + modelBuilder.Entity( + b => + { + b.ToTable("Entities"); + b.OwnsOne(x => x.OptionalReference).ToJson(); + b.OwnsOne(x => x.RequiredReference).ToJson(); + b.OwnsMany(x => x.Collection).ToJson(); + }); + } + + #endregion + + #region 32310 + + protected override void OnModelCreating32310(ModelBuilder modelBuilder) + { + base.OnModelCreating32310(modelBuilder); + + modelBuilder.Entity().OwnsOne(e => e.Visits).ToJson().HasColumnType(JsonColumnType); + } + + #endregion + + #region 29219 + + protected override void OnModelCreating29219(ModelBuilder modelBuilder) + { + base.OnModelCreating29219(modelBuilder); + + modelBuilder.Entity( + b => + { + b.ToTable("Entities"); + b.OwnsOne(x => x.Reference).ToJson().HasColumnType(JsonColumnType); + b.OwnsMany(x => x.Collection).ToJson().HasColumnType(JsonColumnType); + }); + } + + #endregion + + #region 30028 + + protected override void OnModelCreating30028(ModelBuilder modelBuilder) + { + base.OnModelCreating30028(modelBuilder); + + modelBuilder.Entity(b => + { + b.ToTable("Entities"); + b.OwnsOne(x => x.Json, nb => + { + nb.ToJson().HasColumnType(JsonColumnType); + }); + }); + } + + #endregion + + #region 32939 + + protected override void OnModelCreating32939(ModelBuilder modelBuilder) + { + base.OnModelCreating32939(modelBuilder); + + modelBuilder.Entity().OwnsOne(x => x.Empty, b => b.ToJson().HasColumnType(JsonColumnType)); + modelBuilder.Entity().OwnsOne(x => x.FieldOnly, b => b.ToJson().HasColumnType(JsonColumnType)); + } + + #endregion + + #region 33046 + + protected override void OnModelCreating33046(ModelBuilder modelBuilder) + { + base.OnModelCreating33046(modelBuilder); + + modelBuilder.Entity(b => + { + b.ToTable("Reviews"); + b.OwnsMany(x => x.Rounds, ownedBuilder => + { + ownedBuilder.ToJson().HasColumnType(JsonColumnType); + }); + }); + } + + #endregion + + #region 34293 + + [ConditionalFact] + public virtual async Task Project_entity_with_optional_json_entity_owned_by_required_json() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreating34293, + seed: ctx => ctx.Seed()); + + using var context = contextFactory.CreateContext(); + var entityProjection = await context.Set().ToListAsync(); + + Assert.Equal(3, entityProjection.Count); + } + + [ConditionalFact] + public virtual async Task Project_required_json_entity() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreating34293, + seed: ctx => ctx.Seed()); + + using var context = contextFactory.CreateContext(); + + var rootProjection = await context.Set().AsNoTracking().Where(x => x.Id != 3).Select(x => x.Json).ToListAsync(); + Assert.Equal(2, rootProjection.Count); + + var branchProjection = await context.Set().AsNoTracking().Where(x => x.Id != 3).Select(x => x.Json.Required).ToListAsync(); + Assert.Equal(2, rootProjection.Count); + + var badRootProjectionMessage = (await Assert.ThrowsAsync( + () => context.Set().AsNoTracking().Where(x => x.Id == 3).Select(x => x.Json).ToListAsync())).Message; + Assert.Equal(RelationalStrings.JsonRequiredEntityWithNullJson(nameof(Context34293.JsonBranch)), badRootProjectionMessage); + + var badBranchProjectionMessage = (await Assert.ThrowsAsync( + () => context.Set().AsNoTracking().Where(x => x.Id == 3).Select(x => x.Json.Required).ToListAsync())).Message; + Assert.Equal(RelationalStrings.JsonRequiredEntityWithNullJson(nameof(Context34293.JsonBranch)), badBranchProjectionMessage); + } + + [ConditionalFact] + public virtual async Task Project_optional_json_entity_owned_by_required_json_entity() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreating34293, + seed: ctx => ctx.Seed()); + + using var context = contextFactory.CreateContext(); + var leafProjection = await context.Set().AsNoTracking().Select(x => x.Json.Required.Optional).ToListAsync(); + Assert.Equal(3, leafProjection.Count); + } + + protected class Context34293(DbContextOptions options) : DbContext(options) + { + public DbSet Entities { get; set; } + + public class Entity + { + public int Id { get; set; } + public JsonRoot Json { get; set; } + } + + public class JsonRoot + { + public DateTime Date { get; set; } + + public JsonBranch Required { get; set; } + } + + public class JsonBranch + { + public int Number { get; set; } + public JsonLeaf Optional { get; set; } + } + + public class JsonLeaf + { + public string Name { get; set; } + } + + public async Task Seed() + { + // everything - ok + var e1 = new Entity + { + Id = 1, + Json = new JsonRoot + { + Date = new DateTime(2001, 1, 1), + Required = new JsonBranch + { + Number = 1, + Optional = new JsonLeaf { Name = "optional 1" } + } + } + }; + + // null leaf - ok (optional nav) + var e2 = new Entity + { + Id = 2, + Json = new JsonRoot + { + Date = new DateTime(2002, 2, 2), + Required = new JsonBranch + { + Number = 2, + Optional = null + } + } + }; + + // null branch - invalid (required nav) + var e3 = new Entity + { + Id = 3, + Json = new JsonRoot + { + Date = new DateTime(2003, 3, 3), + Required = null, + } + }; + + Entities.AddRange(e1, e2, e3); + await SaveChangesAsync(); + } + } + + protected virtual void OnModelCreating34293(ModelBuilder modelBuilder) + => modelBuilder.Entity( + b => + { + b.Property(x => x.Id).ValueGeneratedNever(); + b.OwnsOne( + x => x.Json, b => + { + b.ToJson().HasColumnType(JsonColumnType); + b.OwnsOne(x => x.Required, bb => + { + bb.OwnsOne(x => x.Optional); + bb.Navigation(x => x.Optional).IsRequired(false); + }); + b.Navigation(x => x.Required).IsRequired(true); + }); + b.Navigation(x => x.Json).IsRequired(true); + }); + + #endregion + + #region 34960 + + public override async Task Try_project_collection_but_JSON_is_entity() + { + var message = (await Assert.ThrowsAsync( + () => base.Try_project_collection_but_JSON_is_entity())).Message; + + Assert.Equal( + CoreStrings.JsonReaderInvalidTokenType(nameof(JsonTokenType.StartObject)), + message); + } + + public override async Task Try_project_reference_but_JSON_is_collection() + { + var message = (await Assert.ThrowsAsync( + () => base.Try_project_reference_but_JSON_is_collection())).Message; + + Assert.Equal( + CoreStrings.JsonReaderInvalidTokenType(nameof(JsonTokenType.StartArray)), + message); + } + + protected override void OnModelCreating34960(ModelBuilder modelBuilder) + { + base.OnModelCreating34960(modelBuilder); + + modelBuilder.Entity(b => + { + b.ToTable("Entities"); + + b.OwnsOne(x => x.Reference, b => + { + b.ToJson().HasColumnType(JsonColumnType); + }); + + b.OwnsMany(x => x.Collection, b => + { + b.ToJson().HasColumnType(JsonColumnType); + }); + }); + + modelBuilder.Entity(b => + { + b.ToTable("Junk"); + + b.OwnsOne(x => x.Reference, b => + { + b.ToJson().HasColumnType(JsonColumnType); + }); + + b.OwnsMany(x => x.Collection, b => + { + b.ToJson().HasColumnType(JsonColumnType); + }); + }); + } + + #endregion + + #region ArrayOfPrimitives + + protected override void OnModelCreatingArrayOfPrimitives(ModelBuilder modelBuilder) + { + base.OnModelCreatingArrayOfPrimitives(modelBuilder); + + modelBuilder.Entity().OwnsOne( + x => x.Reference, b => b.ToJson().HasColumnType(JsonColumnType)); + + modelBuilder.Entity().OwnsMany( + x => x.Collection, b => b.ToJson().HasColumnType(JsonColumnType)); + } + + #endregion + + #region JunkInJson + + protected override void OnModelCreatingJunkInJson(ModelBuilder modelBuilder) + { + base.OnModelCreatingJunkInJson(modelBuilder); + + modelBuilder.Entity(b => + { + b.ToTable("Entities"); + + b.OwnsOne(x => x.Reference, b => + { + b.ToJson().HasColumnType(JsonColumnType); + }); + + b.OwnsOne(x => x.ReferenceWithCtor, b => + { + b.ToJson().HasColumnType(JsonColumnType); + }); + + b.OwnsMany(x => x.Collection, b => + { + b.ToJson().HasColumnType(JsonColumnType); + }); + + b.OwnsMany(x => x.CollectionWithCtor, b => + { + b.ToJson().HasColumnType(JsonColumnType); + }); + }); + } + + #endregion + + #region TrickyBuffering + + protected override void OnModelCreatingTrickyBuffering(ModelBuilder modelBuilder) + { + base.OnModelCreatingTrickyBuffering(modelBuilder); + + modelBuilder.Entity(b => + { + b.ToTable("Entities"); + b.OwnsOne(x => x.Reference, b => + { + b.ToJson().HasColumnType(JsonColumnType); + }); + }); + } + + #endregion + + #region ShadowProperties + + protected override void OnModelCreatingShadowProperties(ModelBuilder modelBuilder) + { + base.OnModelCreatingShadowProperties(modelBuilder); + + modelBuilder.Entity(b => + { + b.ToTable("Entities"); + + b.OwnsOne(x => x.Reference, b => + { + b.ToJson().HasColumnType(JsonColumnType); + }); + + b.OwnsOne(x => x.ReferenceWithCtor, b => + { + b.ToJson().HasColumnType(JsonColumnType); + b.Property("Shadow_Int").HasJsonPropertyName("ShadowInt"); + }); + + b.OwnsMany(x => x.Collection, b => + { + b.ToJson().HasColumnType(JsonColumnType); + }); + + b.OwnsMany(x => x.CollectionWithCtor, b => + { + b.ToJson().HasColumnType(JsonColumnType); + }); + }); + } + + #endregion + + #region LazyLoadingProxies + + protected override void OnModelCreatingLazyLoadingProxies(ModelBuilder modelBuilder) + { + base.OnModelCreatingLazyLoadingProxies(modelBuilder); + + modelBuilder.Entity().OwnsOne(x => x.Reference, b => b.ToJson().HasColumnType(JsonColumnType)); + modelBuilder.Entity().OwnsMany(x => x.Collection, b => b.ToJson().HasColumnType(JsonColumnType)); + } + + //protected void OnConfiguringLazyLoadingProxies(DbContextOptionsBuilder optionsBuilder) + // => optionsBuilder.UseLazyLoadingProxies(); + + //protected IServiceCollection AddServicesLazyLoadingProxies(IServiceCollection addServices) + // => addServices.AddEntityFrameworkProxies(); + + //private Task SeedLazyLoadingProxies(DbContext ctx) + //{ + // var r1 = new MyJsonEntityLazyLoadingProxiesWithCtor("r1", 1); + // var c11 = new MyJsonEntityLazyLoadingProxies { Name = "c11", Number = 11 }; + // var c12 = new MyJsonEntityLazyLoadingProxies { Name = "c12", Number = 12 }; + // var c13 = new MyJsonEntityLazyLoadingProxies { Name = "c13", Number = 13 }; + + // var r2 = new MyJsonEntityLazyLoadingProxiesWithCtor("r2", 2); + // var c21 = new MyJsonEntityLazyLoadingProxies { Name = "c21", Number = 21 }; + // var c22 = new MyJsonEntityLazyLoadingProxies { Name = "c22", Number = 22 }; + + // var e1 = new MyEntityLazyLoadingProxies + // { + // Id = 1, + // Name = "e1", + // Reference = r1, + // Collection = + // [ + // c11, + // c12, + // c13 + // ] + // }; + + // var e2 = new MyEntityLazyLoadingProxies + // { + // Id = 2, + // Name = "e2", + // Reference = r2, + // Collection = [c21, c22] + // }; + + // ctx.Set().AddRange(e1, e2); + // return ctx.SaveChangesAsync(); + //} + + #endregion + + #region NotICollection + + protected override void OnModelCreatingNotICollection(ModelBuilder modelBuilder) + { + base.OnModelCreatingNotICollection(modelBuilder); + + modelBuilder.Entity(b => + { + b.ToTable("Entities"); + b.OwnsOne(cr => cr.Json, nb => + { + nb.ToJson().HasColumnType(JsonColumnType); + }); + }); + } + + #endregion + + #region BadJsonProperties + + public override async Task Bad_json_properties_duplicated_navigations(bool noTracking) + { + // tracking returns different results - see #35807 + if (noTracking) + { + await base.Bad_json_properties_duplicated_navigations(noTracking); + } + } + + public override Task Bad_json_properties_null_navigations(bool noTracking) + => Assert.ThrowsAnyAsync( + () => base.Bad_json_properties_null_navigations(noTracking)); + + public override async Task Bad_json_properties_null_scalars(bool noTracking) + { + var message = (await Assert.ThrowsAnyAsync( + () => base.Bad_json_properties_null_scalars(noTracking))).Message; + + Assert.StartsWith("'n' is an invalid start of a property name. Expected a '\"'.", message); + } + + protected override void OnModelCreatingBadJsonProperties(ModelBuilder modelBuilder) + { + base.OnModelCreatingBadJsonProperties(modelBuilder); + + modelBuilder.Entity(b => + { + b.ToTable("Entities"); + + b.OwnsOne(x => x.RequiredReference, b => + { + b.ToJson().HasColumnType(JsonColumnType); + }); + + b.OwnsOne(x => x.OptionalReference, b => + { + b.ToJson().HasColumnType(JsonColumnType); + }); + + b.OwnsMany(x => x.Collection, b => + { + b.ToJson().HasColumnType(JsonColumnType); + }); + }); + } + + #endregion + + protected TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + protected virtual string JsonColumnType + => null; +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryTestBase.cs deleted file mode 100644 index ff85628c439..00000000000 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryTestBase.cs +++ /dev/null @@ -1,1765 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Text.Json; - -namespace Microsoft.EntityFrameworkCore.Query; - -#nullable disable - -public abstract class AdHocJsonQueryTestBase : NonSharedModelTestBase -{ - protected override string StoreName - => "AdHocJsonQueryTest"; - - protected virtual void ConfigureWarnings(WarningsConfigurationBuilder builder) - { - } - - #region 32310 - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Contains_on_nested_collection_with_init_only_navigation(bool async) - { - var contextFactory = await InitializeAsync( - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - onModelCreating: b => b.Entity().OwnsOne(e => e.Visits).ToJson().HasColumnType(JsonColumnType), - seed: Seed32310); - - await using var context = contextFactory.CreateContext(); - - var query = context.Set() - .Where(u => u.Visits.DaysVisited.Contains(new DateOnly(2023, 1, 1))); - - var result = async - ? await query.FirstOrDefaultAsync()! - : query.FirstOrDefault()!; - - Assert.Equal("FBI", result.Name); - Assert.Equal(new DateOnly(2023, 1, 1), result.Visits.DaysVisited.Single()); - } - - protected virtual async Task Seed32310(DbContext context) - { - var user = new Pub32310 - { - Name = "FBI", Visits = new Visits32310 { LocationTag = "tag", DaysVisited = [new DateOnly(2023, 1, 1)] } - }; - - context.Add(user); - await context.SaveChangesAsync(); - } - - public class Pub32310 - { - public int Id { get; set; } - public required string Name { get; set; } - public Visits32310 Visits { get; set; } = null!; - } - - public class Visits32310 - { - public string LocationTag { get; set; } - public required List DaysVisited { get; init; } - } - - #endregion - - #region 29219 - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Optional_json_properties_materialized_as_null_when_the_element_in_json_is_not_present(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModel29219, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: Seed29219); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set().Where(x => x.Id == 3); - - var result = async - ? await query.SingleAsync() - : query.Single(); - - Assert.Equal(3, result.Id); - Assert.Null(result.Reference.NullableScalar); - Assert.Null(result.Collection[0].NullableScalar); - } - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Can_project_nullable_json_property_when_the_element_in_json_is_not_present(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModel29219, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: Seed29219); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set().OrderBy(x => x.Id).Select(x => x.Reference.NullableScalar); - - var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Equal(3, result.Count); - Assert.Equal(11, result[0]); - Assert.Null(result[1]); - Assert.Null(result[2]); - } - } - - protected void BuildModel29219(ModelBuilder modelBuilder) - => modelBuilder.Entity( - b => - { - b.ToTable("Entities"); - b.Property(x => x.Id).ValueGeneratedNever(); - b.OwnsOne(x => x.Reference).ToJson().HasColumnType(JsonColumnType); - b.OwnsMany(x => x.Collection).ToJson().HasColumnType(JsonColumnType); - }); - - protected abstract Task Seed29219(DbContext ctx); - - public class MyEntity29219 - { - public int Id { get; set; } - public MyJsonEntity29219 Reference { get; set; } - public List Collection { get; set; } - } - - public class MyJsonEntity29219 - { - public int NonNullableScalar { get; set; } - public int? NullableScalar { get; set; } - } - - #endregion - - #region 30028 - - protected abstract Task Seed30028(DbContext ctx); - - protected virtual void BuildModel30028(ModelBuilder modelBuilder) - => modelBuilder.Entity( - b => - { - b.Property(x => x.Id).ValueGeneratedNever(); - b.ToTable("Entities"); - b.OwnsOne( - x => x.Json, nb => - { - nb.ToJson().HasColumnType(JsonColumnType); - nb.OwnsMany(x => x.Collection, nnb => nnb.OwnsOne(x => x.Nested)); - nb.OwnsOne(x => x.OptionalReference, nnb => nnb.OwnsOne(x => x.Nested)); - nb.OwnsOne(x => x.RequiredReference, nnb => nnb.OwnsOne(x => x.Nested)); - nb.Navigation(x => x.RequiredReference).IsRequired(); - }); - }); - - public class MyEntity30028 - { - public int Id { get; set; } - public MyJsonRootEntity30028 Json { get; set; } - } - - public class MyJsonRootEntity30028 - { - public string RootName { get; set; } - public MyJsonBranchEntity30028 RequiredReference { get; set; } - public MyJsonBranchEntity30028 OptionalReference { get; set; } - public List Collection { get; set; } - } - - public class MyJsonBranchEntity30028 - { - public string BranchName { get; set; } - public MyJsonLeafEntity30028 Nested { get; set; } - } - - public class MyJsonLeafEntity30028 - { - public string LeafName { get; set; } - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Accessing_missing_navigation_works(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModel30028, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: Seed30028); - - using (var context = contextFactory.CreateContext()) - { - var result = context.Set().OrderBy(x => x.Id).ToList(); - Assert.Equal(4, result.Count); - Assert.NotNull(result[0].Json.Collection); - Assert.NotNull(result[0].Json.OptionalReference); - Assert.NotNull(result[0].Json.RequiredReference); - - Assert.Null(result[1].Json.Collection); - Assert.NotNull(result[1].Json.OptionalReference); - Assert.NotNull(result[1].Json.RequiredReference); - - Assert.NotNull(result[2].Json.Collection); - Assert.Null(result[2].Json.OptionalReference); - Assert.NotNull(result[2].Json.RequiredReference); - - Assert.NotNull(result[3].Json.Collection); - Assert.NotNull(result[3].Json.OptionalReference); - Assert.Null(result[3].Json.RequiredReference); - } - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Missing_navigation_works_with_deduplication(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModel30028, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: Seed30028); - - using (var context = contextFactory.CreateContext()) - { - var queryable = context.Set().OrderBy(x => x.Id).Select( - x => new - { - x, - x.Json, - x.Json.OptionalReference, - x.Json.RequiredReference, - NestedOptional = x.Json.OptionalReference.Nested, - NestedRequired = x.Json.RequiredReference.Nested, - x.Json.Collection, - }).AsNoTracking(); - - var result = async ? await queryable.ToListAsync() : queryable.ToList(); - - Assert.Equal(4, result.Count); - Assert.NotNull(result[0].OptionalReference); - Assert.NotNull(result[0].RequiredReference); - Assert.NotNull(result[0].NestedOptional); - Assert.NotNull(result[0].NestedRequired); - Assert.NotNull(result[0].Collection); - - Assert.NotNull(result[1].OptionalReference); - Assert.NotNull(result[1].RequiredReference); - Assert.NotNull(result[1].NestedOptional); - Assert.NotNull(result[1].NestedRequired); - Assert.Null(result[1].Collection); - - Assert.Null(result[2].OptionalReference); - Assert.NotNull(result[2].RequiredReference); - Assert.Null(result[2].NestedOptional); - Assert.NotNull(result[2].NestedRequired); - Assert.NotNull(result[2].Collection); - - Assert.NotNull(result[3].OptionalReference); - Assert.Null(result[3].RequiredReference); - Assert.NotNull(result[3].NestedOptional); - Assert.Null(result[3].NestedRequired); - Assert.NotNull(result[3].Collection); - } - } - - #endregion - - #region 32939 - - [ConditionalFact] - public virtual async Task Project_json_with_no_properties() - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModel32939, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: Seed32939); - - using var context = contextFactory.CreateContext(); - context.Set().ToList(); - } - - protected Task Seed32939(DbContext ctx) - { - var entity = new Entity32939 { Empty = new JsonEmpty32939(), FieldOnly = new JsonFieldOnly32939() }; - - ctx.Add(entity); - return ctx.SaveChangesAsync(); - } - - protected virtual void BuildModel32939(ModelBuilder modelBuilder) - { - modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); - modelBuilder.Entity().OwnsOne(x => x.Empty, b => b.ToJson().HasColumnType(JsonColumnType)); - modelBuilder.Entity().OwnsOne(x => x.FieldOnly, b => b.ToJson().HasColumnType(JsonColumnType)); - } - - public class Entity32939 - { - public int Id { get; set; } - public JsonEmpty32939 Empty { get; set; } - public JsonFieldOnly32939 FieldOnly { get; set; } - } - - public class JsonEmpty32939 - { - } - - public class JsonFieldOnly32939 - { - public int Field; - } - - #endregion - - #region 33046 - - protected abstract Task Seed33046(DbContext ctx); - - [ConditionalFact] - public virtual async Task Query_with_nested_json_collection_mapped_to_private_field_via_IReadOnlyList() - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModel33046, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: Seed33046); - - using var context = contextFactory.CreateContext(); - var query = context.Set().ToList(); - Assert.Equal(1, query.Count); - } - - protected virtual void BuildModel33046(ModelBuilder modelBuilder) - => modelBuilder.Entity( - b => - { - b.ToTable("Reviews"); - b.Property(x => x.Id).ValueGeneratedNever(); - b.OwnsMany( - x => x.Rounds, ownedBuilder => - { - ownedBuilder.ToJson().HasColumnType(JsonColumnType); - ownedBuilder.OwnsMany(r => r.SubRounds); - }); - }); - - public class Review - { - public int Id { get; set; } - -#pragma warning disable IDE0044 // Add readonly modifier - private List _rounds = []; -#pragma warning restore IDE0044 // Add readonly modifier - public IReadOnlyList Rounds - => _rounds.AsReadOnly(); - } - - public class ReviewRound - { - public int RoundNumber { get; set; } - -#pragma warning disable IDE0044 // Add readonly modifier - private readonly List _subRounds = []; -#pragma warning restore IDE0044 // Add readonly modifier - public IReadOnlyList SubRounds - => _subRounds.AsReadOnly(); - } - - public class SubRound - { - public int SubRoundNumber { get; set; } - } - - #endregion - - #region 34293 - - [ConditionalFact] - public virtual async Task Project_entity_with_optional_json_entity_owned_by_required_json() - { - var contextFactory = await InitializeAsync( - onModelCreating: OnModelCreating34293, - seed: ctx => ctx.Seed()); - - using var context = contextFactory.CreateContext(); - var entityProjection = await context.Set().ToListAsync(); - - Assert.Equal(3, entityProjection.Count); - } - - [ConditionalFact] - public virtual async Task Project_required_json_entity() - { - var contextFactory = await InitializeAsync( - onModelCreating: OnModelCreating34293, - seed: ctx => ctx.Seed()); - - using var context = contextFactory.CreateContext(); - - var rootProjection = await context.Set().AsNoTracking().Where(x => x.Id != 3).Select(x => x.Json).ToListAsync(); - Assert.Equal(2, rootProjection.Count); - - var branchProjection = await context.Set().AsNoTracking().Where(x => x.Id != 3).Select(x => x.Json.Required).ToListAsync(); - Assert.Equal(2, rootProjection.Count); - - var badRootProjectionMessage = (await Assert.ThrowsAsync( - () => context.Set().AsNoTracking().Where(x => x.Id == 3).Select(x => x.Json).ToListAsync())).Message; - Assert.Equal(RelationalStrings.JsonRequiredEntityWithNullJson(nameof(Context34293.JsonBranch)), badRootProjectionMessage); - - var badBranchProjectionMessage = (await Assert.ThrowsAsync( - () => context.Set().AsNoTracking().Where(x => x.Id == 3).Select(x => x.Json.Required).ToListAsync())).Message; - Assert.Equal(RelationalStrings.JsonRequiredEntityWithNullJson(nameof(Context34293.JsonBranch)), badBranchProjectionMessage); - } - - [ConditionalFact] - public virtual async Task Project_optional_json_entity_owned_by_required_json_entity() - { - var contextFactory = await InitializeAsync( - onModelCreating: OnModelCreating34293, - seed: ctx => ctx.Seed()); - - using var context = contextFactory.CreateContext(); - var leafProjection = await context.Set().AsNoTracking().Select(x => x.Json.Required.Optional).ToListAsync(); - Assert.Equal(3, leafProjection.Count); - } - - protected class Context34293(DbContextOptions options) : DbContext(options) - { - public DbSet Entities { get; set; } - - public class Entity - { - public int Id { get; set; } - public JsonRoot Json { get; set; } - } - - public class JsonRoot - { - public DateTime Date { get; set; } - - public JsonBranch Required { get; set; } - } - - public class JsonBranch - { - public int Number { get; set; } - public JsonLeaf Optional { get; set; } - } - - public class JsonLeaf - { - public string Name { get; set; } - } - - public async Task Seed() - { - // everything - ok - var e1 = new Entity - { - Id = 1, - Json = new JsonRoot - { - Date = new DateTime(2001, 1, 1), - Required = new JsonBranch - { - Number = 1, - Optional = new JsonLeaf { Name = "optional 1" } - } - } - }; - - // null leaf - ok (optional nav) - var e2 = new Entity - { - Id = 2, - Json = new JsonRoot - { - Date = new DateTime(2002, 2, 2), - Required = new JsonBranch - { - Number = 2, - Optional = null - } - } - }; - - // null branch - invalid (required nav) - var e3 = new Entity - { - Id = 3, - Json = new JsonRoot - { - Date = new DateTime(2003, 3, 3), - Required = null, - } - }; - - Entities.AddRange(e1, e2, e3); - await SaveChangesAsync(); - } - } - - protected virtual void OnModelCreating34293(ModelBuilder modelBuilder) - { - modelBuilder.Entity( - b => - { - b.Property(x => x.Id).ValueGeneratedNever(); - b.OwnsOne( - x => x.Json, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.OwnsOne(x => x.Required, bb => - { - bb.OwnsOne(x => x.Optional); - bb.Navigation(x => x.Optional).IsRequired(false); - }); - b.Navigation(x => x.Required).IsRequired(true); - }); - b.Navigation(x => x.Json).IsRequired(true); - }); - } - - #endregion - - #region 34960 - - [ConditionalFact] - public virtual async Task Project_entity_with_json_null_values() - { - var contextFactory = await InitializeAsync(seed: Seed34960, onModelCreating: OnModelCreating34960); - - using var context = contextFactory.CreateContext(); - var query = await context.Entities.ToListAsync(); - } - - [ConditionalFact] - public virtual async Task Try_project_collection_but_JSON_is_entity() - { - var contextFactory = await InitializeAsync(seed: Seed34960, onModelCreating: OnModelCreating34960); - using var context = contextFactory.CreateContext(); - - Assert.Equal( - (await Assert.ThrowsAsync( - () => context.Junk.AsNoTracking().Where(x => x.Id == 1).Select(x => x.Collection).FirstOrDefaultAsync())).Message, - CoreStrings.JsonReaderInvalidTokenType(nameof(JsonTokenType.StartObject))); - } - - [ConditionalFact] - public virtual async Task Try_project_reference_but_JSON_is_collection() - { - var contextFactory = await InitializeAsync(seed: Seed34960, onModelCreating: OnModelCreating34960); - using var context = contextFactory.CreateContext(); - - Assert.Equal( - (await Assert.ThrowsAsync( - () => context.Junk.AsNoTracking().Where(x => x.Id == 2).Select(x => x.Reference).FirstOrDefaultAsync())).Message, - CoreStrings.JsonReaderInvalidTokenType(nameof(JsonTokenType.StartArray))); - } - - protected class Context34960(DbContextOptions options) : DbContext(options) - { - public DbSet Entities { get; set; } - public DbSet Junk { get; set; } - - public class Entity - { - public int Id { get; set; } - public JsonEntity Reference { get; set; } - public List Collection { get; set; } - } - - public class JsonEntity - { - public string Name { get; set; } - public double Number { get; set; } - - public JsonEntityNested NestedReference { get; set; } - public List NestedCollection { get; set; } - } - - public class JsonEntityNested - { - public DateTime DoB { get; set; } - public string Text { get; set; } - } - - public class JunkEntity - { - public int Id { get; set; } - public JsonEntity Reference { get; set; } - public List Collection { get; set; } - } - } - - protected virtual void OnModelCreating34960(ModelBuilder modelBuilder) - { - modelBuilder.Entity( - b => - { - b.ToTable("Entities"); - b.Property(x => x.Id).ValueGeneratedNever(); - - b.OwnsOne( - x => x.Reference, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.OwnsOne(x => x.NestedReference); - b.OwnsMany(x => x.NestedCollection); - }); - - b.OwnsMany( - x => x.Collection, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.OwnsOne(x => x.NestedReference); - b.OwnsMany(x => x.NestedCollection); - }); - }); - - modelBuilder.Entity( - b => - { - b.ToTable("Junk"); - b.Property(x => x.Id).ValueGeneratedNever(); - - b.OwnsOne( - x => x.Reference, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.Ignore(x => x.NestedReference); - b.Ignore(x => x.NestedCollection); - }); - - b.OwnsMany( - x => x.Collection, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.Ignore(x => x.NestedReference); - b.Ignore(x => x.NestedCollection); - }); - }); - } - - protected virtual async Task Seed34960(Context34960 ctx) - { - // everything - var e1 = new Context34960.Entity - { - Id = 1, - Reference = new Context34960.JsonEntity - { - Name = "ref1", - Number = 1.5f, - NestedReference = new Context34960.JsonEntityNested - { - DoB = new DateTime(2000, 1, 1), - Text = "nested ref 1" - }, - NestedCollection = - [ - new Context34960.JsonEntityNested - { - DoB = new DateTime(2001, 1, 1), - Text = "nested col 1 1" - }, - new Context34960.JsonEntityNested - { - DoB = new DateTime(2001, 2, 2), - Text = "nested col 1 2" - }, - ], - }, - - Collection = - [ - new Context34960.JsonEntity - { - Name = "col 1 1", - Number = 2.5f, - NestedReference = new Context34960.JsonEntityNested - { - DoB = new DateTime(2010, 1, 1), - Text = "nested col 1 1 ref 1" - }, - NestedCollection = - [ - new Context34960.JsonEntityNested - { - DoB = new DateTime(2011, 1, 1), - Text = "nested col 1 1 col 1 1" - }, - new Context34960.JsonEntityNested - { - DoB = new DateTime(2011, 2, 2), - Text = "nested col 1 1 col 1 2" - }, - ], - }, - new Context34960.JsonEntity - { - Name = "col 1 2", - Number = 2.5f, - NestedReference = new Context34960.JsonEntityNested - { - DoB = new DateTime(2020, 1, 1), - Text = "nested col 1 2 ref 1" - }, - NestedCollection = - [ - new Context34960.JsonEntityNested - { - DoB = new DateTime(2021, 1, 1), - Text = "nested col 1 2 col 1 1" - }, - new Context34960.JsonEntityNested - { - DoB = new DateTime(2021, 2, 2), - Text = "nested col 1 2 col 1 2" - }, - ], - }, - ], - }; - - // relational nulls - var e2 = new Context34960.Entity - { - Id = 2, - Reference = null, - Collection = null - }; - - // nested relational nulls - var e3 = new Context34960.Entity - { - Id = 3, - Reference = new Context34960.JsonEntity - { - Name = "ref3", - Number = 3.5f, - NestedReference = null, - NestedCollection = null - }, - - Collection = - [ - new Context34960.JsonEntity - { - Name = "col 3 1", - Number = 32.5f, - NestedReference = null, - NestedCollection = null, - }, - new Context34960.JsonEntity - { - Name = "col 3 2", - Number = 33.5f, - NestedReference = null, - NestedCollection = null, - }, - ], - }; - - ctx.Entities.AddRange(e1, e2, e3); - await ctx.SaveChangesAsync(); - } - - #endregion - - #region ArrayOfPrimitives - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Project_json_array_of_primitives_on_reference(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelArrayOfPrimitives, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedArrayOfPrimitives); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set().OrderBy(x => x.Id) - .Select(x => new { x.Reference.IntArray, x.Reference.ListOfString }); - - var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Equal(2, result.Count); - Assert.Equal(3, result[0].IntArray.Length); - Assert.Equal(3, result[0].ListOfString.Count); - Assert.Equal(3, result[1].IntArray.Length); - Assert.Equal(3, result[1].ListOfString.Count); - } - } - - [ConditionalTheory(Skip = "Issue #32611")] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Project_json_array_of_primitives_on_collection(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelArrayOfPrimitives, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedArrayOfPrimitives); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set().OrderBy(x => x.Id) - .Select(x => new { x.Collection[0].IntArray, x.Collection[1].ListOfString }); - - var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Equal(2, result.Count); - Assert.Equal(3, result[0].IntArray.Length); - Assert.Equal(2, result[0].ListOfString.Count); - Assert.Equal(3, result[1].IntArray.Length); - Assert.Equal(2, result[1].ListOfString.Count); - } - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Project_element_of_json_array_of_primitives(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelArrayOfPrimitives, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedArrayOfPrimitives); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set().OrderBy(x => x.Id).Select( - x => new { ArrayElement = x.Reference.IntArray[0], ListElement = x.Reference.ListOfString[1] }); - - var result = async - ? await query.ToListAsync() - : query.ToList(); - } - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Predicate_based_on_element_of_json_array_of_primitives1(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelArrayOfPrimitives, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedArrayOfPrimitives); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set().Where(x => x.Reference.IntArray[0] == 1); - - var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Equal(1, result.Count); - Assert.Equal(1, result[0].Reference.IntArray[0]); - } - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Predicate_based_on_element_of_json_array_of_primitives2(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelArrayOfPrimitives, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedArrayOfPrimitives); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set().Where(x => x.Reference.ListOfString[1] == "Bar"); - - var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Equal(1, result.Count); - Assert.Equal("Bar", result[0].Reference.ListOfString[1]); - } - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Predicate_based_on_element_of_json_array_of_primitives3(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelArrayOfPrimitives, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedArrayOfPrimitives); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set().Where( - x => x.Reference.IntArray.AsQueryable().ElementAt(0) == 1 - || x.Reference.ListOfString.AsQueryable().ElementAt(1) == "Bar") - .OrderBy(e => e.Id); - - var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Equal(1, result.Count); - Assert.Equal(1, result[0].Reference.IntArray[0]); - Assert.Equal("Bar", result[0].Reference.ListOfString[1]); - } - } - - protected abstract Task SeedArrayOfPrimitives(DbContext ctx); - - protected virtual void BuildModelArrayOfPrimitives(ModelBuilder modelBuilder) - { - modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); - modelBuilder.Entity().OwnsOne( - x => x.Reference, b => b.ToJson().HasColumnType(JsonColumnType)); - - modelBuilder.Entity().OwnsMany( - x => x.Collection, b => b.ToJson().HasColumnType(JsonColumnType)); - } - - public class MyEntityArrayOfPrimitives - { - public int Id { get; set; } - public MyJsonEntityArrayOfPrimitives Reference { get; set; } - public List Collection { get; set; } - } - - public class MyJsonEntityArrayOfPrimitives - { - public int[] IntArray { get; set; } - public List ListOfString { get; set; } - } - - #endregion - - #region JunkInJson - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Junk_in_json_basic_tracking(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelJunkInJson, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedJunkInJson); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set(); - - var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Equal(1, result.Count); - Assert.Equal(2, result[0].Collection.Count); - Assert.Equal(2, result[0].CollectionWithCtor.Count); - Assert.Equal(2, result[0].Reference.NestedCollection.Count); - Assert.NotNull(result[0].Reference.NestedReference); - Assert.Equal(2, result[0].ReferenceWithCtor.NestedCollection.Count); - Assert.NotNull(result[0].ReferenceWithCtor.NestedReference); - } - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Junk_in_json_basic_no_tracking(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelJunkInJson, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedJunkInJson); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set().AsNoTracking(); - - var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Equal(1, result.Count); - Assert.Equal(2, result[0].Collection.Count); - Assert.Equal(2, result[0].CollectionWithCtor.Count); - Assert.Equal(2, result[0].Reference.NestedCollection.Count); - Assert.NotNull(result[0].Reference.NestedReference); - Assert.Equal(2, result[0].ReferenceWithCtor.NestedCollection.Count); - Assert.NotNull(result[0].ReferenceWithCtor.NestedReference); - } - } - - protected abstract Task SeedJunkInJson(DbContext ctx); - - protected virtual void BuildModelJunkInJson(ModelBuilder modelBuilder) - => modelBuilder.Entity( - b => - { - b.ToTable("Entities"); - b.Property(x => x.Id).ValueGeneratedNever(); - - b.OwnsOne( - x => x.Reference, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.OwnsOne(x => x.NestedReference); - b.OwnsMany(x => x.NestedCollection); - }); - - b.OwnsOne( - x => x.ReferenceWithCtor, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.OwnsOne(x => x.NestedReference); - b.OwnsMany(x => x.NestedCollection); - }); - - b.OwnsMany( - x => x.Collection, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.OwnsOne(x => x.NestedReference); - b.OwnsMany(x => x.NestedCollection); - }); - - b.OwnsMany( - x => x.CollectionWithCtor, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.OwnsOne(x => x.NestedReference); - b.OwnsMany(x => x.NestedCollection); - }); - }); - - public class MyEntityJunkInJson - { - public int Id { get; set; } - public MyJsonEntityJunkInJson Reference { get; set; } - public MyJsonEntityJunkInJsonWithCtor ReferenceWithCtor { get; set; } - public List Collection { get; set; } - public List CollectionWithCtor { get; set; } - } - - public class MyJsonEntityJunkInJson - { - public string Name { get; set; } - public double Number { get; set; } - - public MyJsonEntityJunkInJsonNested NestedReference { get; set; } - public List NestedCollection { get; set; } - } - - public class MyJsonEntityJunkInJsonNested - { - public DateTime DoB { get; set; } - } - - public class MyJsonEntityJunkInJsonWithCtor(bool myBool, string name) - { - public bool MyBool { get; set; } = myBool; - public string Name { get; set; } = name; - - public MyJsonEntityJunkInJsonWithCtorNested NestedReference { get; set; } - public List NestedCollection { get; set; } - } - - public class MyJsonEntityJunkInJsonWithCtorNested(DateTime doB) - { - public DateTime DoB { get; set; } = doB; - } - - #endregion - - #region TrickyBuffering - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Tricky_buffering_basic(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelTrickyBuffering, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedTrickyBuffering); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set(); - - var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Equal(1, result.Count); - Assert.Equal("r1", result[0].Reference.Name); - Assert.Equal(7, result[0].Reference.Number); - Assert.Equal(new DateTime(2000, 1, 1), result[0].Reference.NestedReference.DoB); - Assert.Equal(2, result[0].Reference.NestedCollection.Count); - } - } - - protected abstract Task SeedTrickyBuffering(DbContext ctx); - - protected virtual void BuildModelTrickyBuffering(ModelBuilder modelBuilder) - => modelBuilder.Entity( - b => - { - b.ToTable("Entities"); - b.Property(x => x.Id).ValueGeneratedNever(); - b.OwnsOne( - x => x.Reference, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.OwnsOne(x => x.NestedReference); - b.OwnsMany(x => x.NestedCollection); - }); - }); - - public class MyEntityTrickyBuffering - { - public int Id { get; set; } - public MyJsonEntityTrickyBuffering Reference { get; set; } - } - - public class MyJsonEntityTrickyBuffering - { - public string Name { get; set; } - public int Number { get; set; } - public MyJsonEntityJunkInJsonNested NestedReference { get; set; } - public List NestedCollection { get; set; } - } - - public class MyJsonEntityTrickyBufferingNested - { - public DateTime DoB { get; set; } - } - - #endregion - - #region ShadowProperties - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Shadow_properties_basic_tracking(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelShadowProperties, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedShadowProperties); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set(); - - var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Equal(1, result.Count); - Assert.Equal(2, result[0].Collection.Count); - Assert.Equal(2, result[0].CollectionWithCtor.Count); - Assert.NotNull(result[0].Reference); - Assert.NotNull(result[0].ReferenceWithCtor); - - var referenceEntry = context.ChangeTracker.Entries().Single(x => x.Entity == result[0].Reference); - Assert.Equal("Foo", referenceEntry.Property("ShadowString").CurrentValue); - - var referenceCtorEntry = context.ChangeTracker.Entries().Single(x => x.Entity == result[0].ReferenceWithCtor); - Assert.Equal(143, referenceCtorEntry.Property("Shadow_Int").CurrentValue); - - var collectionEntry1 = context.ChangeTracker.Entries().Single(x => x.Entity == result[0].Collection[0]); - var collectionEntry2 = context.ChangeTracker.Entries().Single(x => x.Entity == result[0].Collection[1]); - Assert.Equal(5.5, collectionEntry1.Property("ShadowDouble").CurrentValue); - Assert.Equal(20.5, collectionEntry2.Property("ShadowDouble").CurrentValue); - - var collectionCtorEntry1 = context.ChangeTracker.Entries().Single(x => x.Entity == result[0].CollectionWithCtor[0]); - var collectionCtorEntry2 = context.ChangeTracker.Entries().Single(x => x.Entity == result[0].CollectionWithCtor[1]); - Assert.Equal((byte)6, collectionCtorEntry1.Property("ShadowNullableByte").CurrentValue); - Assert.Null(collectionCtorEntry2.Property("ShadowNullableByte").CurrentValue); - } - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Shadow_properties_basic_no_tracking(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelShadowProperties, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedShadowProperties); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set().AsNoTracking(); - - var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Equal(1, result.Count); - Assert.Equal(2, result[0].Collection.Count); - Assert.Equal(2, result[0].CollectionWithCtor.Count); - Assert.NotNull(result[0].Reference); - Assert.NotNull(result[0].ReferenceWithCtor); - } - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Project_shadow_properties_from_json_entity(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelShadowProperties, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedShadowProperties); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set().Select( - x => new - { - ShadowString = EF.Property(x.Reference, "ShadowString"), - ShadowInt = EF.Property(x.ReferenceWithCtor, "Shadow_Int"), - }); - - var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Equal(1, result.Count); - Assert.Equal("Foo", result[0].ShadowString); - Assert.Equal(143, result[0].ShadowInt); - } - } - - protected abstract Task SeedShadowProperties(DbContext ctx); - - protected virtual void BuildModelShadowProperties(ModelBuilder modelBuilder) - => modelBuilder.Entity( - b => - { - b.ToTable("Entities"); - b.Property(x => x.Id).ValueGeneratedNever(); - - b.OwnsOne( - x => x.Reference, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.Property("ShadowString"); - }); - - b.OwnsOne( - x => x.ReferenceWithCtor, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.Property("Shadow_Int").HasJsonPropertyName("ShadowInt"); - }); - - b.OwnsMany( - x => x.Collection, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.Property("ShadowDouble"); - }); - - b.OwnsMany( - x => x.CollectionWithCtor, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.Property("ShadowNullableByte"); - }); - }); - - public class MyEntityShadowProperties - { - public int Id { get; set; } - public string Name { get; set; } - - public MyJsonEntityShadowProperties Reference { get; set; } - public List Collection { get; set; } - public MyJsonEntityShadowPropertiesWithCtor ReferenceWithCtor { get; set; } - public List CollectionWithCtor { get; set; } - } - - public class MyJsonEntityShadowProperties - { - public string Name { get; set; } - } - - public class MyJsonEntityShadowPropertiesWithCtor(string name) - { - public string Name { get; set; } = name; - } - - #endregion - - #region LazyLoadingProxies - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Project_proxies_entity_with_json(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelLazyLoadingProxies, - seed: SeedLazyLoadingProxies, - onConfiguring: b => - { - b = b.ConfigureWarnings(ConfigureWarnings); - OnConfiguringLazyLoadingProxies(b); - }, - addServices: AddServicesLazyLoadingProxies); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set(); - - var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Equal(2, result.Count); - } - } - - protected void OnConfiguringLazyLoadingProxies(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseLazyLoadingProxies(); - - protected IServiceCollection AddServicesLazyLoadingProxies(IServiceCollection addServices) - => addServices.AddEntityFrameworkProxies(); - - private Task SeedLazyLoadingProxies(DbContext ctx) - { - var r1 = new MyJsonEntityLazyLoadingProxiesWithCtor("r1", 1); - var c11 = new MyJsonEntityLazyLoadingProxies { Name = "c11", Number = 11 }; - var c12 = new MyJsonEntityLazyLoadingProxies { Name = "c12", Number = 12 }; - var c13 = new MyJsonEntityLazyLoadingProxies { Name = "c13", Number = 13 }; - - var r2 = new MyJsonEntityLazyLoadingProxiesWithCtor("r2", 2); - var c21 = new MyJsonEntityLazyLoadingProxies { Name = "c21", Number = 21 }; - var c22 = new MyJsonEntityLazyLoadingProxies { Name = "c22", Number = 22 }; - - var e1 = new MyEntityLazyLoadingProxies - { - Id = 1, - Name = "e1", - Reference = r1, - Collection = - [ - c11, - c12, - c13 - ] - }; - - var e2 = new MyEntityLazyLoadingProxies - { - Id = 2, - Name = "e2", - Reference = r2, - Collection = [c21, c22] - }; - - ctx.Set().AddRange(e1, e2); - return ctx.SaveChangesAsync(); - } - - protected virtual void BuildModelLazyLoadingProxies(ModelBuilder modelBuilder) - { - modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); - modelBuilder.Entity().OwnsOne(x => x.Reference, b => b.ToJson().HasColumnType(JsonColumnType)); - modelBuilder.Entity().OwnsMany(x => x.Collection, b => b.ToJson().HasColumnType(JsonColumnType)); - } - - public class MyEntityLazyLoadingProxies - { - public int Id { get; set; } - public string Name { get; set; } - - public virtual MyJsonEntityLazyLoadingProxiesWithCtor Reference { get; set; } - public virtual List Collection { get; set; } - } - - public class MyJsonEntityLazyLoadingProxiesWithCtor(string name, int number) - { - public string Name { get; set; } = name; - public int Number { get; set; } = number; - } - - public class MyJsonEntityLazyLoadingProxies - { - public string Name { get; set; } - public int Number { get; set; } - } - - #endregion - - #region NotICollection - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Not_ICollection_basic_projection(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelNotICollection, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedNotICollection); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set(); - - var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Equal(2, result.Count); - } - } - - protected abstract Task SeedNotICollection(DbContext ctx); - - public class MyEntityNotICollection - { - public int Id { get; set; } - - public MyJsonEntityNotICollection Json { get; set; } - } - - public class MyJsonEntityNotICollection - { - private readonly List _collection = []; - - public IEnumerable Collection - => _collection.AsReadOnly(); - } - - public class MyJsonNestedEntityNotICollection - { - public string Foo { get; set; } - public int Bar { get; set; } - } - - protected virtual void BuildModelNotICollection(ModelBuilder modelBuilder) - => modelBuilder.Entity( - b => - { - b.ToTable("Entities"); - b.Property(x => x.Id).ValueGeneratedNever(); - b.OwnsOne( - cr => cr.Json, nb => - { - nb.ToJson().HasColumnType(JsonColumnType); - nb.OwnsMany(x => x.Collection); - }); - }); - - #endregion - - #region BadJsonProperties - - [ConditionalFact] - public virtual async Task Bad_json_properties_duplicated_navigations_tracking() - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelBadJsonProperties, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedBadJsonProperties); - - using (var context = contextFactory.CreateContext()) - { - var baseline = await context.Entities.SingleAsync(x => x.Scenario == "baseline"); - var dupNavs = await context.Entities.SingleAsync(x => x.Scenario == "duplicated navigations"); - - // for tracking, first one wins - Assert.Equal(baseline.RequiredReference.NestedOptional.Text, dupNavs.RequiredReference.NestedOptional.Text); - Assert.Equal(baseline.RequiredReference.NestedRequired.Text, dupNavs.RequiredReference.NestedRequired.Text); - Assert.Equal(baseline.RequiredReference.NestedCollection[0].Text, dupNavs.RequiredReference.NestedCollection[0].Text); - Assert.Equal(baseline.RequiredReference.NestedCollection[1].Text, dupNavs.RequiredReference.NestedCollection[1].Text); - - Assert.Equal(baseline.OptionalReference.NestedOptional.Text, dupNavs.OptionalReference.NestedOptional.Text); - Assert.Equal(baseline.OptionalReference.NestedRequired.Text, dupNavs.OptionalReference.NestedRequired.Text); - Assert.Equal(baseline.OptionalReference.NestedCollection[0].Text, dupNavs.OptionalReference.NestedCollection[0].Text); - Assert.Equal(baseline.OptionalReference.NestedCollection[1].Text, dupNavs.OptionalReference.NestedCollection[1].Text); - - Assert.Equal(baseline.Collection[0].NestedOptional.Text, dupNavs.Collection[0].NestedOptional.Text); - Assert.Equal(baseline.Collection[0].NestedRequired.Text, dupNavs.Collection[0].NestedRequired.Text); - Assert.Equal(baseline.Collection[0].NestedCollection[0].Text, dupNavs.Collection[0].NestedCollection[0].Text); - Assert.Equal(baseline.Collection[0].NestedCollection[1].Text, dupNavs.Collection[0].NestedCollection[1].Text); - - Assert.Equal(baseline.Collection[1].NestedOptional.Text, dupNavs.Collection[1].NestedOptional.Text); - Assert.Equal(baseline.Collection[1].NestedRequired.Text, dupNavs.Collection[1].NestedRequired.Text); - Assert.Equal(baseline.Collection[1].NestedCollection[0].Text, dupNavs.Collection[1].NestedCollection[0].Text); - Assert.Equal(baseline.Collection[1].NestedCollection[1].Text, dupNavs.Collection[1].NestedCollection[1].Text); - } - } - - [ConditionalFact] - public virtual async Task Bad_json_properties_duplicated_navigations_no_tracking() - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelBadJsonProperties, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedBadJsonProperties); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Entities.AsNoTracking(); - - var baseline = query.Single(x => x.Scenario == "baseline"); - var dupNavs = query.Single(x => x.Scenario == "duplicated navigations"); - - // for no tracking, last one wins - Assert.Equal(baseline.RequiredReference.NestedOptional.Text + " dupnav", dupNavs.RequiredReference.NestedOptional.Text); - Assert.Equal(baseline.RequiredReference.NestedRequired.Text + " dupnav", dupNavs.RequiredReference.NestedRequired.Text); - Assert.Equal(baseline.RequiredReference.NestedCollection[0].Text + " dupnav", dupNavs.RequiredReference.NestedCollection[0].Text); - Assert.Equal(baseline.RequiredReference.NestedCollection[1].Text + " dupnav", dupNavs.RequiredReference.NestedCollection[1].Text); - - Assert.Equal(baseline.OptionalReference.NestedOptional.Text + " dupnav", dupNavs.OptionalReference.NestedOptional.Text); - Assert.Equal(baseline.OptionalReference.NestedRequired.Text + " dupnav", dupNavs.OptionalReference.NestedRequired.Text); - Assert.Equal(baseline.OptionalReference.NestedCollection[0].Text + " dupnav", dupNavs.OptionalReference.NestedCollection[0].Text); - Assert.Equal(baseline.OptionalReference.NestedCollection[1].Text + " dupnav", dupNavs.OptionalReference.NestedCollection[1].Text); - - Assert.Equal(baseline.Collection[0].NestedOptional.Text + " dupnav", dupNavs.Collection[0].NestedOptional.Text); - Assert.Equal(baseline.Collection[0].NestedRequired.Text + " dupnav", dupNavs.Collection[0].NestedRequired.Text); - Assert.Equal(baseline.Collection[0].NestedCollection[0].Text + " dupnav", dupNavs.Collection[0].NestedCollection[0].Text); - Assert.Equal(baseline.Collection[0].NestedCollection[1].Text + " dupnav", dupNavs.Collection[0].NestedCollection[1].Text); - - Assert.Equal(baseline.Collection[1].NestedOptional.Text + " dupnav", dupNavs.Collection[1].NestedOptional.Text); - Assert.Equal(baseline.Collection[1].NestedRequired.Text + " dupnav", dupNavs.Collection[1].NestedRequired.Text); - Assert.Equal(baseline.Collection[1].NestedCollection[0].Text + " dupnav", dupNavs.Collection[1].NestedCollection[0].Text); - Assert.Equal(baseline.Collection[1].NestedCollection[1].Text + " dupnav", dupNavs.Collection[1].NestedCollection[1].Text); - } - } - - [ConditionalTheory] - [InlineData(true)] - [InlineData(false)] - public virtual async Task Bad_json_properties_duplicated_scalars(bool noTracking) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelBadJsonProperties, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedBadJsonProperties); - - using (var context = contextFactory.CreateContext()) - { - var query = noTracking ? context.Entities.AsNoTracking() : context.Entities; - - var baseline = await query.SingleAsync(x => x.Scenario == "baseline"); - var dupProps = await query.SingleAsync(x => x.Scenario == "duplicated scalars"); - - Assert.Equal(baseline.RequiredReference.NestedOptional.Text + " dupprop", dupProps.RequiredReference.NestedOptional.Text); - Assert.Equal(baseline.RequiredReference.NestedRequired.Text + " dupprop", dupProps.RequiredReference.NestedRequired.Text); - Assert.Equal(baseline.RequiredReference.NestedCollection[0].Text + " dupprop", dupProps.RequiredReference.NestedCollection[0].Text); - Assert.Equal(baseline.RequiredReference.NestedCollection[1].Text + " dupprop", dupProps.RequiredReference.NestedCollection[1].Text); - - Assert.Equal(baseline.OptionalReference.NestedOptional.Text + " dupprop", dupProps.OptionalReference.NestedOptional.Text); - Assert.Equal(baseline.OptionalReference.NestedRequired.Text + " dupprop", dupProps.OptionalReference.NestedRequired.Text); - Assert.Equal(baseline.OptionalReference.NestedCollection[0].Text + " dupprop", dupProps.OptionalReference.NestedCollection[0].Text); - Assert.Equal(baseline.OptionalReference.NestedCollection[1].Text + " dupprop", dupProps.OptionalReference.NestedCollection[1].Text); - - Assert.Equal(baseline.Collection[0].NestedOptional.Text + " dupprop", dupProps.Collection[0].NestedOptional.Text); - Assert.Equal(baseline.Collection[0].NestedRequired.Text + " dupprop", dupProps.Collection[0].NestedRequired.Text); - Assert.Equal(baseline.Collection[0].NestedCollection[0].Text + " dupprop", dupProps.Collection[0].NestedCollection[0].Text); - Assert.Equal(baseline.Collection[0].NestedCollection[1].Text + " dupprop", dupProps.Collection[0].NestedCollection[1].Text); - - Assert.Equal(baseline.Collection[1].NestedOptional.Text + " dupprop", dupProps.Collection[1].NestedOptional.Text); - Assert.Equal(baseline.Collection[1].NestedRequired.Text + " dupprop", dupProps.Collection[1].NestedRequired.Text); - Assert.Equal(baseline.Collection[1].NestedCollection[0].Text + " dupprop", dupProps.Collection[1].NestedCollection[0].Text); - Assert.Equal(baseline.Collection[1].NestedCollection[1].Text + " dupprop", dupProps.Collection[1].NestedCollection[1].Text); - } - } - - [ConditionalTheory] - [InlineData(true)] - [InlineData(false)] - public virtual async Task Bad_json_properties_empty_navigations(bool noTracking) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelBadJsonProperties, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedBadJsonProperties); - - using (var context = contextFactory.CreateContext()) - { - var query = noTracking ? context.Entities.AsNoTracking() : context.Entities; - var emptyNavs = await query.SingleAsync(x => x.Scenario == "empty navigation property names"); - - Assert.Null(emptyNavs.RequiredReference.NestedOptional); - Assert.Null(emptyNavs.RequiredReference.NestedRequired); - Assert.Null(emptyNavs.RequiredReference.NestedCollection); - - Assert.Null(emptyNavs.OptionalReference.NestedOptional); - Assert.Null(emptyNavs.OptionalReference.NestedRequired); - Assert.Null(emptyNavs.OptionalReference.NestedCollection); - - Assert.Null(emptyNavs.Collection[0].NestedOptional); - Assert.Null(emptyNavs.Collection[0].NestedRequired); - Assert.Null(emptyNavs.Collection[0].NestedCollection); - - Assert.Null(emptyNavs.Collection[1].NestedOptional); - Assert.Null(emptyNavs.Collection[1].NestedRequired); - Assert.Null(emptyNavs.Collection[1].NestedCollection); - } - } - - [ConditionalTheory] - [InlineData(true)] - [InlineData(false)] - public virtual async Task Bad_json_properties_empty_scalars(bool noTracking) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelBadJsonProperties, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedBadJsonProperties); - - using (var context = contextFactory.CreateContext()) - { - var query = noTracking ? context.Entities.AsNoTracking() : context.Entities; - var emptyNavs = await query.SingleAsync(x => x.Scenario == "empty scalar property names"); - - Assert.Null(emptyNavs.RequiredReference.NestedOptional.Text); - Assert.Null(emptyNavs.RequiredReference.NestedRequired.Text); - Assert.Null(emptyNavs.RequiredReference.NestedCollection[0].Text); - Assert.Null(emptyNavs.RequiredReference.NestedCollection[1].Text); - - Assert.Null(emptyNavs.OptionalReference.NestedOptional.Text); - Assert.Null(emptyNavs.OptionalReference.NestedRequired.Text); - Assert.Null(emptyNavs.OptionalReference.NestedCollection[0].Text); - Assert.Null(emptyNavs.OptionalReference.NestedCollection[1].Text); - - Assert.Null(emptyNavs.Collection[0].NestedOptional.Text); - Assert.Null(emptyNavs.Collection[0].NestedRequired.Text); - Assert.Null(emptyNavs.Collection[0].NestedCollection[0].Text); - Assert.Null(emptyNavs.Collection[0].NestedCollection[1].Text); - - Assert.Null(emptyNavs.Collection[1].NestedOptional.Text); - Assert.Null(emptyNavs.Collection[1].NestedRequired.Text); - Assert.Null(emptyNavs.Collection[1].NestedCollection[0].Text); - Assert.Null(emptyNavs.Collection[1].NestedCollection[1].Text); - } - } - - [ConditionalTheory] - [InlineData(true)] - [InlineData(false)] - public virtual async Task Bad_json_properties_null_navigations(bool noTracking) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelBadJsonProperties, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedBadJsonProperties); - - using (var context = contextFactory.CreateContext()) - { - var query = noTracking ? context.Entities.AsNoTracking() : context.Entities; - - await Assert.ThrowsAnyAsync( - () => query.SingleAsync(x => x.Scenario == "null navigation property names")); - } - } - - [ConditionalTheory] - [InlineData(true)] - [InlineData(false)] - public virtual async Task Bad_json_properties_null_scalars(bool noTracking) - { - var contextFactory = await InitializeAsync( - onModelCreating: BuildModelBadJsonProperties, - onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), - seed: SeedBadJsonProperties); - - using (var context = contextFactory.CreateContext()) - { - var query = noTracking ? context.Entities.AsNoTracking() : context.Entities; - - var message = (await Assert.ThrowsAnyAsync( - () => query.SingleAsync(x => x.Scenario == "null scalar property names"))).Message; - - Assert.StartsWith("'n' is an invalid start of a property name. Expected a '\"'.", message); - } - } - - protected class ContextBadJsonProperties(DbContextOptions options) : DbContext(options) - { - public DbSet Entities { get; set; } - - public class Entity - { - public int Id { get; set; } - public string Scenario { get; set; } - public JsonRoot OptionalReference { get; set; } - public JsonRoot RequiredReference { get; set; } - public List Collection { get; set; } - } - - public class JsonRoot - { - public JsonBranch NestedRequired { get; set; } - public JsonBranch NestedOptional { get; set; } - public List NestedCollection { get; set; } - } - - public class JsonBranch - { - public string Text { get; set; } - } - } - - protected abstract Task SeedBadJsonProperties(ContextBadJsonProperties ctx); - - protected virtual void BuildModelBadJsonProperties(ModelBuilder modelBuilder) - => modelBuilder.Entity( - b => - { - b.ToTable("Entities"); - b.Property(x => x.Id).ValueGeneratedNever(); - - b.OwnsOne( - x => x.RequiredReference, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.OwnsOne(x => x.NestedOptional); - b.OwnsOne(x => x.NestedRequired); - b.OwnsMany(x => x.NestedCollection); - }); - - b.OwnsOne( - x => x.OptionalReference, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.OwnsOne(x => x.NestedOptional); - b.OwnsOne(x => x.NestedRequired); - b.OwnsMany(x => x.NestedCollection); - }); - - b.OwnsMany( - x => x.Collection, b => - { - b.ToJson().HasColumnType(JsonColumnType); - b.OwnsOne(x => x.NestedOptional); - b.OwnsOne(x => x.NestedRequired); - b.OwnsMany(x => x.NestedCollection); - }); - }); - - #endregion - - protected TestSqlLoggerFactory TestSqlLoggerFactory - => (TestSqlLoggerFactory)ListLoggerFactory; - - protected virtual string JsonColumnType - => null; -} diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocManyToManyQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocManyToManyQueryRelationalTestBase.cs index 319ddcf5caa..fd6b5c64fd4 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocManyToManyQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocManyToManyQueryRelationalTestBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public abstract class AdHocManyToManyQueryRelationalTestBase : AdHocManyToManyQueryTestBase +public abstract class AdHocManyToManyQueryRelationalTestBase(NonSharedFixture fixture) : AdHocManyToManyQueryTestBase(fixture) { protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocMiscellaneousQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocMiscellaneousQueryRelationalTestBase.cs index 6f58c2da809..65ed5fd090c 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocMiscellaneousQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocMiscellaneousQueryRelationalTestBase.cs @@ -4,11 +4,12 @@ #nullable disable using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json; using NameSpace1; namespace Microsoft.EntityFrameworkCore.Query { - public abstract class AdHocMiscellaneousQueryRelationalTestBase : AdHocMiscellaneousQueryTestBase + public abstract class AdHocMiscellaneousQueryRelationalTestBase(NonSharedFixture fixture) : AdHocMiscellaneousQueryTestBase(fixture) { protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; @@ -238,6 +239,83 @@ public static DateTime Modify(DateTime date) } #endregion + + #region 34752 + + [ConditionalFact] + public virtual async Task Mapping_JsonElement_property_throws_a_meaningful_exception() + { + var message = (await Assert.ThrowsAsync( + () => InitializeAsync())).Message; + + Assert.Equal( + CoreStrings.PropertyNotAdded(nameof(Context34752.Entity), nameof(Context34752.Entity.Json), nameof(JsonElement)), + message); + } + + protected class Context34752(DbContextOptions options) : DbContext(options) + { + public DbSet Entities { get; set; } + + public class Entity + { + public int Id { get; set; } + public JsonElement Json { get; set; } + } + } + + #endregion + + #region Inlined redacting + + protected abstract DbContextOptionsBuilder SetTranslateParameterizedCollectionsToConstants(DbContextOptionsBuilder optionsBuilder); + + [ConditionalTheory] + [MemberData(nameof(InlinedRedactingData))] + public virtual async Task Check_inlined_constants_redacting(bool async, bool enableSensitiveDataLogging) + { + var contextFactory = await InitializeAsync( + onConfiguring: o => + { + SetTranslateParameterizedCollectionsToConstants(o); + o.EnableSensitiveDataLogging(enableSensitiveDataLogging); + }); + using var context = contextFactory.CreateContext(); + + var id = 1; + var ids = new[] { id, 2, 3 }; + var query1 = context.TestEntities.Where(x => ids.Contains(x.Id)); + var query2 = context.TestEntities.Where(x => ids.Where(y => y == x.Id).Any()); + var query3 = context.TestEntities.Where(x => EF.Constant(id) == x.Id); + + if (async) + { + await query1.ToListAsync(); + await query2.ToListAsync(); + await query3.ToListAsync(); + } + else + { + query1.ToList(); + query2.ToList(); + query3.ToList(); + } + } + + protected class InlinedRedactingContext(DbContextOptions options) : DbContext(options) + { + public DbSet TestEntities { get; set; } + + public class TestEntity + { + public int Id { get; set; } + public string Name { get; set; } + } + } + + public static readonly IEnumerable InlinedRedactingData = [[true, true], [true, false], [false, true], [false, false]]; + + #endregion } } diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocNavigationsQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocNavigationsQueryRelationalTestBase.cs index d4da75680e7..f4d630e189f 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocNavigationsQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocNavigationsQueryRelationalTestBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public abstract class AdHocNavigationsQueryRelationalTestBase : AdHocNavigationsQueryTestBase +public abstract class AdHocNavigationsQueryRelationalTestBase(NonSharedFixture fixture) : AdHocNavigationsQueryTestBase(fixture) { protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocPrecompiledQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocPrecompiledQueryRelationalTestBase.cs index 9b0cd2d0269..8fe76436dd7 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocPrecompiledQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocPrecompiledQueryRelationalTestBase.cs @@ -8,8 +8,14 @@ namespace Microsoft.EntityFrameworkCore.Query; [Collection("PrecompiledQuery")] -public abstract class AdHocPrecompiledQueryRelationalTestBase(ITestOutputHelper testOutputHelper) : NonSharedModelTestBase +public abstract class AdHocPrecompiledQueryRelationalTestBase: NonSharedModelTestBase, IClassFixture { + public AdHocPrecompiledQueryRelationalTestBase(NonSharedFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + TestOutputHelper = testOutputHelper; + } + [ConditionalFact] public virtual async Task Index_no_evaluatability() { @@ -352,7 +358,7 @@ protected virtual Task Test( Action>? precompilationErrorAsserter = null, [CallerMemberName] string callerName = "") => PrecompiledQueryTestHelpers.Test( - sourceCode, dbContextOptions, dbContextType, interceptorCodeAsserter, precompilationErrorAsserter, testOutputHelper, + sourceCode, dbContextOptions, dbContextType, interceptorCodeAsserter, precompilationErrorAsserter, TestOutputHelper!, AlwaysPrintGeneratedSources, callerName); diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocQueryFiltersQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocQueryFiltersQueryRelationalTestBase.cs index 97fae596075..2e3389bee75 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocQueryFiltersQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocQueryFiltersQueryRelationalTestBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public abstract class AdHocQueryFiltersQueryRelationalTestBase : AdHocQueryFiltersQueryTestBase +public abstract class AdHocQueryFiltersQueryRelationalTestBase(NonSharedFixture fixture) : AdHocQueryFiltersQueryTestBase(fixture) { protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocQuerySplittingQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocQuerySplittingQueryTestBase.cs index 94e3b92137e..4798a07b4f4 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocQuerySplittingQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocQuerySplittingQueryTestBase.cs @@ -8,7 +8,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public abstract class AdHocQuerySplittingQueryTestBase : NonSharedModelTestBase +public abstract class AdHocQuerySplittingQueryTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { protected override string StoreName => "AdHocQuerySplittingQueryTests"; @@ -218,8 +218,9 @@ void Query(Context25225 context, Guid parentId, Guid collectionId) private async Task<(Context25225, Context25225)> CreateTwoContext25225() { - var context1 = (await CreateContext25225Async()).CreateContext(); - var context2 = (await CreateContext25225Async()).CreateContext(); + var factory = await CreateContext25225Async(); + var context1 = factory.CreateContext(); + var context2 = factory.CreateContext(); // Can't run in parallel with the same connection instance. Issue #22921 Assert.NotSame(context1.Database.GetDbConnection(), context2.Database.GetDbConnection()); @@ -231,10 +232,14 @@ private async Task> CreateContext25225Async() => await InitializeAsync( seed: c => c.SeedAsync(), onConfiguring: o => SetQuerySplittingBehavior(o, QuerySplittingBehavior.SplitQuery), - createTestStore: () => CreateTestStore25225()); + createTestStore: CreateTestStore25225); - protected virtual Task CreateTestStore25225() - => Task.FromResult(base.CreateTestStore()); + protected virtual TestStore CreateTestStore25225() + { + var testStore = (RelationalTestStore)base.CreateTestStore(); + testStore.UseConnectionString = true; + return testStore; + } private static IQueryable SelectParent25225(Context25225 context, Guid parentId) => context @@ -379,7 +384,7 @@ public virtual async Task NoTrackingWithIdentityResolution_split_query_basic(boo { blog.Id, Posts = blog.Posts.Select( - blogPost => new + blogPost => new { blogPost.Id, blogPost.Author diff --git a/test/EFCore.Relational.Specification.Tests/Query/EntitySplittingQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/EntitySplittingQueryTestBase.cs index 19bfbbb4a6d..6524ad223dc 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/EntitySplittingQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/EntitySplittingQueryTestBase.cs @@ -8,9 +8,10 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public abstract class EntitySplittingQueryTestBase : NonSharedModelTestBase +public abstract class EntitySplittingQueryTestBase: NonSharedModelTestBase, IClassFixture { - protected EntitySplittingQueryTestBase() + protected EntitySplittingQueryTestBase(NonSharedFixture fixture) + : base(fixture) => _setSourceCreator = GetSetSourceCreator(); [ConditionalTheory] diff --git a/test/EFCore.Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs index e08f86ad249..91595f5bfc7 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public abstract class NonSharedPrimitiveCollectionsQueryRelationalTestBase : NonSharedPrimitiveCollectionsQueryTestBase +public abstract class NonSharedPrimitiveCollectionsQueryRelationalTestBase(NonSharedFixture fixture) : NonSharedPrimitiveCollectionsQueryTestBase(fixture) { // On relational databases, byte[] gets mapped to a special binary data type, which isn't queryable as a regular primitive collection. [ConditionalFact] diff --git a/test/EFCore.Relational.Specification.Tests/Query/NorthwindSqlQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NorthwindSqlQueryTestBase.cs index 986a42bf980..eed4cacf773 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/NorthwindSqlQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/NorthwindSqlQueryTestBase.cs @@ -22,7 +22,7 @@ protected NorthwindSqlQueryTestBase(TFixture fixture) protected TFixture Fixture { get; } - public static IEnumerable IsAsyncData = new object[][] { [false], [true] }; + public static readonly IEnumerable IsAsyncData = [[false], [true]]; [ConditionalTheory] [MemberData(nameof(IsAsyncData))] diff --git a/test/EFCore.Relational.Specification.Tests/Query/OperatorsProceduralQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/OperatorsProceduralQueryTestBase.cs index f44b4ec2392..4425bfe4796 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/OperatorsProceduralQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/OperatorsProceduralQueryTestBase.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public abstract class OperatorsProceduralQueryTestBase : NonSharedModelTestBase +public abstract class OperatorsProceduralQueryTestBase : NonSharedModelTestBase, IClassFixture { private static readonly MethodInfo LikeMethodInfo = typeof(DbFunctionsExtensions).GetRuntimeMethod( @@ -27,7 +27,8 @@ private static readonly MethodInfo StringConcatMethodInfo protected ExpectedQueryRewritingVisitor ExpectedQueryRewriter { get; init; } - protected OperatorsProceduralQueryTestBase() + protected OperatorsProceduralQueryTestBase(NonSharedFixture fixture) + : base(fixture) { Binaries = [ diff --git a/test/EFCore.Relational.Specification.Tests/Query/OperatorsQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/OperatorsQueryTestBase.cs index 2993c608d65..456d25de41c 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/OperatorsQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/OperatorsQueryTestBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; -public abstract class OperatorsQueryTestBase : NonSharedModelTestBase +public abstract class OperatorsQueryTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { protected OperatorsData ExpectedData { get; init; } = OperatorsData.Instance; diff --git a/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs index bfee9bfd966..5e755de1f2c 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public abstract class OwnedEntityQueryRelationalTestBase : OwnedEntityQueryTestBase +public abstract class OwnedEntityQueryRelationalTestBase(NonSharedFixture fixture) : OwnedEntityQueryTestBase(fixture) { protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.Relational.Specification.Tests/Query/PrecompiledQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/PrecompiledQueryRelationalTestBase.cs index defb75d7297..de7f7d8cf4e 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/PrecompiledQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/PrecompiledQueryRelationalTestBase.cs @@ -1326,5 +1326,5 @@ public class Post public Blog? Blog { get; set; } } - public static IEnumerable IsAsyncData = new object[][] { [false], [true] }; + public static readonly IEnumerable IsAsyncData = [[false], [true]]; } diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexRelationshipsQueryRelationalFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexRelationsjipsRelationalFixtureBase.cs similarity index 86% rename from test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexRelationshipsQueryRelationalFixtureBase.cs rename to test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexRelationsjipsRelationalFixtureBase.cs index 99e81317959..0989a7ee3d9 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexRelationshipsQueryRelationalFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexRelationsjipsRelationalFixtureBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public abstract class ComplexRelationshipsQueryRelationalFixtureBase : ComplexRelationshipsQueryFixtureBase +public abstract class ComplexRelationsjipsRelationalFixtureBase : ComplexRelationshipsFixtureBase { protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionNoTrackingQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionNoTrackingQueryRelationalTestBase.cs deleted file mode 100644 index fa56634fb11..00000000000 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionNoTrackingQueryRelationalTestBase.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public abstract class ComplexRelationshipsInProjectionNoTrackingQueryRelationalTestBase(TFixture fixture) - : ComplexRelationshipsInProjectionNoTrackingQueryTestBase(fixture) - where TFixture : ComplexRelationshipsQueryRelationalFixtureBase, new() -{ -} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionQueryRelationalTestBase.cs deleted file mode 100644 index fd873625935..00000000000 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionQueryRelationalTestBase.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public abstract class ComplexRelationshipsInProjectionQueryRelationalTestBase(TFixture fixture) - : ComplexRelationshipsInProjectionQueryTestBase(fixture) - where TFixture : ComplexRelationshipsQueryRelationalFixtureBase, new() -{ -} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/EntityRelationshipsInProjectionNoTrackingQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/EntityRelationshipsInProjectionNoTrackingQueryRelationalTestBase.cs deleted file mode 100644 index dc74cb8d5a3..00000000000 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/EntityRelationshipsInProjectionNoTrackingQueryRelationalTestBase.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public abstract class EntityRelationshipsInProjectionNoTrackingQueryRelationalTestBase(TFixture fixture) - : EntityRelationshipsInProjectionNoTrackingQueryTestBase(fixture) - where TFixture : EntityRelationshipsQueryRelationalFixtureBase, new() -{ -} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/EntityRelationshipsInProjectionQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/EntityRelationshipsInProjectionQueryRelationalTestBase.cs deleted file mode 100644 index 7641634a224..00000000000 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/EntityRelationshipsInProjectionQueryRelationalTestBase.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public abstract class EntityRelationshipsInProjectionQueryRelationalTestBase(TFixture fixture) - : EntityRelationshipsInProjectionQueryTestBase(fixture) - where TFixture : EntityRelationshipsQueryRelationalFixtureBase, new() -{ -} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/JsonRelationshipsInProjectionNoTrackingQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/JsonRelationshipsInProjectionNoTrackingQueryRelationalTestBase.cs deleted file mode 100644 index df73aae676a..00000000000 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/JsonRelationshipsInProjectionNoTrackingQueryRelationalTestBase.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public abstract class JsonRelationshipsInProjectionNoTrackingQueryRelationalTestBase(TFixture fixture) - : JsonRelationshipsInProjectionNoTrackingQueryTestBase(fixture) - where TFixture : JsonRelationshipsQueryRelationalFixtureBase, new() -{ -} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/JsonRelationshipsInProjectionQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/JsonRelationshipsInProjectionQueryRelationalTestBase.cs deleted file mode 100644 index 0c2a03171de..00000000000 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/JsonRelationshipsInProjectionQueryRelationalTestBase.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public abstract class JsonRelationshipsInProjectionQueryRelationalTestBase(TFixture fixture) - : JsonRelationshipsInProjectionQueryTestBase(fixture) - where TFixture : JsonRelationshipsQueryRelationalFixtureBase, new() -{ -} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionNoTrackingQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionNoTrackingQueryRelationalTestBase.cs deleted file mode 100644 index e04e947d6a5..00000000000 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionNoTrackingQueryRelationalTestBase.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public abstract class OwnedRelationshipsInProjectionNoTrackingQueryRelationalTestBase(TFixture fixture) - : OwnedRelationshipsInProjectionNoTrackingQueryTestBase(fixture) - where TFixture : OwnedRelationshipsQueryRelationalFixtureBase, new() -{ -} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionQueryRelationalTestBase.cs deleted file mode 100644 index 4205cfe94c8..00000000000 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionQueryRelationalTestBase.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public abstract class OwnedRelationshipsInProjectionQueryRelationalTestBase(TFixture fixture) - : OwnedRelationshipsInProjectionQueryTestBase(fixture) - where TFixture : OwnedRelationshipsQueryRelationalFixtureBase, new() -{ -} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Include/EntityRelationshipsIncludeQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Include/EntityRelationshipsIncludeQueryRelationalTestBase.cs deleted file mode 100644 index 234c0b13998..00000000000 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Include/EntityRelationshipsIncludeQueryRelationalTestBase.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.Include; - -public abstract class EntityRelationshipsIncludeQueryRelationalTestBase(TFixture fixture) - : EntityRelationshipsIncludeQueryTestBase(fixture) - where TFixture : EntityRelationshipsQueryRelationalFixtureBase, new() -{ -} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Include/EntityRelationshipsIncludeQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Include/NavigationIncludeRelationalTestBase.cs similarity index 50% rename from test/EFCore.Specification.Tests/Query/Relationships/Include/EntityRelationshipsIncludeQueryTestBase.cs rename to test/EFCore.Relational.Specification.Tests/Query/Relationships/Include/NavigationIncludeRelationalTestBase.cs index c972a8de155..cdc6c90023e 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/Include/EntityRelationshipsIncludeQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Include/NavigationIncludeRelationalTestBase.cs @@ -3,8 +3,8 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships.Include; -public class EntityRelationshipsIncludeQueryTestBase(TFixture fixture) - : RelationshipsIncludeQueryTestBase(fixture) - where TFixture : EntityRelationshipsQueryFixtureBase, new() +public abstract class NavigationIncludeRelationalTestBase(TFixture fixture) + : NavigationIncludeTestBase(fixture) + where TFixture : NavigationRelationshipsRelationalFixtureBase, new() { } diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/EntityRelationshipsQueryRelationalFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/NavigationRelationshipsRelationalFixtureBase.cs similarity index 87% rename from test/EFCore.Relational.Specification.Tests/Query/Relationships/EntityRelationshipsQueryRelationalFixtureBase.cs rename to test/EFCore.Relational.Specification.Tests/Query/Relationships/NavigationRelationshipsRelationalFixtureBase.cs index 6c9ec38fa8d..7434ec7e327 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/EntityRelationshipsQueryRelationalFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/NavigationRelationshipsRelationalFixtureBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public abstract class EntityRelationshipsQueryRelationalFixtureBase : EntityRelationshipsQueryFixtureBase +public abstract class NavigationRelationshipsRelationalFixtureBase : NavigationRelationshipsFixtureBase { protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/JsonRelationshipsQueryRelationalFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedJsonRelationshipsRelationalFixtureBase.cs similarity index 88% rename from test/EFCore.Relational.Specification.Tests/Query/Relationships/JsonRelationshipsQueryRelationalFixtureBase.cs rename to test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedJsonRelationshipsRelationalFixtureBase.cs index a3d4bf2ec1f..f7cd592206e 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/JsonRelationshipsQueryRelationalFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedJsonRelationshipsRelationalFixtureBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public abstract class JsonRelationshipsQueryRelationalFixtureBase : JsonRelationshipsQueryFixtureBase +public abstract class OwnedJsonRelationshipsRelationalFixtureBase : OwnedJsonRelationshipsFixtureBase { protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedRelationshipsQueryRelationalFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedRelationshipsRelationalFixtureBase.cs similarity index 97% rename from test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedRelationshipsQueryRelationalFixtureBase.cs rename to test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedRelationshipsRelationalFixtureBase.cs index eca4dc93f0b..7fb0489dbe3 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedRelationshipsQueryRelationalFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedRelationshipsRelationalFixtureBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public abstract class OwnedRelationshipsQueryRelationalFixtureBase : OwnedRelationshipsQueryFixtureBase +public abstract class OwnedRelationshipsRelationalFixtureBase : OwnedRelationshipsFixtureBase { protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedTableSplittingRelationshipsRelationalFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedTableSplittingRelationshipsRelationalFixtureBase.cs new file mode 100644 index 00000000000..a2ea243dab1 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedTableSplittingRelationshipsRelationalFixtureBase.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestModels.RelationshipsModel; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships; + +public abstract class OwnedTableSplittingRelationshipsRelationalFixtureBase : OwnedRelationshipsRelationalFixtureBase +{ + protected override string StoreName => "OwnedTableSplittingRelationshipsQueryTest"; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + base.OnModelCreating(modelBuilder, context); + + modelBuilder.Entity() + .OwnsOne(x => x.OptionalReferenceTrunk, b => + { + b.ToTable("Root_OptionalReferenceTrunk"); + b.OwnsOne(x => x.OptionalReferenceBranch, bb => + { + bb.ToTable("Root_OptionalReferenceTrunk_OptionalReferenceBranch"); + bb.OwnsOne(x => x.RequiredReferenceLeaf).ToTable("Root_OptionalReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf"); + bb.OwnsOne(x => x.OptionalReferenceLeaf).ToTable("Root_OptionalReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf"); + }); + + b.OwnsOne(x => x.RequiredReferenceBranch, bb => + { + bb.ToTable("Root_OptionalReferenceTrunk_RequiredReferenceBranch"); + bb.OwnsOne(x => x.RequiredReferenceLeaf).ToTable("Root_OptionalReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf"); + bb.OwnsOne(x => x.OptionalReferenceLeaf).ToTable("Root_OptionalReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf"); + }); + + b.OwnsMany(x => x.CollectionBranch, bb => + { + bb.OwnsOne(x => x.RequiredReferenceLeaf).ToTable("Root_OptionalReferenceTrunk_CollectionBranch_RequiredReferenceLeaf"); + bb.OwnsOne(x => x.OptionalReferenceLeaf).ToTable("Root_OptionalReferenceTrunk_CollectionBranch_OptionalReferenceLeaf"); + }); + }); + + modelBuilder.Entity() + .OwnsOne(x => x.RequiredReferenceTrunk, b => + { + b.ToTable("Root_RequiredReferenceTrunk"); + b.OwnsOne(x => x.OptionalReferenceBranch, bb => + { + bb.ToTable("Root_RequiredReferenceTrunk_OptionalReferenceBranch"); + bb.OwnsOne(x => x.RequiredReferenceLeaf).ToTable("Root_RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf"); + bb.OwnsOne(x => x.OptionalReferenceLeaf).ToTable("Root_RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf"); + }); + + b.OwnsOne(x => x.RequiredReferenceBranch, bb => + { + bb.ToTable("Root_RequiredReferenceTrunk_RequiredReferenceBranch"); + bb.OwnsOne(x => x.RequiredReferenceLeaf).ToTable("Root_RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf"); + bb.OwnsOne(x => x.OptionalReferenceLeaf).ToTable("Root_RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf"); + }); + + b.OwnsMany(x => x.CollectionBranch, bb => + { + bb.OwnsOne(x => x.RequiredReferenceLeaf).ToTable("Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf"); + bb.OwnsOne(x => x.OptionalReferenceLeaf).ToTable("Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf"); + }); + }); + + modelBuilder.Entity() + .OwnsMany(x => x.CollectionTrunk, b => + { + b.OwnsOne(x => x.OptionalReferenceBranch, bb => + { + bb.ToTable("Root_CollectionTrunk_OptionalReferenceBranch"); + bb.OwnsOne(x => x.RequiredReferenceLeaf).ToTable("Root_CollectionTrunk_OptionalReferenceBranch_RequiredReferenceLeaf"); + bb.OwnsOne(x => x.OptionalReferenceLeaf).ToTable("Root_CollectionTrunk_OptionalReferenceBranch_OptionalReferenceLeaf"); + }); + + b.OwnsOne(x => x.RequiredReferenceBranch, bb => + { + bb.ToTable("Root_CollectionTrunk_RequiredReferenceBranch"); + bb.OwnsOne(x => x.RequiredReferenceLeaf).ToTable("Root_CollectionTrunk_RequiredReferenceBranch_RequiredReferenceLeaf"); + bb.OwnsOne(x => x.OptionalReferenceLeaf).ToTable("Root_CollectionTrunk_RequiredReferenceBranch_OptionalReferenceLeaf"); + }); + + b.OwnsMany(x => x.CollectionBranch, bb => + { + bb.OwnsOne(x => x.RequiredReferenceLeaf).ToTable("Root_CollectionTrunk_CollectionBranch_RequiredReferenceLeaf"); + bb.OwnsOne(x => x.OptionalReferenceLeaf).ToTable("Root_CollectionTrunk_CollectionBranch_OptionalReferenceLeaf"); + }); + }); + } +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/ComplexNoTrackingProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/ComplexNoTrackingProjectionRelationalTestBase.cs new file mode 100644 index 00000000000..305b91baca1 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/ComplexNoTrackingProjectionRelationalTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class ComplexNoTrackingProjectionRelationalTestBase(TFixture fixture) + : ComplexNoTrackingProjectionTestBase(fixture) + where TFixture : ComplexRelationsjipsRelationalFixtureBase, new() +{ +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/ComplexProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/ComplexProjectionRelationalTestBase.cs new file mode 100644 index 00000000000..7dd43ad7e37 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/ComplexProjectionRelationalTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class ComplexProjectionRelationalTestBase(TFixture fixture) + : ComplexProjectionTestBase(fixture) + where TFixture : ComplexRelationsjipsRelationalFixtureBase, new() +{ +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/NavigationNoTrackingProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/NavigationNoTrackingProjectionRelationalTestBase.cs new file mode 100644 index 00000000000..d97e96a0a5c --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/NavigationNoTrackingProjectionRelationalTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class NavigationNoTrackingProjectionRelationalTestBase(TFixture fixture) + : NavigationNoTrackingProjectionTestBase(fixture) + where TFixture : NavigationRelationshipsRelationalFixtureBase, new() +{ +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/NavigationProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/NavigationProjectionRelationalTestBase.cs new file mode 100644 index 00000000000..a1ad0068369 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/NavigationProjectionRelationalTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class NavigationProjectionRelationalTestBase(TFixture fixture) + : NavigationProjectionTestBase(fixture) + where TFixture : NavigationRelationshipsRelationalFixtureBase, new() +{ +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/NavigationReferenceNoTrackingProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/NavigationReferenceNoTrackingProjectionRelationalTestBase.cs new file mode 100644 index 00000000000..36a3f097a42 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/NavigationReferenceNoTrackingProjectionRelationalTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class NavigationReferenceNoTrackingProjectionRelationalTestBase(TFixture fixture) + : NavigationReferenceNoTrackingProjectionTestBase(fixture) + where TFixture : NavigationRelationshipsRelationalFixtureBase, new() +{ +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/NavigationReferenceProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/NavigationReferenceProjectionRelationalTestBase.cs new file mode 100644 index 00000000000..5cfa52d08fe --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/NavigationReferenceProjectionRelationalTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class NavigationReferenceProjectionRelationalTestBase(TFixture fixture) + : NavigationReferenceProjectionTestBase(fixture) + where TFixture : NavigationRelationshipsRelationalFixtureBase, new() +{ +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedJsonNoTrackingProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedJsonNoTrackingProjectionRelationalTestBase.cs new file mode 100644 index 00000000000..278ad375442 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedJsonNoTrackingProjectionRelationalTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedJsonNoTrackingProjectionRelationalTestBase(TFixture fixture) + : OwnedJsonNoTrackingProjectionTestBase(fixture) + where TFixture : OwnedJsonRelationshipsRelationalFixtureBase, new() +{ +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedJsonProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedJsonProjectionRelationalTestBase.cs new file mode 100644 index 00000000000..4157008b3e0 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedJsonProjectionRelationalTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedJsonProjectionRelationalTestBase(TFixture fixture) + : OwnedJsonProjectionTestBase(fixture) + where TFixture : OwnedJsonRelationshipsRelationalFixtureBase, new() +{ +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedJsonReferenceNoTrackingProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedJsonReferenceNoTrackingProjectionRelationalTestBase.cs new file mode 100644 index 00000000000..119be8f9804 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedJsonReferenceNoTrackingProjectionRelationalTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedJsonReferenceNoTrackingProjectionRelationalTestBase(TFixture fixture) + : OwnedJsonReferenceNoTrackingProjectionTestBase(fixture) + where TFixture : OwnedJsonRelationshipsRelationalFixtureBase, new() +{ +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedJsonReferenceProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedJsonReferenceProjectionRelationalTestBase.cs new file mode 100644 index 00000000000..ed841787e79 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedJsonReferenceProjectionRelationalTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedJsonReferenceProjectionRelationalTestBase(TFixture fixture) + : OwnedJsonReferenceProjectionTestBase(fixture) + where TFixture : OwnedJsonRelationshipsRelationalFixtureBase, new() +{ +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedNoTrackingProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedNoTrackingProjectionRelationalTestBase.cs new file mode 100644 index 00000000000..3acf2943fdc --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedNoTrackingProjectionRelationalTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedNoTrackingProjectionRelationalTestBase(TFixture fixture) + : OwnedNoTrackingProjectionTestBase(fixture) + where TFixture : OwnedRelationshipsRelationalFixtureBase, new() +{ +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedProjectionRelationalTestBase.cs new file mode 100644 index 00000000000..76050f4105b --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedProjectionRelationalTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedProjectionRelationalTestBase(TFixture fixture) + : OwnedProjectionTestBase(fixture) + where TFixture : OwnedRelationshipsRelationalFixtureBase, new() +{ +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedReferenceNoTrackingProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedReferenceNoTrackingProjectionRelationalTestBase.cs new file mode 100644 index 00000000000..53c798b5119 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedReferenceNoTrackingProjectionRelationalTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedReferenceNoTrackingProjectionRelationalTestBase(TFixture fixture) + : OwnedReferenceNoTrackingProjectionTestBase(fixture) + where TFixture : OwnedRelationshipsRelationalFixtureBase, new() +{ +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedReferenceProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedReferenceProjectionRelationalTestBase.cs new file mode 100644 index 00000000000..8d3d60a9aa5 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedReferenceProjectionRelationalTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedReferenceProjectionRelationalTestBase(TFixture fixture) + : OwnedReferenceProjectionTestBase(fixture) + where TFixture : OwnedRelationshipsRelationalFixtureBase, new() +{ +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedTableSplittingNoTrackingProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedTableSplittingNoTrackingProjectionRelationalTestBase.cs new file mode 100644 index 00000000000..0481cd93eaa --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedTableSplittingNoTrackingProjectionRelationalTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedTableSplittingNoTrackingProjectionRelationalTestBase(TFixture fixture) + : OwnedNoTrackingProjectionTestBase(fixture) + where TFixture : OwnedTableSplittingRelationshipsRelationalFixtureBase, new() +{ +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedTableSplittingReferenceProjectionNoTrackingRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedTableSplittingReferenceProjectionNoTrackingRelationalTestBase.cs new file mode 100644 index 00000000000..e7064f18205 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Projection/OwnedTableSplittingReferenceProjectionNoTrackingRelationalTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedTableSplittingReferenceProjectionNoTrackingRelationalTestBase(TFixture fixture) + : OwnedReferenceNoTrackingProjectionTestBase(fixture) + where TFixture : OwnedTableSplittingRelationshipsRelationalFixtureBase, new() +{ +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/SharedTypeQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/SharedTypeQueryRelationalTestBase.cs index 0d2c0d4da7c..67e5cd5fa71 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/SharedTypeQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/SharedTypeQueryRelationalTestBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public abstract class SharedTypeQueryRelationalTestBase : SharedTypeQueryTestBase +public abstract class SharedTypeQueryRelationalTestBase(NonSharedFixture fixture) : SharedTypeQueryTestBase(fixture) { protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.Relational.Specification.Tests/Query/ToSqlQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/ToSqlQueryTestBase.cs index 516f1b8511f..32bae4cef53 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/ToSqlQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/ToSqlQueryTestBase.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query; -public abstract class ToSqlQueryTestBase : NonSharedModelTestBase +public abstract class ToSqlQueryTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { protected override string StoreName => "ToSqlQueryTests"; diff --git a/test/EFCore.Relational.Specification.Tests/Scaffolding/CompiledModelRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Scaffolding/CompiledModelRelationalTestBase.cs index 2be53109a1f..b4da22cb3b1 100644 --- a/test/EFCore.Relational.Specification.Tests/Scaffolding/CompiledModelRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Scaffolding/CompiledModelRelationalTestBase.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Scaffolding; -public abstract class CompiledModelRelationalTestBase : CompiledModelTestBase +public abstract class CompiledModelRelationalTestBase(NonSharedFixture fixture) : CompiledModelTestBase(fixture) { [ConditionalFact] public virtual Task BigModel_with_JSON_columns() diff --git a/test/EFCore.Relational.Specification.Tests/TPTTableSplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/TPTTableSplittingTestBase.cs index 0c82e111e62..ba9b02dc893 100644 --- a/test/EFCore.Relational.Specification.Tests/TPTTableSplittingTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/TPTTableSplittingTestBase.cs @@ -8,7 +8,7 @@ namespace Microsoft.EntityFrameworkCore; #nullable disable -public abstract class TPTTableSplittingTestBase(ITestOutputHelper testOutputHelper) : TableSplittingTestBase(testOutputHelper) +public abstract class TPTTableSplittingTestBase(NonSharedFixture fixture, ITestOutputHelper testOutputHelper) : TableSplittingTestBase(fixture, testOutputHelper) { public override Task Can_use_optional_dependents_with_shared_concurrency_tokens() // TODO: Issue #22060 diff --git a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs index d7ae7f68a1c..bc949dbc559 100644 --- a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Reflection.Emit; using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.TestModels.TransportationModel; @@ -10,9 +9,10 @@ namespace Microsoft.EntityFrameworkCore; #nullable disable -public abstract class TableSplittingTestBase : NonSharedModelTestBase +public abstract class TableSplittingTestBase : NonSharedModelTestBase, IClassFixture { - protected TableSplittingTestBase(ITestOutputHelper testOutputHelper) + protected TableSplittingTestBase(NonSharedFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) { // TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } diff --git a/test/EFCore.Relational.Specification.Tests/TestUtilities/PrecompiledQueryTestHelpers.cs b/test/EFCore.Relational.Specification.Tests/TestUtilities/PrecompiledQueryTestHelpers.cs index 14b4f0bc9ca..6cfd60b610d 100644 --- a/test/EFCore.Relational.Specification.Tests/TestUtilities/PrecompiledQueryTestHelpers.cs +++ b/test/EFCore.Relational.Specification.Tests/TestUtilities/PrecompiledQueryTestHelpers.cs @@ -84,7 +84,7 @@ public async Task FullSourceTest( // This turns on the interceptors feature for the designated namespace(s). var parseOptions = new CSharpParseOptions().WithFeatures( [ - new KeyValuePair("InterceptorsPreviewNamespaces", "Microsoft.EntityFrameworkCore.GeneratedInterceptors") + new KeyValuePair("InterceptorsNamespaces", "Microsoft.EntityFrameworkCore.GeneratedInterceptors") ]); var syntaxTree = CSharpSyntaxTree.ParseText(source, parseOptions, path: "Test.cs"); diff --git a/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs b/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs index 150fb559a13..57e3055ff95 100644 --- a/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs +++ b/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs @@ -100,7 +100,7 @@ public void AssertBaseline(string[] expected, bool assertOrder = true, bool forU var testInfo = testName + " : " + lineNumber + FileNewLine; const string indent = FileNewLine + " "; - //if (Environment.GetEnvironmentVariable("EF_TEST_REWRITE_BASELINES")?.ToUpper() is "1" or "TRUE") + if (Environment.GetEnvironmentVariable("EF_TEST_REWRITE_BASELINES")?.ToUpper() is "1" or "TRUE") { RewriteSourceWithNewBaseline(fileName, lineNumber); } diff --git a/test/EFCore.Relational.Specification.Tests/Update/NonSharedModelUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/Update/NonSharedModelUpdatesTestBase.cs index 23c3daadf32..f7603c5aa60 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/NonSharedModelUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/NonSharedModelUpdatesTestBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Update; -public abstract class NonSharedModelUpdatesTestBase : NonSharedModelTestBase +public abstract class NonSharedModelUpdatesTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { protected override string StoreName => "NonSharedModelUpdatesTestBase"; diff --git a/test/EFCore.Relational.Specification.Tests/Update/StoreValueGenerationTestBase.cs b/test/EFCore.Relational.Specification.Tests/Update/StoreValueGenerationTestBase.cs index 6b19e793a20..eb59b40234a 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/StoreValueGenerationTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/StoreValueGenerationTestBase.cs @@ -363,7 +363,7 @@ protected virtual int ShouldExecuteInNumberOfCommands( protected StoreValueGenerationContext CreateContext() => Fixture.CreateContext(); - public static IEnumerable IsAsyncData = new object[][] { [false], [true] }; + public static readonly IEnumerable IsAsyncData = [[false], [true]]; protected virtual void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Relational.Specification.Tests/Update/StoredProcedureUpdateTestBase.cs b/test/EFCore.Relational.Specification.Tests/Update/StoredProcedureUpdateTestBase.cs index ad801d0c031..229cbb938b2 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/StoredProcedureUpdateTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/StoredProcedureUpdateTestBase.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.Update; #nullable disable -public abstract class StoredProcedureUpdateTestBase : NonSharedModelTestBase +public abstract class StoredProcedureUpdateTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { protected override string StoreName => "StoredProcedureUpdateTest"; diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalEventIdTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalEventIdTest.cs index 018a4e11014..5e641dbe141 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalEventIdTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalEventIdTest.cs @@ -172,6 +172,8 @@ private class FakeRelationalCommand : IRelationalCommand { public string CommandText { get; } = ""; + public string LogCommandText { get; } = ""; + public IReadOnlyList Parameters { get; } = []; public DbCommand CreateDbCommand(RelationalCommandParameterObject parameterObject, Guid commandId, DbCommandMethod commandMethod) diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index d6040f9efd0..1747d5dbca4 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -43,19 +43,6 @@ public void Model_differ_does_not_detect_views_with_weak_types() }), upOperations => Assert.Equal(0, upOperations.Count)); - [ConditionalFact] - public void Model_differ_does_not_detect_defining_queries() - { - DbContext context = null; - Execute( - _ => { }, -#pragma warning disable CS0618 // Type or member is obsolete - modelBuilder => modelBuilder.Entity().HasNoKey().ToQuery( - () => context.Set().FromSqlRaw("SELECT * FROM Vista")), -#pragma warning restore CS0618 // Type or member is obsolete - result => Assert.Empty(result)); - } - [ConditionalFact] public void Model_differ_does_not_detect_queries() => Execute( @@ -8644,9 +8631,8 @@ public void Split_out_subtype_with_seed_data() { x.Property("Id"); x.Property("Name"); - x.Property("Discriminator"); - x.HasDiscriminator() + x.HasDiscriminator("Discriminator") .HasValue(1) .HasValue(2); diff --git a/test/EFCore.Relational.Tests/Migrations/MigrationCommandExecutorTest.cs b/test/EFCore.Relational.Tests/Migrations/MigrationCommandExecutorTest.cs index 6f491c81db3..c3889c8f7e9 100644 --- a/test/EFCore.Relational.Tests/Migrations/MigrationCommandExecutorTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/MigrationCommandExecutorTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.EntityFrameworkCore.TestUtilities.FakeProvider; @@ -396,13 +397,16 @@ private static IDbContextOptions CreateOptions(RelationalOptionsExtension option private IRelationalCommand CreateRelationalCommand( string commandText = "Command Text", + string logCommandText = "Log Command Text", IReadOnlyList parameters = null) => new RelationalCommand( new RelationalCommandBuilderDependencies( new TestRelationalTypeMappingSource( TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()), - new ExceptionDetector()), + new ExceptionDetector(), + new LoggingOptions()), commandText, + logCommandText, parameters ?? []); } diff --git a/test/EFCore.Relational.Tests/Migrations/MigrationCommandListBuilderTest.cs b/test/EFCore.Relational.Tests/Migrations/MigrationCommandListBuilderTest.cs index 1f504658822..6f744344597 100644 --- a/test/EFCore.Relational.Tests/Migrations/MigrationCommandListBuilderTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/MigrationCommandListBuilderTest.cs @@ -137,7 +137,8 @@ private MigrationCommandListBuilder CreateBuilder() new RelationalCommandBuilderFactory( new RelationalCommandBuilderDependencies( typeMappingSource, - new ExceptionDetector())), + new ExceptionDetector(), + new LoggingOptions())), new FakeSqlGenerator( new UpdateSqlGeneratorDependencies( generationHelper, diff --git a/test/EFCore.Relational.Tests/Query/Internal/BufferedDataReaderTest.cs b/test/EFCore.Relational.Tests/Query/Internal/BufferedDataReaderTest.cs index 7a3fb447668..e4a53b5178f 100644 --- a/test/EFCore.Relational.Tests/Query/Internal/BufferedDataReaderTest.cs +++ b/test/EFCore.Relational.Tests/Query/Internal/BufferedDataReaderTest.cs @@ -8,7 +8,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal; public class BufferedDataReaderTest { - public static IEnumerable IsAsyncData = new object[][] { [false], [true] }; + public static readonly IEnumerable IsAsyncData = [[false], [true]]; [ConditionalTheory] [MemberData(nameof(IsAsyncData))] diff --git a/test/EFCore.Relational.Tests/Query/Internal/QuerySqlGeneratorTest.cs b/test/EFCore.Relational.Tests/Query/Internal/QuerySqlGeneratorTest.cs index a0d11e551e2..fd09c2b12f5 100644 --- a/test/EFCore.Relational.Tests/Query/Internal/QuerySqlGeneratorTest.cs +++ b/test/EFCore.Relational.Tests/Query/Internal/QuerySqlGeneratorTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Storage.Internal; namespace Microsoft.EntityFrameworkCore.Query.Internal; @@ -53,7 +54,8 @@ private DummyQuerySqlGenerator CreateDummyQuerySqlGenerator() new TestRelationalTypeMappingSource( TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()), - new ExceptionDetector())), + new ExceptionDetector(), + new LoggingOptions())), new RelationalSqlGenerationHelper( new RelationalSqlGenerationHelperDependencies()))); diff --git a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs index 3916535c30d..fb1a22acf44 100644 --- a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs +++ b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs @@ -16,15 +16,6 @@ protected override void AddServices(ServiceCollection serviceCollection) protected override Assembly TargetAssembly => typeof(RelationalDatabase).Assembly; - protected override HashSet NonCancellableAsyncMethods - { - get - { - var methods = base.NonCancellableAsyncMethods; - methods.Add(typeof(DbConnectionInterceptor).GetMethod(nameof(DbConnectionInterceptor.ConnectionDisposedAsync))); - return methods; - } - } [ConditionalFact] public void Readonly_relational_metadata_methods_have_expected_name() @@ -302,7 +293,7 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase } }; - public override HashSet NonVirtualMethods { get; } + public override HashSet VirtualMethodExceptions { get; } = [ typeof(RelationalCompiledQueryCacheKeyGenerator) @@ -570,32 +561,6 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase typeof(IMutableStoredProcedure).GetMethod(nameof(IMutableStoredProcedure.AddResultColumn)) ]; - public override HashSet VirtualMethodExceptions { get; } = - [ - // non-sealed record -#pragma warning disable EF9100 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - typeof(RelationalMaterializerLiftableConstantContext).GetMethod("get_RelationalDependencies"), - typeof(RelationalMaterializerLiftableConstantContext).GetMethod("set_RelationalDependencies"), - typeof(RelationalMaterializerLiftableConstantContext).GetMethod("get_CommandBuilderDependencies"), - typeof(RelationalMaterializerLiftableConstantContext).GetMethod("set_CommandBuilderDependencies"), - typeof(RelationalMaterializerLiftableConstantContext).GetMethod( - "Deconstruct", [typeof(ShapedQueryCompilingExpressionVisitorDependencies).MakeByRefType()]), - typeof(RelationalMaterializerLiftableConstantContext).GetMethod( - "Deconstruct", - [ - typeof(ShapedQueryCompilingExpressionVisitorDependencies).MakeByRefType(), - typeof(RelationalShapedQueryCompilingExpressionVisitorDependencies).MakeByRefType() - ]), - typeof(RelationalMaterializerLiftableConstantContext).GetMethod( - "Deconstruct", - [ - typeof(ShapedQueryCompilingExpressionVisitorDependencies).MakeByRefType(), - typeof(RelationalShapedQueryCompilingExpressionVisitorDependencies).MakeByRefType(), - typeof(RelationalCommandBuilderDependencies).MakeByRefType() - ]), -#pragma warning restore EF9100 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - ]; - public List> RelationalMetadataMethods { get; } = []; protected override void Initialize() @@ -650,6 +615,8 @@ protected override void Initialize() MirrorTypes.Add( typeof(RelationalComplexTypePrimitiveCollectionBuilderExtensions), typeof(RelationalComplexTypePropertyBuilderExtensions)); + NonCancellableAsyncMethods.Add(typeof(DbConnectionInterceptor).GetMethod(nameof(DbConnectionInterceptor.ConnectionDisposedAsync))); + base.Initialize(); } } diff --git a/test/EFCore.Relational.Tests/RelationalConnectionTest.cs b/test/EFCore.Relational.Tests/RelationalConnectionTest.cs index 2d26f147675..d89ada2dac0 100644 --- a/test/EFCore.Relational.Tests/RelationalConnectionTest.cs +++ b/test/EFCore.Relational.Tests/RelationalConnectionTest.cs @@ -1073,6 +1073,32 @@ public void Does_not_dispose_when_changing_DbConnection_if_current_is_open_and_n Assert.Equal(ConnectionState.Open, dbConnection.State); } + [ConditionalFact] + public async Task Reports_command_diagnostic_on_cancellation() + { + var exception = new OperationCanceledException(); + + var connection = new FakeRelationalConnection( + CreateOptions(new FakeRelationalOptionsExtension().WithConnectionString("Database=FrodoLives"))); + + var diagnostics = connection.ConnectionDiagnosticEvents; + + await Assert.ThrowsAsync(async () => await connection.OpenAsync(new CancellationToken(canceled: true))); + + Assert.Collection( + diagnostics, + d => Assert.Equal(RelationalEventId.ConnectionCreating.Name, d.Item1), + d => Assert.Equal(RelationalEventId.ConnectionCreated.Name, d.Item1), + d => Assert.Equal(RelationalEventId.ConnectionOpening.Name, d.Item1), + d => + { + Assert.Equal(RelationalEventId.ConnectionCanceled.Name, d.Item1); + var eventData = (ConnectionEndEventData)d.Item2; + Assert.Same(connection.DbConnection, eventData.Connection); + Assert.True(eventData.IsAsync); + }); + } + private static IDbContextOptions CreateOptions(params RelationalOptionsExtension[] optionsExtensions) { var optionsBuilder = new DbContextOptionsBuilder(); @@ -1084,4 +1110,26 @@ private static IDbContextOptions CreateOptions(params RelationalOptionsExtension return optionsBuilder.Options; } + + private class FakeLoggingOptions(bool sensitiveDataLoggingEnabled, bool detailedErrorsEnabled = false) : ILoggingOptions + { + public void Initialize(IDbContextOptions options) + { + } + + public void Validate(IDbContextOptions options) + { + } + + public bool IsSensitiveDataLoggingEnabled { get; } = sensitiveDataLoggingEnabled; + public bool IsSensitiveDataLoggingWarned { get; set; } + + public bool DetailedErrorsEnabled { get; } = detailedErrorsEnabled; + + public WarningsConfiguration WarningsConfiguration + => null; + + public virtual bool ShouldWarnForStringEnumValueInJson(Type enumType) + => true; + } } diff --git a/test/EFCore.Relational.Tests/Storage/RawSqlCommandBuilderTest.cs b/test/EFCore.Relational.Tests/Storage/RawSqlCommandBuilderTest.cs index 7c12ba1ad0a..4d1c6e3cf1e 100644 --- a/test/EFCore.Relational.Tests/Storage/RawSqlCommandBuilderTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RawSqlCommandBuilderTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Storage.Internal; namespace Microsoft.EntityFrameworkCore.Storage; @@ -25,7 +26,8 @@ private static RawSqlCommandBuilder CreateBuilder() new TestRelationalTypeMappingSource( TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()), - new ExceptionDetector())), + new ExceptionDetector(), + new LoggingOptions())), new RelationalSqlGenerationHelper( new RelationalSqlGenerationHelperDependencies()), new ParameterNameGeneratorFactory( diff --git a/test/EFCore.Relational.Tests/Storage/RelationalCommandBuilderTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalCommandBuilderTest.cs index 019fdb8b430..7eea16980cd 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalCommandBuilderTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalCommandBuilderTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Data; +using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Storage.Internal; namespace Microsoft.EntityFrameworkCore.Storage; @@ -43,7 +44,8 @@ private static RelationalCommandBuilder CreateCommandBuilder() new TestRelationalTypeMappingSource( TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()), - new ExceptionDetector()); + new ExceptionDetector(), + new LoggingOptions()); var commandBuilder = new RelationalCommandBuilder(dependencies); return commandBuilder; diff --git a/test/EFCore.Relational.Tests/Storage/RelationalCommandTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalCommandTest.cs index a751990e9fc..0594a31675f 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalCommandTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalCommandTest.cs @@ -849,19 +849,22 @@ await Assert.ThrowsAsync( private class ReaderThrowingRelationalCommand( RelationalCommandBuilderDependencies dependencies, string commandText, - IReadOnlyList parameters) : RelationalCommand(dependencies, commandText, parameters) + string logCommandText, + IReadOnlyList parameters) : RelationalCommand(dependencies, commandText, logCommandText, parameters) { protected override RelationalDataReader CreateRelationalDataReader() => new ThrowingRelationalReader(); - public static IRelationalCommand Create(string commandText = "Command Text") + public static IRelationalCommand Create(string commandText = "Command Text", string logCommandText = "Log Command Text") => new ReaderThrowingRelationalCommand( new RelationalCommandBuilderDependencies( new TestRelationalTypeMappingSource( TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()), - new ExceptionDetector()), + new ExceptionDetector(), + new LoggingOptions()), commandText, + logCommandText, []); private class ThrowingRelationalReader : RelationalDataReader @@ -993,6 +996,7 @@ public async Task Logs_commands_without_parameter_values( var relationalCommand = CreateRelationalCommand( commandText: "Logged Command", + logCommandText: "Logged Command2", parameters: new[] { new TypeMappedRelationalParameter( @@ -1021,7 +1025,7 @@ public async Task Logs_commands_without_parameter_values( foreach (var (_, _, message, _, _) in logFactory.Log.Skip(3)) { Assert.EndsWith( - "[Parameters=[FirstParameter='?' (DbType = Int32)], CommandType='0', CommandTimeout='30']" + _eol + "Logged Command", + "[Parameters=[FirstParameter='?' (DbType = Int32)], CommandType='0', CommandTimeout='30']" + _eol + "Logged Command2", message); } } @@ -1051,6 +1055,7 @@ public async Task Logs_commands_parameter_values( var relationalCommand = CreateRelationalCommand( commandText: "Logged Command", + logCommandText: "Logged Command2", parameters: new[] { new TypeMappedRelationalParameter( @@ -1083,7 +1088,7 @@ public async Task Logs_commands_parameter_values( foreach (var (_, _, message, _, _) in logFactory.Log.Skip(4)) { Assert.EndsWith( - "[Parameters=[FirstParameter='17'], CommandType='0', CommandTimeout='30']" + _eol + "Logged Command", + "[Parameters=[FirstParameter='17'], CommandType='0', CommandTimeout='30']" + _eol + "Logged Command2", message); } } @@ -1345,14 +1350,17 @@ public virtual bool ShouldWarnForStringEnumValueInJson(Type enumType) private IRelationalCommand CreateRelationalCommand( string commandText = "Command Text", + string logCommandText = "Log Command Text", IReadOnlyList parameters = null) => new RelationalCommand( new RelationalCommandBuilderDependencies( new TestRelationalTypeMappingSource( TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()), - new ExceptionDetector()), + new ExceptionDetector(), + new LoggingOptions()), commandText, + logCommandText, parameters ?? []); private Task ExecuteReader( @@ -1366,5 +1374,5 @@ private Task ExecuteReader( private Task Read(RelationalDataReader relationalReader, bool async) => async ? relationalReader.ReadAsync() : Task.FromResult(relationalReader.Read()); - public static IEnumerable IsAsyncData = new object[][] { [false], [true] }; + public static readonly IEnumerable IsAsyncData = [[false], [true]]; } diff --git a/test/EFCore.Relational.Tests/Storage/RelationalDataReaderTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalDataReaderTest.cs index 6b1c56b4786..83d93d301c3 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalDataReaderTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalDataReaderTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.EntityFrameworkCore.TestUtilities.FakeProvider; @@ -59,15 +60,18 @@ private static IDbContextOptions CreateOptions( private IRelationalCommand CreateRelationalCommand( string commandText = "Command Text", + string logCommandText = "Log Command Text", IReadOnlyList parameters = null) => new RelationalCommand( new RelationalCommandBuilderDependencies( new TestRelationalTypeMappingSource( TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()), - new ExceptionDetector()), + new ExceptionDetector(), + new LoggingOptions()), commandText, + logCommandText, parameters ?? []); - public static IEnumerable IsAsyncData = new object[][] { [false], [true] }; + public static readonly IEnumerable IsAsyncData = [[false], [true]]; } diff --git a/test/EFCore.Relational.Tests/Storage/RelationalParameterBuilderTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalParameterBuilderTest.cs index 8be3b7ba3a9..09c99f0cc09 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalParameterBuilderTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalParameterBuilderTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Data; +using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Storage.Internal; namespace Microsoft.EntityFrameworkCore.Storage; @@ -21,7 +22,8 @@ public void Can_add_type_mapped_parameter_by_type(bool nullable) var parameterBuilder = new RelationalCommandBuilder( new RelationalCommandBuilderDependencies( typeMapper, - new ExceptionDetector())); + new ExceptionDetector(), + new LoggingOptions())); parameterBuilder.AddParameter( "InvariantName", @@ -58,7 +60,7 @@ public void Can_add_type_mapped_parameter_by_property(bool nullable) var property = model.GetEntityTypes().Single().FindProperty("MyProp"); var parameterBuilder = new RelationalCommandBuilder( - new RelationalCommandBuilderDependencies(typeMapper, new ExceptionDetector())); + new RelationalCommandBuilderDependencies(typeMapper, new ExceptionDetector(), new LoggingOptions())); parameterBuilder.AddParameter( "InvariantName", @@ -87,7 +89,8 @@ public void Can_add_composite_parameter() var parameterBuilder = new RelationalCommandBuilder( new RelationalCommandBuilderDependencies( typeMapper, - new ExceptionDetector())); + new ExceptionDetector(), + new LoggingOptions())); parameterBuilder.AddCompositeParameter( "CompositeInvariant", @@ -124,7 +127,8 @@ public void Does_not_add_empty_composite_parameter() var parameterBuilder = new RelationalCommandBuilder( new RelationalCommandBuilderDependencies( typeMapper, - new ExceptionDetector())); + new ExceptionDetector(), + new LoggingOptions())); parameterBuilder.AddCompositeParameter( "CompositeInvariant", diff --git a/test/EFCore.Relational.Tests/TestUtilities/FakeProvider/FakeDbConnection.cs b/test/EFCore.Relational.Tests/TestUtilities/FakeProvider/FakeDbConnection.cs index 5017c4b2b51..09552f40be2 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/FakeProvider/FakeDbConnection.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/FakeProvider/FakeDbConnection.cs @@ -50,6 +50,7 @@ public override void Open() public override Task OpenAsync(CancellationToken cancellationToken) { OpenAsyncCount++; + cancellationToken.ThrowIfCancellationRequested(); return base.OpenAsync(cancellationToken); } diff --git a/test/EFCore.Relational.Tests/TestUtilities/FakeProvider/FakeRelationalConnection.cs b/test/EFCore.Relational.Tests/TestUtilities/FakeProvider/FakeRelationalConnection.cs index 98caf4177df..d36deb066ad 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/FakeProvider/FakeRelationalConnection.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/FakeProvider/FakeRelationalConnection.cs @@ -7,34 +7,37 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities.FakeProvider; -public class FakeRelationalConnection(IDbContextOptions options = null) : RelationalConnection( - new RelationalConnectionDependencies( - options ?? CreateOptions(), - new DiagnosticsLogger( - new LoggerFactory(), - new LoggingOptions(), - new DiagnosticListener("FakeDiagnosticListener"), - new TestRelationalLoggingDefinitions(), - new NullDbContextLogger()), - new RelationalConnectionDiagnosticsLogger( - new LoggerFactory(), - new LoggingOptions(), - new DiagnosticListener("FakeDiagnosticListener"), - new TestRelationalLoggingDefinitions(), - new NullDbContextLogger(), - CreateOptions()), - new NamedConnectionStringResolver(options ?? CreateOptions()), - new RelationalTransactionFactory( - new RelationalTransactionFactoryDependencies( - new RelationalSqlGenerationHelper( - new RelationalSqlGenerationHelperDependencies()))), - new CurrentDbContext(new FakeDbContext()), - new RelationalCommandBuilderFactory( - new RelationalCommandBuilderDependencies( - new TestRelationalTypeMappingSource( - TestServiceFactory.Instance.Create(), - TestServiceFactory.Instance.Create()), - new ExceptionDetector())))) +public class FakeRelationalConnection(IDbContextOptions options = null) + : RelationalConnection( + new RelationalConnectionDependencies( + options ?? CreateOptions(), + new DiagnosticsLogger( + new LoggerFactory(), + new LoggingOptions(), + new ListDiagnosticSource(new List>()), + new TestRelationalLoggingDefinitions(), + new NullDbContextLogger()), + new RelationalConnectionDiagnosticsLogger( + new LoggerFactory(), + new LoggingOptions(), + new ListDiagnosticSource(new List>()), + new TestRelationalLoggingDefinitions(), + new NullDbContextLogger(), + CreateOptions()), + new NamedConnectionStringResolver(options ?? CreateOptions()), + new RelationalTransactionFactory( + new RelationalTransactionFactoryDependencies( + new RelationalSqlGenerationHelper( + new RelationalSqlGenerationHelperDependencies()))), + new CurrentDbContext(new FakeDbContext()), + new RelationalCommandBuilderFactory( + new RelationalCommandBuilderDependencies( + new TestRelationalTypeMappingSource( + TestServiceFactory.Instance.Create(), + TestServiceFactory.Instance.Create()), + new ExceptionDetector(), + new LoggingOptions())), + new ExceptionDetector())) { private DbConnection _connection; @@ -61,6 +64,12 @@ public override DbConnection DbConnection public IReadOnlyList DbConnections => _dbConnections; + public List> TransactionDiagnosticEvents + => ((ListDiagnosticSource)Dependencies.TransactionLogger.DiagnosticSource).DiagnosticList; + + public List> ConnectionDiagnosticEvents + => ((ListDiagnosticSource)Dependencies.ConnectionLogger.DiagnosticSource).DiagnosticList; + protected override DbConnection CreateDbConnection() { var connection = new FakeDbConnection(ConnectionString); diff --git a/test/EFCore.Relational.Tests/TestUtilities/FakeRelationalCommandDiagnosticsLogger.cs b/test/EFCore.Relational.Tests/TestUtilities/FakeRelationalCommandDiagnosticsLogger.cs index ae25c3e5158..a3bcdd28f7d 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/FakeRelationalCommandDiagnosticsLogger.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/FakeRelationalCommandDiagnosticsLogger.cs @@ -45,6 +45,7 @@ public DbCommand CommandInitialized( public InterceptionResult CommandReaderExecuting( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -55,6 +56,7 @@ public InterceptionResult CommandReaderExecuting( public InterceptionResult CommandScalarExecuting( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -65,6 +67,7 @@ public InterceptionResult CommandScalarExecuting( public InterceptionResult CommandNonQueryExecuting( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -75,6 +78,7 @@ public InterceptionResult CommandNonQueryExecuting( public ValueTask> CommandReaderExecutingAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -86,6 +90,7 @@ public ValueTask> CommandReaderExecutingAsync( public ValueTask> CommandScalarExecutingAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -97,6 +102,7 @@ public ValueTask> CommandScalarExecutingAsync( public ValueTask> CommandNonQueryExecutingAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -108,6 +114,7 @@ public ValueTask> CommandNonQueryExecutingAsync( public DbDataReader CommandReaderExecuted( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -120,6 +127,7 @@ public DbDataReader CommandReaderExecuted( public object? CommandScalarExecuted( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -132,6 +140,7 @@ public DbDataReader CommandReaderExecuted( public int CommandNonQueryExecuted( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -144,6 +153,7 @@ public int CommandNonQueryExecuted( public ValueTask CommandReaderExecutedAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -157,6 +167,7 @@ public ValueTask CommandReaderExecutedAsync( public ValueTask CommandScalarExecutedAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -170,6 +181,7 @@ public ValueTask CommandReaderExecutedAsync( public ValueTask CommandNonQueryExecutedAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, Guid commandId, Guid connectionId, @@ -211,6 +223,7 @@ public Task CommandExceptionAsync( public void CommandError( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -225,6 +238,7 @@ public void CommandError( public Task CommandErrorAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -239,6 +253,7 @@ public Task CommandErrorAsync( public void CommandCanceled( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, @@ -252,6 +267,7 @@ public void CommandCanceled( public Task CommandCanceledAsync( IRelationalConnection connection, DbCommand command, + string logCommandText, DbContext? context, DbCommandMethod executeMethod, Guid commandId, diff --git a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs index eb6480fc09f..fdae1ce5d95 100644 --- a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs +++ b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.EntityFrameworkCore.TestUtilities.FakeProvider; @@ -732,7 +733,8 @@ private static ModificationCommandBatchFactoryDependencies CreateDependencies( new RelationalCommandBuilderFactory( new RelationalCommandBuilderDependencies( typeMappingSource, - new ExceptionDetector())), + new ExceptionDetector(), + new LoggingOptions())), new RelationalSqlGenerationHelper( new RelationalSqlGenerationHelperDependencies()), sqlGenerator, diff --git a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs index 531aaeccc5f..f27d8e102fa 100644 --- a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs +++ b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs @@ -32,6 +32,22 @@ protected static bool IsCompilerSynthesizedMethod(MethodBase method) protected virtual TFixture Fixture { get; } = fixture; + [ConditionalFact] + public void Exceptions_are_valid() + { + Assert.DoesNotContain(null, Fixture.AsyncMethodExceptions); + Assert.DoesNotContain(null, Fixture.MetadataMethodExceptions); + Assert.DoesNotContain(null, Fixture.UnmatchedMetadataMethods); + Assert.DoesNotContain(null, Fixture.VirtualMethodExceptions); + Assert.DoesNotContain(null, Fixture.NotAnnotatedMethods); + Assert.DoesNotContain(null, Fixture.NonCancellableAsyncMethods); + + foreach (var unmatched in Fixture.UnmatchedMirrorMethods) + { + Assert.DoesNotContain(null, unmatched.Value); + } + } + [ConditionalFact] public void Fluent_api_methods_should_not_return_void() { @@ -940,7 +956,7 @@ public void Convention_metadata_methods_have_expected_shape() { var errors = Fixture.MetadataMethods - .SelectMany(m => m.Convention.Select(ValidateConventionMethod)) + .SelectMany(m => m.Convention.Select(mi => ValidateConventionMethod(mi, m.Convention[0].DeclaringType))) .Where(e => e != null) .ToList(); @@ -949,7 +965,7 @@ public void Convention_metadata_methods_have_expected_shape() "\r\n-- Errors: --\r\n" + string.Join(Environment.NewLine, errors)); } - private string ValidateConventionMethod(MethodInfo conventionMethod) + private string ValidateConventionMethod(MethodInfo conventionMethod, Type type) { var message = ValidateMethodName(conventionMethod); if (message != null) @@ -1069,9 +1085,10 @@ var nonVirtualMethods where type.IsVisible && !type.IsSealed && !type.GetCustomAttributes().Any() - from method in type.GetMethods(AnyInstance) + && type.GetMethod("$") == null // Exclude records + from method in type.GetMethods(AnyInstance) where method.DeclaringType == type - && !Fixture.NonVirtualMethods.Contains(method) + && !Fixture.VirtualMethodExceptions.Contains(method) && !Fixture.VirtualMethodExceptions.Contains(method) && !method.IsVirtual && !method.Name.StartsWith("add_", StringComparison.Ordinal) @@ -1086,11 +1103,6 @@ from method in type.GetMethods(AnyInstance) "\r\n-- Missing virtual APIs --\r\n" + string.Join(Environment.NewLine, nonVirtualMethods)); } - private static readonly HashSet _nonCancellableAsyncMethods = []; - - protected virtual HashSet NonCancellableAsyncMethods - => _nonCancellableAsyncMethods; - [ConditionalFact] public virtual void Async_methods_should_have_overload_with_cancellation_token_and_end_with_async_suffix() { @@ -1110,7 +1122,7 @@ where method.GetParameters().Any(pi => pi.ParameterType == typeof(CancellationTo var asyncMethodsWithoutToken = (from method in asyncMethods - where !NonCancellableAsyncMethods.Contains(method) + where !Fixture.NonCancellableAsyncMethods.Contains(method) && method.GetParameters().All(pi => pi.ParameterType != typeof(CancellationToken)) select method).ToList(); @@ -1275,7 +1287,7 @@ protected ApiConsistencyFixtureBase() public virtual Dictionary MirrorTypes { get; } = new(); - public virtual HashSet NonVirtualMethods { get; } = []; + public virtual HashSet VirtualMethodExceptions { get; } = []; public virtual HashSet NotAnnotatedMethods { get; } = []; public virtual HashSet AsyncMethodExceptions { get; } = []; public virtual HashSet UnmatchedMetadataMethods { get; } = []; @@ -1283,16 +1295,6 @@ protected ApiConsistencyFixtureBase() public virtual Dictionary MetadataMethodNameTransformers { get; } = new(); public virtual HashSet MetadataMethodExceptions { get; } = []; - public virtual HashSet VirtualMethodExceptions { get; } = - [ - // un-sealed record -#pragma warning disable EF9100 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - typeof(MaterializerLiftableConstantContext).GetMethod("get_Dependencies"), - typeof(MaterializerLiftableConstantContext).GetMethod("set_Dependencies"), - typeof(MaterializerLiftableConstantContext).GetMethod("Deconstruct"), -#pragma warning restore EF9100 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - ]; - public virtual HashSet ComputedDependencyProperties { get; } = [ typeof(ProviderConventionSetBuilderDependencies).GetProperty( @@ -1418,6 +1420,7 @@ protected ApiConsistencyFixtureBase() public Dictionary MutableMetadataTypes { get; } = new(); public Dictionary ConventionMetadataTypes { get; } = new(); + public virtual HashSet NonCancellableAsyncMethods { get; } = new(); public virtual Dictionary !IsObsolete(m)).ToArray(); - var mutableMethods = typeTuple.Value.Mutable.GetMethods(PublicInstance) - .Where(m => !IsObsolete(m)).ToArray(); - var conventionMethods = typeTuple.Value.Convention.GetMethods(PublicInstance) - .Where(m => !IsObsolete(m)).ToArray(); - var conventionBuilderMethods = typeTuple.Value.ConventionBuilder?.GetMethods(PublicInstance) - .Where(m => !IsObsolete(m)).ToArray() - ?? []; - var runtimeMethods = typeTuple.Value.Runtime?.GetMethods(PublicInstance) - .Where(m => !IsObsolete(m)).ToArray() - ?? []; + var readOnlyMethods = GetMethods(typeTuple.Key).ToArray(); + var mutableMethods = GetMethods(typeTuple.Value.Mutable).ToArray(); + var conventionMethods = GetMethods(typeTuple.Value.Convention).ToArray(); + var conventionBuilderMethods = GetMethods(typeTuple.Value.ConventionBuilder).ToArray(); + var runtimeMethods = GetMethods(typeTuple.Value.Runtime).ToArray(); MetadataMethods.Add((readOnlyMethods, mutableMethods, conventionMethods, conventionBuilderMethods, runtimeMethods)); } + + static IEnumerable GetMethods(Type type) + => type == null ? [] : (IEnumerable)type.GetMethods(PublicInstance); } public bool IsObsolete(MethodInfo method) diff --git a/test/EFCore.Specification.Tests/BulkUpdates/BulkUpdatesTestBase.cs b/test/EFCore.Specification.Tests/BulkUpdates/BulkUpdatesTestBase.cs index 656768cf04f..8b03348e0a0 100644 --- a/test/EFCore.Specification.Tests/BulkUpdates/BulkUpdatesTestBase.cs +++ b/test/EFCore.Specification.Tests/BulkUpdates/BulkUpdatesTestBase.cs @@ -21,7 +21,7 @@ protected BulkUpdatesTestBase(TFixture fixture) protected virtual Expression RewriteServerQueryExpression(Expression serverQueryExpression) => serverQueryExpression; - public static IEnumerable IsAsyncData = new object[][] { [false], [true] }; + public static readonly IEnumerable IsAsyncData = [[false], [true]]; public Task AssertDelete( bool async, diff --git a/test/EFCore.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs b/test/EFCore.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs index 9929cc3dbf0..933d19a8039 100644 --- a/test/EFCore.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs +++ b/test/EFCore.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public abstract class NonSharedModelBulkUpdatesTestBase : NonSharedModelTestBase +public abstract class NonSharedModelBulkUpdatesTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { protected override string StoreName => "NonSharedModelBulkUpdatesTests"; diff --git a/test/EFCore.Specification.Tests/ConcurrencyDetectorTestBase.cs b/test/EFCore.Specification.Tests/ConcurrencyDetectorTestBase.cs index d5b80382199..f5f70b7fa69 100644 --- a/test/EFCore.Specification.Tests/ConcurrencyDetectorTestBase.cs +++ b/test/EFCore.Specification.Tests/ConcurrencyDetectorTestBase.cs @@ -97,5 +97,5 @@ protected override Task SeedAsync(ConcurrencyDetectorDbContext context) => ConcurrencyDetectorDbContext.SeedAsync(context); } - public static IEnumerable IsAsyncData = new object[][] { [false], [true] }; + public static readonly IEnumerable IsAsyncData = [[false], [true]]; } diff --git a/test/EFCore.Specification.Tests/JsonTypesTestBase.cs b/test/EFCore.Specification.Tests/JsonTypesTestBase.cs index c0096a11891..12dd330f29a 100644 --- a/test/EFCore.Specification.Tests/JsonTypesTestBase.cs +++ b/test/EFCore.Specification.Tests/JsonTypesTestBase.cs @@ -17,7 +17,7 @@ namespace Microsoft.EntityFrameworkCore; -public abstract class JsonTypesTestBase : NonSharedModelTestBase +public abstract class JsonTypesTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { [ConditionalTheory] [InlineData(sbyte.MinValue, """{"Prop":-128}""")] @@ -3975,7 +3975,7 @@ protected virtual Task Can_read_and_write_JSON_collection_value existingObject: existingObject, facets: facets); - protected virtual async Task Can_read_and_write_JSON_value( + protected virtual Task Can_read_and_write_JSON_value( Action buildModel, Action? configureConventions, string propertyName, @@ -3986,7 +3986,7 @@ protected virtual async Task Can_read_and_write_JSON_value( Dictionary? facets = null) where TEntity : class { - var contextFactory = await CreateContextFactory( + var contextFactory = CreateContextFactory( buildModel, configureConventions: configureConventions); using var context = contextFactory.CreateContext(); @@ -4060,6 +4060,8 @@ protected virtual async Task Can_read_and_write_JSON_value( { Assert.Null(element); } + + return Task.CompletedTask; } protected override string StoreName diff --git a/test/EFCore.Specification.Tests/LoadTestBase.cs b/test/EFCore.Specification.Tests/LoadTestBase.cs index 1b216612cc2..109dd169a2a 100644 --- a/test/EFCore.Specification.Tests/LoadTestBase.cs +++ b/test/EFCore.Specification.Tests/LoadTestBase.cs @@ -5059,14 +5059,31 @@ public virtual async Task Lazy_loading_is_thread_safe(bool noTracking, bool asyn var parent = query.Single(); var children = (await parent.LazyLoadChildren(async))?.Select(x => x.Id).OrderBy(x => x).ToList(); + var childrenInvert = (await parent.LazyLoadChildren(!async))?.Select(x => x.Id).OrderBy(x => x).ToList(); + var singlePkToPk = (await parent.LazyLoadSinglePkToPk(async))?.Id; + var singlePkToPkInvert = (await parent.LazyLoadSinglePkToPk(!async))?.Id; + var single = (await parent.LazyLoadSingle(async))?.Id; + var singleInvert = (await parent.LazyLoadSingle(!async))?.Id; + var childrenAk = (await parent.LazyLoadChildrenAk(async))?.Select(x => x.Id).OrderBy(x => x).ToList(); + var childrenAkInvert = (await parent.LazyLoadChildrenAk(!async))?.Select(x => x.Id).OrderBy(x => x).ToList(); + var singleAk = (await parent.LazyLoadSingleAk(async))?.Id; + var singleAkInvert = (await parent.LazyLoadSingleAk(!async))?.Id; + var childrenShadowFk = (await parent.LazyLoadChildrenShadowFk(async))?.Select(x => x.Id).OrderBy(x => x).ToList(); + var childrenShadowFkInvert = (await parent.LazyLoadChildrenShadowFk(!async))?.Select(x => x.Id).OrderBy(x => x).ToList(); + var singleShadowFk = (await parent.LazyLoadSingleShadowFk(async))?.Id; + var singleShadowFkInvert = (await parent.LazyLoadSingleShadowFk(!async))?.Id; + var childrenCompositeKey = (await parent.LazyLoadChildrenCompositeKey(async))?.Select(x => x.Id).OrderBy(x => x).ToList(); + var childrenCompositeKeyInvert = (await parent.LazyLoadChildrenCompositeKey(!async))?.Select(x => x.Id).OrderBy(x => x).ToList(); + var singleCompositeKey = (await parent.LazyLoadSingleCompositeKey(async))?.Id; + var singleCompositeKeyInvert = (await parent.LazyLoadSingleCompositeKey(!async))?.Id; var parent2 = query2.Single(); @@ -5075,18 +5092,57 @@ public virtual async Task Lazy_loading_is_thread_safe(bool noTracking, bool asyn MaxDegreeOfParallelism = Environment.ProcessorCount * 500 }; - await Parallel.ForAsync(0, 50000, parallelOptions, async (i, ct) => - { - Assert.Equal(children, (await parent2.LazyLoadChildren(async))?.Select(x => x.Id).OrderBy(x => x).ToList()); - Assert.Equal(singlePkToPk, (await parent2.LazyLoadSinglePkToPk(async))?.Id); - Assert.Equal(single, (await parent2.LazyLoadSingle(async))?.Id); - Assert.Equal(childrenAk, (await parent2.LazyLoadChildrenAk(async))?.Select(x => x.Id).OrderBy(x => x).ToList()); - Assert.Equal(singleAk, (await parent2.LazyLoadSingleAk(async))?.Id); - Assert.Equal(childrenShadowFk, (await parent2.LazyLoadChildrenShadowFk(async))?.Select(x => x.Id).OrderBy(x => x).ToList()); - Assert.Equal(singleShadowFk, (await parent2.LazyLoadSingleShadowFk(async))?.Id); - Assert.Equal(childrenCompositeKey, (await parent2.LazyLoadChildrenCompositeKey(async))?.Select(x => x.Id).OrderBy(x => x).ToList()); - Assert.Equal(singleCompositeKey, (await parent2.LazyLoadSingleCompositeKey(async))?.Id); + await Parallel.ForAsync(0, 10000, parallelOptions, async (i, ct) => + { + await Task.WhenAll( + AssertEqual( + (children, async () => (await parent2.LazyLoadChildren(async))?.Select(x => x.Id).OrderBy(x => x).ToList()), + (childrenInvert, async () => (await parent2.LazyLoadChildren(!async))?.Select(x => x.Id).OrderBy(x => x).ToList()) + ), + AssertEqual( + (singlePkToPk, async () => (await parent2.LazyLoadSinglePkToPk(async))?.Id), + (singlePkToPkInvert, async () => (await parent2.LazyLoadSinglePkToPk(!async))?.Id) + ), + AssertEqual( + (single, async () => (await parent2.LazyLoadSingle(async))?.Id), + (singleInvert, async () => (await parent2.LazyLoadSingle(!async))?.Id) + ), + AssertEqual( + (childrenAk, async () => (await parent2.LazyLoadChildrenAk(async))?.Select(x => x.Id).OrderBy(x => x).ToList()), + (childrenAkInvert, async () => (await parent2.LazyLoadChildrenAk(!async))?.Select(x => x.Id).OrderBy(x => x).ToList()) + ), + AssertEqual( + (singleAk, async () => (await parent2.LazyLoadSingleAk(async))?.Id), + (singleAkInvert, async () => (await parent2.LazyLoadSingleAk(!async))?.Id) + ), + AssertEqual( + (childrenShadowFk, async () => (await parent2.LazyLoadChildrenShadowFk(async))?.Select(x => x.Id).OrderBy(x => x).ToList()), + (childrenShadowFkInvert, async () => (await parent2.LazyLoadChildrenShadowFk(!async))?.Select(x => x.Id).OrderBy(x => x).ToList()) + ), + AssertEqual( + (singleShadowFk, async () => (await parent2.LazyLoadSingleShadowFk(async))?.Id), + (singleShadowFkInvert, async () => (await parent2.LazyLoadSingleShadowFk(!async))?.Id) + ), + AssertEqual( + (childrenCompositeKey, async () => (await parent2.LazyLoadChildrenCompositeKey(async))?.Select(x => x.Id).OrderBy(x => x).ToList()), + (childrenCompositeKeyInvert, async () => (await parent2.LazyLoadChildrenCompositeKey(!async))?.Select(x => x.Id).OrderBy(x => x).ToList()) + ), + AssertEqual( + (singleCompositeKey, async () => (await parent2.LazyLoadSingleCompositeKey(async))?.Id), + (singleCompositeKeyInvert, async () => (await parent2.LazyLoadSingleCompositeKey(!async))?.Id) + ) + ); }); + + static async Task AssertEqual((T Data, Func> Expected) data, (T Data, Func> Expected) dataInvert) + { + //Do the processing at the same time + var dataTask = data.Expected(); + var dataInvertTask = dataInvert.Expected(); + + Assert.Equal(data.Data, await dataTask); + Assert.Equal(dataInvert.Data, await dataInvertTask); + } } private static void SetState( diff --git a/test/EFCore.Specification.Tests/MaterializationInterceptionTestBase.cs b/test/EFCore.Specification.Tests/MaterializationInterceptionTestBase.cs index a14aa2762a2..7f03146f1bd 100644 --- a/test/EFCore.Specification.Tests/MaterializationInterceptionTestBase.cs +++ b/test/EFCore.Specification.Tests/MaterializationInterceptionTestBase.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore; -public abstract class MaterializationInterceptionTestBase : SingletonInterceptorsTestBase +public abstract class MaterializationInterceptionTestBase(NonSharedFixture fixture) : SingletonInterceptorsTestBase(fixture) where TContext : SingletonInterceptorsTestBase.LibraryContext { protected override string StoreName diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexType.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexType.cs index a8fbc2dcbf0..4598eecfa42 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexType.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexType.cs @@ -5,7 +5,7 @@ using System.Dynamic; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using static Microsoft.EntityFrameworkCore.DbLoggerCategory; +using Microsoft.EntityFrameworkCore.TestModels.BasicTypesModel; // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ModelBuilding; @@ -38,7 +38,7 @@ public virtual void Can_set_complex_property_annotation() Assert.Equal("bar2", complexProperty["foo2"]); Assert.Equal(typeof(Customer).Name, complexProperty.Name); Assert.Equal( - @"Customer (Customer) Required + @"Customer (Customer) ComplexType: ComplexProperties.Customer#Customer Properties: " + @" @@ -2224,5 +2224,51 @@ public virtual void Can_call_PrimitiveCollection_on_a_field() Assert.Null(property.PropertyInfo); Assert.NotNull(property.FieldInfo); } + + [ConditionalFact] + public virtual void Can_specify_discriminator_without_explicit_value() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty( + e => e.Quarks, + b => b.HasDiscriminator("Discriminator")); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + Assert.Equal(nameof(Quarks), complexType.GetDiscriminatorValue()); + + var discriminator = complexType.FindDiscriminatorProperty()!; + Assert.False(discriminator.IsNullable); + Assert.Equal(PropertySaveBehavior.Throw, discriminator.GetAfterSaveBehavior()); + Assert.NotNull(discriminator.GetValueGeneratorFactory()); + } + + [ConditionalFact] + public virtual void Can_specify_discriminator_value() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity() + .ComplexProperty( + e => e.Quarks, + b => + { + b.HasDiscriminator("EnumType").HasValue(BasicEnum.Two); + }); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + Assert.Equal(BasicEnum.Two, complexType.GetDiscriminatorValue()); + } } } diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.Generic.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.Generic.cs index abacffdbb5c..f24233a95cc 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.Generic.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.Generic.cs @@ -3,6 +3,8 @@ // ReSharper disable InconsistentNaming +using Microsoft.EntityFrameworkCore.Metadata.Internal; + namespace Microsoft.EntityFrameworkCore.ModelBuilding; public abstract partial class ModelBuilderTest @@ -453,6 +455,15 @@ public override TestComplexPropertyBuilder UsePropertyAccessMode(Prope public override TestComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode) => Wrap(PropertyBuilder.UseDefaultPropertyAccessMode(propertyAccessMode)); + public override TestComplexTypeDiscriminatorBuilder HasDiscriminator(Expression> propertyExpression) + => new GenericTestComplexTypeDiscriminatorBuilder(PropertyBuilder.HasDiscriminator(propertyExpression)); + + public override TestComplexTypeDiscriminatorBuilder HasDiscriminator(string propertyName) + => new GenericTestComplexTypeDiscriminatorBuilder(PropertyBuilder.HasDiscriminator(propertyName)); + + public override TestComplexPropertyBuilder HasNoDiscriminator() + => Wrap(PropertyBuilder.HasNoDiscriminator()); + public ComplexPropertyBuilder Instance => PropertyBuilder; } @@ -481,6 +492,18 @@ public override TestDiscriminatorBuilder HasValue(string entityT => Wrap(DiscriminatorBuilder.HasValue(entityTypeName, value)); } + protected class GenericTestComplexTypeDiscriminatorBuilder(ComplexTypeDiscriminatorBuilder discriminatorBuilder) + : TestComplexTypeDiscriminatorBuilder + { + protected ComplexTypeDiscriminatorBuilder DiscriminatorBuilder { get; } = discriminatorBuilder; + + protected virtual TestComplexTypeDiscriminatorBuilder Wrap(ComplexTypeDiscriminatorBuilder discriminatorBuilder) + => new GenericTestComplexTypeDiscriminatorBuilder(discriminatorBuilder); + + public override TestComplexTypeDiscriminatorBuilder HasValue(TDiscriminator value) + => Wrap(DiscriminatorBuilder.HasValue(value)); + } + protected class GenericTestOwnedEntityTypeBuilder(OwnedEntityTypeBuilder ownedEntityTypeBuilder) : TestOwnedEntityTypeBuilder, IInfrastructure> diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs index ccd94bd3435..b98a21b9418 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs @@ -521,6 +521,16 @@ public override TestComplexPropertyBuilder UsePropertyAccessMode(Prope public override TestComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode) => Wrap(PropertyBuilder.UseDefaultPropertyAccessMode(propertyAccessMode)); + public override TestComplexTypeDiscriminatorBuilder HasDiscriminator(Expression> propertyExpression) + => new NonGenericTestComplexTypeDiscriminatorBuilder(PropertyBuilder.HasDiscriminator( + propertyExpression.GetMemberAccess().GetSimpleMemberName(), propertyExpression.GetMemberAccess().GetMemberType())); + + public override TestComplexTypeDiscriminatorBuilder HasDiscriminator(string propertyName) + => new NonGenericTestComplexTypeDiscriminatorBuilder(PropertyBuilder.HasDiscriminator(propertyName, typeof(TDiscriminator))); + + public override TestComplexPropertyBuilder HasNoDiscriminator() + => Wrap(PropertyBuilder.HasNoDiscriminator()); + public ComplexPropertyBuilder Instance => PropertyBuilder; } @@ -549,6 +559,18 @@ public override TestDiscriminatorBuilder HasValue(string entityT => Wrap(DiscriminatorBuilder.HasValue(entityTypeName, value)); } + protected class NonGenericTestComplexTypeDiscriminatorBuilder(ComplexTypeDiscriminatorBuilder discriminatorBuilder) + : TestComplexTypeDiscriminatorBuilder + { + protected ComplexTypeDiscriminatorBuilder DiscriminatorBuilder { get; } = discriminatorBuilder; + + protected virtual TestComplexTypeDiscriminatorBuilder Wrap(ComplexTypeDiscriminatorBuilder discriminatorBuilder) + => new NonGenericTestComplexTypeDiscriminatorBuilder(discriminatorBuilder); + + public override TestComplexTypeDiscriminatorBuilder HasValue(TDiscriminator value) + => Wrap(DiscriminatorBuilder.HasValue(value)); + } + protected class NonGenericTestOwnedEntityTypeBuilder(OwnedEntityTypeBuilder ownedEntityTypeBuilder) : TestOwnedEntityTypeBuilder, IInfrastructure diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.TestModel.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.TestModel.cs index 29aa3cff881..d363138f6e6 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.TestModel.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.TestModel.cs @@ -886,7 +886,7 @@ protected class ComplexPropertiesBase protected class ComplexProperties : ComplexPropertiesBase { - public required Customer Customer { get; set; } + public Customer? Customer { get; set; } public required DoubleProperty DoubleProperty { get; set; } public required IndexedClass IndexedClass { get; set; } public required Quarks Quarks { get; set; } diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.cs index 1664aa0fb16..4dbd03b3f51 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.cs @@ -376,6 +376,12 @@ public abstract TestComplexPropertyBuilder Ignore( public abstract TestComplexPropertyBuilder HasChangeTrackingStrategy(ChangeTrackingStrategy changeTrackingStrategy); public abstract TestComplexPropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode); public abstract TestComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode); + + public abstract TestComplexTypeDiscriminatorBuilder HasDiscriminator( + Expression> propertyExpression); + + public abstract TestComplexTypeDiscriminatorBuilder HasDiscriminator(string propertyName); + public abstract TestComplexPropertyBuilder HasNoDiscriminator(); } public abstract class TestDiscriminatorBuilder @@ -391,6 +397,11 @@ public abstract class TestDiscriminatorBuilder public abstract TestDiscriminatorBuilder HasValue(string entityTypeName, TDiscriminator value); } + public abstract class TestComplexTypeDiscriminatorBuilder + { + public abstract TestComplexTypeDiscriminatorBuilder HasValue(TDiscriminator value); + } + public abstract class TestOwnedEntityTypeBuilder where TEntity : class; diff --git a/test/EFCore.Specification.Tests/NonSharedModelTestBase.cs b/test/EFCore.Specification.Tests/NonSharedModelTestBase.cs index 3d49751a8a8..def5acf6edb 100644 --- a/test/EFCore.Specification.Tests/NonSharedModelTestBase.cs +++ b/test/EFCore.Specification.Tests/NonSharedModelTestBase.cs @@ -7,30 +7,35 @@ namespace Microsoft.EntityFrameworkCore; public abstract class NonSharedModelTestBase : IAsyncLifetime { - public static IEnumerable IsAsyncData = [[false], [true]]; + public static readonly IEnumerable IsAsyncData = [[false], [true]]; protected abstract string StoreName { get; } protected abstract ITestStoreFactory TestStoreFactory { get; } + protected NonSharedFixture? Fixture { get; set; } + protected virtual ITestOutputHelper? TestOutputHelper { get; set; } private ServiceProvider? _serviceProvider; - protected IServiceProvider ServiceProvider => _serviceProvider ?? throw new InvalidOperationException( $"You must call `await {nameof(InitializeAsync)}(\"DatabaseName\");` at the beginning of the test."); private TestStore? _testStore; - protected TestStore TestStore => _testStore ?? throw new InvalidOperationException( $"You must call `await {nameof(InitializeAsync)}(\"DatabaseName\");` at the beginning of the test."); private ListLoggerFactory? _listLoggerFactory; - protected ListLoggerFactory ListLoggerFactory => _listLoggerFactory ??= (ListLoggerFactory)ServiceProvider.GetRequiredService(); + protected NonSharedModelTestBase() + { } + + protected NonSharedModelTestBase(NonSharedFixture fixture) + => Fixture = fixture; + public virtual Task InitializeAsync() => Task.CompletedTask; @@ -41,13 +46,25 @@ protected virtual async Task> InitializeAsync Action? configureConventions = null, Func? seed = null, Func? shouldLogCategory = null, - Func>? createTestStore = null, + Func? createTestStore = null, bool usePooling = true, bool useServiceProvider = true) where TContext : DbContext { - var contextFactory = await CreateContextFactory( - onModelCreating, onConfiguring, addServices, configureConventions, shouldLogCategory, createTestStore, usePooling, + if (Fixture == null && _testStore != null) + { + await _testStore.DisposeAsync(); + _serviceProvider?.Dispose(); + } + + var contextFactory = CreateContextFactory( + onModelCreating, + onConfiguring, + addServices, + configureConventions, + shouldLogCategory, + createTestStore, + usePooling, useServiceProvider); await TestStore.InitializeAsync(_serviceProvider, contextFactory.CreateContext, seed == null ? null : c => seed((TContext)c)); @@ -57,20 +74,28 @@ protected virtual async Task> InitializeAsync return contextFactory; } - protected async Task> CreateContextFactory( + protected virtual ContextFactory CreateContextFactory( Action? onModelCreating = null, Action? onConfiguring = null, Func? addServices = null, Action? configureConventions = null, Func? shouldLogCategory = null, - Func>? createTestStore = null, + Func? createTestStore = null, bool usePooling = true, bool useServiceProvider = true) where TContext : DbContext { - _testStore = createTestStore != null - ? await createTestStore() - : CreateTestStore(); + if (createTestStore != null) + { + _testStore = createTestStore(); + Fixture = null; + } + else + { + _testStore = Fixture != null + ? Fixture.GetOrCreateTestStore(CreateTestStore) + : CreateTestStore(); + } shouldLogCategory ??= _ => false; var services = AddServices( @@ -144,7 +169,7 @@ public virtual void Dispose() public virtual async Task DisposeAsync() { - if (_testStore != null) + if (Fixture == null && _testStore != null) { await _testStore.DisposeAsync(); _testStore = null; diff --git a/test/EFCore.Specification.Tests/Query/AdHocAdvancedMappingsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocAdvancedMappingsQueryTestBase.cs index 33ffeae80b6..c17c2cc57f3 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocAdvancedMappingsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocAdvancedMappingsQueryTestBase.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public abstract class AdHocAdvancedMappingsQueryTestBase : NonSharedModelTestBase +public abstract class AdHocAdvancedMappingsQueryTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { protected override string StoreName => "AdHocAdvancedMappingsQueryTests"; diff --git a/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs index 1d7350ad47f..3c26e2c3e27 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs @@ -4,7 +4,7 @@ namespace Microsoft.EntityFrameworkCore.Query; // ReSharper disable ClassNeverInstantiated.Local -public abstract class AdHocComplexTypeQueryTestBase : NonSharedModelTestBase +public abstract class AdHocComplexTypeQueryTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { #region 33449 diff --git a/test/EFCore.Specification.Tests/Query/AdHocJsonQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocJsonQueryTestBase.cs new file mode 100644 index 00000000000..c4db05e62ea --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/AdHocJsonQueryTestBase.cs @@ -0,0 +1,1896 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json; + +namespace Microsoft.EntityFrameworkCore.Query; + +#nullable disable + +public abstract class AdHocJsonQueryTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture +{ + protected override string StoreName + => "AdHocJsonQueryTests"; + + protected virtual void ClearLog() + => ListLoggerFactory.Clear(); + + protected virtual void ConfigureWarnings(WarningsConfigurationBuilder builder) + { + } + + #region 21006 + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Project_root_with_missing_scalars(bool async) + { + var contextFactory = await InitializeAsync( + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + onModelCreating: OnModelCreating21006, + seed: Seed21006); + + await using var context = contextFactory.CreateContext(); + + var query = context.Set().Where(x => x.Id < 4); + + var result = async + ? await query.ToListAsync() + : query.ToList()!; + + var topLevel = result.Single(x => x.Id == 2); + var nested = result.Single(x => x.Id == 3); + + Assert.Equal(default, topLevel.OptionalReference.Number); + Assert.Equal(default, topLevel.RequiredReference.Number); + Assert.True(topLevel.Collection.All(x => x.Number == default)); + + Assert.Equal(default, nested.RequiredReference.NestedRequiredReference.DoB); + Assert.Equal(default, nested.RequiredReference.NestedOptionalReference.DoB); + Assert.Equal(default, nested.OptionalReference.NestedRequiredReference.DoB); + Assert.Equal(default, nested.OptionalReference.NestedOptionalReference.DoB); + Assert.True(nested.Collection.SelectMany(x => x.NestedCollection).All(x => x.DoB == default)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Project_top_level_json_entity_with_missing_scalars(bool async) + { + var contextFactory = await InitializeAsync( + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + onModelCreating: OnModelCreating21006, + seed: Seed21006); + + await using var context = contextFactory.CreateContext(); + + var query = context.Set().Where(x => x.Id < 4).Select(x => new + { + x.Id, + x.OptionalReference, + x.RequiredReference, + x.Collection + }).AsNoTracking(); + + var result = async + ? await query.ToListAsync() + : query.ToList(); + + var topLevel = result.Single(x => x.Id == 2); + var nested = result.Single(x => x.Id == 3); + + Assert.Equal(default, topLevel.OptionalReference.Number); + Assert.Equal(default, topLevel.RequiredReference.Number); + Assert.True(topLevel.Collection.All(x => x.Number == default)); + + Assert.Equal(default, nested.RequiredReference.NestedRequiredReference.DoB); + Assert.Equal(default, nested.RequiredReference.NestedOptionalReference.DoB); + Assert.Equal(default, nested.OptionalReference.NestedRequiredReference.DoB); + Assert.Equal(default, nested.OptionalReference.NestedOptionalReference.DoB); + Assert.True(nested.Collection.SelectMany(x => x.NestedCollection).All(x => x.DoB == default)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Project_nested_json_entity_with_missing_scalars(bool async) + { + var contextFactory = await InitializeAsync( + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + onModelCreating: OnModelCreating21006, + seed: Seed21006); + + await using var context = contextFactory.CreateContext(); + + var query = context.Set().Where(x => x.Id < 4).Select(x => new + { + x.Id, + x.OptionalReference.NestedOptionalReference, + x.RequiredReference.NestedRequiredReference, + x.Collection[0].NestedCollection + }).AsNoTracking(); + + var result = async + ? await query.ToListAsync() + : query.ToList(); + + var topLevel = result.Single(x => x.Id == 2); + var nested = result.Single(x => x.Id == 3); + + Assert.Equal(default, nested.NestedOptionalReference.DoB); + Assert.Equal(default, nested.NestedRequiredReference.DoB); + Assert.True(nested.NestedCollection.All(x => x.DoB == default)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Project_top_level_entity_with_null_value_required_scalars(bool async) + { + var contextFactory = await InitializeAsync( + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + onModelCreating: OnModelCreating21006, + seed: Seed21006); + + await using var context = contextFactory.CreateContext(); + + var query = context.Set().Where(x => x.Id == 4).Select(x => new + { + x.Id, + x.RequiredReference, + }).AsNoTracking(); + + var result = async + ? await query.ToListAsync() + : query.ToList(); + + var nullScalars = result.Single(); + + Assert.Equal(default, nullScalars.RequiredReference.Number); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Project_root_entity_with_missing_required_navigation(bool async) + { + var contextFactory = await InitializeAsync( + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + onModelCreating: OnModelCreating21006, + seed: Seed21006); + + await using var context = contextFactory.CreateContext(); + + var query = context.Set().Where(x => x.Id == 5).AsNoTracking(); + + var result = async + ? await query.ToListAsync() + : query.ToList(); + + var missingRequiredNav = result.Single(); + + Assert.Equal(default, missingRequiredNav.RequiredReference.NestedRequiredReference); + Assert.Equal(default, missingRequiredNav.OptionalReference.NestedRequiredReference); + Assert.True(missingRequiredNav.Collection.All(x => x.NestedRequiredReference == default)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Project_missing_required_navigation(bool async) + { + var contextFactory = await InitializeAsync( + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + onModelCreating: OnModelCreating21006, + seed: Seed21006); + + await using var context = contextFactory.CreateContext(); + + var query = context.Set().Where(x => x.Id == 5).Select(x => x.RequiredReference.NestedRequiredReference).AsNoTracking(); + + var result = async + ? await query.ToListAsync() + : query.ToList(); + + var missingRequiredNav = result.Single(); + + Assert.Equal(default, missingRequiredNav); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Project_root_entity_with_null_required_navigation(bool async) + { + var contextFactory = await InitializeAsync( + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + onModelCreating: OnModelCreating21006, + seed: Seed21006); + + await using var context = contextFactory.CreateContext(); + + var query = context.Set().Where(x => x.Id == 6).AsNoTracking(); + + var result = async + ? await query.ToListAsync() + : query.ToList(); + + var nullRequiredNav = result.Single(); + + Assert.Equal(default, nullRequiredNav.RequiredReference.NestedRequiredReference); + Assert.Equal(default, nullRequiredNav.OptionalReference.NestedRequiredReference); + Assert.True(nullRequiredNav.Collection.All(x => x.NestedRequiredReference == default)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Project_null_required_navigation(bool async) + { + var contextFactory = await InitializeAsync( + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + onModelCreating: OnModelCreating21006, + seed: Seed21006); + + await using var context = contextFactory.CreateContext(); + + var query = context.Set().Where(x => x.Id == 6).Select(x => x.RequiredReference).AsNoTracking(); + + var result = async + ? await query.ToListAsync() + : query.ToList(); + + var nullRequiredNav = result.Single(); + + Assert.Equal(default, nullRequiredNav.NestedRequiredReference); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Project_missing_required_scalar(bool async) + { + var contextFactory = await InitializeAsync( + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + onModelCreating: OnModelCreating21006, + seed: Seed21006); + + await using var context = contextFactory.CreateContext(); + + var query = context.Set() + .Where(x => x.Id == 2) + .Select(x => new + { + x.Id, + Number = (double?)x.RequiredReference.Number + }); + + var result = async + ? await query.ToListAsync() + : query.ToList(); + + Assert.Null(result.Single().Number); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Project_null_required_scalar(bool async) + { + var contextFactory = await InitializeAsync( + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + onModelCreating: OnModelCreating21006, + seed: Seed21006); + + await using var context = contextFactory.CreateContext(); + + var query = context.Set() + .Where(x => x.Id == 4) + .Select(x => new + { + x.Id, + Number = (double?)x.RequiredReference.Number, + }); + + var result = async + ? await query.ToListAsync() + : query.ToList(); + + Assert.Null(result.Single().Number); + } + + protected virtual void OnModelCreating21006(ModelBuilder modelBuilder) + => modelBuilder.Entity(b => + { + b.Property(x => x.Id).ValueGeneratedNever(); + b.OwnsOne(x => x.OptionalReference, bb => + { + bb.OwnsOne(x => x.NestedOptionalReference); + bb.OwnsOne(x => x.NestedRequiredReference); + bb.Navigation(x => x.NestedRequiredReference).IsRequired(); + bb.OwnsMany(x => x.NestedCollection); + }); + b.OwnsOne(x => x.RequiredReference, bb => + { + bb.OwnsOne(x => x.NestedOptionalReference); + bb.OwnsOne(x => x.NestedRequiredReference); + bb.Navigation(x => x.NestedRequiredReference).IsRequired(); + bb.OwnsMany(x => x.NestedCollection); + }); + b.Navigation(x => x.RequiredReference).IsRequired(); + b.OwnsMany(x => x.Collection, bb => + { + bb.OwnsOne(x => x.NestedOptionalReference); + bb.OwnsOne(x => x.NestedRequiredReference); + bb.Navigation(x => x.NestedRequiredReference).IsRequired(); + bb.OwnsMany(x => x.NestedCollection); + }); + }); + + protected virtual async Task Seed21006(Context21006 context) + { + // everything + var e1 = new Context21006.Entity + { + Id = 1, + Name = "e1", + OptionalReference = new Context21006.JsonEntity + { + Number = 7, + Text = "e1 or", + NestedOptionalReference = new Context21006.JsonEntityNested { DoB = new DateTime(2000, 1, 1), Text = "e1 or nor" }, + NestedRequiredReference = new Context21006.JsonEntityNested { DoB = new DateTime(2000, 1, 1), Text = "e1 or nrr" }, + NestedCollection = new List + { + new Context21006.JsonEntityNested { DoB = new DateTime(2000, 1, 1), Text = "e1 or c1" }, + new Context21006.JsonEntityNested { DoB = new DateTime(2000, 1, 1), Text = "e1 or c2" }, + } + }, + + RequiredReference = new Context21006.JsonEntity + { + Number = 7, + Text = "e1 rr", + NestedOptionalReference = new Context21006.JsonEntityNested { DoB = new DateTime(2000, 1, 1), Text = "e1 rr nor" }, + NestedRequiredReference = new Context21006.JsonEntityNested { DoB = new DateTime(2000, 1, 1), Text = "e1 rr nrr" }, + NestedCollection = new List + { + new Context21006.JsonEntityNested { DoB = new DateTime(2000, 1, 1), Text = "e1 rr c1" }, + new Context21006.JsonEntityNested { DoB = new DateTime(2000, 1, 1), Text = "e1 rr c2" }, + } + }, + Collection = new List + { + new Context21006.JsonEntity + { + Number = 7, + Text = "e1 c1", + NestedOptionalReference = new Context21006.JsonEntityNested { DoB = new DateTime(2000, 1, 1), Text = "e1 c1 nor" }, + NestedRequiredReference = new Context21006.JsonEntityNested { DoB = new DateTime(2000, 1, 1), Text = "e1 c1 nrr" }, + NestedCollection = new List + { + new Context21006.JsonEntityNested { DoB = new DateTime(2000, 1, 1), Text = "e1 c1 c1" }, + new Context21006.JsonEntityNested { DoB = new DateTime(2000, 1, 1), Text = "e1 c1 c2" }, + } + }, + new Context21006.JsonEntity + { + Number = 7, + Text = "e1 c2", + NestedOptionalReference = new Context21006.JsonEntityNested { DoB = new DateTime(2000, 1, 1), Text = "e1 c2 nor" }, + NestedRequiredReference = new Context21006.JsonEntityNested { DoB = new DateTime(2000, 1, 1), Text = "e1 c2 nrr" }, + NestedCollection = new List + { + new Context21006.JsonEntityNested { DoB = new DateTime(2000, 1, 1), Text = "e1 c2 c1" }, + new Context21006.JsonEntityNested { DoB = new DateTime(2000, 1, 1), Text = "e1 c2 c2" }, + } + }, + } + }; + + context.Add(e1); + await context.SaveChangesAsync(); + } + + protected class Context21006(DbContextOptions options) : DbContext(options) + { + public DbSet Entities { get; set; } + + public class Entity + { + public int Id { get; set; } + public string Name { get; set; } + public JsonEntity OptionalReference { get; set; } + public JsonEntity RequiredReference { get; set; } + public List Collection { get; set; } + } + + public class JsonEntity + { + public string Text { get; set; } + public double Number { get; set; } + + public JsonEntityNested NestedOptionalReference { get; set; } + public JsonEntityNested NestedRequiredReference { get; set; } + public List NestedCollection { get; set; } + } + + public class JsonEntityNested + { + public DateTime DoB { get; set; } + public string Text { get; set; } + } + } + + #endregion + + #region 29219 + + [ConditionalFact] + public virtual async Task Optional_json_properties_materialized_as_null_when_the_element_in_json_is_not_present() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreating29219, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: Seed29219); + + using var context = contextFactory.CreateContext(); + var query = context.Set().Where(x => x.Id == 3); + var result = await query.SingleAsync(); + + Assert.Equal(3, result.Id); + Assert.Null(result.Reference.NullableScalar); + Assert.Null(result.Collection[0].NullableScalar); + } + + [ConditionalFact] + public virtual async Task Can_project_nullable_json_property_when_the_element_in_json_is_not_present() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreating29219, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: Seed29219); + + using var context = contextFactory.CreateContext(); + + var query = context.Set().OrderBy(x => x.Id).Select(x => x.Reference.NullableScalar); + var result = await query.ToListAsync(); + + Assert.Equal(3, result.Count); + Assert.Equal(11, result[0]); + Assert.Null(result[1]); + Assert.Null(result[2]); + } + + protected virtual void OnModelCreating29219(ModelBuilder modelBuilder) + => modelBuilder.Entity(b => + { + b.Property(x => x.Id).ValueGeneratedNever(); + b.OwnsOne(x => x.Reference); + b.OwnsMany(x => x.Collection); + }); + + protected virtual async Task Seed29219(DbContext ctx) + { + var entity1 = new Context29219.MyEntity + { + Id = 1, + Reference = new Context29219.MyJsonEntity { NonNullableScalar = 10, NullableScalar = 11 }, + Collection = + [ + new Context29219.MyJsonEntity { NonNullableScalar = 100, NullableScalar = 101 }, + new Context29219.MyJsonEntity { NonNullableScalar = 200, NullableScalar = 201 }, + new Context29219.MyJsonEntity { NonNullableScalar = 300, NullableScalar = null } + ] + }; + + var entity2 = new Context29219.MyEntity + { + Id = 2, + Reference = new Context29219.MyJsonEntity { NonNullableScalar = 20, NullableScalar = null }, + Collection = [new Context29219.MyJsonEntity { NonNullableScalar = 1001, NullableScalar = null }] + }; + + ctx.AddRange(entity1, entity2); + await ctx.SaveChangesAsync(); + } + + protected class Context29219(DbContextOptions options) : DbContext(options) + { + public class MyEntity + { + public int Id { get; set; } + public MyJsonEntity Reference { get; set; } + public List Collection { get; set; } + } + + public class MyJsonEntity + { + public int NonNullableScalar { get; set; } + public int? NullableScalar { get; set; } + } + } + + #endregion + + #region 30028 + + [ConditionalFact] + public virtual async Task Accessing_missing_navigation_works() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreating30028, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: Seed30028); + + using var context = contextFactory.CreateContext(); + var result = await context.Set().OrderBy(x => x.Id).ToListAsync(); + Assert.Equal(4, result.Count); + Assert.NotNull(result[0].Json.Collection); + Assert.NotNull(result[0].Json.OptionalReference); + Assert.NotNull(result[0].Json.RequiredReference); + + Assert.Null(result[1].Json.Collection); + Assert.NotNull(result[1].Json.OptionalReference); + Assert.NotNull(result[1].Json.RequiredReference); + + Assert.NotNull(result[2].Json.Collection); + Assert.Null(result[2].Json.OptionalReference); + Assert.NotNull(result[2].Json.RequiredReference); + + Assert.NotNull(result[3].Json.Collection); + Assert.NotNull(result[3].Json.OptionalReference); + Assert.Null(result[3].Json.RequiredReference); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Missing_navigation_works_with_deduplication(bool async) + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreating30028, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: Seed30028); + + using var context = contextFactory.CreateContext(); + var queryable = context.Set().OrderBy(x => x.Id).Select( + x => new + { + x, + x.Json, + x.Json.OptionalReference, + x.Json.RequiredReference, + NestedOptional = x.Json.OptionalReference.Nested, + NestedRequired = x.Json.RequiredReference.Nested, + x.Json.Collection, + }).AsNoTracking(); + + var result = async ? await queryable.ToListAsync() : queryable.ToList(); + + Assert.Equal(4, result.Count); + Assert.NotNull(result[0].OptionalReference); + Assert.NotNull(result[0].RequiredReference); + Assert.NotNull(result[0].NestedOptional); + Assert.NotNull(result[0].NestedRequired); + Assert.NotNull(result[0].Collection); + + Assert.NotNull(result[1].OptionalReference); + Assert.NotNull(result[1].RequiredReference); + Assert.NotNull(result[1].NestedOptional); + Assert.NotNull(result[1].NestedRequired); + Assert.Null(result[1].Collection); + + Assert.Null(result[2].OptionalReference); + Assert.NotNull(result[2].RequiredReference); + Assert.Null(result[2].NestedOptional); + Assert.NotNull(result[2].NestedRequired); + Assert.NotNull(result[2].Collection); + + Assert.NotNull(result[3].OptionalReference); + Assert.Null(result[3].RequiredReference); + Assert.NotNull(result[3].NestedOptional); + Assert.Null(result[3].NestedRequired); + Assert.NotNull(result[3].Collection); + } + + protected virtual void OnModelCreating30028(ModelBuilder modelBuilder) + => modelBuilder.Entity(b => + { + b.Property(x => x.Id).ValueGeneratedNever(); + b.OwnsOne(x => x.Json, nb => + { + nb.OwnsMany(x => x.Collection, nnb => nnb.OwnsOne(x => x.Nested)); + nb.OwnsOne(x => x.OptionalReference, nnb => nnb.OwnsOne(x => x.Nested)); + nb.OwnsOne(x => x.RequiredReference, nnb => nnb.OwnsOne(x => x.Nested)); + nb.Navigation(x => x.RequiredReference).IsRequired(); + }); + }); + + protected abstract Task Seed30028(DbContext ctx); + + protected class Context30028(DbContextOptions options) : DbContext(options) + { + public class MyEntity + { + public int Id { get; set; } + public MyJsonRootEntity Json { get; set; } + } + + public class MyJsonRootEntity + { + public string RootName { get; set; } + public MyJsonBranchEntity RequiredReference { get; set; } + public MyJsonBranchEntity OptionalReference { get; set; } + public List Collection { get; set; } + } + + public class MyJsonBranchEntity + { + public string BranchName { get; set; } + public MyJsonLeafEntity Nested { get; set; } + } + + public class MyJsonLeafEntity + { + public string LeafName { get; set; } + } + } + + #endregion + + #region 32310 + + [ConditionalFact] + public virtual async Task Contains_on_nested_collection_with_init_only_navigation() + { + var contextFactory = await InitializeAsync( + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + onModelCreating: OnModelCreating32310, + seed: Seed32310); + + await using var context = contextFactory.CreateContext(); + + var query = context.Set() + .Where(u => u.Visits.DaysVisited.Contains(new DateOnly(2023, 1, 1))); + + var result = await query.FirstOrDefaultAsync(); + + Assert.Equal("FBI", result.Name); + Assert.Equal(new DateOnly(2023, 1, 1), result.Visits.DaysVisited.Single()); + } + + protected virtual void OnModelCreating32310(ModelBuilder modelBuilder) + => modelBuilder.Entity().OwnsOne(e => e.Visits); + + protected virtual async Task Seed32310(DbContext context) + { + var user = new Context32310.Pub + { + Name = "FBI", + Visits = new Context32310.Visits { LocationTag = "tag", DaysVisited = [new DateOnly(2023, 1, 1)] } + }; + + context.Add(user); + await context.SaveChangesAsync(); + } + + protected class Context32310(DbContextOptions options) : DbContext(options) + { + public class Pub + { + public int Id { get; set; } + public required string Name { get; set; } + public Visits Visits { get; set; } = null!; + } + + public class Visits + { + public string LocationTag { get; set; } + public required List DaysVisited { get; init; } + } + } + + #endregion + + #region 32939 + + [ConditionalFact] + public virtual async Task Project_json_with_no_properties() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreating32939, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: Seed32939); + + using var context = contextFactory.CreateContext(); + await context.Set().ToListAsync(); + } + + protected virtual void OnModelCreating32939(ModelBuilder modelBuilder) + { + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne(x => x.Empty); + modelBuilder.Entity().OwnsOne(x => x.FieldOnly); + } + + protected Task Seed32939(DbContext ctx) + { + var entity = new Context32939.Entity { Empty = new Context32939.JsonEmpty(), FieldOnly = new Context32939.JsonFieldOnly() }; + + ctx.Add(entity); + return ctx.SaveChangesAsync(); + } + + protected class Context32939(DbContextOptions options) : DbContext(options) + { + public class Entity + { + public int Id { get; set; } + public JsonEmpty Empty { get; set; } + public JsonFieldOnly FieldOnly { get; set; } + } + + public class JsonEmpty + { + } + + public class JsonFieldOnly + { + public int Field; + } + } + + #endregion + + #region 33046 + + [ConditionalFact] + public virtual async Task Query_with_nested_json_collection_mapped_to_private_field_via_IReadOnlyList() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreating33046, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: Seed33046); + + using var context = contextFactory.CreateContext(); + var query = await context.Set().ToListAsync(); + Assert.Equal(1, query.Count); + } + + protected virtual void OnModelCreating33046(ModelBuilder modelBuilder) + => modelBuilder.Entity(b => + { + b.Property(x => x.Id).ValueGeneratedNever(); + b.OwnsMany(x => x.Rounds, ownedBuilder => + { + ownedBuilder.OwnsMany(r => r.SubRounds); + }); + }); + + protected abstract Task Seed33046(DbContext ctx); + + protected class Context33046(DbContextOptions options) : DbContext(options) + { + public class Review + { + public int Id { get; set; } + +#pragma warning disable IDE0044 // Add readonly modifier + private List _rounds = []; +#pragma warning restore IDE0044 // Add readonly modifier + public IReadOnlyList Rounds + => _rounds.AsReadOnly(); + } + + public class ReviewRound + { + public int RoundNumber { get; set; } + +#pragma warning disable IDE0044 // Add readonly modifier + private readonly List _subRounds = []; +#pragma warning restore IDE0044 // Add readonly modifier + public IReadOnlyList SubRounds + => _subRounds.AsReadOnly(); + } + + public class SubRound + { + public int SubRoundNumber { get; set; } + } + } + + #endregion + + #region 34960 + + [ConditionalFact] + public virtual async Task Project_entity_with_json_null_values() + { + var contextFactory = await InitializeAsync(seed: Seed34960, onModelCreating: OnModelCreating34960); + + using var context = contextFactory.CreateContext(); + var query = await context.Entities.ToListAsync(); + } + + [ConditionalFact] + public virtual async Task Try_project_collection_but_JSON_is_entity() + { + var contextFactory = await InitializeAsync(seed: Seed34960, onModelCreating: OnModelCreating34960); + using var context = contextFactory.CreateContext(); + + await context.Junk.AsNoTracking().Where(x => x.Id == 1).Select(x => x.Collection).FirstOrDefaultAsync(); + } + + [ConditionalFact] + public virtual async Task Try_project_reference_but_JSON_is_collection() + { + var contextFactory = await InitializeAsync(seed: Seed34960, onModelCreating: OnModelCreating34960); + using var context = contextFactory.CreateContext(); + + await context.Junk.AsNoTracking().Where(x => x.Id == 2).Select(x => x.Reference).FirstOrDefaultAsync(); + } + + protected class Context34960(DbContextOptions options) : DbContext(options) + { + public DbSet Entities { get; set; } + public DbSet Junk { get; set; } + + public class Entity + { + public int Id { get; set; } + public JsonEntity Reference { get; set; } + public List Collection { get; set; } + } + + public class JsonEntity + { + public string Name { get; set; } + public double Number { get; set; } + + public JsonEntityNested NestedReference { get; set; } + public List NestedCollection { get; set; } + } + + public class JsonEntityNested + { + public DateTime DoB { get; set; } + public string Text { get; set; } + } + + public class JunkEntity + { + public int Id { get; set; } + public JsonEntity Reference { get; set; } + public List Collection { get; set; } + } + } + + protected virtual void OnModelCreating34960(ModelBuilder modelBuilder) + { + modelBuilder.Entity(b => + { + b.Property(x => x.Id).ValueGeneratedNever(); + + b.OwnsOne(x => x.Reference, b => + { + b.OwnsOne(x => x.NestedReference); + b.OwnsMany(x => x.NestedCollection); + }); + + b.OwnsMany(x => x.Collection, b => + { + b.OwnsOne(x => x.NestedReference); + b.OwnsMany(x => x.NestedCollection); + }); + }); + + modelBuilder.Entity(b => + { + b.Property(x => x.Id).ValueGeneratedNever(); + + b.OwnsOne(x => x.Reference, b => + { + b.Ignore(x => x.NestedReference); + b.Ignore(x => x.NestedCollection); + }); + + b.OwnsMany(x => x.Collection, b => + { + b.Ignore(x => x.NestedReference); + b.Ignore(x => x.NestedCollection); + }); + }); + } + + protected virtual async Task Seed34960(Context34960 ctx) + { + // everything + var e1 = new Context34960.Entity + { + Id = 1, + Reference = new Context34960.JsonEntity + { + Name = "ref1", + Number = 1.5f, + NestedReference = new Context34960.JsonEntityNested + { + DoB = new DateTime(2000, 1, 1), + Text = "nested ref 1" + }, + NestedCollection = + [ + new Context34960.JsonEntityNested + { + DoB = new DateTime(2001, 1, 1), + Text = "nested col 1 1" + }, + new Context34960.JsonEntityNested + { + DoB = new DateTime(2001, 2, 2), + Text = "nested col 1 2" + }, + ], + }, + + Collection = + [ + new Context34960.JsonEntity + { + Name = "col 1 1", + Number = 2.5f, + NestedReference = new Context34960.JsonEntityNested + { + DoB = new DateTime(2010, 1, 1), + Text = "nested col 1 1 ref 1" + }, + NestedCollection = + [ + new Context34960.JsonEntityNested + { + DoB = new DateTime(2011, 1, 1), + Text = "nested col 1 1 col 1 1" + }, + new Context34960.JsonEntityNested + { + DoB = new DateTime(2011, 2, 2), + Text = "nested col 1 1 col 1 2" + }, + ], + }, + new Context34960.JsonEntity + { + Name = "col 1 2", + Number = 2.5f, + NestedReference = new Context34960.JsonEntityNested + { + DoB = new DateTime(2020, 1, 1), + Text = "nested col 1 2 ref 1" + }, + NestedCollection = + [ + new Context34960.JsonEntityNested + { + DoB = new DateTime(2021, 1, 1), + Text = "nested col 1 2 col 1 1" + }, + new Context34960.JsonEntityNested + { + DoB = new DateTime(2021, 2, 2), + Text = "nested col 1 2 col 1 2" + }, + ], + }, + ], + }; + + // relational nulls + var e2 = new Context34960.Entity + { + Id = 2, + Reference = null, + Collection = null + }; + + // nested relational nulls + var e3 = new Context34960.Entity + { + Id = 3, + Reference = new Context34960.JsonEntity + { + Name = "ref3", + Number = 3.5f, + NestedReference = null, + NestedCollection = null + }, + + Collection = + [ + new Context34960.JsonEntity + { + Name = "col 3 1", + Number = 32.5f, + NestedReference = null, + NestedCollection = null, + }, + new Context34960.JsonEntity + { + Name = "col 3 2", + Number = 33.5f, + NestedReference = null, + NestedCollection = null, + }, + ], + }; + + ctx.Entities.AddRange(e1, e2, e3); + await ctx.SaveChangesAsync(); + } + + #endregion + + #region ArrayOfPrimitives + + [ConditionalFact] + public virtual async Task Project_json_array_of_primitives_on_reference() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingArrayOfPrimitives, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedArrayOfPrimitives); + + using var context = contextFactory.CreateContext(); + var query = context.Set().OrderBy(x => x.Id) + .Select(x => new { x.Reference.IntArray, x.Reference.ListOfString }); + + var result = await query.ToListAsync(); + + Assert.Equal(2, result.Count); + Assert.Equal(3, result[0].IntArray.Length); + Assert.Equal(3, result[0].ListOfString.Count); + Assert.Equal(3, result[1].IntArray.Length); + Assert.Equal(3, result[1].ListOfString.Count); + } + + [ConditionalFact(Skip = "Issue #32611")] + public virtual async Task Project_json_array_of_primitives_on_collection() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingArrayOfPrimitives, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedArrayOfPrimitives); + + using var context = contextFactory.CreateContext(); + var query = context.Set().OrderBy(x => x.Id) + .Select(x => new { x.Collection[0].IntArray, x.Collection[1].ListOfString }); + + var result = await query.ToListAsync(); + + Assert.Equal(2, result.Count); + Assert.Equal(3, result[0].IntArray.Length); + Assert.Equal(2, result[0].ListOfString.Count); + Assert.Equal(3, result[1].IntArray.Length); + Assert.Equal(2, result[1].ListOfString.Count); + } + + [ConditionalFact] + public virtual async Task Project_element_of_json_array_of_primitives() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingArrayOfPrimitives, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedArrayOfPrimitives); + + using var context = contextFactory.CreateContext(); + var query = context.Set().OrderBy(x => x.Id).Select( + x => new { ArrayElement = x.Reference.IntArray[0], ListElement = x.Reference.ListOfString[1] }); + var result = await query.ToListAsync(); + } + + [ConditionalFact] + public virtual async Task Predicate_based_on_element_of_json_array_of_primitives1() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingArrayOfPrimitives, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedArrayOfPrimitives); + + using var context = contextFactory.CreateContext(); + var query = context.Set().Where(x => x.Reference.IntArray[0] == 1); + var result = await query.ToListAsync(); + + Assert.Equal(1, result.Count); + Assert.Equal(1, result[0].Reference.IntArray[0]); + } + + [ConditionalFact] + public virtual async Task Predicate_based_on_element_of_json_array_of_primitives2() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingArrayOfPrimitives, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedArrayOfPrimitives); + + using var context = contextFactory.CreateContext(); + var query = context.Set().Where(x => x.Reference.ListOfString[1] == "Bar"); + var result = await query.ToListAsync(); + + Assert.Equal(1, result.Count); + Assert.Equal("Bar", result[0].Reference.ListOfString[1]); + } + + [ConditionalFact] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Predicate_based_on_element_of_json_array_of_primitives3() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingArrayOfPrimitives, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedArrayOfPrimitives); + + using var context = contextFactory.CreateContext(); + var query = context.Set() + .Where(x => x.Reference.IntArray.AsQueryable().ElementAt(0) == 1 + || x.Reference.ListOfString.AsQueryable().ElementAt(1) == "Bar") + .OrderBy(e => e.Id); + var result = await query.ToListAsync(); + + Assert.Equal(1, result.Count); + Assert.Equal(1, result[0].Reference.IntArray[0]); + Assert.Equal("Bar", result[0].Reference.ListOfString[1]); + } + + protected Task SeedArrayOfPrimitives(DbContext ctx) + { + var entity1 = new ContextArrayOfPrimitives.MyEntity + { + Id = 1, + Reference = new ContextArrayOfPrimitives.MyJsonEntity + { + IntArray = [1, 2, 3], + ListOfString = + [ + "Foo", + "Bar", + "Baz" + ] + }, + Collection = + [ + new ContextArrayOfPrimitives.MyJsonEntity { IntArray = [111, 112, 113], ListOfString = ["Foo11", "Bar11"] }, + new ContextArrayOfPrimitives.MyJsonEntity { IntArray = [211, 212, 213], ListOfString = ["Foo12", "Bar12"] } + ] + }; + + var entity2 = new ContextArrayOfPrimitives.MyEntity + { + Id = 2, + Reference = new ContextArrayOfPrimitives.MyJsonEntity + { + IntArray = [10, 20, 30], + ListOfString = + [ + "A", + "B", + "C" + ] + }, + Collection = + [ + new ContextArrayOfPrimitives.MyJsonEntity { IntArray = [110, 120, 130], ListOfString = ["A1", "Z1"] }, + new ContextArrayOfPrimitives.MyJsonEntity { IntArray = [210, 220, 230], ListOfString = ["A2", "Z2"] } + ] + }; + + ctx.Set().AddRange(entity1, entity2); + + return ctx.SaveChangesAsync(); + } + + protected virtual void OnModelCreatingArrayOfPrimitives(ModelBuilder modelBuilder) + { + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne(x => x.Reference); + modelBuilder.Entity().OwnsMany(x => x.Collection); + } + + protected class ContextArrayOfPrimitives(DbContextOptions options) : DbContext(options) + { + public class MyEntity + { + public int Id { get; set; } + public MyJsonEntity Reference { get; set; } + public List Collection { get; set; } + } + + public class MyJsonEntity + { + public int[] IntArray { get; set; } + public List ListOfString { get; set; } + } + } + + #endregion + + #region JunkInJson + + [ConditionalFact] + public virtual async Task Junk_in_json_basic_tracking() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingJunkInJson, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedJunkInJson); + + using var context = contextFactory.CreateContext(); + var query = context.Set(); + var result = await query.ToListAsync(); + + Assert.Equal(1, result.Count); + Assert.Equal(2, result[0].Collection.Count); + Assert.Equal(2, result[0].CollectionWithCtor.Count); + Assert.Equal(2, result[0].Reference.NestedCollection.Count); + Assert.NotNull(result[0].Reference.NestedReference); + Assert.Equal(2, result[0].ReferenceWithCtor.NestedCollection.Count); + Assert.NotNull(result[0].ReferenceWithCtor.NestedReference); + } + + [ConditionalFact] + public virtual async Task Junk_in_json_basic_no_tracking() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingJunkInJson, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedJunkInJson); + + using var context = contextFactory.CreateContext(); + var query = context.Set().AsNoTracking(); + var result = await query.ToListAsync(); + + Assert.Equal(1, result.Count); + Assert.Equal(2, result[0].Collection.Count); + Assert.Equal(2, result[0].CollectionWithCtor.Count); + Assert.Equal(2, result[0].Reference.NestedCollection.Count); + Assert.NotNull(result[0].Reference.NestedReference); + Assert.Equal(2, result[0].ReferenceWithCtor.NestedCollection.Count); + Assert.NotNull(result[0].ReferenceWithCtor.NestedReference); + } + + protected abstract Task SeedJunkInJson(DbContext ctx); + + protected virtual void OnModelCreatingJunkInJson(ModelBuilder modelBuilder) + => modelBuilder.Entity( + b => + { + b.Property(x => x.Id).ValueGeneratedNever(); + + b.OwnsOne(x => x.Reference, b => + { + b.OwnsOne(x => x.NestedReference); + b.OwnsMany(x => x.NestedCollection); + }); + + b.OwnsOne(x => x.ReferenceWithCtor, b => + { + b.OwnsOne(x => x.NestedReference); + b.OwnsMany(x => x.NestedCollection); + }); + + b.OwnsMany(x => x.Collection, b => + { + b.OwnsOne(x => x.NestedReference); + b.OwnsMany(x => x.NestedCollection); + }); + + b.OwnsMany(x => x.CollectionWithCtor, b => + { + b.OwnsOne(x => x.NestedReference); + b.OwnsMany(x => x.NestedCollection); + }); + }); + + protected class ContextJunkInJson(DbContextOptions options) : DbContext(options) + { + public class MyEntity + { + public int Id { get; set; } + public MyJsonEntity Reference { get; set; } + public MyJsonEntityWithCtor ReferenceWithCtor { get; set; } + public List Collection { get; set; } + public List CollectionWithCtor { get; set; } + } + + public class MyJsonEntity + { + public string Name { get; set; } + public double Number { get; set; } + + public MyJsonEntityNested NestedReference { get; set; } + public List NestedCollection { get; set; } + } + + public class MyJsonEntityNested + { + public DateTime DoB { get; set; } + } + + public class MyJsonEntityWithCtor(bool myBool, string name) + { + public bool MyBool { get; set; } = myBool; + public string Name { get; set; } = name; + + public MyJsonEntityWithCtorNested NestedReference { get; set; } + public List NestedCollection { get; set; } + } + + public class MyJsonEntityWithCtorNested(DateTime doB) + { + public DateTime DoB { get; set; } = doB; + } + } + + #endregion + + #region TrickyBuffering + + [ConditionalFact] + public virtual async Task Tricky_buffering_basic() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingTrickyBuffering, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedTrickyBuffering); + + using var context = contextFactory.CreateContext(); + var query = context.Set(); + var result = await query.ToListAsync(); + + Assert.Equal(1, result.Count); + Assert.Equal("r1", result[0].Reference.Name); + Assert.Equal(7, result[0].Reference.Number); + Assert.Equal(new DateTime(2000, 1, 1), result[0].Reference.NestedReference.DoB); + Assert.Equal(2, result[0].Reference.NestedCollection.Count); + } + + protected abstract Task SeedTrickyBuffering(DbContext ctx); + + protected virtual void OnModelCreatingTrickyBuffering(ModelBuilder modelBuilder) + => modelBuilder.Entity(b => + { + b.Property(x => x.Id).ValueGeneratedNever(); + b.OwnsOne(x => x.Reference, b => + { + b.OwnsOne(x => x.NestedReference); + b.OwnsMany(x => x.NestedCollection); + }); + }); + + protected class ContextTrickyBuffering(DbContextOptions options) : DbContext(options) + { + public class MyEntity + { + public int Id { get; set; } + public MyJsonEntity Reference { get; set; } + } + + public class MyJsonEntity + { + public string Name { get; set; } + public int Number { get; set; } + public MyJsonEntityNested NestedReference { get; set; } + public List NestedCollection { get; set; } + } + + public class MyJsonEntityNested + { + public DateTime DoB { get; set; } + } + } + + #endregion + + #region ShadowProperties + + [ConditionalFact] + public virtual async Task Shadow_properties_basic_tracking() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingShadowProperties, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedShadowProperties); + + using var context = contextFactory.CreateContext(); + var query = context.Set(); + var result = await query.ToListAsync(); + + Assert.Equal(1, result.Count); + Assert.Equal(2, result[0].Collection.Count); + Assert.Equal(2, result[0].CollectionWithCtor.Count); + Assert.NotNull(result[0].Reference); + Assert.NotNull(result[0].ReferenceWithCtor); + + var referenceEntry = context.ChangeTracker.Entries().Single(x => x.Entity == result[0].Reference); + Assert.Equal("Foo", referenceEntry.Property("ShadowString").CurrentValue); + + var referenceCtorEntry = context.ChangeTracker.Entries().Single(x => x.Entity == result[0].ReferenceWithCtor); + Assert.Equal(143, referenceCtorEntry.Property("Shadow_Int").CurrentValue); + + var collectionEntry1 = context.ChangeTracker.Entries().Single(x => x.Entity == result[0].Collection[0]); + var collectionEntry2 = context.ChangeTracker.Entries().Single(x => x.Entity == result[0].Collection[1]); + Assert.Equal(5.5, collectionEntry1.Property("ShadowDouble").CurrentValue); + Assert.Equal(20.5, collectionEntry2.Property("ShadowDouble").CurrentValue); + + var collectionCtorEntry1 = context.ChangeTracker.Entries().Single(x => x.Entity == result[0].CollectionWithCtor[0]); + var collectionCtorEntry2 = context.ChangeTracker.Entries().Single(x => x.Entity == result[0].CollectionWithCtor[1]); + Assert.Equal((byte)6, collectionCtorEntry1.Property("ShadowNullableByte").CurrentValue); + Assert.Null(collectionCtorEntry2.Property("ShadowNullableByte").CurrentValue); + } + + [ConditionalFact] + public virtual async Task Shadow_properties_basic_no_tracking() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingShadowProperties, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedShadowProperties); + + using var context = contextFactory.CreateContext(); + var query = context.Set().AsNoTracking(); + var result = await query.ToListAsync(); + + Assert.Equal(1, result.Count); + Assert.Equal(2, result[0].Collection.Count); + Assert.Equal(2, result[0].CollectionWithCtor.Count); + Assert.NotNull(result[0].Reference); + Assert.NotNull(result[0].ReferenceWithCtor); + } + + [ConditionalFact] + public virtual async Task Project_shadow_properties_from_json_entity() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingShadowProperties, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedShadowProperties); + + using var context = contextFactory.CreateContext(); + var query = context.Set().Select( + x => new + { + ShadowString = EF.Property(x.Reference, "ShadowString"), + ShadowInt = EF.Property(x.ReferenceWithCtor, "Shadow_Int"), + }); + + var result = await query.ToListAsync(); + + Assert.Equal(1, result.Count); + Assert.Equal("Foo", result[0].ShadowString); + Assert.Equal(143, result[0].ShadowInt); + } + + protected abstract Task SeedShadowProperties(DbContext ctx); + + protected virtual void OnModelCreatingShadowProperties(ModelBuilder modelBuilder) + => modelBuilder.Entity(b => + { + b.Property(x => x.Id).ValueGeneratedNever(); + + b.OwnsOne(x => x.Reference, b => + { + b.Property("ShadowString"); + }); + + b.OwnsOne(x => x.ReferenceWithCtor, b => + { + b.Property("Shadow_Int"); + }); + + b.OwnsMany(x => x.Collection, b => + { + b.Property("ShadowDouble"); + }); + + b.OwnsMany(x => x.CollectionWithCtor, b => + { + b.Property("ShadowNullableByte"); + }); + }); + + protected class ContextShadowProperties(DbContextOptions options) : DbContext(options) + { + public class MyEntity + { + public int Id { get; set; } + public string Name { get; set; } + + public MyJsonEntity Reference { get; set; } + public List Collection { get; set; } + public MyJsonEntityWithCtor ReferenceWithCtor { get; set; } + public List CollectionWithCtor { get; set; } + } + + public class MyJsonEntity + { + public string Name { get; set; } + } + + public class MyJsonEntityWithCtor(string name) + { + public string Name { get; set; } = name; + } + } + + #endregion + + #region LazyLoadingProxies + + [ConditionalFact] + public virtual async Task Project_proxies_entity_with_json() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingLazyLoadingProxies, + seed: SeedLazyLoadingProxies, + onConfiguring: b => + { + b = b.ConfigureWarnings(ConfigureWarnings); + OnConfiguringLazyLoadingProxies(b); + }, + addServices: AddServicesLazyLoadingProxies); + + using var context = contextFactory.CreateContext(); + var query = context.Set(); + var result = await query.ToListAsync(); + + Assert.Equal(2, result.Count); + } + + protected void OnConfiguringLazyLoadingProxies(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseLazyLoadingProxies(); + + protected IServiceCollection AddServicesLazyLoadingProxies(IServiceCollection addServices) + => addServices.AddEntityFrameworkProxies(); + + private Task SeedLazyLoadingProxies(DbContext ctx) + { + var r1 = new ContextLazyLoadingProxies.MyJsonEntityWithCtor("r1", 1); + var c11 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c11", Number = 11 }; + var c12 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c12", Number = 12 }; + var c13 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c13", Number = 13 }; + + var r2 = new ContextLazyLoadingProxies.MyJsonEntityWithCtor("r2", 2); + var c21 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c21", Number = 21 }; + var c22 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c22", Number = 22 }; + + var e1 = new ContextLazyLoadingProxies.MyEntity + { + Id = 1, + Name = "e1", + Reference = r1, + Collection = + [ + c11, + c12, + c13 + ] + }; + + var e2 = new ContextLazyLoadingProxies.MyEntity + { + Id = 2, + Name = "e2", + Reference = r2, + Collection = [c21, c22] + }; + + ctx.Set().AddRange(e1, e2); + return ctx.SaveChangesAsync(); + } + + protected virtual void OnModelCreatingLazyLoadingProxies(ModelBuilder modelBuilder) + { + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne(x => x.Reference); + modelBuilder.Entity().OwnsMany(x => x.Collection); + } + + public class ContextLazyLoadingProxies(DbContextOptions options) : DbContext(options) + { + public class MyEntity + { + public int Id { get; set; } + public string Name { get; set; } + + public virtual MyJsonEntityWithCtor Reference { get; set; } + public virtual List Collection { get; set; } + } + + public class MyJsonEntityWithCtor(string name, int number) + { + public string Name { get; set; } = name; + public int Number { get; set; } = number; + } + + public class MyJsonEntity + { + public string Name { get; set; } + public int Number { get; set; } + } + } + + #endregion + + #region NotICollection + + [ConditionalFact] + public virtual async Task Not_ICollection_basic_projection() + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingNotICollection, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedNotICollection); + + using var context = contextFactory.CreateContext(); + var query = context.Set(); + var result = await query.ToListAsync(); + + Assert.Equal(2, result.Count); + } + + protected abstract Task SeedNotICollection(DbContext ctx); + + protected virtual void OnModelCreatingNotICollection(ModelBuilder modelBuilder) + => modelBuilder.Entity(b => + { + b.Property(x => x.Id).ValueGeneratedNever(); + b.OwnsOne(cr => cr.Json, nb => + { + nb.OwnsMany(x => x.Collection); + }); + }); + + + protected class ContextNotICollection(DbContextOptions options) : DbContext(options) + { + public class MyEntity + { + public int Id { get; set; } + + public MyJsonEntity Json { get; set; } + } + + public class MyJsonEntity + { + private readonly List _collection = []; + + public IEnumerable Collection + => _collection.AsReadOnly(); + } + + public class MyJsonNestedEntity + { + public string Foo { get; set; } + public int Bar { get; set; } + } + } + + #endregion + + #region BadJsonProperties + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Bad_json_properties_duplicated_navigations(bool noTracking) + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingBadJsonProperties, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedBadJsonProperties); + + using var context = contextFactory.CreateContext(); + var query = noTracking ? context.Entities.AsNoTracking() : context.Entities; + var baseline = await query.SingleAsync(x => x.Scenario == "baseline"); + var dupNavs = await query.SingleAsync(x => x.Scenario == "duplicated navigations"); + + // for no tracking, last one wins + Assert.Equal(baseline.RequiredReference.NestedOptional.Text + " dupnav", dupNavs.RequiredReference.NestedOptional.Text); + Assert.Equal(baseline.RequiredReference.NestedRequired.Text + " dupnav", dupNavs.RequiredReference.NestedRequired.Text); + Assert.Equal(baseline.RequiredReference.NestedCollection[0].Text + " dupnav", dupNavs.RequiredReference.NestedCollection[0].Text); + Assert.Equal(baseline.RequiredReference.NestedCollection[1].Text + " dupnav", dupNavs.RequiredReference.NestedCollection[1].Text); + + Assert.Equal(baseline.OptionalReference.NestedOptional.Text + " dupnav", dupNavs.OptionalReference.NestedOptional.Text); + Assert.Equal(baseline.OptionalReference.NestedRequired.Text + " dupnav", dupNavs.OptionalReference.NestedRequired.Text); + Assert.Equal(baseline.OptionalReference.NestedCollection[0].Text + " dupnav", dupNavs.OptionalReference.NestedCollection[0].Text); + Assert.Equal(baseline.OptionalReference.NestedCollection[1].Text + " dupnav", dupNavs.OptionalReference.NestedCollection[1].Text); + + Assert.Equal(baseline.Collection[0].NestedOptional.Text + " dupnav", dupNavs.Collection[0].NestedOptional.Text); + Assert.Equal(baseline.Collection[0].NestedRequired.Text + " dupnav", dupNavs.Collection[0].NestedRequired.Text); + Assert.Equal(baseline.Collection[0].NestedCollection[0].Text + " dupnav", dupNavs.Collection[0].NestedCollection[0].Text); + Assert.Equal(baseline.Collection[0].NestedCollection[1].Text + " dupnav", dupNavs.Collection[0].NestedCollection[1].Text); + + Assert.Equal(baseline.Collection[1].NestedOptional.Text + " dupnav", dupNavs.Collection[1].NestedOptional.Text); + Assert.Equal(baseline.Collection[1].NestedRequired.Text + " dupnav", dupNavs.Collection[1].NestedRequired.Text); + Assert.Equal(baseline.Collection[1].NestedCollection[0].Text + " dupnav", dupNavs.Collection[1].NestedCollection[0].Text); + Assert.Equal(baseline.Collection[1].NestedCollection[1].Text + " dupnav", dupNavs.Collection[1].NestedCollection[1].Text); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Bad_json_properties_duplicated_scalars(bool noTracking) + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingBadJsonProperties, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedBadJsonProperties); + + using var context = contextFactory.CreateContext(); + var query = noTracking ? context.Entities.AsNoTracking() : context.Entities; + + var baseline = await query.SingleAsync(x => x.Scenario == "baseline"); + var dupProps = await query.SingleAsync(x => x.Scenario == "duplicated scalars"); + + Assert.Equal(baseline.RequiredReference.NestedOptional.Text + " dupprop", dupProps.RequiredReference.NestedOptional.Text); + Assert.Equal(baseline.RequiredReference.NestedRequired.Text + " dupprop", dupProps.RequiredReference.NestedRequired.Text); + Assert.Equal(baseline.RequiredReference.NestedCollection[0].Text + " dupprop", dupProps.RequiredReference.NestedCollection[0].Text); + Assert.Equal(baseline.RequiredReference.NestedCollection[1].Text + " dupprop", dupProps.RequiredReference.NestedCollection[1].Text); + + Assert.Equal(baseline.OptionalReference.NestedOptional.Text + " dupprop", dupProps.OptionalReference.NestedOptional.Text); + Assert.Equal(baseline.OptionalReference.NestedRequired.Text + " dupprop", dupProps.OptionalReference.NestedRequired.Text); + Assert.Equal(baseline.OptionalReference.NestedCollection[0].Text + " dupprop", dupProps.OptionalReference.NestedCollection[0].Text); + Assert.Equal(baseline.OptionalReference.NestedCollection[1].Text + " dupprop", dupProps.OptionalReference.NestedCollection[1].Text); + + Assert.Equal(baseline.Collection[0].NestedOptional.Text + " dupprop", dupProps.Collection[0].NestedOptional.Text); + Assert.Equal(baseline.Collection[0].NestedRequired.Text + " dupprop", dupProps.Collection[0].NestedRequired.Text); + Assert.Equal(baseline.Collection[0].NestedCollection[0].Text + " dupprop", dupProps.Collection[0].NestedCollection[0].Text); + Assert.Equal(baseline.Collection[0].NestedCollection[1].Text + " dupprop", dupProps.Collection[0].NestedCollection[1].Text); + + Assert.Equal(baseline.Collection[1].NestedOptional.Text + " dupprop", dupProps.Collection[1].NestedOptional.Text); + Assert.Equal(baseline.Collection[1].NestedRequired.Text + " dupprop", dupProps.Collection[1].NestedRequired.Text); + Assert.Equal(baseline.Collection[1].NestedCollection[0].Text + " dupprop", dupProps.Collection[1].NestedCollection[0].Text); + Assert.Equal(baseline.Collection[1].NestedCollection[1].Text + " dupprop", dupProps.Collection[1].NestedCollection[1].Text); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Bad_json_properties_empty_navigations(bool noTracking) + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingBadJsonProperties, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedBadJsonProperties); + + using var context = contextFactory.CreateContext(); + var query = noTracking ? context.Entities.AsNoTracking() : context.Entities; + var emptyNavs = await query.SingleAsync(x => x.Scenario == "empty navigation property names"); + + Assert.Null(emptyNavs.RequiredReference.NestedOptional); + Assert.Null(emptyNavs.RequiredReference.NestedRequired); + Assert.Null(emptyNavs.RequiredReference.NestedCollection); + + Assert.Null(emptyNavs.OptionalReference.NestedOptional); + Assert.Null(emptyNavs.OptionalReference.NestedRequired); + Assert.Null(emptyNavs.OptionalReference.NestedCollection); + + Assert.Null(emptyNavs.Collection[0].NestedOptional); + Assert.Null(emptyNavs.Collection[0].NestedRequired); + Assert.Null(emptyNavs.Collection[0].NestedCollection); + + Assert.Null(emptyNavs.Collection[1].NestedOptional); + Assert.Null(emptyNavs.Collection[1].NestedRequired); + Assert.Null(emptyNavs.Collection[1].NestedCollection); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Bad_json_properties_empty_scalars(bool noTracking) + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingBadJsonProperties, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedBadJsonProperties); + + using var context = contextFactory.CreateContext(); + var query = noTracking ? context.Entities.AsNoTracking() : context.Entities; + var emptyNavs = await query.SingleAsync(x => x.Scenario == "empty scalar property names"); + + Assert.Null(emptyNavs.RequiredReference.NestedOptional.Text); + Assert.Null(emptyNavs.RequiredReference.NestedRequired.Text); + Assert.Null(emptyNavs.RequiredReference.NestedCollection[0].Text); + Assert.Null(emptyNavs.RequiredReference.NestedCollection[1].Text); + + Assert.Null(emptyNavs.OptionalReference.NestedOptional.Text); + Assert.Null(emptyNavs.OptionalReference.NestedRequired.Text); + Assert.Null(emptyNavs.OptionalReference.NestedCollection[0].Text); + Assert.Null(emptyNavs.OptionalReference.NestedCollection[1].Text); + + Assert.Null(emptyNavs.Collection[0].NestedOptional.Text); + Assert.Null(emptyNavs.Collection[0].NestedRequired.Text); + Assert.Null(emptyNavs.Collection[0].NestedCollection[0].Text); + Assert.Null(emptyNavs.Collection[0].NestedCollection[1].Text); + + Assert.Null(emptyNavs.Collection[1].NestedOptional.Text); + Assert.Null(emptyNavs.Collection[1].NestedRequired.Text); + Assert.Null(emptyNavs.Collection[1].NestedCollection[0].Text); + Assert.Null(emptyNavs.Collection[1].NestedCollection[1].Text); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Bad_json_properties_null_navigations(bool noTracking) + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingBadJsonProperties, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedBadJsonProperties); + + using var context = contextFactory.CreateContext(); + var query = noTracking ? context.Entities.AsNoTracking() : context.Entities; + var _ = await query.SingleAsync(x => x.Scenario == "null navigation property names"); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Bad_json_properties_null_scalars(bool noTracking) + { + var contextFactory = await InitializeAsync( + onModelCreating: OnModelCreatingBadJsonProperties, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedBadJsonProperties); + + using var context = contextFactory.CreateContext(); + var query = noTracking ? context.Entities.AsNoTracking() : context.Entities; + var _ = await query.SingleAsync(x => x.Scenario == "null scalar property names"); + } + + protected abstract Task SeedBadJsonProperties(ContextBadJsonProperties ctx); + + protected virtual void OnModelCreatingBadJsonProperties(ModelBuilder modelBuilder) + => modelBuilder.Entity(b => + { + b.Property(x => x.Id).ValueGeneratedNever(); + + b.OwnsOne(x => x.RequiredReference, b => + { + b.OwnsOne(x => x.NestedOptional); + b.OwnsOne(x => x.NestedRequired); + b.OwnsMany(x => x.NestedCollection); + }); + + b.OwnsOne(x => x.OptionalReference, b => + { + b.OwnsOne(x => x.NestedOptional); + b.OwnsOne(x => x.NestedRequired); + b.OwnsMany(x => x.NestedCollection); + }); + + b.OwnsMany(x => x.Collection, b => + { + b.OwnsOne(x => x.NestedOptional); + b.OwnsOne(x => x.NestedRequired); + b.OwnsMany(x => x.NestedCollection); + }); + }); + + protected class ContextBadJsonProperties(DbContextOptions options) : DbContext(options) + { + public DbSet Entities { get; set; } + + public class Entity + { + public int Id { get; set; } + public string Scenario { get; set; } + public JsonRoot OptionalReference { get; set; } + public JsonRoot RequiredReference { get; set; } + public List Collection { get; set; } + } + + public class JsonRoot + { + public JsonBranch NestedRequired { get; set; } + public JsonBranch NestedOptional { get; set; } + public List NestedCollection { get; set; } + } + + public class JsonBranch + { + public string Text { get; set; } + } + } + + #endregion +} diff --git a/test/EFCore.Specification.Tests/Query/AdHocManyToManyQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocManyToManyQueryTestBase.cs index 5db2b2bc962..e6f3a3cdfe2 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocManyToManyQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocManyToManyQueryTestBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore; #nullable disable -public abstract class AdHocManyToManyQueryTestBase : NonSharedModelTestBase +public abstract class AdHocManyToManyQueryTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { protected override string StoreName => "AdHocManyToManyQueryTests"; diff --git a/test/EFCore.Specification.Tests/Query/AdHocMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocMiscellaneousQueryTestBase.cs index a512eea2a21..00e371277d1 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocMiscellaneousQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocMiscellaneousQueryTestBase.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore; #nullable disable -public abstract class AdHocMiscellaneousQueryTestBase : NonSharedModelTestBase +public abstract class AdHocMiscellaneousQueryTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { protected override string StoreName => "AdHocMiscellaneousQueryTests"; diff --git a/test/EFCore.Specification.Tests/Query/AdHocNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocNavigationsQueryTestBase.cs index e078a525e00..6c75cadded8 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocNavigationsQueryTestBase.cs @@ -10,7 +10,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public abstract class AdHocNavigationsQueryTestBase : NonSharedModelTestBase +public abstract class AdHocNavigationsQueryTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { protected override string StoreName => "AdHocNavigationsQueryTests"; diff --git a/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs index 8c24c6d3ab4..24b1bcc0b35 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public abstract class AdHocQueryFiltersQueryTestBase : NonSharedModelTestBase +public abstract class AdHocQueryFiltersQueryTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { protected override string StoreName => "AdHocQueryFiltersQueryTests"; @@ -417,132 +417,6 @@ public class User18759 #endregion - #region 19708 - - [ConditionalFact] - public virtual async Task GroupJoin_SelectMany_gets_flattened() - { - var contextFactory = await InitializeAsync(seed: c => c.SeedAsync()); - using (var context = contextFactory.CreateContext()) - { - var query = context.CustomerFilters.ToList(); - } - - using (var context = contextFactory.CreateContext()) - { - var query = context.Set().ToList(); - - Assert.Collection( - query, - t => AssertCustomerView(t, 1, "First", 1, "FirstChild"), - t => AssertCustomerView(t, 2, "Second", 2, "SecondChild1"), - t => AssertCustomerView(t, 2, "Second", 3, "SecondChild2"), - t => AssertCustomerView(t, 3, "Third", null, "")); - - static void AssertCustomerView( - Context19708.CustomerView19708 actual, - int id, - string name, - int? customerMembershipId, - string customerMembershipName) - { - Assert.Equal(id, actual.Id); - Assert.Equal(name, actual.Name); - Assert.Equal(customerMembershipId, actual.CustomerMembershipId); - Assert.Equal(customerMembershipName, actual.CustomerMembershipName); - } - } - } - - protected class Context19708(DbContextOptions options) : DbContext(options) - { - public DbSet Customers { get; set; } - public DbSet CustomerMemberships { get; set; } - public DbSet CustomerFilters { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity() - .HasQueryFilter( - e => (from a in (from c in Customers - join cm in CustomerMemberships on c.Id equals cm.CustomerId into g - from cm in g.DefaultIfEmpty() - select new { c.Id, CustomerMembershipId = (int?)cm.Id }) - where a.CustomerMembershipId != null && a.Id == e.CustomerId - select a).Count() - > 0) - .HasKey(e => e.CustomerId); - -#pragma warning disable CS0618 // Type or member is obsolete - modelBuilder.Entity().HasNoKey().ToQuery(Build_Customers_Sql_View_InMemory()); -#pragma warning restore CS0618 // Type or member is obsolete - } - - public Task SeedAsync() - { - var customer1 = new Customer19708 { Name = "First" }; - var customer2 = new Customer19708 { Name = "Second" }; - var customer3 = new Customer19708 { Name = "Third" }; - - var customerMembership1 = new CustomerMembership19708 { Name = "FirstChild", Customer = customer1 }; - var customerMembership2 = new CustomerMembership19708 { Name = "SecondChild1", Customer = customer2 }; - var customerMembership3 = new CustomerMembership19708 { Name = "SecondChild2", Customer = customer2 }; - - AddRange(customer1, customer2, customer3); - AddRange(customerMembership1, customerMembership2, customerMembership3); - - return SaveChangesAsync(); - } - - private Expression>> Build_Customers_Sql_View_InMemory() - { - Expression>> query = () => - from customer in Customers - join customerMembership in CustomerMemberships on customer.Id equals customerMembership.CustomerId into - nullableCustomerMemberships - from customerMembership in nullableCustomerMemberships.DefaultIfEmpty() - select new CustomerView19708 - { - Id = customer.Id, - Name = customer.Name, - CustomerMembershipId = customerMembership != null ? customerMembership.Id : default(int?), - CustomerMembershipName = customerMembership != null ? customerMembership.Name : "" - }; - return query; - } - - public class Customer19708 - { - public int Id { get; set; } - public string Name { get; set; } - } - - public class CustomerMembership19708 - { - public int Id { get; set; } - public string Name { get; set; } - - public int CustomerId { get; set; } - public Customer19708 Customer { get; set; } - } - - public class CustomerFilter19708 - { - public int CustomerId { get; set; } - public int CustomerMembershipId { get; set; } - } - - public class CustomerView19708 - { - public int Id { get; set; } - public string Name { get; set; } - public int? CustomerMembershipId { get; set; } - public string CustomerMembershipName { get; set; } - } - } - - #endregion - #region 26428 #nullable enable diff --git a/test/EFCore.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryTestBase.cs index a49d0f9dc8d..e744a0cb034 100644 --- a/test/EFCore.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryTestBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; using static Expression; -public abstract class NonSharedPrimitiveCollectionsQueryTestBase : NonSharedModelTestBase +public abstract class NonSharedPrimitiveCollectionsQueryTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { #region Support for specific element types diff --git a/test/EFCore.Specification.Tests/Query/NorthwindCompiledQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindCompiledQueryTestBase.cs index f83e32ebcf6..ca258ed8632 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindCompiledQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindCompiledQueryTestBase.cs @@ -854,5 +854,5 @@ protected async Task CountAsync(IAsyncEnumerable source) protected NorthwindContext CreateContext() => Fixture.CreateContext(); - public static IEnumerable IsAsyncData = new object[][] { [false], [true] }; + public static readonly IEnumerable IsAsyncData = [[false], [true]]; } diff --git a/test/EFCore.Specification.Tests/Query/OwnedEntityQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/OwnedEntityQueryTestBase.cs index 25b210961c9..79fd107759d 100644 --- a/test/EFCore.Specification.Tests/Query/OwnedEntityQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/OwnedEntityQueryTestBase.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore; #nullable disable -public abstract class OwnedEntityQueryTestBase : NonSharedModelTestBase +public abstract class OwnedEntityQueryTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { protected override string StoreName => "OwnedEntityQueryTests"; diff --git a/test/EFCore.Specification.Tests/Query/QueryTestBase.cs b/test/EFCore.Specification.Tests/Query/QueryTestBase.cs index 3b7f268f4be..85a5b589fea 100644 --- a/test/EFCore.Specification.Tests/Query/QueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/QueryTestBase.cs @@ -39,7 +39,7 @@ protected virtual Expression RewriteServerQueryExpression(Expression serverQuery protected virtual Expression RewriteExpectedQueryExpression(Expression expectedQueryExpression) => new ExpectedQueryRewritingVisitor().Visit(expectedQueryExpression); - public static IEnumerable IsAsyncData = new object[][] { [false], [true] }; + public static readonly IEnumerable IsAsyncData = [[false], [true]]; public Task AssertQuery( bool async, diff --git a/test/EFCore.Specification.Tests/Query/Relationships/ComplexRelationshipsQueryFixtureBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/ComplexRelationshipsFixtureBase.cs similarity index 93% rename from test/EFCore.Specification.Tests/Query/Relationships/ComplexRelationshipsQueryFixtureBase.cs rename to test/EFCore.Specification.Tests/Query/Relationships/ComplexRelationshipsFixtureBase.cs index 9a0831ce077..f418570a254 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/ComplexRelationshipsQueryFixtureBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/ComplexRelationshipsFixtureBase.cs @@ -5,7 +5,9 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public abstract class ComplexRelationshipsQueryFixtureBase : RelationshipsQueryFixtureBase +// collections are not supported for non-json complex types +// so only use this for reference test case +public abstract class ComplexRelationshipsFixtureBase : RelationshipsQueryFixtureBase { protected override string StoreName => "ComplexRelationshipsQueryTest"; @@ -39,11 +41,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .OnDelete(DeleteBehavior.Restrict) .IsRequired(true); - modelBuilder.Entity() - .HasMany(x => x.CollectionTrunk) - .WithOne(x => x.CollectionInverseRoot) - .HasForeignKey(x => x.CollectionRootId) - .OnDelete(DeleteBehavior.Restrict); + modelBuilder.Entity().Ignore(x => x.CollectionTrunk); + modelBuilder.Entity().Ignore(x => x.CollectionInverseRoot); // TODO: issue #31376 - complex optional references modelBuilder.Entity() @@ -77,7 +76,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con bb.Ignore(x => x.CollectionLeaf); }); - // TODO: issue #31237 - complex collections + // collections are not supported for non-json compex types modelBuilder.Entity().Ignore(x => x.CollectionBranch); } diff --git a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionNoTrackingQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionNoTrackingQueryTestBase.cs deleted file mode 100644 index 0af57981cc7..00000000000 --- a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionNoTrackingQueryTestBase.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public abstract class ComplexRelationshipsInProjectionNoTrackingQueryTestBase(TFixture fixture) - : ComplexRelationshipsInProjectionQueryTestBase(fixture) - where TFixture : ComplexRelationshipsQueryFixtureBase, new() -{ - private readonly NoTrackingRewriter _noTrackingRewriter = new(); - - protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) - { - var rewritten = _noTrackingRewriter.Visit(serverQueryExpression); - - return rewritten; - } -} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionQueryTestBase.cs deleted file mode 100644 index 421e8052d6a..00000000000 --- a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionQueryTestBase.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.EntityFrameworkCore.TestModels.RelationshipsModel; - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public abstract class ComplexRelationshipsInProjectionQueryTestBase(TFixture fixture) - : RelationshipsInProjectionQueryTestBase(fixture) - where TFixture : ComplexRelationshipsQueryFixtureBase, new() -{ - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_everything(bool async) - => AssertQuery( - async, - ss => from r in ss.Set() - join t in ss.Set() on r.Id equals t.Id - select new { r, t }, - elementSorter: e => e.r.Id, - elementAsserter: (e, a) => - { - AssertEqual(e.r, a.r); - AssertEqual(e.t, a.t); - }); - - [ConditionalTheory(Skip = "issue #31412")] - public override Task Project_branch_required_optional(bool async) - => base.Project_branch_required_optional(async); - - [ConditionalTheory(Skip = "issue #31237")] - public override Task Project_branch_required_collection(bool async) - => base.Project_branch_required_collection(async); - - [ConditionalTheory(Skip = "issue #31376")] - public override Task Project_branch_optional_optional(bool async) - => base.Project_branch_optional_optional(async); - - [ConditionalTheory(Skip = "issue #31412")] - public override Task Project_branch_optional_required(bool async) - => base.Project_branch_optional_required(async); - - [ConditionalTheory(Skip = "issue #31237")] - public override Task Project_branch_optional_collection(bool async) - => base.Project_branch_optional_collection(async); - - public override Task Project_root_duplicated(bool async) - => base.Project_root_duplicated(async); - - [ConditionalTheory(Skip = "issue #31412")] - public override Task Project_trunk_and_branch_duplicated(bool async) - => base.Project_trunk_and_branch_duplicated(async); - - [ConditionalTheory(Skip = "issue #31412")] - public override Task Project_trunk_and_trunk_duplicated(bool async) - => base.Project_trunk_and_trunk_duplicated(async); - - [ConditionalTheory(Skip = "issue #31237")] - public override Task Project_multiple_branch_leaf(bool async) - => base.Project_multiple_branch_leaf(async); - - [ConditionalTheory(Skip = "issue #31237")] - public override Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) - => base.Project_subquery_root_set_required_trunk_FirstOrDefault_branch(async); - - [ConditionalTheory(Skip = "issue #31412")] - public override Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) - => base.Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); - - [ConditionalTheory(Skip = "issue #31237")] - public override Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - => base.Project_subquery_root_set_trunk_FirstOrDefault_collection(async); - - [ConditionalTheory(Skip = "issue #31237")] - public override Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - => base.Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); - - [ConditionalTheory(Skip = "issue #31237")] - public override Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - => base.Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); - - [ConditionalTheory(Skip = "issue #31237")] - public override Task SelectMany_trunk_collection(bool async) - => base.SelectMany_trunk_collection(async); - - [ConditionalTheory(Skip = "issue #31237")] - public override Task SelectMany_required_trunk_reference_branch_collection(bool async) - => base.SelectMany_required_trunk_reference_branch_collection(async); - - [ConditionalTheory(Skip = "issue #31237")] - public override Task SelectMany_optional_trunk_reference_branch_collection(bool async) - => base.SelectMany_optional_trunk_reference_branch_collection(async); -} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/EntityRelationshipsInProjectionNoTrackingQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/InProjection/EntityRelationshipsInProjectionNoTrackingQueryTestBase.cs deleted file mode 100644 index fdd1f4c8278..00000000000 --- a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/EntityRelationshipsInProjectionNoTrackingQueryTestBase.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public abstract class EntityRelationshipsInProjectionNoTrackingQueryTestBase(TFixture fixture) - : EntityRelationshipsInProjectionQueryTestBase(fixture) - where TFixture : EntityRelationshipsQueryFixtureBase, new() -{ - protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) - { - return base.RewriteServerQueryExpression(serverQueryExpression); - } -} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/JsonRelationshipsInProjectionNoTrackingQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/InProjection/JsonRelationshipsInProjectionNoTrackingQueryTestBase.cs deleted file mode 100644 index c40c011c03a..00000000000 --- a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/JsonRelationshipsInProjectionNoTrackingQueryTestBase.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public abstract class JsonRelationshipsInProjectionNoTrackingQueryTestBase(TFixture fixture) - : JsonRelationshipsInProjectionQueryTestBase(fixture) - where TFixture : JsonRelationshipsQueryFixtureBase, new() -{ - private readonly NoTrackingRewriter _noTrackingRewriter = new(); - - protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) - { - var rewritten = _noTrackingRewriter.Visit(serverQueryExpression); - - return rewritten; - } -} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/NoTrackingRewriter.cs b/test/EFCore.Specification.Tests/Query/Relationships/InProjection/NoTrackingRewriter.cs deleted file mode 100644 index 7e08cbe767e..00000000000 --- a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/NoTrackingRewriter.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public class NoTrackingRewriter : ExpressionVisitor -{ - private static readonly MethodInfo AsNoTrackingMethodInfo - = typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethod(nameof(EntityFrameworkQueryableExtensions.AsNoTracking))!; - - protected override Expression VisitExtension(Expression expression) - { - if (expression is EntityQueryRootExpression eqr) - { - return Expression.Call(AsNoTrackingMethodInfo.MakeGenericMethod(eqr.ElementType), eqr); - } - - return base.VisitExtension(expression); - } -} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionNoTrackingQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionNoTrackingQueryTestBase.cs deleted file mode 100644 index 2cb435c2d60..00000000000 --- a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionNoTrackingQueryTestBase.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public abstract class OwnedRelationshipsInProjectionNoTrackingQueryTestBase(TFixture fixture) - : OwnedRelationshipsInProjectionQueryTestBase(fixture) - where TFixture : OwnedRelationshipsQueryFixtureBase, new() -{ - private readonly NoTrackingRewriter _noTrackingRewriter = new(); - - protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) - { - var rewritten = _noTrackingRewriter.Visit(serverQueryExpression); - - return rewritten; - } -} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionQueryTestBase.cs deleted file mode 100644 index 166bb949322..00000000000 --- a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionQueryTestBase.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public abstract class OwnedRelationshipsInProjectionQueryTestBase(TFixture fixture) - : RelationshipsInProjectionQueryTestBase(fixture) - where TFixture : OwnedRelationshipsQueryFixtureBase, new() -{ -} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/RelationshipsInProjectionQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/InProjection/RelationshipsInProjectionQueryTestBase.cs deleted file mode 100644 index 734b2fa3d40..00000000000 --- a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/RelationshipsInProjectionQueryTestBase.cs +++ /dev/null @@ -1,326 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.EntityFrameworkCore.TestModels.RelationshipsModel; - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -/// -/// Tests for using navigations in projection - mostly to test shaper code around -/// -public abstract class RelationshipsInProjectionQueryTestBase(TFixture fixture) : QueryTestBase(fixture) - where TFixture : RelationshipsQueryFixtureBase, new() -{ - protected RelationshipsContext CreateContext() - => Fixture.CreateContext(); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_root(bool async) - => AssertQuery( - async, - ss => ss.Set()); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_trunk_optional(bool async) - => AssertQuery( - async, - ss => ss.Set().OrderBy(x => x.Id).Select(x => x.OptionalReferenceTrunk), - assertOrder: true); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_trunk_required(bool async) - => AssertQuery( - async, - ss => ss.Set().OrderBy(x => x.Id).Select(x => x.RequiredReferenceTrunk), - assertOrder: true); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_trunk_collection(bool async) - => AssertQuery( - async, - ss => ss.Set().OrderBy(x => x.Id).Select(x => x.CollectionTrunk), - assertOrder: true, - elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: ee => ee.Name)); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_branch_required_required(bool async) - => AssertQuery( - async, - ss => ss.Set().OrderBy(x => x.Id).Select(x => x.RequiredReferenceTrunk.RequiredReferenceBranch), - assertOrder: true); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_branch_required_optional(bool async) - => AssertQuery( - async, - ss => ss.Set().OrderBy(x => x.Id).Select(x => x.RequiredReferenceTrunk.OptionalReferenceBranch), - assertOrder: true); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_branch_required_collection(bool async) - => AssertQuery( - async, - ss => ss.Set().OrderBy(x => x.Id).Select(x => x.RequiredReferenceTrunk.CollectionBranch), - assertOrder: true, - elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: ee => ee.Name)); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_branch_optional_required(bool async) - => AssertQuery( - async, - ss => ss.Set().OrderBy(x => x.Id).Select(x => x.RequiredReferenceTrunk.RequiredReferenceBranch), - assertOrder: true); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_branch_optional_optional(bool async) - => AssertQuery( - async, - ss => ss.Set().OrderBy(x => x.Id).Select(x => x.RequiredReferenceTrunk.OptionalReferenceBranch), - assertOrder: true); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_branch_optional_collection(bool async) - => AssertQuery( - async, - ss => ss.Set().OrderBy(x => x.Id).Select(x => x.RequiredReferenceTrunk.CollectionBranch), - assertOrder: true, - elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: ee => ee.Name)); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_root_duplicated(bool async) - => AssertQuery( - async, - ss => ss.Set().Select(x => new { First = x, Second = x }), - elementSorter: e => e.First.Id, - elementAsserter: (e, a) => - { - AssertEqual(e.First, a.First); - AssertEqual(e.Second, a.Second); - }); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_trunk_and_branch_duplicated(bool async) - => AssertQuery( - async, - ss => ss.Set() - .OrderBy(x => x.Id) - .Select( - x => new - { - Trunk1 = x.OptionalReferenceTrunk, - Branch1 = x.OptionalReferenceTrunk!.RequiredReferenceBranch, - Trunk2 = x.OptionalReferenceTrunk, - Branch2 = x.OptionalReferenceTrunk.RequiredReferenceBranch, - }), - assertOrder: true, - elementAsserter: (e, a) => - { - AssertEqual(e.Trunk1, a.Trunk1); - AssertEqual(e.Trunk2, a.Trunk2); - AssertEqual(e.Branch1, a.Branch1); - AssertEqual(e.Branch2, a.Branch2); - }); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_trunk_and_trunk_duplicated(bool async) - => AssertQuery( - async, - ss => ss.Set() - .OrderBy(x => x.Id) - .Select( - x => new - { - Trunk1 = x.RequiredReferenceTrunk, - Leaf1 = x.RequiredReferenceTrunk!.OptionalReferenceBranch!.RequiredReferenceLeaf, - Trunk2 = x.RequiredReferenceTrunk, - Leaf2 = x.RequiredReferenceTrunk.OptionalReferenceBranch.RequiredReferenceLeaf, - }), - assertOrder: true, - elementAsserter: (e, a) => - { - AssertEqual(e.Trunk1, a.Trunk1); - AssertEqual(e.Trunk2, a.Trunk2); - AssertEqual(e.Leaf1, a.Leaf1); - AssertEqual(e.Leaf2, a.Leaf2); - }); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_multiple_branch_leaf(bool async) - => AssertQuery( - async, - ss => ss.Set().Select( - x => new - { - x.Id, - x.RequiredReferenceTrunk.RequiredReferenceBranch, - x.RequiredReferenceTrunk.RequiredReferenceBranch.OptionalReferenceLeaf, - x.RequiredReferenceTrunk.RequiredReferenceBranch.CollectionLeaf, - x.RequiredReferenceTrunk.CollectionBranch, - x.RequiredReferenceTrunk.RequiredReferenceBranch.OptionalReferenceLeaf!.Name - }), - elementSorter: e => e.Id, - elementAsserter: (e, a) => - { - Assert.Equal(e.Id, a.Id); - AssertEqual(e.RequiredReferenceBranch, a.RequiredReferenceBranch); - AssertEqual(e.OptionalReferenceLeaf, a.OptionalReferenceLeaf); - AssertCollection(e.CollectionLeaf, a.CollectionLeaf, ordered: true); - AssertCollection(e.CollectionBranch, a.CollectionBranch, ordered: true); - Assert.Equal(e.Name, a.Name); - }); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_leaf_trunk_root(bool async) - => AssertQuery( - async, - ss => ss.Set() - .Select( - x => new - { - x.RequiredReferenceTrunk.RequiredReferenceBranch.RequiredReferenceLeaf, - x.RequiredReferenceTrunk, - x - }), - elementSorter: e => e.x.Id, - elementAsserter: (e, a) => - { - AssertEqual(e.RequiredReferenceLeaf, a.RequiredReferenceLeaf); - AssertEqual(e.RequiredReferenceTrunk, a.RequiredReferenceTrunk); - AssertEqual(e.x, a.x); - }); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) - => AssertQuery( - async, - ss => ss.Set() - .OrderBy(x => x.Id) - .Select( - x => ss.Set() - .OrderBy(xx => xx.Id) - .Select(xx => xx.RequiredReferenceTrunk) - .FirstOrDefault()!.RequiredReferenceBranch), - assertOrder: true); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) - => AssertQuery( - async, - ss => ss.Set() - .OrderBy(x => x.Id) - .Select( - x => ss.Set() - .OrderBy(xx => xx.Id) - .Select(xx => xx.OptionalReferenceTrunk) - .FirstOrDefault()!.OptionalReferenceBranch), - assertOrder: true); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - => AssertQuery( - async, - ss => ss.Set() - .OrderBy(x => x.Id) - .Select( - x => ss.Set() - .OrderBy(xx => xx.Id) - .Select(xx => xx.RequiredReferenceTrunk) - .FirstOrDefault()!.CollectionBranch), - assertOrder: true, - elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: ee => ee)); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - => AssertQuery( - async, - ss => ss.Set() - .OrderBy(x => x.Id) - .Select( - x => ss.Set() - .OrderBy(xx => xx.Id) - .Select( - xx => new - { - OuterBranch = x.RequiredReferenceTrunk.CollectionBranch, - xx.RequiredReferenceTrunk, - xx.RequiredReferenceTrunk.RequiredReferenceBranch, - xx.RequiredReferenceTrunk.Name, - OuterName = x.RequiredReferenceTrunk!.RequiredReferenceBranch.Name - }).FirstOrDefault()), - assertOrder: true, - elementAsserter: (e, a) => - { - AssertCollection(e.OuterBranch, a.OuterBranch); - AssertEqual(e.RequiredReferenceTrunk, a.RequiredReferenceTrunk); - AssertEqual(e.RequiredReferenceBranch, a.RequiredReferenceBranch); - AssertEqual(e.Name, a.Name); - AssertEqual(e.OuterName, a.OuterName); - }); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - => AssertQuery( - async, - ss => ss.Set() - .OrderBy(x => x.Id) - .Select( - x => ss.Set() - .OrderBy(xx => xx.Id) - .Select( - xx => new - { - OuterBranchCollection = x.RequiredReferenceTrunk.CollectionBranch, - xx.RequiredReferenceTrunk, - xx.RequiredReferenceTrunk.RequiredReferenceBranch, - xx.RequiredReferenceTrunk.Name, - OuterName = x.RequiredReferenceTrunk.RequiredReferenceBranch.Name - }).FirstOrDefault()!.OuterBranchCollection), - assertOrder: true, - elementAsserter: (e, a) => AssertCollection(e, a)); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task SelectMany_trunk_collection(bool async) - => AssertQuery( - async, - ss => ss.Set() - .SelectMany(x => x.CollectionTrunk)); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task SelectMany_required_trunk_reference_branch_collection(bool async) - => AssertQuery( - async, - ss => ss.Set() - .SelectMany(x => x.RequiredReferenceTrunk.CollectionBranch)); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task SelectMany_optional_trunk_reference_branch_collection(bool async) - => AssertQuery( - async, - ss => ss.Set() - .SelectMany(x => x.OptionalReferenceTrunk!.CollectionBranch), - ss => ss.Set() - .SelectMany(x => x.OptionalReferenceTrunk.Maybe(xx => xx!.CollectionBranch) ?? new List())); -} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Include/RelationshipsIncludeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Include/NavigationIncludeTestBase.cs similarity index 96% rename from test/EFCore.Specification.Tests/Query/Relationships/Include/RelationshipsIncludeQueryTestBase.cs rename to test/EFCore.Specification.Tests/Query/Relationships/Include/NavigationIncludeTestBase.cs index 6215a29651c..d4d7dcedebb 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/Include/RelationshipsIncludeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/Include/NavigationIncludeTestBase.cs @@ -5,8 +5,8 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships.Include; -public abstract class RelationshipsIncludeQueryTestBase(TFixture fixture) : QueryTestBase(fixture) - where TFixture : RelationshipsQueryFixtureBase, new() +public abstract class NavigationIncludeTestBase(TFixture fixture) : QueryTestBase(fixture) + where TFixture : NavigationRelationshipsFixtureBase, new() { protected RelationshipsContext CreateContext() => Fixture.CreateContext(); diff --git a/test/EFCore.Specification.Tests/Query/Relationships/EntityRelationshipsQueryFixtureBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/NavigationRelationshipsFixtureBase.cs similarity index 97% rename from test/EFCore.Specification.Tests/Query/Relationships/EntityRelationshipsQueryFixtureBase.cs rename to test/EFCore.Specification.Tests/Query/Relationships/NavigationRelationshipsFixtureBase.cs index f0bb3e26a69..f8e61fb57a5 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/EntityRelationshipsQueryFixtureBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/NavigationRelationshipsFixtureBase.cs @@ -5,9 +5,9 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public abstract class EntityRelationshipsQueryFixtureBase : RelationshipsQueryFixtureBase +public abstract class NavigationRelationshipsFixtureBase : RelationshipsQueryFixtureBase { - protected override string StoreName => "EntityRelationshipsQueryTest"; + protected override string StoreName => "NavigationRelationshipsQueryTest"; protected override Task SeedAsync(RelationshipsContext context) { diff --git a/test/EFCore.Specification.Tests/Query/Relationships/JsonRelationshipsQueryFixtureBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/OwnedJsonRelationshipsFixtureBase.cs similarity index 83% rename from test/EFCore.Specification.Tests/Query/Relationships/JsonRelationshipsQueryFixtureBase.cs rename to test/EFCore.Specification.Tests/Query/Relationships/OwnedJsonRelationshipsFixtureBase.cs index f7cbb45d47b..1cbc51d59dd 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/JsonRelationshipsQueryFixtureBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/OwnedJsonRelationshipsFixtureBase.cs @@ -5,9 +5,9 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public abstract class JsonRelationshipsQueryFixtureBase : OwnedRelationshipsQueryFixtureBase +public abstract class OwnedJsonRelationshipsFixtureBase : OwnedRelationshipsFixtureBase { - protected override string StoreName => "JsonRelationshipsQueryTest"; + protected override string StoreName => "OwnedJsonRelationshipsQueryTest"; protected override Task SeedAsync(RelationshipsContext context) { diff --git a/test/EFCore.Specification.Tests/Query/Relationships/OwnedRelationshipsQueryFixtureBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/OwnedRelationshipsFixtureBase.cs similarity index 99% rename from test/EFCore.Specification.Tests/Query/Relationships/OwnedRelationshipsQueryFixtureBase.cs rename to test/EFCore.Specification.Tests/Query/Relationships/OwnedRelationshipsFixtureBase.cs index 1bff81c89ad..6d3e2f1d9b0 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/OwnedRelationshipsQueryFixtureBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/OwnedRelationshipsFixtureBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public abstract class OwnedRelationshipsQueryFixtureBase : RelationshipsQueryFixtureBase +public abstract class OwnedRelationshipsFixtureBase : RelationshipsQueryFixtureBase { protected override string StoreName => "OwnedRelationshipsQueryTest"; diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Projection/ComplexNoTrackingProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Projection/ComplexNoTrackingProjectionTestBase.cs new file mode 100644 index 00000000000..e6fdadac404 --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/Projection/ComplexNoTrackingProjectionTestBase.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class ComplexNoTrackingProjectionTestBase(TFixture fixture) + : ComplexProjectionTestBase(fixture) + where TFixture : ComplexRelationshipsFixtureBase, new() +{ + private readonly TrackingRewriter _trackingRewriter = new(); + + protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) + { + var rewritten = _trackingRewriter.Visit(serverQueryExpression); + + return rewritten; + } +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Projection/ComplexProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Projection/ComplexProjectionTestBase.cs new file mode 100644 index 00000000000..b0ae65aa9a0 --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/Projection/ComplexProjectionTestBase.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestModels.RelationshipsModel; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class ComplexProjectionTestBase(TFixture fixture) + : ReferenceProjectionTestBase(fixture) + where TFixture : ComplexRelationshipsFixtureBase, new() +{ + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_everything(bool async) + => AssertQuery( + async, + ss => from r in ss.Set() + join t in ss.Set() on r.Id equals t.Id + select new { r, t }, + elementSorter: e => e.r.Id, + elementAsserter: (e, a) => + { + AssertEqual(e.r, a.r); + AssertEqual(e.t, a.t); + }); + + [ConditionalTheory(Skip = "issue #31412")] + public override Task Select_branch_required_optional(bool async) + => base.Select_branch_required_optional(async); + + [ConditionalTheory(Skip = "issue #31376")] + public override Task Select_branch_optional_optional(bool async) + => base.Select_branch_optional_optional(async); + + [ConditionalTheory(Skip = "issue #31412")] + public override Task Select_branch_optional_required(bool async) + => base.Select_branch_optional_required(async); + + public override Task Select_root_duplicated(bool async) + => base.Select_root_duplicated(async); + + [ConditionalTheory(Skip = "issue #31412")] + public override Task Select_trunk_and_branch_duplicated(bool async) + => base.Select_trunk_and_branch_duplicated(async); + + [ConditionalTheory(Skip = "issue #31412")] + public override Task Select_trunk_and_trunk_duplicated(bool async) + => base.Select_trunk_and_trunk_duplicated(async); + + [ConditionalTheory(Skip = "issue #31237")] + public override Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + => base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async); + + [ConditionalTheory(Skip = "issue #31412")] + public override Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + => base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Projection/NavigationNoTrackingProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Projection/NavigationNoTrackingProjectionTestBase.cs new file mode 100644 index 00000000000..9f7574ec18f --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/Projection/NavigationNoTrackingProjectionTestBase.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class NavigationNoTrackingProjectionTestBase(TFixture fixture) + : NavigationProjectionTestBase(fixture) + where TFixture : NavigationRelationshipsFixtureBase, new() +{ + private readonly TrackingRewriter _trackingRewriter = new(); + + protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) + { + var rewritten = _trackingRewriter.Visit(serverQueryExpression); + + return rewritten; + } +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/EntityRelationshipsInProjectionQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Projection/NavigationProjectionTestBase.cs similarity index 72% rename from test/EFCore.Specification.Tests/Query/Relationships/InProjection/EntityRelationshipsInProjectionQueryTestBase.cs rename to test/EFCore.Specification.Tests/Query/Relationships/Projection/NavigationProjectionTestBase.cs index 094c77d14ea..01ee19666b5 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/EntityRelationshipsInProjectionQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/Projection/NavigationProjectionTestBase.cs @@ -3,15 +3,15 @@ using Microsoft.EntityFrameworkCore.TestModels.RelationshipsModel; -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; -public abstract class EntityRelationshipsInProjectionQueryTestBase(TFixture fixture) - : RelationshipsInProjectionQueryTestBase(fixture) - where TFixture : EntityRelationshipsQueryFixtureBase, new() +public abstract class NavigationProjectionTestBase(TFixture fixture) + : ProjectionTestBase(fixture) + where TFixture : NavigationRelationshipsFixtureBase, new() { [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Project_everything(bool async) + public virtual Task Select_everything_using_joins(bool async) => AssertQuery( async, ss => from r in ss.Set() diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Projection/NavigationReferenceNoTrackingProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Projection/NavigationReferenceNoTrackingProjectionTestBase.cs new file mode 100644 index 00000000000..401385aac9e --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/Projection/NavigationReferenceNoTrackingProjectionTestBase.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class NavigationReferenceNoTrackingProjectionTestBase(TFixture fixture) + : NavigationReferenceProjectionTestBase(fixture) + where TFixture : NavigationRelationshipsFixtureBase, new() +{ + private readonly TrackingRewriter _trackingRewriter = new(); + + protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) + { + var rewritten = _trackingRewriter.Visit(serverQueryExpression); + + return rewritten; + } +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Projection/NavigationReferenceProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Projection/NavigationReferenceProjectionTestBase.cs new file mode 100644 index 00000000000..9ec8650e605 --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/Projection/NavigationReferenceProjectionTestBase.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestModels.RelationshipsModel; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class NavigationReferenceProjectionTestBase(TFixture fixture) + : ReferenceProjectionTestBase(fixture) + where TFixture : NavigationRelationshipsFixtureBase, new() +{ + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_everything(bool async) + => AssertQuery( + async, + ss => from r in ss.Set() + join t in ss.Set() on r.Id equals t.Id + join b in ss.Set() on t.Id equals b.Id + join l in ss.Set() on b.Id equals l.Id + select new { r, t, b, l }, + elementSorter: e => e.r.Id, + elementAsserter: (e, a) => + { + AssertEqual(e.r, a.r); + AssertEqual(e.t, a.t); + AssertEqual(e.b, a.b); + AssertEqual(e.l, a.l); + }); +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedJsonNoTrackingProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedJsonNoTrackingProjectionTestBase.cs new file mode 100644 index 00000000000..07ed78d1f65 --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedJsonNoTrackingProjectionTestBase.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedJsonNoTrackingProjectionTestBase(TFixture fixture) + : OwnedJsonProjectionTestBase(fixture) + where TFixture : OwnedJsonRelationshipsFixtureBase, new() +{ + private readonly TrackingRewriter _trackingRewriter = new(); + + protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) + { + var rewritten = _trackingRewriter.Visit(serverQueryExpression); + + return rewritten; + } +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/JsonRelationshipsInProjectionQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedJsonProjectionTestBase.cs similarity index 69% rename from test/EFCore.Specification.Tests/Query/Relationships/InProjection/JsonRelationshipsInProjectionQueryTestBase.cs rename to test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedJsonProjectionTestBase.cs index 906d36d8e7b..6b615495ecc 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/InProjection/JsonRelationshipsInProjectionQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedJsonProjectionTestBase.cs @@ -3,11 +3,11 @@ using Microsoft.EntityFrameworkCore.TestModels.RelationshipsModel; -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; -public abstract class JsonRelationshipsInProjectionQueryTestBase(TFixture fixture) - : RelationshipsInProjectionQueryTestBase(fixture) - where TFixture : JsonRelationshipsQueryFixtureBase, new() +public abstract class OwnedJsonProjectionTestBase(TFixture fixture) + : ProjectionTestBase(fixture) + where TFixture : OwnedJsonRelationshipsFixtureBase, new() { [ConditionalTheory] [MemberData(nameof(IsAsyncData))] @@ -17,6 +17,4 @@ public virtual Task Project_branch_collection_element_using_indexer_constant(boo ss => ss.Set().OrderBy(x => x.Id).Select(x => x.RequiredReferenceTrunk.CollectionBranch), assertOrder: true, elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: ee => ee.Name)); - - } diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedJsonReferenceNoTrackingProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedJsonReferenceNoTrackingProjectionTestBase.cs new file mode 100644 index 00000000000..6964862ae52 --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedJsonReferenceNoTrackingProjectionTestBase.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedJsonReferenceNoTrackingProjectionTestBase(TFixture fixture) + : OwnedJsonReferenceProjectionTestBase(fixture) + where TFixture : OwnedJsonRelationshipsFixtureBase, new() +{ + private readonly TrackingRewriter _trackingRewriter = new(); + + protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) + { + var rewritten = _trackingRewriter.Visit(serverQueryExpression); + + return rewritten; + } +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedJsonReferenceProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedJsonReferenceProjectionTestBase.cs new file mode 100644 index 00000000000..fb82cc24311 --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedJsonReferenceProjectionTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedJsonReferenceProjectionTestBase(TFixture fixture) + : ReferenceProjectionTestBase(fixture) + where TFixture : OwnedJsonRelationshipsFixtureBase, new() +{ +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedNoTrackingProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedNoTrackingProjectionTestBase.cs new file mode 100644 index 00000000000..a55b81c8628 --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedNoTrackingProjectionTestBase.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedNoTrackingProjectionTestBase(TFixture fixture) + : OwnedProjectionTestBase(fixture) + where TFixture : OwnedRelationshipsFixtureBase, new() +{ + private readonly TrackingRewriter _trackingRewriter = new(); + + protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) + { + var rewritten = _trackingRewriter.Visit(serverQueryExpression); + + return rewritten; + } +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedProjectionTestBase.cs new file mode 100644 index 00000000000..c1f64308333 --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedProjectionTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedProjectionTestBase(TFixture fixture) + : ProjectionTestBase(fixture) + where TFixture : OwnedRelationshipsFixtureBase, new() +{ +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedReferenceNoTrackingProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedReferenceNoTrackingProjectionTestBase.cs new file mode 100644 index 00000000000..28779949bf7 --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedReferenceNoTrackingProjectionTestBase.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedReferenceNoTrackingProjectionTestBase(TFixture fixture) + : OwnedReferenceProjectionTestBase(fixture) + where TFixture : OwnedRelationshipsFixtureBase, new() +{ + private readonly TrackingRewriter _trackingRewriter = new(); + + protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) + { + var rewritten = _trackingRewriter.Visit(serverQueryExpression); + + return rewritten; + } +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedReferenceProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedReferenceProjectionTestBase.cs new file mode 100644 index 00000000000..cb23b46e2cf --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/Projection/OwnedReferenceProjectionTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class OwnedReferenceProjectionTestBase(TFixture fixture) + : ReferenceProjectionTestBase(fixture) + where TFixture : OwnedRelationshipsFixtureBase, new() +{ +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Projection/ProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Projection/ProjectionTestBase.cs new file mode 100644 index 00000000000..65e2be89cbc --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/Projection/ProjectionTestBase.cs @@ -0,0 +1,158 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestModels.RelationshipsModel; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class ProjectionTestBase(TFixture fixture) : QueryTestBase(fixture) + where TFixture : RelationshipsQueryFixtureBase, new() +{ + protected RelationshipsContext CreateContext() + => Fixture.CreateContext(); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_trunk_collection(bool async) + => AssertQuery( + async, + ss => ss.Set().OrderBy(x => x.Id).Select(x => x.CollectionTrunk), + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: ee => ee.Name)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_branch_required_collection(bool async) + => AssertQuery( + async, + ss => ss.Set().OrderBy(x => x.Id).Select(x => x.RequiredReferenceTrunk.CollectionBranch), + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: ee => ee.Name)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_branch_optional_collection(bool async) + => AssertQuery( + async, + ss => ss.Set().OrderBy(x => x.Id).Select(x => x.RequiredReferenceTrunk.CollectionBranch), + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: ee => ee.Name)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_multiple_branch_leaf(bool async) + => AssertQuery( + async, + ss => ss.Set().Select( + x => new + { + x.Id, + x.RequiredReferenceTrunk.RequiredReferenceBranch, + x.RequiredReferenceTrunk.RequiredReferenceBranch.OptionalReferenceLeaf, + x.RequiredReferenceTrunk.RequiredReferenceBranch.CollectionLeaf, + x.RequiredReferenceTrunk.CollectionBranch, + x.RequiredReferenceTrunk.RequiredReferenceBranch.OptionalReferenceLeaf!.Name + }), + elementSorter: e => e.Id, + elementAsserter: (e, a) => + { + Assert.Equal(e.Id, a.Id); + AssertEqual(e.RequiredReferenceBranch, a.RequiredReferenceBranch); + AssertEqual(e.OptionalReferenceLeaf, a.OptionalReferenceLeaf); + AssertCollection(e.CollectionLeaf, a.CollectionLeaf, ordered: true); + AssertCollection(e.CollectionBranch, a.CollectionBranch, ordered: true); + Assert.Equal(e.Name, a.Name); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_subquery_root_set_trunk_FirstOrDefault_collection(bool async) + => AssertQuery( + async, + ss => ss.Set() + .OrderBy(x => x.Id) + .Select( + x => ss.Set() + .OrderBy(xx => xx.Id) + .Select(xx => xx.RequiredReferenceTrunk) + .FirstOrDefault()!.CollectionBranch), + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: ee => ee)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) + => AssertQuery( + async, + ss => ss.Set() + .OrderBy(x => x.Id) + .Select( + x => ss.Set() + .OrderBy(xx => xx.Id) + .Select( + xx => new + { + OuterBranch = x.RequiredReferenceTrunk.CollectionBranch, + xx.RequiredReferenceTrunk, + xx.RequiredReferenceTrunk.RequiredReferenceBranch, + xx.RequiredReferenceTrunk.Name, + OuterName = x.RequiredReferenceTrunk!.RequiredReferenceBranch.Name + }).FirstOrDefault()), + assertOrder: true, + elementAsserter: (e, a) => + { + AssertCollection(e.OuterBranch, a.OuterBranch); + AssertEqual(e.RequiredReferenceTrunk, a.RequiredReferenceTrunk); + AssertEqual(e.RequiredReferenceBranch, a.RequiredReferenceBranch); + AssertEqual(e.Name, a.Name); + AssertEqual(e.OuterName, a.OuterName); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) + => AssertQuery( + async, + ss => ss.Set() + .OrderBy(x => x.Id) + .Select( + x => ss.Set() + .OrderBy(xx => xx.Id) + .Select( + xx => new + { + OuterBranchCollection = x.RequiredReferenceTrunk.CollectionBranch, + xx.RequiredReferenceTrunk, + xx.RequiredReferenceTrunk.RequiredReferenceBranch, + xx.RequiredReferenceTrunk.Name, + OuterName = x.RequiredReferenceTrunk.RequiredReferenceBranch.Name + }).FirstOrDefault()!.OuterBranchCollection), + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_trunk_collection(bool async) + => AssertQuery( + async, + ss => ss.Set() + .SelectMany(x => x.CollectionTrunk)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_required_trunk_reference_branch_collection(bool async) + => AssertQuery( + async, + ss => ss.Set() + .SelectMany(x => x.RequiredReferenceTrunk.CollectionBranch)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_optional_trunk_reference_branch_collection(bool async) + => AssertQuery( + async, + ss => ss.Set() + .SelectMany(x => x.OptionalReferenceTrunk!.CollectionBranch), + ss => ss.Set() + .SelectMany(x => x.OptionalReferenceTrunk.Maybe(xx => xx!.CollectionBranch) ?? new List())); +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Projection/ReferenceProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Projection/ReferenceProjectionTestBase.cs new file mode 100644 index 00000000000..4ca725db57b --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/Projection/ReferenceProjectionTestBase.cs @@ -0,0 +1,178 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestModels.RelationshipsModel; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public abstract class ReferenceProjectionTestBase(TFixture fixture) : QueryTestBase(fixture) + where TFixture : RelationshipsQueryFixtureBase, new() +{ + protected RelationshipsContext CreateContext() + => Fixture.CreateContext(); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_root(bool async) + => AssertQuery( + async, + ss => ss.Set()); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_trunk_optional(bool async) + => AssertQuery( + async, + ss => ss.Set().OrderBy(x => x.Id).Select(x => x.OptionalReferenceTrunk), + assertOrder: true); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_trunk_required(bool async) + => AssertQuery( + async, + ss => ss.Set().OrderBy(x => x.Id).Select(x => x.RequiredReferenceTrunk), + assertOrder: true); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_branch_required_required(bool async) + => AssertQuery( + async, + ss => ss.Set().OrderBy(x => x.Id).Select(x => x.RequiredReferenceTrunk.RequiredReferenceBranch), + assertOrder: true); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_branch_required_optional(bool async) + => AssertQuery( + async, + ss => ss.Set().OrderBy(x => x.Id).Select(x => x.RequiredReferenceTrunk.OptionalReferenceBranch), + assertOrder: true); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_branch_optional_required(bool async) + => AssertQuery( + async, + ss => ss.Set().OrderBy(x => x.Id).Select(x => x.RequiredReferenceTrunk.RequiredReferenceBranch), + assertOrder: true); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_branch_optional_optional(bool async) + => AssertQuery( + async, + ss => ss.Set().OrderBy(x => x.Id).Select(x => x.RequiredReferenceTrunk.OptionalReferenceBranch), + assertOrder: true); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_root_duplicated(bool async) + => AssertQuery( + async, + ss => ss.Set().Select(x => new { First = x, Second = x }), + elementSorter: e => e.First.Id, + elementAsserter: (e, a) => + { + AssertEqual(e.First, a.First); + AssertEqual(e.Second, a.Second); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_trunk_and_branch_duplicated(bool async) + => AssertQuery( + async, + ss => ss.Set() + .OrderBy(x => x.Id) + .Select( + x => new + { + Trunk1 = x.OptionalReferenceTrunk, + Branch1 = x.OptionalReferenceTrunk!.RequiredReferenceBranch, + Trunk2 = x.OptionalReferenceTrunk, + Branch2 = x.OptionalReferenceTrunk.RequiredReferenceBranch, + }), + assertOrder: true, + elementAsserter: (e, a) => + { + AssertEqual(e.Trunk1, a.Trunk1); + AssertEqual(e.Trunk2, a.Trunk2); + AssertEqual(e.Branch1, a.Branch1); + AssertEqual(e.Branch2, a.Branch2); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_trunk_and_trunk_duplicated(bool async) + => AssertQuery( + async, + ss => ss.Set() + .OrderBy(x => x.Id) + .Select( + x => new + { + Trunk1 = x.RequiredReferenceTrunk, + Leaf1 = x.RequiredReferenceTrunk!.OptionalReferenceBranch!.RequiredReferenceLeaf, + Trunk2 = x.RequiredReferenceTrunk, + Leaf2 = x.RequiredReferenceTrunk.OptionalReferenceBranch.RequiredReferenceLeaf, + }), + assertOrder: true, + elementAsserter: (e, a) => + { + AssertEqual(e.Trunk1, a.Trunk1); + AssertEqual(e.Trunk2, a.Trunk2); + AssertEqual(e.Leaf1, a.Leaf1); + AssertEqual(e.Leaf2, a.Leaf2); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_leaf_trunk_root(bool async) + => AssertQuery( + async, + ss => ss.Set() + .Select( + x => new + { + x.RequiredReferenceTrunk.RequiredReferenceBranch.RequiredReferenceLeaf, + x.RequiredReferenceTrunk, + x + }), + elementSorter: e => e.x.Id, + elementAsserter: (e, a) => + { + AssertEqual(e.RequiredReferenceLeaf, a.RequiredReferenceLeaf); + AssertEqual(e.RequiredReferenceTrunk, a.RequiredReferenceTrunk); + AssertEqual(e.x, a.x); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + => AssertQuery( + async, + ss => ss.Set() + .OrderBy(x => x.Id) + .Select( + x => ss.Set() + .OrderBy(xx => xx.Id) + .Select(xx => xx.RequiredReferenceTrunk) + .FirstOrDefault()!.RequiredReferenceBranch), + assertOrder: true); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + => AssertQuery( + async, + ss => ss.Set() + .OrderBy(x => x.Id) + .Select( + x => ss.Set() + .OrderBy(xx => xx.Id) + .Select(xx => xx.OptionalReferenceTrunk) + .FirstOrDefault()!.OptionalReferenceBranch), + assertOrder: true); +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsQueryFixtureBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsQueryFixtureBase.cs index 93af35ad1bc..36a6a0a2e9f 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsQueryFixtureBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsQueryFixtureBase.cs @@ -22,14 +22,6 @@ protected override Task SeedAsync(RelationshipsContext context) public abstract IReadOnlyDictionary EntitySorters { get; } - //public IReadOnlyDictionary EntitySorters { get; } = new Dictionary> - //{ - // { typeof(RelationshipsRootEntity), e => ((RelationshipsRootEntity?)e)?.Id }, - // { typeof(RelationshipsTrunkEntity), e => ((RelationshipsTrunkEntity?)e)?.Id }, - // { typeof(RelationshipsBranchEntity), e => ((RelationshipsBranchEntity?)e)?.Id }, - // { typeof(RelationshipsLeafEntity), e => ((RelationshipsLeafEntity?)e)?.Id } - //}.ToDictionary(e => e.Key, e => (object)e.Value); - public abstract IReadOnlyDictionary EntityAsserters { get; } protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) diff --git a/test/EFCore.Specification.Tests/Query/Relationships/TrackingRewriter.cs b/test/EFCore.Specification.Tests/Query/Relationships/TrackingRewriter.cs new file mode 100644 index 00000000000..f06fcfac002 --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/TrackingRewriter.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships; + +public class TrackingRewriter : ExpressionVisitor +{ + private readonly bool _noTrackingWithIdentityResolution; + + public TrackingRewriter(bool noTrackingWithIdentityResolution = false) + { + _noTrackingWithIdentityResolution = noTrackingWithIdentityResolution; + } + + private static readonly MethodInfo AsNoTrackingMethodInfo + = typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethod(nameof(EntityFrameworkQueryableExtensions.AsNoTracking))!; + + private static readonly MethodInfo AsNoTrackingWithIdentityResolutionMethodInfo + = typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethod(nameof(EntityFrameworkQueryableExtensions.AsNoTrackingWithIdentityResolution))!; + + protected override Expression VisitExtension(Expression expression) + { + if (expression is EntityQueryRootExpression eqr) + { + return _noTrackingWithIdentityResolution + ? Expression.Call(AsNoTrackingWithIdentityResolutionMethodInfo.MakeGenericMethod(eqr.ElementType), eqr) + : Expression.Call(AsNoTrackingMethodInfo.MakeGenericMethod(eqr.ElementType), eqr); + } + + return base.VisitExtension(expression); + } +} diff --git a/test/EFCore.Specification.Tests/Query/SharedTypeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/SharedTypeQueryTestBase.cs index 6a73dc0d5be..f3ebba9559b 100644 --- a/test/EFCore.Specification.Tests/Query/SharedTypeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/SharedTypeQueryTestBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore; #nullable disable -public abstract class SharedTypeQueryTestBase : NonSharedModelTestBase +public abstract class SharedTypeQueryTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { protected override string StoreName => "SharedTypeQueryTests"; diff --git a/test/EFCore.Specification.Tests/Query/Translations/BasicTypesQueryFixtureBase.cs b/test/EFCore.Specification.Tests/Query/Translations/BasicTypesQueryFixtureBase.cs index a1d77db06ab..08611b027bc 100644 --- a/test/EFCore.Specification.Tests/Query/Translations/BasicTypesQueryFixtureBase.cs +++ b/test/EFCore.Specification.Tests/Query/Translations/BasicTypesQueryFixtureBase.cs @@ -13,7 +13,7 @@ protected override string StoreName => "BasicTypesTest"; public Func GetContextCreator() - => () => CreateContext(); + => CreateContext; protected override Task SeedAsync(BasicTypesContext context) { diff --git a/test/EFCore.Specification.Tests/Query/Translations/StringTranslationsTestBase.cs b/test/EFCore.Specification.Tests/Query/Translations/StringTranslationsTestBase.cs index 5360768ac35..9f3e9fcc201 100644 --- a/test/EFCore.Specification.Tests/Query/Translations/StringTranslationsTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Translations/StringTranslationsTestBase.cs @@ -125,6 +125,17 @@ public virtual Task IndexOf(bool async) ss => ss.Set().Where(b => b.String.IndexOf("Eattl") != -1), ss => ss.Set().Where(b => b.String.IndexOf("Eattl", StringComparison.OrdinalIgnoreCase) != -1)); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task IndexOf_Char(bool async) + => IsCaseSensitive + ? AssertQuery( + async, + ss => ss.Set().Where(b => b.String.IndexOf('e') != -1)) + : AssertQuery( + async, + ss => ss.Set().Where(b => b.String.IndexOf('e') != -1)); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task IndexOf_with_empty_string(bool async) @@ -156,6 +167,29 @@ public virtual Task IndexOf_with_one_parameter_arg(bool async) } } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task IndexOf_with_one_parameter_arg_char(bool async) + { + if (IsCaseSensitive) + { + var pattern = 'e'; + + return AssertQuery( + async, + ss => ss.Set().Where(b => b.String.IndexOf(pattern) == 1)); + } + else + { + var pattern = 'e'; + + return AssertQuery( + async, + ss => ss.Set().Where(b => b.String.IndexOf(pattern) == 1), + ss => ss.Set().Where(b => b.String.IndexOf(pattern, StringComparison.OrdinalIgnoreCase) == 1)); + } + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task IndexOf_with_constant_starting_position(bool async) @@ -163,6 +197,13 @@ public virtual Task IndexOf_with_constant_starting_position(bool async) async, ss => ss.Set().Where(b => b.String.Length > 2 && b.String.IndexOf("e", 2) == 6)); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task IndexOf_with_constant_starting_position_char(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(b => b.String.Length > 2 && b.String.IndexOf('e', 2) == 6)); + #pragma warning disable CA1866 // Use 'string.Method(char)' instead of 'string.Method(string)' for string with single char [ConditionalTheory] [MemberData(nameof(IsAsyncData))] @@ -183,6 +224,22 @@ public virtual Task IndexOf_with_parameter_starting_position(bool async) } #pragma warning restore CA1866 + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task IndexOf_with_parameter_starting_position_char(bool async) + { + var start = 2; + + return IsCaseSensitive + ? AssertQuery( + async, + ss => ss.Set().Where(b => b.String.Length > 2 && b.String.IndexOf('e', start) == 6)) + : AssertQuery( + async, + ss => ss.Set().Where( + b => b.String.Length > 2 && b.String.IndexOf('e', start) == 6)); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task IndexOf_after_ToString(bool async) @@ -213,6 +270,13 @@ public virtual Task Replace(bool async) ss => ss.Set().Where(b => b.String.Replace("sea", "rea") == "reattle"), ss => ss.Set().Where(b => b.String.Replace("sea", "rea", StringComparison.OrdinalIgnoreCase) == "reattle")); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Replace_Char(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(b => b.String.Replace('S', 'R') == "Reattle")); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Replace_with_empty_string(bool async) @@ -353,6 +417,18 @@ public virtual Task StartsWith_Literal(bool async) ss => ss.Set().Where(b => b.String.StartsWith("se")), ss => ss.Set().Where(b => b.String.StartsWith("se", StringComparison.OrdinalIgnoreCase))); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task StartsWith_Literal_Char(bool async) + => IsCaseSensitive + ? AssertQuery( + async, + ss => ss.Set().Where(b => b.String.StartsWith('S'))) + : AssertQuery( + async, + ss => ss.Set().Where(b => b.String.StartsWith('S')), + ss => ss.Set().Where(b => b.String.StartsWith('S'))); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task StartsWith_Parameter(bool async) @@ -376,6 +452,29 @@ public virtual Task StartsWith_Parameter(bool async) } } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task StartsWith_Parameter_Char(bool async) + { + if (IsCaseSensitive) + { + var pattern = 'S'; + + return AssertQuery( + async, + ss => ss.Set().Where(b => b.String.StartsWith(pattern))); + } + else + { + var pattern = 'S'; + + return AssertQuery( + async, + ss => ss.Set().Where(b => b.String.StartsWith(pattern)), + ss => ss.Set().Where(b => b.String.StartsWith(pattern))); + } + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task StartsWith_Column(bool async) @@ -442,6 +541,17 @@ public virtual Task EndsWith_Literal(bool async) ss => ss.Set().Where(b => b.String.EndsWith("Le")), ss => ss.Set().Where(b => b.String.EndsWith("Le", StringComparison.OrdinalIgnoreCase))); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task EndsWith_Literal_Char(bool async) + => IsCaseSensitive + ? AssertQuery( + async, + ss => ss.Set().Where(b => b.String.EndsWith('e'))) + : AssertQuery( + async, + ss => ss.Set().Where(b => b.String.EndsWith('e'))); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task EndsWith_Parameter(bool async) @@ -465,6 +575,29 @@ public virtual Task EndsWith_Parameter(bool async) } } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task EndsWith_Parameter_Char(bool async) + { + if (IsCaseSensitive) + { + var pattern = 'e'; + + return AssertQuery( + async, + ss => ss.Set().Where(b => b.String.EndsWith(pattern))); + } + else + { + var pattern = 'e'; + + return AssertQuery( + async, + ss => ss.Set().Where(b => b.String.EndsWith(pattern)), + ss => ss.Set().Where(b => b.String.EndsWith(pattern))); + } + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task EndsWith_Column(bool async) @@ -531,6 +664,18 @@ public virtual Task Contains_Literal(bool async) ss => ss.Set().Where(b => b.String.Contains("Eattl")), ss => ss.Set().Where(b => b.String.Contains("Eattl", StringComparison.OrdinalIgnoreCase))); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_Literal_Char(bool async) + => IsCaseSensitive + ? AssertQuery( + async, + ss => ss.Set().Where(b => b.String.Contains('e'))) + : AssertQuery( + async, + ss => ss.Set().Where(b => b.String.Contains('e')), + ss => ss.Set().Where(b => b.String.Contains('e'))); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual async Task Contains_Column(bool async) diff --git a/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs b/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs index 49d25825ebf..af3e6fa0196 100644 --- a/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs +++ b/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Scaffolding; -public abstract class CompiledModelTestBase : NonSharedModelTestBase +public abstract class CompiledModelTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { [ConditionalFact] public virtual Task SimpleModel() @@ -1973,7 +1973,7 @@ protected virtual Task Test( [CallerMemberName] string testName = "") where TContext : DbContext { - var contextFactory = await CreateContextFactory( + var contextFactory = CreateContextFactory( modelBuilder => { var model = modelBuilder.Model; @@ -2031,17 +2031,16 @@ protected virtual Task Test( if (useContext != null) { + await TestStore.InitializeAsync(ServiceProvider, contextFactory.CreateContext); ListLoggerFactory.Clear(); - var testStore = await TestStore.InitializeAsync(ServiceProvider, contextFactory.CreateContext); - await using var _ = testStore; - using var compiledModelContext = (await CreateContextFactory( + using var compiledModelContext = CreateContextFactory( onConfiguring: options => { onConfiguring?.Invoke(options); options.UseModel(compiledModel); }, - addServices: addServices)) + addServices: addServices) .CreateContext(); await useContext(compiledModelContext); } diff --git a/test/EFCore.Specification.Tests/SingletonInterceptorsTestBase.cs b/test/EFCore.Specification.Tests/SingletonInterceptorsTestBase.cs index 2ca60f238df..b8480ef2757 100644 --- a/test/EFCore.Specification.Tests/SingletonInterceptorsTestBase.cs +++ b/test/EFCore.Specification.Tests/SingletonInterceptorsTestBase.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore; -public abstract class SingletonInterceptorsTestBase : NonSharedModelTestBase +public abstract class SingletonInterceptorsTestBase(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture where TContext : SingletonInterceptorsTestBase.LibraryContext { protected class Book diff --git a/test/EFCore.Specification.Tests/TestUtilities/NonSharedFixture.cs b/test/EFCore.Specification.Tests/TestUtilities/NonSharedFixture.cs new file mode 100644 index 00000000000..66a5a3d0504 --- /dev/null +++ b/test/EFCore.Specification.Tests/TestUtilities/NonSharedFixture.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore; + +public class NonSharedFixture : IAsyncLifetime +{ + private TestStore? _testStore; + + public virtual TestStore GetOrCreateTestStore(Func createTestStore) + => _testStore ??= createTestStore(); + + public virtual void Dispose() + { + } + + public Task InitializeAsync() => Task.CompletedTask; + + public virtual async Task DisposeAsync() + { + if (_testStore != null) + { + await _testStore.DisposeAsync(); + _testStore = null; + } + } +} diff --git a/test/EFCore.Specification.Tests/Update/UpdatesTestBase.cs b/test/EFCore.Specification.Tests/Update/UpdatesTestBase.cs index f0c0d83e3d7..6de5d590d49 100644 --- a/test/EFCore.Specification.Tests/Update/UpdatesTestBase.cs +++ b/test/EFCore.Specification.Tests/Update/UpdatesTestBase.cs @@ -14,7 +14,7 @@ public abstract class UpdatesTestBase(TFixture fixture) : IClassFixtur { protected TFixture Fixture { get; } = fixture; - public static IEnumerable IsAsyncData = new object[][] { [false], [true] }; + public static readonly IEnumerable IsAsyncData = [[false], [true]]; [ConditionalTheory] // Issue #25905 [InlineData(false)] diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs index aee74180982..dbd19852f1b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public class NonSharedModelBulkUpdatesSqlServerTest : NonSharedModelBulkUpdatesRelationalTestBase +public class NonSharedModelBulkUpdatesSqlServerTest(NonSharedFixture fixture) : NonSharedModelBulkUpdatesRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/EntitySplittingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/EntitySplittingSqlServerTest.cs index 343897cb86d..8b57cdb8443 100644 --- a/test/EFCore.SqlServer.FunctionalTests/EntitySplittingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/EntitySplittingSqlServerTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore; #nullable disable -public class EntitySplittingSqlServerTest(ITestOutputHelper testOutputHelper) : EntitySplittingTestBase(testOutputHelper) +public class EntitySplittingSqlServerTest(NonSharedFixture fixture, ITestOutputHelper testOutputHelper) : EntitySplittingTestBase(fixture, testOutputHelper) { [ConditionalFact] public virtual async Task Can_roundtrip_with_triggers() diff --git a/test/EFCore.SqlServer.FunctionalTests/JsonTypesCustomMappingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/JsonTypesCustomMappingSqlServerTest.cs index 700bccd5963..a8d6c527e38 100644 --- a/test/EFCore.SqlServer.FunctionalTests/JsonTypesCustomMappingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/JsonTypesCustomMappingSqlServerTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore; -public class JsonTypesCustomMappingSqlServerTest : JsonTypesSqlServerTestBase +public class JsonTypesCustomMappingSqlServerTest(NonSharedFixture fixture) : JsonTypesSqlServerTestBase(fixture) { protected override IServiceCollection AddServices(IServiceCollection serviceCollection) => serviceCollection.AddSingleton(); diff --git a/test/EFCore.SqlServer.FunctionalTests/JsonTypesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/JsonTypesSqlServerTest.cs index c8e6e9904d5..dbbfe91e371 100644 --- a/test/EFCore.SqlServer.FunctionalTests/JsonTypesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/JsonTypesSqlServerTest.cs @@ -3,4 +3,4 @@ namespace Microsoft.EntityFrameworkCore; -public class JsonTypesSqlServerTest : JsonTypesSqlServerTestBase; +public class JsonTypesSqlServerTest(NonSharedFixture fixture) : JsonTypesSqlServerTestBase(fixture); diff --git a/test/EFCore.SqlServer.FunctionalTests/JsonTypesSqlServerTestBase.cs b/test/EFCore.SqlServer.FunctionalTests/JsonTypesSqlServerTestBase.cs index 554f444ef78..e60d378ccb4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/JsonTypesSqlServerTestBase.cs +++ b/test/EFCore.SqlServer.FunctionalTests/JsonTypesSqlServerTestBase.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore; -public abstract class JsonTypesSqlServerTestBase : JsonTypesRelationalTestBase +public abstract class JsonTypesSqlServerTestBase(NonSharedFixture fixture) : JsonTypesRelationalTestBase(fixture) { public override Task Can_read_write_collection_of_fixed_length_string_JSON_values(object? storeType) => base.Can_read_write_collection_of_fixed_length_string_JSON_values("nchar(32)"); diff --git a/test/EFCore.SqlServer.FunctionalTests/MaterializationInterceptionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/MaterializationInterceptionSqlServerTest.cs index 4918c4c9dfa..b92fdc003dc 100644 --- a/test/EFCore.SqlServer.FunctionalTests/MaterializationInterceptionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/MaterializationInterceptionSqlServerTest.cs @@ -3,8 +3,8 @@ namespace Microsoft.EntityFrameworkCore; -public class MaterializationInterceptionSqlServerTest : - MaterializationInterceptionTestBase +public class MaterializationInterceptionSqlServerTest(NonSharedFixture fixture) : + MaterializationInterceptionTestBase(fixture) { public class SqlServerLibraryContext(DbContextOptions options) : LibraryContext(options) { diff --git a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs index ce336f0a5f9..4d1587e2cc7 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs @@ -3440,6 +3440,90 @@ await Test( """); } + public override async Task Multiop_drop_table_and_create_the_same_table_in_one_migration() + { + await base.Multiop_drop_table_and_create_the_same_table_in_one_migration(); + + AssertSql( + """ +DROP TABLE [Customers]; +""", + // + """ +CREATE TABLE [Customers] ( + [Id] int NOT NULL IDENTITY, + [Name] nvarchar(max) NULL, + CONSTRAINT [PK_Customers] PRIMARY KEY ([Id]) +); +"""); + } + + public override async Task Multiop_create_table_and_drop_it_in_one_migration() + { + await base.Multiop_create_table_and_drop_it_in_one_migration(); + + AssertSql( + """ +CREATE TABLE [Customers] ( + [Id] int NOT NULL IDENTITY, + [Name] nvarchar(max) NULL, + CONSTRAINT [PK_Customers] PRIMARY KEY ([Id]) +); +""", + // + """ +DROP TABLE [Customers]; +"""); + } + + public override async Task Multiop_rename_table_and_drop() + { + await base.Multiop_rename_table_and_drop(); + + AssertSql( + """ +ALTER TABLE [Customers] DROP CONSTRAINT [PK_Customers]; +""", + // + """ +EXEC sp_rename N'[Customers]', N'NewCustomers', 'OBJECT'; +""", + // + """ +ALTER TABLE [NewCustomers] ADD CONSTRAINT [PK_NewCustomers] PRIMARY KEY ([Id]); +""", + // + """ +DROP TABLE [NewCustomers]; +"""); + } + + public override async Task Multiop_rename_table_and_create_new_table_with_the_old_name() + { + await base.Multiop_rename_table_and_create_new_table_with_the_old_name(); + + AssertSql( + """ +ALTER TABLE [Customers] DROP CONSTRAINT [PK_Customers]; +""", + // + """ +EXEC sp_rename N'[Customers]', N'NewCustomers', 'OBJECT'; +""", + // + """ +ALTER TABLE [NewCustomers] ADD CONSTRAINT [PK_NewCustomers] PRIMARY KEY ([Id]); +""", + // + """ +CREATE TABLE [Customers] ( + [Id] int NOT NULL IDENTITY, + [Name] nvarchar(max) NULL, + CONSTRAINT [PK_Customers] PRIMARY KEY ([Id]) +); +"""); + } + [ConditionalFact] public virtual async Task Create_temporal_table_default_column_mappings_and_default_history_table() { @@ -11829,6 +11913,363 @@ await Test( """); } + [ConditionalFact] + public virtual async Task Temporal_multiop_drop_temporal_table_and_add_the_same_table_in_one_migration() + { + await TestComposite( + [ + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.Property("SystemTimeStart").ValueGeneratedOnAddOrUpdate(); + e.Property("SystemTimeEnd").ValueGeneratedOnAddOrUpdate(); + e.HasKey("Id"); + + e.ToTable( + "Customers", tb => tb.IsTemporal( + ttb => + { + ttb.UseHistoryTable("HistoryTable", "historySchema"); + ttb.HasPeriodStart("SystemTimeStart"); + ttb.HasPeriodEnd("SystemTimeEnd"); + })); + }), + builder => { }, + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.Property("SystemTimeStart").ValueGeneratedOnAddOrUpdate(); + e.Property("SystemTimeEnd").ValueGeneratedOnAddOrUpdate(); + e.HasKey("Id"); + + e.ToTable( + "Customers", tb => tb.IsTemporal( + ttb => + { + ttb.UseHistoryTable("HistoryTable", "historySchema"); + ttb.HasPeriodStart("SystemTimeStart"); + ttb.HasPeriodEnd("SystemTimeEnd"); + })); + }) + ]); + + AssertSql( +""" +ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) +""", + // + """ +DROP TABLE [Customers]; +""", + // + """ +DROP TABLE [historySchema].[HistoryTable]; +""", + // + """ +IF SCHEMA_ID(N'historySchema') IS NULL EXEC(N'CREATE SCHEMA [historySchema];'); +""", + // + """ +CREATE TABLE [Customers] ( + [Id] int NOT NULL IDENTITY, + [Name] nvarchar(max) NULL, + [SystemTimeEnd] datetime2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL, + [SystemTimeStart] datetime2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL, + CONSTRAINT [PK_Customers] PRIMARY KEY ([Id]), + PERIOD FOR SYSTEM_TIME([SystemTimeStart], [SystemTimeEnd]) +) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [historySchema].[HistoryTable])); +"""); + } + + [ConditionalFact] + public virtual async Task Temporal_multiop_create_temporal_and_drop() + { + await TestComposite( + [ + builder => { }, + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.Property("SystemTimeStart").ValueGeneratedOnAddOrUpdate(); + e.Property("SystemTimeEnd").ValueGeneratedOnAddOrUpdate(); + e.HasKey("Id"); + + e.ToTable( + "Customers", tb => tb.IsTemporal( + ttb => + { + ttb.UseHistoryTable("HistoryTable", "historySchema"); + ttb.HasPeriodStart("SystemTimeStart"); + ttb.HasPeriodEnd("SystemTimeEnd"); + })); + }), + builder => { }, + ]); + + AssertSql( +""" +IF SCHEMA_ID(N'historySchema') IS NULL EXEC(N'CREATE SCHEMA [historySchema];'); +""", + // + """ +CREATE TABLE [Customers] ( + [Id] int NOT NULL IDENTITY, + [Name] nvarchar(max) NULL, + [SystemTimeEnd] datetime2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL, + [SystemTimeStart] datetime2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL, + CONSTRAINT [PK_Customers] PRIMARY KEY ([Id]), + PERIOD FOR SYSTEM_TIME([SystemTimeStart], [SystemTimeEnd]) +) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [historySchema].[HistoryTable])); +""", + // + """ +ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) +""", + // + """ +DROP TABLE [Customers]; +""", + // + """ +DROP TABLE [historySchema].[HistoryTable]; +"""); + } + + [ConditionalFact] + public virtual async Task Temporal_multiop_rename_temporal_and_drop() + { + await TestComposite( + [ + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.Property("SystemTimeStart").ValueGeneratedOnAddOrUpdate(); + e.Property("SystemTimeEnd").ValueGeneratedOnAddOrUpdate(); + e.HasKey("Id"); + + e.ToTable( + "Customers", tb => tb.IsTemporal( + ttb => + { + ttb.UseHistoryTable("HistoryTable", "historySchema"); + ttb.HasPeriodStart("SystemTimeStart"); + ttb.HasPeriodEnd("SystemTimeEnd"); + })); + }), + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.Property("SystemTimeStart").ValueGeneratedOnAddOrUpdate(); + e.Property("SystemTimeEnd").ValueGeneratedOnAddOrUpdate(); + e.HasKey("Id"); + + e.ToTable( + "NewCustomers", tb => tb.IsTemporal( + ttb => + { + ttb.UseHistoryTable("HistoryTable", "historySchema"); + ttb.HasPeriodStart("SystemTimeStart"); + ttb.HasPeriodEnd("SystemTimeEnd"); + })); + }), + builder => { }, + ]); + + AssertSql( +""" +ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) +""", + // + """ +ALTER TABLE [Customers] DROP CONSTRAINT [PK_Customers]; +""", + // + """ +EXEC sp_rename N'[Customers]', N'NewCustomers', 'OBJECT'; +""", + // + """ +ALTER TABLE [NewCustomers] ADD CONSTRAINT [PK_NewCustomers] PRIMARY KEY ([Id]); +""", + // + """ +DROP TABLE [NewCustomers]; +""", + // + """ +DROP TABLE [historySchema].[HistoryTable]; +"""); + } + + [ConditionalFact] + public virtual async Task Temporal_multiop_rename_period_drop_table_create_as_regular() + { + await TestComposite( + [ + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.Property("SystemTimeStart").ValueGeneratedOnAddOrUpdate(); + e.Property("SystemTimeEnd").ValueGeneratedOnAddOrUpdate(); + e.HasKey("Id"); + + e.ToTable( + "Customers", tb => tb.IsTemporal( + ttb => + { + ttb.UseHistoryTable("HistoryTable", "historySchema"); + ttb.HasPeriodStart("SystemTimeStart"); + ttb.HasPeriodEnd("SystemTimeEnd"); + })); + }), + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.Property("NewSystemTimeStart").ValueGeneratedOnAddOrUpdate(); + e.Property("SystemTimeEnd").ValueGeneratedOnAddOrUpdate(); + e.HasKey("Id"); + + e.ToTable( + "Customers", tb => tb.IsTemporal( + ttb => + { + ttb.UseHistoryTable("HistoryTable", "historySchema"); + ttb.HasPeriodStart("NewSystemTimeStart"); + ttb.HasPeriodEnd("SystemTimeEnd"); + })); + }), + builder => { }, + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.HasKey("Id"); + + e.ToTable("Customers"); + }), + ]); + + AssertSql( +""" +EXEC sp_rename N'[Customers].[SystemTimeStart]', N'NewSystemTimeStart', 'COLUMN'; +""", + // + """ +ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) +""", + // + """ +DROP TABLE [Customers]; +""", + // + """ +DROP TABLE [historySchema].[HistoryTable]; +""", + // + """ +CREATE TABLE [Customers] ( + [Id] int NOT NULL IDENTITY, + [Name] nvarchar(max) NULL, + CONSTRAINT [PK_Customers] PRIMARY KEY ([Id]) +); +"""); + } + + [ConditionalFact] + public virtual async Task Temporal_multiop_rename_column_drop_table_create_as_regular() + { + await TestComposite( + [ + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.Property("SystemTimeStart").ValueGeneratedOnAddOrUpdate(); + e.Property("SystemTimeEnd").ValueGeneratedOnAddOrUpdate(); + e.HasKey("Id"); + + e.ToTable( + "Customers", tb => tb.IsTemporal( + ttb => + { + ttb.UseHistoryTable("HistoryTable", "historySchema"); + ttb.HasPeriodStart("SystemTimeStart"); + ttb.HasPeriodEnd("SystemTimeEnd"); + })); + }), + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("NewName"); + e.Property("SystemTimeStart").ValueGeneratedOnAddOrUpdate(); + e.Property("SystemTimeEnd").ValueGeneratedOnAddOrUpdate(); + e.HasKey("Id"); + + e.ToTable( + "Customers", tb => tb.IsTemporal( + ttb => + { + ttb.UseHistoryTable("HistoryTable", "historySchema"); + ttb.HasPeriodStart("SystemTimeStart"); + ttb.HasPeriodEnd("SystemTimeEnd"); + })); + }), + builder => { }, + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.HasKey("Id"); + + e.ToTable("Customers"); + }), + ]); + + AssertSql( +""" +EXEC sp_rename N'[Customers].[Name]', N'NewName', 'COLUMN'; +""", + // + """ +ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) +""", + // + """ +DROP TABLE [Customers]; +""", + // + """ +DROP TABLE [historySchema].[HistoryTable]; +""", + // + """ +CREATE TABLE [Customers] ( + [Id] int NOT NULL IDENTITY, + [Name] nvarchar(max) NULL, + CONSTRAINT [PK_Customers] PRIMARY KEY ([Id]) +); +"""); + } + [ConditionalFact] public override async Task Add_required_primitive_collection_to_existing_table() { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocAdvancedMappingsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocAdvancedMappingsQuerySqlServerTest.cs index 21200d3fef8..f8afa538679 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocAdvancedMappingsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocAdvancedMappingsQuerySqlServerTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class AdHocAdvancedMappingsQuerySqlServerTest : AdHocAdvancedMappingsQueryRelationalTestBase +public class AdHocAdvancedMappingsQuerySqlServerTest(NonSharedFixture fixture) : AdHocAdvancedMappingsQueryRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocComplexTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocComplexTypeQuerySqlServerTest.cs index d076ba465ec..00386ca871e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocComplexTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocComplexTypeQuerySqlServerTest.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query; -public class AdHocComplexTypeQuerySqlServerTest : AdHocComplexTypeQueryTestBase +public class AdHocComplexTypeQuerySqlServerTest(NonSharedFixture fixture) : AdHocComplexTypeQueryTestBase(fixture) { public override async Task Complex_type_equals_parameter_with_nested_types_with_property_of_same_name() { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerJsonTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerJsonTypeTest.cs index fb0e7c2d8ec..afb7f4fd8ee 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerJsonTypeTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerJsonTypeTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.Query; [SqlServerCondition(SqlServerCondition.SupportsJsonType)] -public class AdHocJsonQuerySqlServerJsonTypeTest : AdHocJsonQuerySqlServerTestBase +public class AdHocJsonQuerySqlServerJsonTypeTest(NonSharedFixture fixture) : AdHocJsonQuerySqlServerTestBase(fixture) { public override async Task Missing_navigation_works_with_deduplication(bool async) { @@ -27,12 +27,12 @@ public override async Task Missing_navigation_works_with_deduplication(bool asyn } } - public override async Task Contains_on_nested_collection_with_init_only_navigation(bool async) + public override async Task Contains_on_nested_collection_with_init_only_navigation() // TODO:SQLJSON (See JsonTypeToFunction.cs) => Assert.Equal( "OpenJson support not yet supported for JSON native data type.", (await Assert.ThrowsAsync( - () => base.Contains_on_nested_collection_with_init_only_navigation(async))).Message); + () => base.Contains_on_nested_collection_with_init_only_navigation())).Message); protected override string StoreName => "AdHocJsonQueryJsonTypeTest"; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerTest.cs index a9da97566d4..37aff5bf1f1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerTest.cs @@ -3,6 +3,6 @@ namespace Microsoft.EntityFrameworkCore.Query; -public class AdHocJsonQuerySqlServerTest : AdHocJsonQuerySqlServerTestBase +public class AdHocJsonQuerySqlServerTest(NonSharedFixture fixture) : AdHocJsonQuerySqlServerTestBase(fixture) { } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerTestBase.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerTestBase.cs index 6cc90cd4c02..758c224de1a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerTestBase.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerTestBase.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public abstract class AdHocJsonQuerySqlServerTestBase : AdHocJsonQueryTestBase +public abstract class AdHocJsonQuerySqlServerTestBase(NonSharedFixture fixture) : AdHocJsonQueryRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; @@ -21,29 +21,186 @@ protected override void ConfigureWarnings(WarningsConfigurationBuilder builder) builder.Log(CoreEventId.StringEnumValueInJson, SqlServerEventId.JsonTypeExperimental); } - protected override async Task Seed29219(DbContext ctx) + protected void AssertSql(params string[] expected) + => TestSqlLoggerFactory.AssertBaseline(expected); + + public override async Task Project_root_with_missing_scalars(bool async) { - var entity1 = new MyEntity29219 - { - Id = 1, - Reference = new MyJsonEntity29219 { NonNullableScalar = 10, NullableScalar = 11 }, - Collection = - [ - new MyJsonEntity29219 { NonNullableScalar = 100, NullableScalar = 101 }, - new MyJsonEntity29219 { NonNullableScalar = 200, NullableScalar = 201 }, - new MyJsonEntity29219 { NonNullableScalar = 300, NullableScalar = null } - ] - }; - - var entity2 = new MyEntity29219 - { - Id = 2, - Reference = new MyJsonEntity29219 { NonNullableScalar = 20, NullableScalar = null }, - Collection = [new MyJsonEntity29219 { NonNullableScalar = 1001, NullableScalar = null }] - }; + await base.Project_root_with_missing_scalars(async); + + AssertSql( + """ +SELECT [e].[Id], [e].[Name], [e].[Collection], [e].[OptionalReference], [e].[RequiredReference] +FROM [Entities] AS [e] +WHERE [e].[Id] < 4 +"""); + } + + public override async Task Project_top_level_json_entity_with_missing_scalars(bool async) + { + await base.Project_top_level_json_entity_with_missing_scalars(async); + + AssertSql( + """ +SELECT [e].[Id], [e].[OptionalReference], [e].[RequiredReference], [e].[Collection] +FROM [Entities] AS [e] +WHERE [e].[Id] < 4 +"""); + } + + public override async Task Project_nested_json_entity_with_missing_scalars(bool async) + { + await base.Project_nested_json_entity_with_missing_scalars(async); + + AssertSql( +""" +SELECT [e].[Id], JSON_QUERY([e].[OptionalReference], '$.NestedOptionalReference'), JSON_QUERY([e].[RequiredReference], '$.NestedRequiredReference'), JSON_QUERY([e].[Collection], '$[0].NestedCollection') +FROM [Entities] AS [e] +WHERE [e].[Id] < 4 +"""); + } + + public override async Task Project_root_entity_with_missing_required_navigation(bool async) + { + await base.Project_root_entity_with_missing_required_navigation(async); + + AssertSql( + """ +SELECT [e].[Id], [e].[Name], [e].[Collection], [e].[OptionalReference], [e].[RequiredReference] +FROM [Entities] AS [e] +WHERE [e].[Id] = 5 +"""); + } + + + public override async Task Project_missing_required_navigation(bool async) + { + await base.Project_missing_required_navigation(async); + + AssertSql( + """ +SELECT JSON_QUERY([e].[RequiredReference], '$.NestedRequiredReference'), [e].[Id] +FROM [Entities] AS [e] +WHERE [e].[Id] = 5 +"""); + } + + public override async Task Project_root_entity_with_null_required_navigation(bool async) + { + await base.Project_root_entity_with_null_required_navigation(async); - ctx.AddRange(entity1, entity2); - await ctx.SaveChangesAsync(); + AssertSql( + """ +SELECT [e].[Id], [e].[Name], [e].[Collection], [e].[OptionalReference], [e].[RequiredReference] +FROM [Entities] AS [e] +WHERE [e].[Id] = 6 +"""); + } + + public override async Task Project_null_required_navigation(bool async) + { + await base.Project_null_required_navigation(async); + + AssertSql( + """ +SELECT [e].[RequiredReference], [e].[Id] +FROM [Entities] AS [e] +WHERE [e].[Id] = 6 +"""); + } + + public override async Task Project_missing_required_scalar(bool async) + { + await base.Project_missing_required_scalar(async); + + AssertSql( + """ +SELECT [e].[Id], CAST(JSON_VALUE([e].[RequiredReference], '$.Number') AS float) AS [Number] +FROM [Entities] AS [e] +WHERE [e].[Id] = 2 +"""); + } + + public override async Task Project_null_required_scalar(bool async) + { + await base.Project_null_required_scalar(async); + + AssertSql( + """ +SELECT [e].[Id], CAST(JSON_VALUE([e].[RequiredReference], '$.Number') AS float) AS [Number] +FROM [Entities] AS [e] +WHERE [e].[Id] = 4 +"""); + } + + protected override async Task Seed21006(Context21006 context) + { + await base.Seed21006(context); + + // missing scalar on top level + await context.Database.ExecuteSqlAsync( + $$$""" +INSERT INTO [Entities] ([Collection], [OptionalReference], [RequiredReference], [Id], [Name]) +VALUES ( +N'[{"Text":"e2 c1","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e2 c1 c1"},{"DoB":"2000-01-01T00:00:00","Text":"e2 c1 c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e2 c1 nor"},"NestedRequiredReference":{"DoB":"2000-01-01T00:00:00","Text":"e2 c1 nrr"}},{"Text":"e2 c2","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e2 c2 c1"},{"DoB":"2000-01-01T00:00:00","Text":"e2 c2 c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e2 c2 nor"},"NestedRequiredReference":{"DoB":"2000-01-01T00:00:00","Text":"e2 c2 nrr"}}]', +N'{"Text":"e2 or","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e2 or c1"},{"DoB":"2000-01-01T00:00:00","Text":"e2 or c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e2 or nor"},"NestedRequiredReference":{"DoB":"2000-01-01T00:00:00","Text":"e2 or nrr"}}', +N'{"Text":"e2 rr","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e2 rr c1"},{"DoB":"2000-01-01T00:00:00","Text":"e2 rr c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e2 rr nor"},"NestedRequiredReference":{"DoB":"2000-01-01T00:00:00","Text":"e2 rr nrr"}}', +2, +N'e2') +"""); + + // missing scalar on nested level + await context.Database.ExecuteSqlAsync( + $$$""" +INSERT INTO [Entities] ([Collection], [OptionalReference], [RequiredReference], [Id], [Name]) +VALUES ( +N'[{"Number":7,"Text":"e3 c1","NestedCollection":[{"Text":"e3 c1 c1"},{"Text":"e3 c1 c2"}],"NestedOptionalReference":{"Text":"e3 c1 nor"},"NestedRequiredReference":{"Text":"e3 c1 nrr"}},{"Number":7,"Text":"e3 c2","NestedCollection":[{"Text":"e3 c2 c1"},{"Text":"e3 c2 c2"}],"NestedOptionalReference":{"Text":"e3 c2 nor"},"NestedRequiredReference":{"Text":"e3 c2 nrr"}}]', +N'{"Number":7,"Text":"e3 or","NestedCollection":[{"Text":"e3 or c1"},{"Text":"e3 or c2"}],"NestedOptionalReference":{"Text":"e3 or nor"},"NestedRequiredReference":{"Text":"e3 or nrr"}}', +N'{"Number":7,"Text":"e3 rr","NestedCollection":[{"Text":"e3 rr c1"},{"Text":"e3 rr c2"}],"NestedOptionalReference":{"Text":"e3 rr nor"},"NestedRequiredReference":{"Text":"e3 rr nrr"}}', +3, +N'e3') +"""); + + // null scalar on top level + await context.Database.ExecuteSqlAsync( + $$$""" +INSERT INTO [Entities] ([Collection], [OptionalReference], [RequiredReference], [Id], [Name]) +VALUES ( +N'[{"Number":null,"Text":"e4 c1","NestedCollection":[{"Text":"e4 c1 c1"},{"Text":"e4 c1 c2"}],"NestedOptionalReference":{"Text":"e4 c1 nor"},"NestedRequiredReference":{"Text":"e4 c1 nrr"}},{"Number":null,"Text":"e4 c2","NestedCollection":[{"Text":"e4 c2 c1"},{"Text":"e4 c2 c2"}],"NestedOptionalReference":{"Text":"e4 c2 nor"},"NestedRequiredReference":{"Text":"e4 c2 nrr"}}]', +N'{"Number":null,"Text":"e4 or","NestedCollection":[{"Text":"e4 or c1"},{"Text":"e4 or c2"}],"NestedOptionalReference":{"Text":"e4 or nor"},"NestedRequiredReference":{"Text":"e4 or nrr"}}', +N'{"Number":null,"Text":"e4 rr","NestedCollection":[{"Text":"e4 rr c1"},{"Text":"e4 rr c2"}],"NestedOptionalReference":{"Text":"e4 rr nor"},"NestedRequiredReference":{"Text":"e4 rr nrr"}}', +4, +N'e4') +"""); + + // missing required navigation + await context.Database.ExecuteSqlAsync( + $$$""" +INSERT INTO [Entities] ([Collection], [OptionalReference], [RequiredReference], [Id], [Name]) +VALUES ( +N'[{"Number":7,"Text":"e5 c1","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e5 c1 c1"},{"DoB":"2000-01-01T00:00:00","Text":"e5 c1 c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e5 c1 nor"}},{"Number":7,"Text":"e5 c2","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e5 c2 c1"},{"DoB":"2000-01-01T00:00:00","Text":"e5 c2 c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e5 c2 nor"}}]', +N'{"Number":7,"Text":"e5 or","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e5 or c1"},{"DoB":"2000-01-01T00:00:00","Text":"e5 or c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e5 or nor"}}', +N'{"Number":7,"Text":"e5 rr","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e5 rr c1"},{"DoB":"2000-01-01T00:00:00","Text":"e5 rr c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e5 rr nor"}}', +5, +N'e5') +"""); + + // null required navigation + await context.Database.ExecuteSqlAsync( + $$$""" +INSERT INTO [Entities] ([Collection], [OptionalReference], [RequiredReference], [Id], [Name]) +VALUES ( +N'[{"Number":7,"Text":"e6 c1","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e6 c1 c1"},{"DoB":"2000-01-01T00:00:00","Text":"e6 c1 c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e6 c1 nor"},"NestedRequiredReference":null},{"Number":7,"Text":"e6 c2","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e6 c2 c1"},{"DoB":"2000-01-01T00:00:00","Text":"e6 c2 c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e6 c2 nor"},"NestedRequiredReference":null}]', +N'{"Number":7,"Text":"e6 or","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e6 or c1"},{"DoB":"2000-01-01T00:00:00","Text":"e6 or c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e6 or nor"},"NestedRequiredReference":null}', +N'{"Number":7,"Text":"e6 rr","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e6 rr c1"},{"DoB":"2000-01-01T00:00:00","Text":"e6 rr c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e6 rr nor"},"NestedRequiredReference":null}', +6, +N'e6') +"""); + } + + protected override async Task Seed29219(DbContext ctx) + { + await base.Seed29219(ctx); await ctx.Database.ExecuteSqlAsync( $$""" @@ -133,52 +290,6 @@ INSERT INTO [Junk] ([Collection], [Reference], [Id]) """); } - protected override Task SeedArrayOfPrimitives(DbContext ctx) - { - var entity1 = new MyEntityArrayOfPrimitives - { - Id = 1, - Reference = new MyJsonEntityArrayOfPrimitives - { - IntArray = [1, 2, 3], - ListOfString = - [ - "Foo", - "Bar", - "Baz" - ] - }, - Collection = - [ - new MyJsonEntityArrayOfPrimitives { IntArray = [111, 112, 113], ListOfString = ["Foo11", "Bar11"] }, - new MyJsonEntityArrayOfPrimitives { IntArray = [211, 212, 213], ListOfString = ["Foo12", "Bar12"] } - ] - }; - - var entity2 = new MyEntityArrayOfPrimitives - { - Id = 2, - Reference = new MyJsonEntityArrayOfPrimitives - { - IntArray = [10, 20, 30], - ListOfString = - [ - "A", - "B", - "C" - ] - }, - Collection = - [ - new MyJsonEntityArrayOfPrimitives { IntArray = [110, 120, 130], ListOfString = ["A1", "Z1"] }, - new MyJsonEntityArrayOfPrimitives { IntArray = [210, 220, 230], ListOfString = ["A2", "Z2"] } - ] - }; - - ctx.AddRange(entity1, entity2); - return ctx.SaveChangesAsync(); - } - protected override Task SeedJunkInJson(DbContext ctx) => ctx.Database.ExecuteSqlAsync( $$$$""" diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocManyToManyQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocManyToManyQuerySqlServerTest.cs index 72b6c26f994..536d70fb0be 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocManyToManyQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocManyToManyQuerySqlServerTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class AdHocManyToManyQuerySqlServerTest : AdHocManyToManyQueryRelationalTestBase +public class AdHocManyToManyQuerySqlServerTest(NonSharedFixture fixture) : AdHocManyToManyQueryRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs index 80c865cd947..8d59ea02a37 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs @@ -14,11 +14,18 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class AdHocMiscellaneousQuerySqlServerTest : AdHocMiscellaneousQueryRelationalTestBase +public class AdHocMiscellaneousQuerySqlServerTest(NonSharedFixture fixture) : AdHocMiscellaneousQueryRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; + protected override DbContextOptionsBuilder SetTranslateParameterizedCollectionsToConstants(DbContextOptionsBuilder optionsBuilder) + { + new SqlServerDbContextOptionsBuilder(optionsBuilder).TranslateParameterizedCollectionsToConstants(); + + return optionsBuilder; + } + protected override Task Seed2951(Context2951 context) => context.Database.ExecuteSqlRawAsync( """ @@ -33,7 +40,7 @@ public virtual async Task Include_group_join_is_per_query_context() { var contextFactory = await InitializeAsync( seed: c => c.SeedAsync(), - createTestStore: async () => await SqlServerTestStore.CreateInitializedAsync(StoreName, multipleActiveResultSets: true)); + createTestStore: () => SqlServerTestStore.Create(StoreName, multipleActiveResultSets: true)); Parallel.For( 0, 10, i => @@ -68,7 +75,7 @@ public virtual async Task Include_group_join_is_per_query_context_async() { var contextFactory = await InitializeAsync( seed: c => c.SeedAsync(), - createTestStore: async () => await SqlServerTestStore.CreateInitializedAsync(StoreName, multipleActiveResultSets: true)); + createTestStore: () => SqlServerTestStore.Create(StoreName, multipleActiveResultSets: true)); await Parallel.ForAsync( 0, 10, async (i, ct) => @@ -2405,4 +2412,58 @@ WHEN [c0].[Id] IS NOT NULL THEN [c1].[CountryName] END = N'COUNTRY' """); } + + public override async Task Check_inlined_constants_redacting(bool async, bool enableSensitiveDataLogging) + { + await base.Check_inlined_constants_redacting(async, enableSensitiveDataLogging); + + if (!enableSensitiveDataLogging) + { + AssertSql( + """ +SELECT [t].[Id], [t].[Name] +FROM [TestEntities] AS [t] +WHERE [t].[Id] IN (?, ?, ?) +""", + // + """ +SELECT [t].[Id], [t].[Name] +FROM [TestEntities] AS [t] +WHERE EXISTS ( + SELECT 1 + FROM (VALUES (?), (?), (?)) AS [i]([Value]) + WHERE [i].[Value] = [t].[Id]) +""", + // + """ +SELECT [t].[Id], [t].[Name] +FROM [TestEntities] AS [t] +WHERE ? = [t].[Id] +"""); + } + else + { + AssertSql( + """ +SELECT [t].[Id], [t].[Name] +FROM [TestEntities] AS [t] +WHERE [t].[Id] IN (1, 2, 3) +""", + // + """ +SELECT [t].[Id], [t].[Name] +FROM [TestEntities] AS [t] +WHERE EXISTS ( + SELECT 1 + FROM (VALUES (1), (2), (3)) AS [i]([Value]) + WHERE [i].[Value] = [t].[Id]) +""", + // + """ +SELECT [t].[Id], [t].[Name] +FROM [TestEntities] AS [t] +WHERE 1 = [t].[Id] +"""); + } + } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocNavigationsQuerySqlServerTest.cs index a11f6773375..a1831786ee1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocNavigationsQuerySqlServerTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class AdHocNavigationsQuerySqlServerTest : AdHocNavigationsQueryRelationalTestBase +public class AdHocNavigationsQuerySqlServerTest(NonSharedFixture fixture) : AdHocNavigationsQueryRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocPrecompiledQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocPrecompiledQuerySqlServerTest.cs index ac1cb5d72a3..bbdc73672ae 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocPrecompiledQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocPrecompiledQuerySqlServerTest.cs @@ -3,8 +3,8 @@ namespace Microsoft.EntityFrameworkCore.Query; -public class AdHocPrecompiledQuerySqlServerTest(ITestOutputHelper testOutputHelper) - : AdHocPrecompiledQueryRelationalTestBase(testOutputHelper) +public class AdHocPrecompiledQuerySqlServerTest(NonSharedFixture fixture, ITestOutputHelper testOutputHelper) + : AdHocPrecompiledQueryRelationalTestBase(fixture, testOutputHelper) { protected override bool AlwaysPrintGeneratedSources => false; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocQueryFiltersQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocQueryFiltersQuerySqlServerTest.cs index ebe77ad8f9c..286113b1b0a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocQueryFiltersQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocQueryFiltersQuerySqlServerTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class AdHocQueryFiltersQuerySqlServerTest : AdHocQueryFiltersQueryRelationalTestBase +public class AdHocQueryFiltersQuerySqlServerTest(NonSharedFixture fixture) : AdHocQueryFiltersQueryRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; @@ -264,31 +264,6 @@ WHERE [u].[Id] IS NOT NULL """); } - public override async Task GroupJoin_SelectMany_gets_flattened() - { - await base.GroupJoin_SelectMany_gets_flattened(); - - AssertSql( - """ -SELECT [c].[CustomerId], [c].[CustomerMembershipId] -FROM [CustomerFilters] AS [c] -WHERE ( - SELECT COUNT(*) - FROM [Customers] AS [c0] - LEFT JOIN [CustomerMemberships] AS [c1] ON [c0].[Id] = [c1].[CustomerId] - WHERE [c1].[Id] IS NOT NULL AND [c0].[Id] = [c].[CustomerId]) > 0 -""", - // - """ -SELECT [c].[Id], [c].[Name], [c0].[Id] AS [CustomerMembershipId], CASE - WHEN [c0].[Id] IS NOT NULL THEN [c0].[Name] - ELSE N'' -END AS [CustomerMembershipName] -FROM [Customers] AS [c] -LEFT JOIN [CustomerMemberships] AS [c0] ON [c].[Id] = [c0].[CustomerId] -"""); - } - public override async Task Group_by_multiple_aggregate_joining_different_tables(bool async) { await base.Group_by_multiple_aggregate_joining_different_tables(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocQuerySplittingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocQuerySplittingQuerySqlServerTest.cs index e715fa17405..b5d55008a8a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocQuerySplittingQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocQuerySplittingQuerySqlServerTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class AdHocQuerySplittingQuerySqlServerTest : AdHocQuerySplittingQueryTestBase +public class AdHocQuerySplittingQuerySqlServerTest(NonSharedFixture fixture) : AdHocQuerySplittingQueryTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; @@ -41,8 +41,12 @@ protected override DbContextOptionsBuilder ClearQuerySplittingBehavior(DbContext return optionsBuilder; } - protected override async Task CreateTestStore25225() - => await SqlServerTestStore.CreateInitializedAsync(StoreName, multipleActiveResultSets: true); + protected override TestStore CreateTestStore25225() + { + var testStore = SqlServerTestStore.Create(StoreName, multipleActiveResultSets: true); + testStore.UseConnectionString = true; + return testStore; + } public override async Task Can_configure_SingleQuery_at_context_level() { @@ -271,7 +275,7 @@ public virtual async Task Using_AsSplitQuery_without_multiple_active_result_sets { var contextFactory = await InitializeAsync( seed: c => c.SeedAsync(), - createTestStore: async () => await SqlServerTestStore.CreateInitializedAsync(StoreName, multipleActiveResultSets: false)); + createTestStore: () => SqlServerTestStore.Create(StoreName, multipleActiveResultSets: false)); using var context = contextFactory.CreateContext(); context.Parents.Include(p => p.Children1).Include(p => p.Children2).AsSplitQuery().ToList(); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/EntitySplittingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/EntitySplittingQuerySqlServerTest.cs index d0331af77b8..2d968ce0ffa 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/EntitySplittingQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/EntitySplittingQuerySqlServerTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class EntitySplittingQuerySqlServerTest : EntitySplittingQueryTestBase +public class EntitySplittingQuerySqlServerTest(NonSharedFixture fixture) : EntitySplittingQueryTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs index 18a3a67016b..9429b192485 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs @@ -8,7 +8,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable using static Expression; -public class NonSharedPrimitiveCollectionsQuerySqlServerTest : NonSharedPrimitiveCollectionsQueryRelationalTestBase +public class NonSharedPrimitiveCollectionsQuerySqlServerTest(NonSharedFixture fixture) : NonSharedPrimitiveCollectionsQueryRelationalTestBase(fixture) { protected override DbContextOptionsBuilder SetTranslateParameterizedCollectionsToConstants(DbContextOptionsBuilder optionsBuilder) { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OperatorsProceduralSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OperatorsProceduralSqlServerTest.cs index 8a4d1fd7da7..29c023e330f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/OperatorsProceduralSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/OperatorsProceduralSqlServerTest.cs @@ -14,7 +14,8 @@ public class OperatorsProceduralSqlServerTest : OperatorsProceduralQueryTestBase nameof(SqlServerDbFunctionsExtensions.AtTimeZone), [typeof(DbFunctions), typeof(DateTimeOffset), typeof(string)])!; - public OperatorsProceduralSqlServerTest(ITestOutputHelper testOutputHelper) + public OperatorsProceduralSqlServerTest(NonSharedFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) { Binaries.AddRange( new List<((Type Left, Type Right) InputTypes, Type ResultType, Func OperatorCreator)> diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OperatorsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OperatorsQuerySqlServerTest.cs index 2a418b1f511..a71e8a4caad 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/OperatorsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/OperatorsQuerySqlServerTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class OperatorsQuerySqlServerTest : OperatorsQueryTestBase +public class OperatorsQuerySqlServerTest(NonSharedFixture fixture) : OperatorsQueryTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedEntityQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedEntityQuerySqlServerTest.cs index 0b204370a48..9ca825bc5c9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedEntityQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedEntityQuerySqlServerTest.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class OwnedEntityQuerySqlServerTest : OwnedEntityQueryRelationalTestBase +public class OwnedEntityQuerySqlServerTest(NonSharedFixture fixture) : OwnedEntityQueryRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/RawSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/RawSqlServerTest.cs deleted file mode 100644 index 0a06adc72f0..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/Query/RawSqlServerTest.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// ReSharper disable InconsistentNaming - -namespace Microsoft.EntityFrameworkCore.Query; - -#nullable disable - -public class RawSqlServerTest : NonSharedModelTestBase -{ - // Issue #13346, #24623 - [ConditionalFact] - public virtual async Task ToQuery_can_use_FromSqlRaw() - { - var contextFactory = await InitializeAsync(seed: c => c.SeedAsync()); - using var context = contextFactory.CreateContext(); - var query = context.Set().ToList(); - - Assert.Equal(4, query.Count); - - AssertSql( - """ -SELECT o.Amount From Orders AS o -- RAW -"""); - } - - public class Context13346(DbContextOptions options) : DbContext(options) - { - public virtual DbSet Orders { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { -#pragma warning disable CS0618 // Type or member is obsolete - modelBuilder.Entity() - .HasNoKey() - .ToQuery(() => Set().FromSqlRaw("SELECT o.Amount From Orders AS o -- RAW")); -#pragma warning restore CS0618 // Type or member is obsolete - } - - public Task SeedAsync() - { - AddRange( - new Order { Amount = 1 }, - new Order { Amount = 2 }, - new Order { Amount = 3 }, - new Order { Amount = 4 } - ); - - return SaveChangesAsync(); - } - - public class Order - { - public int Id { get; set; } - public int Amount { get; set; } - } - - public class OrderSummary - { - public int Amount { get; set; } - } - } - - protected override string StoreName - => "RawSqlServerTest"; - - protected TestSqlLoggerFactory TestSqlLoggerFactory - => (TestSqlLoggerFactory)ListLoggerFactory; - - protected override ITestStoreFactory TestStoreFactory - => SqlServerTestStoreFactory.Instance; - - protected void AssertSql(params string[] expected) - => TestSqlLoggerFactory.AssertBaseline(expected); - - protected void ClearLog() - => TestSqlLoggerFactory.Clear(); -} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/JsonRelationshipsQuerySqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexRelationshipsSqlServerFixture.cs similarity index 77% rename from test/EFCore.SqlServer.FunctionalTests/Query/Relationships/JsonRelationshipsQuerySqlServerFixture.cs rename to test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexRelationshipsSqlServerFixture.cs index c2a3be4731b..cbfea197397 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/JsonRelationshipsQuerySqlServerFixture.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexRelationshipsSqlServerFixture.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public class JsonRelationshipsQuerySqlServerFixture : JsonRelationshipsQueryRelationalFixtureBase, ITestSqlLoggerFactory +public class ComplexRelationshipsSqlServerFixture : ComplexRelationsjipsRelationalFixtureBase, ITestSqlLoggerFactory { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionNoTrackingQuerySqlServerTest.cs deleted file mode 100644 index 7464b3b0f65..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionNoTrackingQuerySqlServerTest.cs +++ /dev/null @@ -1,228 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public class ComplexRelationshipsInProjectionNoTrackingQuerySqlServerTest - : ComplexRelationshipsInProjectionNoTrackingQueryRelationalTestBase -{ - public ComplexRelationshipsInProjectionNoTrackingQuerySqlServerTest(ComplexRelationshipsQuerySqlServerFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - public override async Task Project_everything(bool async) - { - await base.Project_everything(async); - - AssertSql( -""" -SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[Id] -"""); - } - - public override async Task Project_root(bool async) - { - await base.Project_root(async); - - AssertSql( - """ -SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] -FROM [RootEntities] AS [r] -"""); - } - - public override async Task Project_trunk_optional(bool async) - { - await base.Project_trunk_optional(async); - - AssertSql( - """ -SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -LEFT JOIN [TrunkEntities] AS [t] ON [r].[OptionalReferenceTrunkId] = [t].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_trunk_required(bool async) - { - await base.Project_trunk_required(async); - - AssertSql( - """ -SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_trunk_collection(bool async) - { - await base.Project_trunk_collection(async); - - AssertSql( - """ -SELECT [r].[Id], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -LEFT JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[CollectionRootId] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_required_required(bool async) - { - await base.Project_branch_required_required(async); - - AssertSql( - """ -SELECT [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_required_optional(bool async) - { - await base.Project_branch_required_optional(async); - - AssertSql(); - } - - public override async Task Project_branch_required_collection(bool async) - { - await base.Project_branch_required_collection(async); - - AssertSql(); - } - - public override async Task Project_branch_optional_required(bool async) - { - await base.Project_branch_optional_required(async); - - AssertSql(); - } - - public override async Task Project_branch_optional_optional(bool async) - { - await base.Project_branch_optional_optional(async); - - AssertSql(); - } - - public override async Task Project_branch_optional_collection(bool async) - { - await base.Project_branch_optional_collection(async); - - AssertSql(); - } - - public override async Task Project_root_duplicated(bool async) - { - await base.Project_root_duplicated(async); - - AssertSql( - """ -SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] -FROM [RootEntities] AS [r] -"""); - } - - public override async Task Project_trunk_and_branch_duplicated(bool async) - { - await base.Project_trunk_and_branch_duplicated(async); - - AssertSql(); - } - - public override async Task Project_trunk_and_trunk_duplicated(bool async) - { - await base.Project_trunk_and_trunk_duplicated(async); - - AssertSql(); - } - - public override async Task Project_multiple_branch_leaf(bool async) - { - await base.Project_multiple_branch_leaf(async); - - AssertSql(); - } - - public override async Task Project_leaf_trunk_root(bool async) - { - await base.Project_leaf_trunk_root(async); - - AssertSql( - """ -SELECT [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -"""); - } - - public override async Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) - { - await base.Project_subquery_root_set_required_trunk_FirstOrDefault_branch(async); - - AssertSql(); - } - - public override async Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) - { - await base.Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); - - AssertSql(); - } - - public override async Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - { - await base.Project_subquery_root_set_trunk_FirstOrDefault_collection(async); - - AssertSql(); - } - - public override async Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - { - await base.Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); - - AssertSql(); - } - - public override async Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - { - await base.Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); - - AssertSql(); - } - - public override async Task SelectMany_trunk_collection(bool async) - { - await base.SelectMany_trunk_collection(async); - - AssertSql(); - } - - public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) - { - await base.SelectMany_required_trunk_reference_branch_collection(async); - - AssertSql(); - } - - public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) - { - await base.SelectMany_optional_trunk_reference_branch_collection(async); - - AssertSql(); - } - - private void AssertSql(params string[] expected) - => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); -} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionQuerySqlServerTest.cs deleted file mode 100644 index e5ece903148..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionQuerySqlServerTest.cs +++ /dev/null @@ -1,228 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public class ComplexRelationshipsInProjectionQuerySqlServerTest - : ComplexRelationshipsInProjectionQueryRelationalTestBase -{ - public ComplexRelationshipsInProjectionQuerySqlServerTest(ComplexRelationshipsQuerySqlServerFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - public override async Task Project_everything(bool async) - { - await base.Project_everything(async); - - AssertSql( -""" -SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[Id] -"""); - } - - public override async Task Project_root(bool async) - { - await base.Project_root(async); - - AssertSql( - """ -SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] -FROM [RootEntities] AS [r] -"""); - } - - public override async Task Project_trunk_optional(bool async) - { - await base.Project_trunk_optional(async); - - AssertSql( - """ -SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -LEFT JOIN [TrunkEntities] AS [t] ON [r].[OptionalReferenceTrunkId] = [t].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_trunk_required(bool async) - { - await base.Project_trunk_required(async); - - AssertSql( - """ -SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_trunk_collection(bool async) - { - await base.Project_trunk_collection(async); - - AssertSql( - """ -SELECT [r].[Id], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -LEFT JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[CollectionRootId] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_required_required(bool async) - { - await base.Project_branch_required_required(async); - - AssertSql( - """ -SELECT [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_required_optional(bool async) - { - await base.Project_branch_required_optional(async); - - AssertSql(); - } - - public override async Task Project_branch_required_collection(bool async) - { - await base.Project_branch_required_collection(async); - - AssertSql(); - } - - public override async Task Project_branch_optional_required(bool async) - { - await base.Project_branch_optional_required(async); - - AssertSql(); - } - - public override async Task Project_branch_optional_optional(bool async) - { - await base.Project_branch_optional_optional(async); - - AssertSql(); - } - - public override async Task Project_branch_optional_collection(bool async) - { - await base.Project_branch_optional_collection(async); - - AssertSql(); - } - - public override async Task Project_root_duplicated(bool async) - { - await base.Project_root_duplicated(async); - - AssertSql( - """ -SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] -FROM [RootEntities] AS [r] -"""); - } - - public override async Task Project_trunk_and_branch_duplicated(bool async) - { - await base.Project_trunk_and_branch_duplicated(async); - - AssertSql(); - } - - public override async Task Project_trunk_and_trunk_duplicated(bool async) - { - await base.Project_trunk_and_trunk_duplicated(async); - - AssertSql(); - } - - public override async Task Project_multiple_branch_leaf(bool async) - { - await base.Project_multiple_branch_leaf(async); - - AssertSql(); - } - - public override async Task Project_leaf_trunk_root(bool async) - { - await base.Project_leaf_trunk_root(async); - - AssertSql( - """ -SELECT [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -"""); - } - - public override async Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) - { - await base.Project_subquery_root_set_required_trunk_FirstOrDefault_branch(async); - - AssertSql(); - } - - public override async Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) - { - await base.Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); - - AssertSql(); - } - - public override async Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - { - await base.Project_subquery_root_set_trunk_FirstOrDefault_collection(async); - - AssertSql(); - } - - public override async Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - { - await base.Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); - - AssertSql(); - } - - public override async Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - { - await base.Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); - - AssertSql(); - } - - public override async Task SelectMany_trunk_collection(bool async) - { - await base.SelectMany_trunk_collection(async); - - AssertSql(); - } - - public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) - { - await base.SelectMany_required_trunk_reference_branch_collection(async); - - AssertSql(); - } - - public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) - { - await base.SelectMany_optional_trunk_reference_branch_collection(async); - - AssertSql(); - } - - private void AssertSql(params string[] expected) - => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); -} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/EntityRelationshipsInProjectionNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/EntityRelationshipsInProjectionNoTrackingQuerySqlServerTest.cs deleted file mode 100644 index 6bb058ff270..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/EntityRelationshipsInProjectionNoTrackingQuerySqlServerTest.cs +++ /dev/null @@ -1,404 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public class EntityRelationshipsInProjectionNoTrackingQuerySqlServerTest - : EntityRelationshipsInProjectionNoTrackingQueryRelationalTestBase -{ - public EntityRelationshipsInProjectionNoTrackingQuerySqlServerTest(EntityRelationshipsQuerySqlServerFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - public override async Task Project_everything(bool async) - { - await base.Project_everything(async); - - AssertSql( - """ -SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [l].[Id], [l].[CollectionBranchId], [l].[Name] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[Id] -INNER JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[Id] -INNER JOIN [LeafEntities] AS [l] ON [b].[Id] = [l].[Id] -"""); - } - - public override async Task Project_root(bool async) - { - await base.Project_root(async); - - AssertSql( - """ -SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] -FROM [RootEntities] AS [r] -"""); - } - - public override async Task Project_trunk_optional(bool async) - { - await base.Project_trunk_optional(async); - - AssertSql( - """ -SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId] -FROM [RootEntities] AS [r] -LEFT JOIN [TrunkEntities] AS [t] ON [r].[OptionalReferenceTrunkId] = [t].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_trunk_required(bool async) - { - await base.Project_trunk_required(async); - - AssertSql( - """ -SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_trunk_collection(bool async) - { - await base.Project_trunk_collection(async); - AssertSql( - """ -SELECT [r].[Id], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId] -FROM [RootEntities] AS [r] -LEFT JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[CollectionRootId] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_required_required(bool async) - { - await base.Project_branch_required_required(async); - - AssertSql( - """ -SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_required_optional(bool async) - { - await base.Project_branch_required_optional(async); - - AssertSql( - """ -SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -LEFT JOIN [BranchEntities] AS [b] ON [t].[OptionalReferenceBranchId] = [b].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_required_collection(bool async) - { - await base.Project_branch_required_collection(async); - - AssertSql( - """ -SELECT [r].[Id], [t].[Id], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -LEFT JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] -ORDER BY [r].[Id], [t].[Id] -"""); - } - - public override async Task Project_branch_optional_required(bool async) - { - await base.Project_branch_optional_required(async); - - AssertSql( - """ -SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_optional_optional(bool async) - { - await base.Project_branch_optional_optional(async); - - AssertSql( - """ -SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -LEFT JOIN [BranchEntities] AS [b] ON [t].[OptionalReferenceBranchId] = [b].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_optional_collection(bool async) - { - await base.Project_branch_optional_collection(async); - - AssertSql( - """ -SELECT [r].[Id], [t].[Id], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -LEFT JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] -ORDER BY [r].[Id], [t].[Id] -"""); - } - - - - - - - - - - - - - - - - public override async Task Project_root_duplicated(bool async) - { - await base.Project_root_duplicated(async); - - AssertSql( - """ -SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] -FROM [RootEntities] AS [r] -"""); - } - - public override async Task Project_trunk_and_branch_duplicated(bool async) - { - await base.Project_trunk_and_branch_duplicated(async); - - AssertSql( - """ -SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -LEFT JOIN [TrunkEntities] AS [t] ON [r].[OptionalReferenceTrunkId] = [t].[Id] -LEFT JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_trunk_and_trunk_duplicated(bool async) - { - await base.Project_trunk_and_trunk_duplicated(async); - - AssertSql( - """ -SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [l].[Id], [l].[CollectionBranchId], [l].[Name] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -LEFT JOIN [BranchEntities] AS [b] ON [t].[OptionalReferenceBranchId] = [b].[Id] -LEFT JOIN [LeafEntities] AS [l] ON [b].[RequiredReferenceLeafId] = [l].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_multiple_branch_leaf(bool async) - { - await base.Project_multiple_branch_leaf(async); - - AssertSql( - """ -SELECT [r].[Id], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [l].[Id], [l].[CollectionBranchId], [l].[Name], [t].[Id], [l0].[Id], [l0].[CollectionBranchId], [l0].[Name], [b0].[Id], [b0].[CollectionTrunkId], [b0].[Name], [b0].[OptionalReferenceLeafId], [b0].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] -LEFT JOIN [LeafEntities] AS [l] ON [b].[OptionalReferenceLeafId] = [l].[Id] -LEFT JOIN [LeafEntities] AS [l0] ON [b].[Id] = [l0].[CollectionBranchId] -LEFT JOIN [BranchEntities] AS [b0] ON [t].[Id] = [b0].[CollectionTrunkId] -ORDER BY [r].[Id], [t].[Id], [b].[Id], [l].[Id], [l0].[Id] -"""); - } - - public override async Task Project_leaf_trunk_root(bool async) - { - await base.Project_leaf_trunk_root(async); - - AssertSql( - """ -SELECT [l].[Id], [l].[CollectionBranchId], [l].[Name], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] -INNER JOIN [LeafEntities] AS [l] ON [b].[RequiredReferenceLeafId] = [l].[Id] -"""); - } - - public override async Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) - { - await base.Project_subquery_root_set_required_trunk_FirstOrDefault_branch(async); - - AssertSql( - """ -SELECT [s].[Id], [s].[CollectionTrunkId], [s].[Name], [s].[OptionalReferenceLeafId], [s].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -OUTER APPLY ( - SELECT TOP(1) [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] - FROM [RootEntities] AS [r0] - INNER JOIN [TrunkEntities] AS [t] ON [r0].[RequiredReferenceTrunkId] = [t].[Id] - INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] - ORDER BY [r0].[Id] -) AS [s] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) - { - await base.Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); - - AssertSql( - """ -SELECT [s].[Id], [s].[CollectionTrunkId], [s].[Name], [s].[OptionalReferenceLeafId], [s].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -OUTER APPLY ( - SELECT TOP(1) [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] - FROM [RootEntities] AS [r0] - LEFT JOIN [TrunkEntities] AS [t] ON [r0].[OptionalReferenceTrunkId] = [t].[Id] - LEFT JOIN [BranchEntities] AS [b] ON [t].[OptionalReferenceBranchId] = [b].[Id] - ORDER BY [r0].[Id] -) AS [s] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - { - await base.Project_subquery_root_set_trunk_FirstOrDefault_collection(async); - - AssertSql( - """ -SELECT [r].[Id], [s].[Id], [s].[Id0], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [s].[c] -FROM [RootEntities] AS [r] -OUTER APPLY ( - SELECT TOP(1) 1 AS [c], [r0].[Id], [t].[Id] AS [Id0] - FROM [RootEntities] AS [r0] - INNER JOIN [TrunkEntities] AS [t] ON [r0].[RequiredReferenceTrunkId] = [t].[Id] - ORDER BY [r0].[Id] -) AS [s] -LEFT JOIN [BranchEntities] AS [b] ON [s].[Id0] = [b].[CollectionTrunkId] -ORDER BY [r].[Id], [s].[Id], [s].[Id0] -"""); - } - - public override async Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - { - await base.Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); - - AssertSql( - """ -SELECT [r].[Id], [t].[Id], [b].[Id], [s].[Id1], [s].[Id], [s].[Id0], [b1].[Id], [b1].[CollectionTrunkId], [b1].[Name], [b1].[OptionalReferenceLeafId], [b1].[RequiredReferenceLeafId], [s].[CollectionRootId], [s].[Name], [s].[OptionalReferenceBranchId], [s].[RequiredReferenceBranchId], [s].[CollectionTrunkId], [s].[Name0], [s].[OptionalReferenceLeafId], [s].[RequiredReferenceLeafId], [s].[Name1], [s].[c] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] -OUTER APPLY ( - SELECT TOP(1) [t0].[Id], [t0].[CollectionRootId], [t0].[Name], [t0].[OptionalReferenceBranchId], [t0].[RequiredReferenceBranchId], [b0].[Id] AS [Id0], [b0].[CollectionTrunkId], [b0].[Name] AS [Name0], [b0].[OptionalReferenceLeafId], [b0].[RequiredReferenceLeafId], [b].[Name] AS [Name1], 1 AS [c], [r0].[Id] AS [Id1] - FROM [RootEntities] AS [r0] - INNER JOIN [TrunkEntities] AS [t0] ON [r0].[RequiredReferenceTrunkId] = [t0].[Id] - INNER JOIN [BranchEntities] AS [b0] ON [t0].[RequiredReferenceBranchId] = [b0].[Id] - ORDER BY [r0].[Id] -) AS [s] -LEFT JOIN [BranchEntities] AS [b1] ON [t].[Id] = [b1].[CollectionTrunkId] -ORDER BY [r].[Id], [t].[Id], [b].[Id], [s].[Id1], [s].[Id], [s].[Id0] -"""); - } - - public override async Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - { - await base.Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); - - AssertSql( - """ -SELECT [r].[Id], [t].[Id], [r1].[Id], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [r1].[c] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -OUTER APPLY ( - SELECT TOP(1) 1 AS [c], [r0].[Id] - FROM [RootEntities] AS [r0] - ORDER BY [r0].[Id] -) AS [r1] -LEFT JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] -ORDER BY [r].[Id], [t].[Id], [r1].[Id] -"""); - } - - public override async Task SelectMany_trunk_collection(bool async) - { - await base.SelectMany_trunk_collection(async); - - AssertSql( - """ -SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[CollectionRootId] -"""); - } - - public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) - { - await base.SelectMany_required_trunk_reference_branch_collection(async); - - AssertSql( - """ -SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -INNER JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] -"""); - } - - public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) - { - await base.SelectMany_optional_trunk_reference_branch_collection(async); - - AssertSql( - """ -SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -LEFT JOIN [TrunkEntities] AS [t] ON [r].[OptionalReferenceTrunkId] = [t].[Id] -INNER JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] -"""); - } - - - - - - - - - - - - - - - - - - - private void AssertSql(params string[] expected) - => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); -} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/EntityRelationshipsInProjectionQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/EntityRelationshipsInProjectionQuerySqlServerTest.cs deleted file mode 100644 index 1643ec4e02a..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/EntityRelationshipsInProjectionQuerySqlServerTest.cs +++ /dev/null @@ -1,373 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public class EntityRelationshipsInProjectionQuerySqlServerTest - : EntityRelationshipsInProjectionQueryRelationalTestBase -{ - public EntityRelationshipsInProjectionQuerySqlServerTest(EntityRelationshipsQuerySqlServerFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - public override async Task Project_everything(bool async) - { - await base.Project_everything(async); - - AssertSql( - """ -SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [l].[Id], [l].[CollectionBranchId], [l].[Name] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[Id] -INNER JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[Id] -INNER JOIN [LeafEntities] AS [l] ON [b].[Id] = [l].[Id] -"""); - } - - public override async Task Project_root(bool async) - { - await base.Project_root(async); - - AssertSql( - """ -SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] -FROM [RootEntities] AS [r] -"""); - } - - public override async Task Project_trunk_optional(bool async) - { - await base.Project_trunk_optional(async); - - AssertSql( - """ -SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId] -FROM [RootEntities] AS [r] -LEFT JOIN [TrunkEntities] AS [t] ON [r].[OptionalReferenceTrunkId] = [t].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_trunk_required(bool async) - { - await base.Project_trunk_required(async); - - AssertSql( - """ -SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_trunk_collection(bool async) - { - await base.Project_trunk_collection(async); - - AssertSql( - """ -SELECT [r].[Id], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId] -FROM [RootEntities] AS [r] -LEFT JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[CollectionRootId] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_required_required(bool async) - { - await base.Project_branch_required_required(async); - - AssertSql( -""" -SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_required_optional(bool async) - { - await base.Project_branch_required_optional(async); - - AssertSql( - """ -SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -LEFT JOIN [BranchEntities] AS [b] ON [t].[OptionalReferenceBranchId] = [b].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_required_collection(bool async) - { - await base.Project_branch_required_collection(async); - - AssertSql( - """ -SELECT [r].[Id], [t].[Id], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -LEFT JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] -ORDER BY [r].[Id], [t].[Id] -"""); - } - - public override async Task Project_branch_optional_required(bool async) - { - await base.Project_branch_optional_required(async); - - AssertSql( - """ -SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_optional_optional(bool async) - { - await base.Project_branch_optional_optional(async); - - AssertSql( - """ -SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -LEFT JOIN [BranchEntities] AS [b] ON [t].[OptionalReferenceBranchId] = [b].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_optional_collection(bool async) - { - await base.Project_branch_optional_collection(async); - - AssertSql( - """ -SELECT [r].[Id], [t].[Id], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -LEFT JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] -ORDER BY [r].[Id], [t].[Id] -"""); - } - - public override async Task Project_root_duplicated(bool async) - { - await base.Project_root_duplicated(async); - - AssertSql( - """ -SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] -FROM [RootEntities] AS [r] -"""); - } - - public override async Task Project_trunk_and_branch_duplicated(bool async) - { - await base.Project_trunk_and_branch_duplicated(async); - - AssertSql( - """ -SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -LEFT JOIN [TrunkEntities] AS [t] ON [r].[OptionalReferenceTrunkId] = [t].[Id] -LEFT JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_trunk_and_trunk_duplicated(bool async) - { - await base.Project_trunk_and_trunk_duplicated(async); - - AssertSql( - """ -SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [l].[Id], [l].[CollectionBranchId], [l].[Name] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -LEFT JOIN [BranchEntities] AS [b] ON [t].[OptionalReferenceBranchId] = [b].[Id] -LEFT JOIN [LeafEntities] AS [l] ON [b].[RequiredReferenceLeafId] = [l].[Id] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_multiple_branch_leaf(bool async) - { - await base.Project_multiple_branch_leaf(async); - - AssertSql( - """ -SELECT [r].[Id], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [l].[Id], [l].[CollectionBranchId], [l].[Name], [t].[Id], [l0].[Id], [l0].[CollectionBranchId], [l0].[Name], [b0].[Id], [b0].[CollectionTrunkId], [b0].[Name], [b0].[OptionalReferenceLeafId], [b0].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] -LEFT JOIN [LeafEntities] AS [l] ON [b].[OptionalReferenceLeafId] = [l].[Id] -LEFT JOIN [LeafEntities] AS [l0] ON [b].[Id] = [l0].[CollectionBranchId] -LEFT JOIN [BranchEntities] AS [b0] ON [t].[Id] = [b0].[CollectionTrunkId] -ORDER BY [r].[Id], [t].[Id], [b].[Id], [l].[Id], [l0].[Id] -"""); - } - - public override async Task Project_leaf_trunk_root(bool async) - { - await base.Project_leaf_trunk_root(async); - - AssertSql( - """ -SELECT [l].[Id], [l].[CollectionBranchId], [l].[Name], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] -INNER JOIN [LeafEntities] AS [l] ON [b].[RequiredReferenceLeafId] = [l].[Id] -"""); - } - - public override async Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) - { - await base.Project_subquery_root_set_required_trunk_FirstOrDefault_branch(async); - - AssertSql( - """ -SELECT [s].[Id], [s].[CollectionTrunkId], [s].[Name], [s].[OptionalReferenceLeafId], [s].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -OUTER APPLY ( - SELECT TOP(1) [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] - FROM [RootEntities] AS [r0] - INNER JOIN [TrunkEntities] AS [t] ON [r0].[RequiredReferenceTrunkId] = [t].[Id] - INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] - ORDER BY [r0].[Id] -) AS [s] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) - { - await base.Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); - - AssertSql( - """ -SELECT [s].[Id], [s].[CollectionTrunkId], [s].[Name], [s].[OptionalReferenceLeafId], [s].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -OUTER APPLY ( - SELECT TOP(1) [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] - FROM [RootEntities] AS [r0] - LEFT JOIN [TrunkEntities] AS [t] ON [r0].[OptionalReferenceTrunkId] = [t].[Id] - LEFT JOIN [BranchEntities] AS [b] ON [t].[OptionalReferenceBranchId] = [b].[Id] - ORDER BY [r0].[Id] -) AS [s] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - { - await base.Project_subquery_root_set_trunk_FirstOrDefault_collection(async); - - AssertSql( - """ -SELECT [r].[Id], [s].[Id], [s].[Id0], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [s].[c] -FROM [RootEntities] AS [r] -OUTER APPLY ( - SELECT TOP(1) 1 AS [c], [r0].[Id], [t].[Id] AS [Id0] - FROM [RootEntities] AS [r0] - INNER JOIN [TrunkEntities] AS [t] ON [r0].[RequiredReferenceTrunkId] = [t].[Id] - ORDER BY [r0].[Id] -) AS [s] -LEFT JOIN [BranchEntities] AS [b] ON [s].[Id0] = [b].[CollectionTrunkId] -ORDER BY [r].[Id], [s].[Id], [s].[Id0] -"""); - } - - public override async Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - { - await base.Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); - - AssertSql( - """ -SELECT [r].[Id], [t].[Id], [b].[Id], [s].[Id1], [s].[Id], [s].[Id0], [b1].[Id], [b1].[CollectionTrunkId], [b1].[Name], [b1].[OptionalReferenceLeafId], [b1].[RequiredReferenceLeafId], [s].[CollectionRootId], [s].[Name], [s].[OptionalReferenceBranchId], [s].[RequiredReferenceBranchId], [s].[CollectionTrunkId], [s].[Name0], [s].[OptionalReferenceLeafId], [s].[RequiredReferenceLeafId], [s].[Name1], [s].[c] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] -OUTER APPLY ( - SELECT TOP(1) [t0].[Id], [t0].[CollectionRootId], [t0].[Name], [t0].[OptionalReferenceBranchId], [t0].[RequiredReferenceBranchId], [b0].[Id] AS [Id0], [b0].[CollectionTrunkId], [b0].[Name] AS [Name0], [b0].[OptionalReferenceLeafId], [b0].[RequiredReferenceLeafId], [b].[Name] AS [Name1], 1 AS [c], [r0].[Id] AS [Id1] - FROM [RootEntities] AS [r0] - INNER JOIN [TrunkEntities] AS [t0] ON [r0].[RequiredReferenceTrunkId] = [t0].[Id] - INNER JOIN [BranchEntities] AS [b0] ON [t0].[RequiredReferenceBranchId] = [b0].[Id] - ORDER BY [r0].[Id] -) AS [s] -LEFT JOIN [BranchEntities] AS [b1] ON [t].[Id] = [b1].[CollectionTrunkId] -ORDER BY [r].[Id], [t].[Id], [b].[Id], [s].[Id1], [s].[Id], [s].[Id0] -"""); - } - - public override async Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - { - await base.Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); - - AssertSql( - """ -SELECT [r].[Id], [t].[Id], [r1].[Id], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [r1].[c] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -OUTER APPLY ( - SELECT TOP(1) 1 AS [c], [r0].[Id] - FROM [RootEntities] AS [r0] - ORDER BY [r0].[Id] -) AS [r1] -LEFT JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] -ORDER BY [r].[Id], [t].[Id], [r1].[Id] -"""); - } - - public override async Task SelectMany_trunk_collection(bool async) - { - await base.SelectMany_trunk_collection(async); - - AssertSql( - """ -SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[CollectionRootId] -"""); - } - - public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) - { - await base.SelectMany_required_trunk_reference_branch_collection(async); - - AssertSql( - """ -SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] -INNER JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] -"""); - } - - public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) - { - await base.SelectMany_optional_trunk_reference_branch_collection(async); - - AssertSql( - """ -SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] -FROM [RootEntities] AS [r] -LEFT JOIN [TrunkEntities] AS [t] ON [r].[OptionalReferenceTrunkId] = [t].[Id] -INNER JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] -"""); - } - - private void AssertSql(params string[] expected) - => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); -} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/JsonRelationshipsInProjectionNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/JsonRelationshipsInProjectionNoTrackingQuerySqlServerTest.cs deleted file mode 100644 index 932374e6b5c..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/JsonRelationshipsInProjectionNoTrackingQuerySqlServerTest.cs +++ /dev/null @@ -1,371 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public class JsonRelationshipsInProjectionNoTrackingQuerySqlServerTest - : JsonRelationshipsInProjectionNoTrackingQueryRelationalTestBase -{ - public JsonRelationshipsInProjectionNoTrackingQuerySqlServerTest(JsonRelationshipsQuerySqlServerFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - public override async Task Project_root(bool async) - { - await base.Project_root(async); - - AssertSql( -""" -SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [r].[CollectionTrunk], [r].[OptionalReferenceTrunk], [r].[RequiredReferenceTrunk] -FROM [RootEntities] AS [r] -"""); - } - - public override async Task Project_trunk_optional(bool async) - { - await base.Project_trunk_optional(async); - - AssertSql( - """ -SELECT [r].[OptionalReferenceTrunk], [r].[Id] -FROM [RootEntities] AS [r] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_trunk_required(bool async) - { - await base.Project_trunk_required(async); - - AssertSql( - """ -SELECT [r].[RequiredReferenceTrunk], [r].[Id] -FROM [RootEntities] AS [r] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_trunk_collection(bool async) - { - await base.Project_trunk_collection(async); - - AssertSql( - """ -SELECT [r].[CollectionTrunk], [r].[Id] -FROM [RootEntities] AS [r] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_required_required(bool async) - { - await base.Project_branch_required_required(async); - - AssertSql( - """ -SELECT JSON_QUERY([r].[RequiredReferenceTrunk], '$.RequiredReferenceBranch'), [r].[Id] -FROM [RootEntities] AS [r] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_required_optional(bool async) - { - await base.Project_branch_required_optional(async); - - AssertSql( - """ -SELECT JSON_QUERY([r].[RequiredReferenceTrunk], '$.OptionalReferenceBranch'), [r].[Id] -FROM [RootEntities] AS [r] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_required_collection(bool async) - { - await base.Project_branch_required_collection(async); - - AssertSql( - """ -SELECT JSON_QUERY([r].[RequiredReferenceTrunk], '$.CollectionBranch'), [r].[Id] -FROM [RootEntities] AS [r] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_optional_required(bool async) - { - await base.Project_branch_optional_required(async); - - AssertSql( - """ -SELECT JSON_QUERY([r].[RequiredReferenceTrunk], '$.RequiredReferenceBranch'), [r].[Id] -FROM [RootEntities] AS [r] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_optional_optional(bool async) - { - await base.Project_branch_optional_optional(async); - - AssertSql( - """ -SELECT JSON_QUERY([r].[RequiredReferenceTrunk], '$.OptionalReferenceBranch'), [r].[Id] -FROM [RootEntities] AS [r] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_branch_optional_collection(bool async) - { - await base.Project_branch_optional_collection(async); - - AssertSql( - """ -SELECT JSON_QUERY([r].[RequiredReferenceTrunk], '$.CollectionBranch'), [r].[Id] -FROM [RootEntities] AS [r] -ORDER BY [r].[Id] -"""); - } - - - - - - - - - - - - public override async Task Project_root_duplicated(bool async) - { - await base.Project_root_duplicated(async); - - AssertSql( - """ -SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [r].[CollectionTrunk], [r].[OptionalReferenceTrunk], [r].[RequiredReferenceTrunk], [r].[CollectionTrunk], [r].[OptionalReferenceTrunk], [r].[RequiredReferenceTrunk] -FROM [RootEntities] AS [r] -"""); - } - - public override async Task Project_trunk_and_branch_duplicated(bool async) - { - await base.Project_trunk_and_branch_duplicated(async); - - AssertSql( - """ -SELECT [r].[OptionalReferenceTrunk], [r].[Id], JSON_QUERY([r].[OptionalReferenceTrunk], '$.RequiredReferenceBranch'), [r].[OptionalReferenceTrunk], JSON_QUERY([r].[OptionalReferenceTrunk], '$.RequiredReferenceBranch') -FROM [RootEntities] AS [r] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_trunk_and_trunk_duplicated(bool async) - { - await base.Project_trunk_and_trunk_duplicated(async); - - AssertSql( - """ -SELECT [r].[RequiredReferenceTrunk], [r].[Id], JSON_QUERY([r].[RequiredReferenceTrunk], '$.OptionalReferenceBranch.RequiredReferenceLeaf'), [r].[RequiredReferenceTrunk], JSON_QUERY([r].[RequiredReferenceTrunk], '$.OptionalReferenceBranch.RequiredReferenceLeaf') -FROM [RootEntities] AS [r] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_multiple_branch_leaf(bool async) - { - await base.Project_multiple_branch_leaf(async); - - AssertSql( - """ -SELECT [r].[Id], JSON_QUERY([r].[RequiredReferenceTrunk], '$.RequiredReferenceBranch'), JSON_QUERY([r].[RequiredReferenceTrunk], '$.RequiredReferenceBranch.OptionalReferenceLeaf'), JSON_QUERY([r].[RequiredReferenceTrunk], '$.RequiredReferenceBranch.CollectionLeaf'), JSON_QUERY([r].[RequiredReferenceTrunk], '$.CollectionBranch'), JSON_VALUE([r].[RequiredReferenceTrunk], '$.RequiredReferenceBranch.OptionalReferenceLeaf.Name') -FROM [RootEntities] AS [r] -"""); - } - - public override async Task Project_leaf_trunk_root(bool async) - { - await base.Project_leaf_trunk_root(async); - - AssertSql( - """ -SELECT JSON_QUERY([r].[RequiredReferenceTrunk], '$.RequiredReferenceBranch.RequiredReferenceLeaf'), [r].[Id], [r].[RequiredReferenceTrunk], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [r].[CollectionTrunk], [r].[OptionalReferenceTrunk], [r].[RequiredReferenceTrunk] -FROM [RootEntities] AS [r] -"""); - } - - public override async Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) - { - await base.Project_subquery_root_set_required_trunk_FirstOrDefault_branch(async); - - AssertSql( - """ -SELECT [r1].[c], [r1].[Id] -FROM [RootEntities] AS [r] -OUTER APPLY ( - SELECT TOP(1) JSON_QUERY([r0].[RequiredReferenceTrunk], '$.RequiredReferenceBranch') AS [c], [r0].[Id] - FROM [RootEntities] AS [r0] - ORDER BY [r0].[Id] -) AS [r1] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) - { - await base.Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); - - AssertSql( - """ -SELECT [r1].[c], [r1].[Id] -FROM [RootEntities] AS [r] -OUTER APPLY ( - SELECT TOP(1) JSON_QUERY([r0].[OptionalReferenceTrunk], '$.OptionalReferenceBranch') AS [c], [r0].[Id] - FROM [RootEntities] AS [r0] - ORDER BY [r0].[Id] -) AS [r1] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - { - await base.Project_subquery_root_set_trunk_FirstOrDefault_collection(async); - - AssertSql( - """ -SELECT [r1].[c], [r1].[Id], [r1].[c0] -FROM [RootEntities] AS [r] -OUTER APPLY ( - SELECT TOP(1) JSON_QUERY([r0].[RequiredReferenceTrunk], '$.CollectionBranch') AS [c], [r0].[Id], 1 AS [c0] - FROM [RootEntities] AS [r0] - ORDER BY [r0].[Id] -) AS [r1] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - { - await base.Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); - - AssertSql( - """ -SELECT [r1].[c], [r1].[Id], [r1].[c0], [r1].[Id0], [r1].[c1], [r1].[c2], [r1].[c3], [r1].[c4] -FROM [RootEntities] AS [r] -OUTER APPLY ( - SELECT TOP(1) JSON_QUERY([r].[RequiredReferenceTrunk], '$.CollectionBranch') AS [c], [r].[Id], [r0].[RequiredReferenceTrunk] AS [c0], [r0].[Id] AS [Id0], JSON_QUERY([r0].[RequiredReferenceTrunk], '$.RequiredReferenceBranch') AS [c1], JSON_VALUE([r0].[RequiredReferenceTrunk], '$.Name') AS [c2], JSON_VALUE([r].[RequiredReferenceTrunk], '$.RequiredReferenceBranch.Name') AS [c3], 1 AS [c4] - FROM [RootEntities] AS [r0] - ORDER BY [r0].[Id] -) AS [r1] -ORDER BY [r].[Id] -"""); - } - - public override async Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - { - await base.Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); - - AssertSql( - """ -SELECT [r1].[c], [r1].[Id], [r1].[c0] -FROM [RootEntities] AS [r] -OUTER APPLY ( - SELECT TOP(1) JSON_QUERY([r].[RequiredReferenceTrunk], '$.CollectionBranch') AS [c], [r].[Id], 1 AS [c0] - FROM [RootEntities] AS [r0] - ORDER BY [r0].[Id] -) AS [r1] -ORDER BY [r].[Id] -"""); - } - - public override async Task SelectMany_trunk_collection(bool async) - { - await base.SelectMany_trunk_collection(async); - - AssertSql( - """ -SELECT [r].[Id], [c].[Name], [c].[CollectionBranch], [c].[OptionalReferenceBranch], [c].[RequiredReferenceBranch] -FROM [RootEntities] AS [r] -CROSS APPLY OPENJSON([r].[CollectionTrunk], '$') WITH ( - [Name] nvarchar(max) '$.Name', - [CollectionBranch] nvarchar(max) '$.CollectionBranch' AS JSON, - [OptionalReferenceBranch] nvarchar(max) '$.OptionalReferenceBranch' AS JSON, - [RequiredReferenceBranch] nvarchar(max) '$.RequiredReferenceBranch' AS JSON -) AS [c] -"""); - } - - public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) - { - await base.SelectMany_required_trunk_reference_branch_collection(async); - - AssertSql( - """ -SELECT [r].[Id], [c].[Name], [c].[CollectionLeaf], [c].[OptionalReferenceLeaf], [c].[RequiredReferenceLeaf] -FROM [RootEntities] AS [r] -CROSS APPLY OPENJSON([r].[RequiredReferenceTrunk], '$.CollectionBranch') WITH ( - [Name] nvarchar(max) '$.Name', - [CollectionLeaf] nvarchar(max) '$.CollectionLeaf' AS JSON, - [OptionalReferenceLeaf] nvarchar(max) '$.OptionalReferenceLeaf' AS JSON, - [RequiredReferenceLeaf] nvarchar(max) '$.RequiredReferenceLeaf' AS JSON -) AS [c] -"""); - } - - public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) - { - await base.SelectMany_optional_trunk_reference_branch_collection(async); - - AssertSql( - """ -SELECT [r].[Id], [c].[Name], [c].[CollectionLeaf], [c].[OptionalReferenceLeaf], [c].[RequiredReferenceLeaf] -FROM [RootEntities] AS [r] -CROSS APPLY OPENJSON([r].[OptionalReferenceTrunk], '$.CollectionBranch') WITH ( - [Name] nvarchar(max) '$.Name', - [CollectionLeaf] nvarchar(max) '$.CollectionLeaf' AS JSON, - [OptionalReferenceLeaf] nvarchar(max) '$.OptionalReferenceLeaf' AS JSON, - [RequiredReferenceLeaf] nvarchar(max) '$.RequiredReferenceLeaf' AS JSON -) AS [c] -"""); - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - private void AssertSql(params string[] expected) - => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); -} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/JsonRelationshipsInProjectionQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/JsonRelationshipsInProjectionQuerySqlServerTest.cs deleted file mode 100644 index dc5b5ab10ee..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/JsonRelationshipsInProjectionQuerySqlServerTest.cs +++ /dev/null @@ -1,148 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public class JsonRelationshipsInProjectionQuerySqlServerTest - : JsonRelationshipsInProjectionQueryRelationalTestBase -{ - public JsonRelationshipsInProjectionQuerySqlServerTest(JsonRelationshipsQuerySqlServerFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - public override async Task Project_root(bool async) - { - await base.Project_root(async); - - AssertSql( -""" -SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [r].[CollectionTrunk], [r].[OptionalReferenceTrunk], [r].[RequiredReferenceTrunk] -FROM [RootEntities] AS [r] -"""); - } - - public override async Task Project_root_duplicated(bool async) - { - await base.Project_root_duplicated(async); - - AssertSql( - """ -SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [r].[CollectionTrunk], [r].[OptionalReferenceTrunk], [r].[RequiredReferenceTrunk], [r].[CollectionTrunk], [r].[OptionalReferenceTrunk], [r].[RequiredReferenceTrunk] -FROM [RootEntities] AS [r] -"""); - } - - public override Task Project_trunk_optional(bool async) - => AssertCantTrackJson(() => base.Project_trunk_optional(async)); - - public override Task Project_trunk_required(bool async) - => AssertCantTrackJson(() => base.Project_trunk_required(async)); - - public override Task Project_trunk_collection(bool async) - => AssertCantTrackJson(() => base.Project_trunk_collection(async)); - - public override Task Project_branch_required_required(bool async) - => AssertCantTrackJson(() => base.Project_branch_required_required(async)); - - public override Task Project_branch_required_optional(bool async) - => AssertCantTrackJson(() => base.Project_branch_required_optional(async)); - - public override Task Project_branch_required_collection(bool async) - => AssertCantTrackJson(() => base.Project_branch_required_collection(async)); - - public override Task Project_branch_optional_required(bool async) - => AssertCantTrackJson(() => base.Project_branch_optional_required(async)); - - public override Task Project_branch_optional_optional(bool async) - => AssertCantTrackJson(() => base.Project_branch_optional_optional(async)); - - public override Task Project_branch_optional_collection(bool async) - => AssertCantTrackJson(() => base.Project_branch_optional_collection(async)); - - public override Task Project_branch_collection_element_using_indexer_constant(bool async) - => AssertCantTrackJson(() => base.Project_branch_collection_element_using_indexer_constant(async)); - - - - - - - - - - - - - - public override Task Project_trunk_and_branch_duplicated(bool async) - => AssertCantTrackJson(() => base.Project_trunk_and_branch_duplicated(async)); - - public override Task Project_trunk_and_trunk_duplicated(bool async) - => AssertCantTrackJson(() => base.Project_trunk_and_trunk_duplicated(async)); - - public override Task Project_multiple_branch_leaf(bool async) - => AssertCantTrackJson(() => base.Project_multiple_branch_leaf(async)); - - public override Task Project_leaf_trunk_root(bool async) - => AssertCantTrackJson(() => base.Project_leaf_trunk_root(async)); - - public override Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) - => AssertCantTrackJson(() => base.Project_subquery_root_set_required_trunk_FirstOrDefault_branch(async)); - - public override Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) - => AssertCantTrackJson(() => base.Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(async)); - - public override Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - => AssertCantTrackJson(() => base.Project_subquery_root_set_trunk_FirstOrDefault_collection(async)); - - public override Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - => AssertCantTrackJson(() => base.Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async)); - - public override Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - => AssertCantTrackJson(() => base.Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async)); - - public override Task SelectMany_trunk_collection(bool async) - => AssertCantTrackJson(() => base.SelectMany_trunk_collection(async)); - - public override Task SelectMany_required_trunk_reference_branch_collection(bool async) - => AssertCantTrackJson(() => base.SelectMany_required_trunk_reference_branch_collection(async)); - - public override Task SelectMany_optional_trunk_reference_branch_collection(bool async) - => AssertCantTrackJson(() => base.SelectMany_optional_trunk_reference_branch_collection(async)); - - - - - - - - - - - - - - - - - - - - - - - - private async Task AssertCantTrackJson(Func test) - { - var message = (await Assert.ThrowsAsync(test)).Message; - - Assert.Equal(RelationalStrings.JsonEntityOrCollectionProjectedAtRootLevelInTrackingQuery("AsNoTracking"), message); - AssertSql(); - } - - private void AssertSql(params string[] expected) - => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); -} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/JsonTypeRelationshipsInProjectionNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/JsonTypeRelationshipsInProjectionNoTrackingQuerySqlServerTest.cs deleted file mode 100644 index 8b0050ca097..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/JsonTypeRelationshipsInProjectionNoTrackingQuerySqlServerTest.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -// Only adding NoTracking version - no point to do both and most of the tests don't work in tracking (projecting without owner) -[SqlServerCondition(SqlServerCondition.SupportsJsonType)] -public class JsonTypeRelationshipsInProjectionNoTrackingQuerySqlServerTest - : RelationshipsInProjectionQueryTestBase -{ - private readonly NoTrackingRewriter _noTrackingRewriter = new(); - - public JsonTypeRelationshipsInProjectionNoTrackingQuerySqlServerTest(JsonTypeRelationshipsQuerySqlServerFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) - { - var rewritten = _noTrackingRewriter.Visit(serverQueryExpression); - - return rewritten; - } -} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Include/EntityRelationshipsIncludeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Include/NavigationIncludeSqlServerTest.cs similarity index 76% rename from test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Include/EntityRelationshipsIncludeQuerySqlServerTest.cs rename to test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Include/NavigationIncludeSqlServerTest.cs index 1f9cdf7b055..ef862de22d5 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Include/EntityRelationshipsIncludeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Include/NavigationIncludeSqlServerTest.cs @@ -3,10 +3,10 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships.Include; -public class EntityRelationshipsIncludeQuerySqlServerTest - : EntityRelationshipsIncludeQueryRelationalTestBase +public class NavigationIncludeSqlServerTest + : NavigationIncludeRelationalTestBase { - public EntityRelationshipsIncludeQuerySqlServerTest(EntityRelationshipsQuerySqlServerFixture fixture, ITestOutputHelper testOutputHelper) + public NavigationIncludeSqlServerTest(NavigationRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) { Fixture.TestSqlLoggerFactory.Clear(); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedRelationshipsQuerySqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/NavigationRelationshipsSqlServerFixture.cs similarity index 77% rename from test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedRelationshipsQuerySqlServerFixture.cs rename to test/EFCore.SqlServer.FunctionalTests/Query/Relationships/NavigationRelationshipsSqlServerFixture.cs index 22f1b54995d..47a2c9349ae 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedRelationshipsQuerySqlServerFixture.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/NavigationRelationshipsSqlServerFixture.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public class OwnedRelationshipsQuerySqlServerFixture : OwnedRelationshipsQueryRelationalFixtureBase, ITestSqlLoggerFactory +public class NavigationRelationshipsSqlServerFixture : NavigationRelationshipsRelationalFixtureBase, ITestSqlLoggerFactory { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/EntityRelationshipsQuerySqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJsonRelationshipsSqlServerFixture.cs similarity index 76% rename from test/EFCore.SqlServer.FunctionalTests/Query/Relationships/EntityRelationshipsQuerySqlServerFixture.cs rename to test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJsonRelationshipsSqlServerFixture.cs index aa7473ea625..bcba194e65f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/EntityRelationshipsQuerySqlServerFixture.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJsonRelationshipsSqlServerFixture.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public class EntityRelationshipsQuerySqlServerFixture : EntityRelationshipsQueryRelationalFixtureBase, ITestSqlLoggerFactory +public class OwnedJsonRelationshipsSqlServerFixture : OwnedJsonRelationshipsRelationalFixtureBase, ITestSqlLoggerFactory { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/JsonTypeRelationshipsQuerySqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJsonTypeRelationshipsSqlServerFixture.cs similarity index 91% rename from test/EFCore.SqlServer.FunctionalTests/Query/Relationships/JsonTypeRelationshipsQuerySqlServerFixture.cs rename to test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJsonTypeRelationshipsSqlServerFixture.cs index 15761fb9188..71a9d380ecb 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/JsonTypeRelationshipsQuerySqlServerFixture.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJsonTypeRelationshipsSqlServerFixture.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public class JsonTypeRelationshipsQuerySqlServerFixture : JsonRelationshipsQueryRelationalFixtureBase, ITestSqlLoggerFactory +public class OwnedJsonTypeRelationshipsSqlServerFixture : OwnedJsonRelationshipsRelationalFixtureBase, ITestSqlLoggerFactory { protected override string StoreName => "JsonTypeRelationshipsQueryTest"; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexRelationshipsQuerySqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedRelationshipsSqlServerFixture.cs similarity index 76% rename from test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexRelationshipsQuerySqlServerFixture.cs rename to test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedRelationshipsSqlServerFixture.cs index 62fb66d4741..35ebf765e02 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexRelationshipsQuerySqlServerFixture.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedRelationshipsSqlServerFixture.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public class ComplexRelationshipsQuerySqlServerFixture : ComplexRelationshipsQueryRelationalFixtureBase, ITestSqlLoggerFactory +public class OwnedRelationshipsSqlServerFixture : OwnedRelationshipsRelationalFixtureBase, ITestSqlLoggerFactory { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplittingRelationshipsSqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplittingRelationshipsSqlServerFixture.cs new file mode 100644 index 00000000000..bfabcce47ab --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplittingRelationshipsSqlServerFixture.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships; + +public class OwnedTableSplittingRelationshipsSqlServerFixture : OwnedTableSplittingRelationshipsRelationalFixtureBase, ITestSqlLoggerFactory +{ + protected override ITestStoreFactory TestStoreFactory + => SqlServerTestStoreFactory.Instance; + + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/ComplexNoTrackingProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/ComplexNoTrackingProjectionSqlServerTest.cs new file mode 100644 index 00000000000..89a259a4218 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/ComplexNoTrackingProjectionSqlServerTest.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class ComplexNoTrackingProjectionSqlServerTest + : ComplexNoTrackingProjectionRelationalTestBase +{ + public ComplexNoTrackingProjectionSqlServerTest(ComplexRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_everything(bool async) + { + await base.Select_everything(async); + + AssertSql( +""" +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[Id] +"""); + } + + public override async Task Select_root(bool async) + { + await base.Select_root(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] +FROM [RootEntities] AS [r] +"""); + } + + public override async Task Select_trunk_optional(bool async) + { + await base.Select_trunk_optional(async); + + AssertSql( + """ +SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] +FROM [RootEntities] AS [r] +LEFT JOIN [TrunkEntities] AS [t] ON [r].[OptionalReferenceTrunkId] = [t].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_trunk_required(bool async) + { + await base.Select_trunk_required(async); + + AssertSql( + """ +SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_required_required(bool async) + { + await base.Select_branch_required_required(async); + + AssertSql( + """ +SELECT [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_required_optional(bool async) + { + await base.Select_branch_required_optional(async); + + AssertSql(); + } + + public override async Task Select_branch_optional_required(bool async) + { + await base.Select_branch_optional_required(async); + + AssertSql(); + } + + public override async Task Select_branch_optional_optional(bool async) + { + await base.Select_branch_optional_optional(async); + + AssertSql(); + } + + public override async Task Select_root_duplicated(bool async) + { + await base.Select_root_duplicated(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] +FROM [RootEntities] AS [r] +"""); + } + + public override async Task Select_trunk_and_branch_duplicated(bool async) + { + await base.Select_trunk_and_branch_duplicated(async); + + AssertSql(); + } + + public override async Task Select_trunk_and_trunk_duplicated(bool async) + { + await base.Select_trunk_and_trunk_duplicated(async); + + AssertSql(); + } + + public override async Task Select_leaf_trunk_root(bool async) + { + await base.Select_leaf_trunk_root(async); + + AssertSql( + """ +SELECT [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +"""); + } + + public override async Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + { + await base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async); + + AssertSql(); + } + + public override async Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + { + await base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); + + AssertSql(); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/ComplexProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/ComplexProjectionSqlServerTest.cs new file mode 100644 index 00000000000..b9ae5113d9f --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/ComplexProjectionSqlServerTest.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class ComplexProjectionSqlServerTest + : ComplexProjectionRelationalTestBase +{ + public ComplexProjectionSqlServerTest(ComplexRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_everything(bool async) + { + await base.Select_everything(async); + + AssertSql( +""" +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[Id] +"""); + } + + public override async Task Select_root(bool async) + { + await base.Select_root(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] +FROM [RootEntities] AS [r] +"""); + } + + public override async Task Select_trunk_optional(bool async) + { + await base.Select_trunk_optional(async); + + AssertSql( + """ +SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] +FROM [RootEntities] AS [r] +LEFT JOIN [TrunkEntities] AS [t] ON [r].[OptionalReferenceTrunkId] = [t].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_trunk_required(bool async) + { + await base.Select_trunk_required(async); + + AssertSql( + """ +SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_required_required(bool async) + { + await base.Select_branch_required_required(async); + + AssertSql( + """ +SELECT [t].[RequiredReferenceBranch_Name], [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_required_optional(bool async) + { + await base.Select_branch_required_optional(async); + + AssertSql(); + } + + public override async Task Select_branch_optional_required(bool async) + { + await base.Select_branch_optional_required(async); + + AssertSql(); + } + + public override async Task Select_branch_optional_optional(bool async) + { + await base.Select_branch_optional_optional(async); + + AssertSql(); + } + + public override async Task Select_root_duplicated(bool async) + { + await base.Select_root_duplicated(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] +FROM [RootEntities] AS [r] +"""); + } + + public override async Task Select_trunk_and_branch_duplicated(bool async) + { + await base.Select_trunk_and_branch_duplicated(async); + + AssertSql(); + } + + public override async Task Select_trunk_and_trunk_duplicated(bool async) + { + await base.Select_trunk_and_trunk_duplicated(async); + + AssertSql(); + } + + public override async Task Select_leaf_trunk_root(bool async) + { + await base.Select_leaf_trunk_root(async); + + AssertSql( + """ +SELECT [t].[RequiredReferenceBranch_RequiredReferenceLeaf_Name], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [t].[RequiredReferenceBranch_Name], [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +"""); + } + + public override async Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + { + await base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async); + + AssertSql(); + } + + public override async Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + { + await base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); + + AssertSql(); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/NavigationNoTrackingProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/NavigationNoTrackingProjectionSqlServerTest.cs new file mode 100644 index 00000000000..6953a4c074d --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/NavigationNoTrackingProjectionSqlServerTest.cs @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class NavigationNoTrackingProjectionSqlServerTest + : NavigationNoTrackingProjectionRelationalTestBase +{ + public NavigationNoTrackingProjectionSqlServerTest(NavigationRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_everything_using_joins(bool async) + { + await base.Select_everything_using_joins(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [l].[Id], [l].[CollectionBranchId], [l].[Name] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[Id] +INNER JOIN [LeafEntities] AS [l] ON [b].[Id] = [l].[Id] +"""); + } + + public override async Task Select_trunk_collection(bool async) + { + await base.Select_trunk_collection(async); + AssertSql( + """ +SELECT [r].[Id], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId] +FROM [RootEntities] AS [r] +LEFT JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[CollectionRootId] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_required_collection(bool async) + { + await base.Select_branch_required_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [t].[Id], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +LEFT JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] +ORDER BY [r].[Id], [t].[Id] +"""); + } + + public override async Task Select_branch_optional_collection(bool async) + { + await base.Select_branch_optional_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [t].[Id], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +LEFT JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] +ORDER BY [r].[Id], [t].[Id] +"""); + } + + public override async Task Select_multiple_branch_leaf(bool async) + { + await base.Select_multiple_branch_leaf(async); + + AssertSql( + """ +SELECT [r].[Id], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [l].[Id], [l].[CollectionBranchId], [l].[Name], [t].[Id], [l0].[Id], [l0].[CollectionBranchId], [l0].[Name], [b0].[Id], [b0].[CollectionTrunkId], [b0].[Name], [b0].[OptionalReferenceLeafId], [b0].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] +LEFT JOIN [LeafEntities] AS [l] ON [b].[OptionalReferenceLeafId] = [l].[Id] +LEFT JOIN [LeafEntities] AS [l0] ON [b].[Id] = [l0].[CollectionBranchId] +LEFT JOIN [BranchEntities] AS [b0] ON [t].[Id] = [b0].[CollectionTrunkId] +ORDER BY [r].[Id], [t].[Id], [b].[Id], [l].[Id], [l0].[Id] +"""); + } + + public override async Task Select_subquery_root_set_trunk_FirstOrDefault_collection(bool async) + { + await base.Select_subquery_root_set_trunk_FirstOrDefault_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [s].[Id], [s].[Id0], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [s].[c] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) 1 AS [c], [r0].[Id], [t].[Id] AS [Id0] + FROM [RootEntities] AS [r0] + INNER JOIN [TrunkEntities] AS [t] ON [r0].[RequiredReferenceTrunkId] = [t].[Id] + ORDER BY [r0].[Id] +) AS [s] +LEFT JOIN [BranchEntities] AS [b] ON [s].[Id0] = [b].[CollectionTrunkId] +ORDER BY [r].[Id], [s].[Id], [s].[Id0] +"""); + } + + public override async Task Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) + { + await base.Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); + + AssertSql( + """ +SELECT [r].[Id], [t].[Id], [b].[Id], [s].[Id1], [s].[Id], [s].[Id0], [b1].[Id], [b1].[CollectionTrunkId], [b1].[Name], [b1].[OptionalReferenceLeafId], [b1].[RequiredReferenceLeafId], [s].[CollectionRootId], [s].[Name], [s].[OptionalReferenceBranchId], [s].[RequiredReferenceBranchId], [s].[CollectionTrunkId], [s].[Name0], [s].[OptionalReferenceLeafId], [s].[RequiredReferenceLeafId], [s].[Name1], [s].[c] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] +OUTER APPLY ( + SELECT TOP(1) [t0].[Id], [t0].[CollectionRootId], [t0].[Name], [t0].[OptionalReferenceBranchId], [t0].[RequiredReferenceBranchId], [b0].[Id] AS [Id0], [b0].[CollectionTrunkId], [b0].[Name] AS [Name0], [b0].[OptionalReferenceLeafId], [b0].[RequiredReferenceLeafId], [b].[Name] AS [Name1], 1 AS [c], [r0].[Id] AS [Id1] + FROM [RootEntities] AS [r0] + INNER JOIN [TrunkEntities] AS [t0] ON [r0].[RequiredReferenceTrunkId] = [t0].[Id] + INNER JOIN [BranchEntities] AS [b0] ON [t0].[RequiredReferenceBranchId] = [b0].[Id] + ORDER BY [r0].[Id] +) AS [s] +LEFT JOIN [BranchEntities] AS [b1] ON [t].[Id] = [b1].[CollectionTrunkId] +ORDER BY [r].[Id], [t].[Id], [b].[Id], [s].[Id1], [s].[Id], [s].[Id0] +"""); + } + + public override async Task Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) + { + await base.Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); + + AssertSql( + """ +SELECT [r].[Id], [t].[Id], [r1].[Id], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [r1].[c] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +OUTER APPLY ( + SELECT TOP(1) 1 AS [c], [r0].[Id] + FROM [RootEntities] AS [r0] + ORDER BY [r0].[Id] +) AS [r1] +LEFT JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] +ORDER BY [r].[Id], [t].[Id], [r1].[Id] +"""); + } + + public override async Task SelectMany_trunk_collection(bool async) + { + await base.SelectMany_trunk_collection(async); + + AssertSql( + """ +SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[CollectionRootId] +"""); + } + + public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) + { + await base.SelectMany_required_trunk_reference_branch_collection(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] +"""); + } + + public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) + { + await base.SelectMany_optional_trunk_reference_branch_collection(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +LEFT JOIN [TrunkEntities] AS [t] ON [r].[OptionalReferenceTrunkId] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] +"""); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/NavigationProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/NavigationProjectionSqlServerTest.cs new file mode 100644 index 00000000000..75d62b063f5 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/NavigationProjectionSqlServerTest.cs @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class NavigationProjectionSqlServerTest + : NavigationProjectionRelationalTestBase +{ + public NavigationProjectionSqlServerTest(NavigationRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_everything_using_joins(bool async) + { + await base.Select_everything_using_joins(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [l].[Id], [l].[CollectionBranchId], [l].[Name] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[Id] +INNER JOIN [LeafEntities] AS [l] ON [b].[Id] = [l].[Id] +"""); + } + + public override async Task Select_trunk_collection(bool async) + { + await base.Select_trunk_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId] +FROM [RootEntities] AS [r] +LEFT JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[CollectionRootId] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_required_collection(bool async) + { + await base.Select_branch_required_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [t].[Id], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +LEFT JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] +ORDER BY [r].[Id], [t].[Id] +"""); + } + + public override async Task Select_branch_optional_collection(bool async) + { + await base.Select_branch_optional_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [t].[Id], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +LEFT JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] +ORDER BY [r].[Id], [t].[Id] +"""); + } + + public override async Task Select_multiple_branch_leaf(bool async) + { + await base.Select_multiple_branch_leaf(async); + + AssertSql( + """ +SELECT [r].[Id], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [l].[Id], [l].[CollectionBranchId], [l].[Name], [t].[Id], [l0].[Id], [l0].[CollectionBranchId], [l0].[Name], [b0].[Id], [b0].[CollectionTrunkId], [b0].[Name], [b0].[OptionalReferenceLeafId], [b0].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] +LEFT JOIN [LeafEntities] AS [l] ON [b].[OptionalReferenceLeafId] = [l].[Id] +LEFT JOIN [LeafEntities] AS [l0] ON [b].[Id] = [l0].[CollectionBranchId] +LEFT JOIN [BranchEntities] AS [b0] ON [t].[Id] = [b0].[CollectionTrunkId] +ORDER BY [r].[Id], [t].[Id], [b].[Id], [l].[Id], [l0].[Id] +"""); + } + + public override async Task Select_subquery_root_set_trunk_FirstOrDefault_collection(bool async) + { + await base.Select_subquery_root_set_trunk_FirstOrDefault_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [s].[Id], [s].[Id0], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [s].[c] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) 1 AS [c], [r0].[Id], [t].[Id] AS [Id0] + FROM [RootEntities] AS [r0] + INNER JOIN [TrunkEntities] AS [t] ON [r0].[RequiredReferenceTrunkId] = [t].[Id] + ORDER BY [r0].[Id] +) AS [s] +LEFT JOIN [BranchEntities] AS [b] ON [s].[Id0] = [b].[CollectionTrunkId] +ORDER BY [r].[Id], [s].[Id], [s].[Id0] +"""); + } + + public override async Task Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) + { + await base.Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); + + AssertSql( + """ +SELECT [r].[Id], [t].[Id], [b].[Id], [s].[Id1], [s].[Id], [s].[Id0], [b1].[Id], [b1].[CollectionTrunkId], [b1].[Name], [b1].[OptionalReferenceLeafId], [b1].[RequiredReferenceLeafId], [s].[CollectionRootId], [s].[Name], [s].[OptionalReferenceBranchId], [s].[RequiredReferenceBranchId], [s].[CollectionTrunkId], [s].[Name0], [s].[OptionalReferenceLeafId], [s].[RequiredReferenceLeafId], [s].[Name1], [s].[c] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] +OUTER APPLY ( + SELECT TOP(1) [t0].[Id], [t0].[CollectionRootId], [t0].[Name], [t0].[OptionalReferenceBranchId], [t0].[RequiredReferenceBranchId], [b0].[Id] AS [Id0], [b0].[CollectionTrunkId], [b0].[Name] AS [Name0], [b0].[OptionalReferenceLeafId], [b0].[RequiredReferenceLeafId], [b].[Name] AS [Name1], 1 AS [c], [r0].[Id] AS [Id1] + FROM [RootEntities] AS [r0] + INNER JOIN [TrunkEntities] AS [t0] ON [r0].[RequiredReferenceTrunkId] = [t0].[Id] + INNER JOIN [BranchEntities] AS [b0] ON [t0].[RequiredReferenceBranchId] = [b0].[Id] + ORDER BY [r0].[Id] +) AS [s] +LEFT JOIN [BranchEntities] AS [b1] ON [t].[Id] = [b1].[CollectionTrunkId] +ORDER BY [r].[Id], [t].[Id], [b].[Id], [s].[Id1], [s].[Id], [s].[Id0] +"""); + } + + public override async Task Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) + { + await base.Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); + + AssertSql( + """ +SELECT [r].[Id], [t].[Id], [r1].[Id], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [r1].[c] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +OUTER APPLY ( + SELECT TOP(1) 1 AS [c], [r0].[Id] + FROM [RootEntities] AS [r0] + ORDER BY [r0].[Id] +) AS [r1] +LEFT JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] +ORDER BY [r].[Id], [t].[Id], [r1].[Id] +"""); + } + + public override async Task SelectMany_trunk_collection(bool async) + { + await base.SelectMany_trunk_collection(async); + + AssertSql( + """ +SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[CollectionRootId] +"""); + } + + public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) + { + await base.SelectMany_required_trunk_reference_branch_collection(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] +"""); + } + + public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) + { + await base.SelectMany_optional_trunk_reference_branch_collection(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +LEFT JOIN [TrunkEntities] AS [t] ON [r].[OptionalReferenceTrunkId] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[CollectionTrunkId] +"""); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/NavigationReferenceNoTrackingProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/NavigationReferenceNoTrackingProjectionSqlServerTest.cs new file mode 100644 index 00000000000..8f163e9fc47 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/NavigationReferenceNoTrackingProjectionSqlServerTest.cs @@ -0,0 +1,217 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class NavigationReferenceNoTrackingProjectionSqlServerTest + : NavigationReferenceNoTrackingProjectionRelationalTestBase +{ + public NavigationReferenceNoTrackingProjectionSqlServerTest(NavigationRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_everything(bool async) + { + await base.Select_everything(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [l].[Id], [l].[CollectionBranchId], [l].[Name] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[Id] +INNER JOIN [LeafEntities] AS [l] ON [b].[Id] = [l].[Id] +"""); + } + + public override async Task Select_root(bool async) + { + await base.Select_root(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] +FROM [RootEntities] AS [r] +"""); + } + + public override async Task Select_trunk_optional(bool async) + { + await base.Select_trunk_optional(async); + + AssertSql( + """ +SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId] +FROM [RootEntities] AS [r] +LEFT JOIN [TrunkEntities] AS [t] ON [r].[OptionalReferenceTrunkId] = [t].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_trunk_required(bool async) + { + await base.Select_trunk_required(async); + + AssertSql( + """ +SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_required_required(bool async) + { + await base.Select_branch_required_required(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_required_optional(bool async) + { + await base.Select_branch_required_optional(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +LEFT JOIN [BranchEntities] AS [b] ON [t].[OptionalReferenceBranchId] = [b].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_optional_required(bool async) + { + await base.Select_branch_optional_required(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_optional_optional(bool async) + { + await base.Select_branch_optional_optional(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +LEFT JOIN [BranchEntities] AS [b] ON [t].[OptionalReferenceBranchId] = [b].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_root_duplicated(bool async) + { + await base.Select_root_duplicated(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] +FROM [RootEntities] AS [r] +"""); + } + + public override async Task Select_trunk_and_branch_duplicated(bool async) + { + await base.Select_trunk_and_branch_duplicated(async); + + AssertSql( + """ +SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +LEFT JOIN [TrunkEntities] AS [t] ON [r].[OptionalReferenceTrunkId] = [t].[Id] +LEFT JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_trunk_and_trunk_duplicated(bool async) + { + await base.Select_trunk_and_trunk_duplicated(async); + + AssertSql( + """ +SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [l].[Id], [l].[CollectionBranchId], [l].[Name] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +LEFT JOIN [BranchEntities] AS [b] ON [t].[OptionalReferenceBranchId] = [b].[Id] +LEFT JOIN [LeafEntities] AS [l] ON [b].[RequiredReferenceLeafId] = [l].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_leaf_trunk_root(bool async) + { + await base.Select_leaf_trunk_root(async); + + AssertSql( + """ +SELECT [l].[Id], [l].[CollectionBranchId], [l].[Name], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] +INNER JOIN [LeafEntities] AS [l] ON [b].[RequiredReferenceLeafId] = [l].[Id] +"""); + } + + public override async Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + { + await base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[CollectionTrunkId], [s].[Name], [s].[OptionalReferenceLeafId], [s].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] + FROM [RootEntities] AS [r0] + INNER JOIN [TrunkEntities] AS [t] ON [r0].[RequiredReferenceTrunkId] = [t].[Id] + INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] + ORDER BY [r0].[Id] +) AS [s] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + { + await base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[CollectionTrunkId], [s].[Name], [s].[OptionalReferenceLeafId], [s].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] + FROM [RootEntities] AS [r0] + LEFT JOIN [TrunkEntities] AS [t] ON [r0].[OptionalReferenceTrunkId] = [t].[Id] + LEFT JOIN [BranchEntities] AS [b] ON [t].[OptionalReferenceBranchId] = [b].[Id] + ORDER BY [r0].[Id] +) AS [s] +ORDER BY [r].[Id] +"""); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/NavigationReferenceProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/NavigationReferenceProjectionSqlServerTest.cs new file mode 100644 index 00000000000..c4ffd9c24fa --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/NavigationReferenceProjectionSqlServerTest.cs @@ -0,0 +1,217 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class NavigationReferenceProjectionSqlServerTest + : NavigationReferenceProjectionRelationalTestBase +{ + public NavigationReferenceProjectionSqlServerTest(NavigationRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_everything(bool async) + { + await base.Select_everything(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId], [l].[Id], [l].[CollectionBranchId], [l].[Name] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[Id] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[Id] = [b].[Id] +INNER JOIN [LeafEntities] AS [l] ON [b].[Id] = [l].[Id] +"""); + } + + public override async Task Select_root(bool async) + { + await base.Select_root(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] +FROM [RootEntities] AS [r] +"""); + } + + public override async Task Select_trunk_optional(bool async) + { + await base.Select_trunk_optional(async); + + AssertSql( + """ +SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId] +FROM [RootEntities] AS [r] +LEFT JOIN [TrunkEntities] AS [t] ON [r].[OptionalReferenceTrunkId] = [t].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_trunk_required(bool async) + { + await base.Select_trunk_required(async); + + AssertSql( + """ +SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_required_required(bool async) + { + await base.Select_branch_required_required(async); + + AssertSql( +""" +SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_required_optional(bool async) + { + await base.Select_branch_required_optional(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +LEFT JOIN [BranchEntities] AS [b] ON [t].[OptionalReferenceBranchId] = [b].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_optional_required(bool async) + { + await base.Select_branch_optional_required(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_optional_optional(bool async) + { + await base.Select_branch_optional_optional(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +LEFT JOIN [BranchEntities] AS [b] ON [t].[OptionalReferenceBranchId] = [b].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_root_duplicated(bool async) + { + await base.Select_root_duplicated(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] +FROM [RootEntities] AS [r] +"""); + } + + public override async Task Select_trunk_and_branch_duplicated(bool async) + { + await base.Select_trunk_and_branch_duplicated(async); + + AssertSql( + """ +SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +LEFT JOIN [TrunkEntities] AS [t] ON [r].[OptionalReferenceTrunkId] = [t].[Id] +LEFT JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_trunk_and_trunk_duplicated(bool async) + { + await base.Select_trunk_and_trunk_duplicated(async); + + AssertSql( + """ +SELECT [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [l].[Id], [l].[CollectionBranchId], [l].[Name] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +LEFT JOIN [BranchEntities] AS [b] ON [t].[OptionalReferenceBranchId] = [b].[Id] +LEFT JOIN [LeafEntities] AS [l] ON [b].[RequiredReferenceLeafId] = [l].[Id] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_leaf_trunk_root(bool async) + { + await base.Select_leaf_trunk_root(async); + + AssertSql( + """ +SELECT [l].[Id], [l].[CollectionBranchId], [l].[Name], [t].[Id], [t].[CollectionRootId], [t].[Name], [t].[OptionalReferenceBranchId], [t].[RequiredReferenceBranchId], [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId] +FROM [RootEntities] AS [r] +INNER JOIN [TrunkEntities] AS [t] ON [r].[RequiredReferenceTrunkId] = [t].[Id] +INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] +INNER JOIN [LeafEntities] AS [l] ON [b].[RequiredReferenceLeafId] = [l].[Id] +"""); + } + + public override async Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + { + await base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[CollectionTrunkId], [s].[Name], [s].[OptionalReferenceLeafId], [s].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] + FROM [RootEntities] AS [r0] + INNER JOIN [TrunkEntities] AS [t] ON [r0].[RequiredReferenceTrunkId] = [t].[Id] + INNER JOIN [BranchEntities] AS [b] ON [t].[RequiredReferenceBranchId] = [b].[Id] + ORDER BY [r0].[Id] +) AS [s] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + { + await base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[CollectionTrunkId], [s].[Name], [s].[OptionalReferenceLeafId], [s].[RequiredReferenceLeafId] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) [b].[Id], [b].[CollectionTrunkId], [b].[Name], [b].[OptionalReferenceLeafId], [b].[RequiredReferenceLeafId] + FROM [RootEntities] AS [r0] + LEFT JOIN [TrunkEntities] AS [t] ON [r0].[OptionalReferenceTrunkId] = [t].[Id] + LEFT JOIN [BranchEntities] AS [b] ON [t].[OptionalReferenceBranchId] = [b].[Id] + ORDER BY [r0].[Id] +) AS [s] +ORDER BY [r].[Id] +"""); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonNoTrackingProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonNoTrackingProjectionSqlServerTest.cs new file mode 100644 index 00000000000..810461aac39 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonNoTrackingProjectionSqlServerTest.cs @@ -0,0 +1,167 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedJsonNoTrackingProjectionSqlServerTest + : OwnedJsonNoTrackingProjectionRelationalTestBase +{ + public OwnedJsonNoTrackingProjectionSqlServerTest(OwnedJsonRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_trunk_collection(bool async) + { + await base.Select_trunk_collection(async); + + AssertSql( + """ +SELECT [r].[CollectionTrunk], [r].[Id] +FROM [RootEntities] AS [r] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_required_collection(bool async) + { + await base.Select_branch_required_collection(async); + + AssertSql( + """ +SELECT JSON_QUERY([r].[RequiredReferenceTrunk], '$.CollectionBranch'), [r].[Id] +FROM [RootEntities] AS [r] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_optional_collection(bool async) + { + await base.Select_branch_optional_collection(async); + + AssertSql( + """ +SELECT JSON_QUERY([r].[RequiredReferenceTrunk], '$.CollectionBranch'), [r].[Id] +FROM [RootEntities] AS [r] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_multiple_branch_leaf(bool async) + { + await base.Select_multiple_branch_leaf(async); + + AssertSql( + """ +SELECT [r].[Id], JSON_QUERY([r].[RequiredReferenceTrunk], '$.RequiredReferenceBranch'), JSON_QUERY([r].[RequiredReferenceTrunk], '$.RequiredReferenceBranch.OptionalReferenceLeaf'), JSON_QUERY([r].[RequiredReferenceTrunk], '$.RequiredReferenceBranch.CollectionLeaf'), JSON_QUERY([r].[RequiredReferenceTrunk], '$.CollectionBranch'), JSON_VALUE([r].[RequiredReferenceTrunk], '$.RequiredReferenceBranch.OptionalReferenceLeaf.Name') +FROM [RootEntities] AS [r] +"""); + } + + public override async Task Select_subquery_root_set_trunk_FirstOrDefault_collection(bool async) + { + await base.Select_subquery_root_set_trunk_FirstOrDefault_collection(async); + + AssertSql( + """ +SELECT [r1].[c], [r1].[Id], [r1].[c0] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) JSON_QUERY([r0].[RequiredReferenceTrunk], '$.CollectionBranch') AS [c], [r0].[Id], 1 AS [c0] + FROM [RootEntities] AS [r0] + ORDER BY [r0].[Id] +) AS [r1] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) + { + await base.Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); + + AssertSql( + """ +SELECT [r1].[c], [r1].[Id], [r1].[c0], [r1].[Id0], [r1].[c1], [r1].[c2], [r1].[c3], [r1].[c4] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) JSON_QUERY([r].[RequiredReferenceTrunk], '$.CollectionBranch') AS [c], [r].[Id], [r0].[RequiredReferenceTrunk] AS [c0], [r0].[Id] AS [Id0], JSON_QUERY([r0].[RequiredReferenceTrunk], '$.RequiredReferenceBranch') AS [c1], JSON_VALUE([r0].[RequiredReferenceTrunk], '$.Name') AS [c2], JSON_VALUE([r].[RequiredReferenceTrunk], '$.RequiredReferenceBranch.Name') AS [c3], 1 AS [c4] + FROM [RootEntities] AS [r0] + ORDER BY [r0].[Id] +) AS [r1] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) + { + await base.Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); + + AssertSql( + """ +SELECT [r1].[c], [r1].[Id], [r1].[c0] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) JSON_QUERY([r].[RequiredReferenceTrunk], '$.CollectionBranch') AS [c], [r].[Id], 1 AS [c0] + FROM [RootEntities] AS [r0] + ORDER BY [r0].[Id] +) AS [r1] +ORDER BY [r].[Id] +"""); + } + + public override async Task SelectMany_trunk_collection(bool async) + { + await base.SelectMany_trunk_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [c].[Name], [c].[CollectionBranch], [c].[OptionalReferenceBranch], [c].[RequiredReferenceBranch] +FROM [RootEntities] AS [r] +CROSS APPLY OPENJSON([r].[CollectionTrunk], '$') WITH ( + [Name] nvarchar(max) '$.Name', + [CollectionBranch] nvarchar(max) '$.CollectionBranch' AS JSON, + [OptionalReferenceBranch] nvarchar(max) '$.OptionalReferenceBranch' AS JSON, + [RequiredReferenceBranch] nvarchar(max) '$.RequiredReferenceBranch' AS JSON +) AS [c] +"""); + } + + public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) + { + await base.SelectMany_required_trunk_reference_branch_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [c].[Name], [c].[CollectionLeaf], [c].[OptionalReferenceLeaf], [c].[RequiredReferenceLeaf] +FROM [RootEntities] AS [r] +CROSS APPLY OPENJSON([r].[RequiredReferenceTrunk], '$.CollectionBranch') WITH ( + [Name] nvarchar(max) '$.Name', + [CollectionLeaf] nvarchar(max) '$.CollectionLeaf' AS JSON, + [OptionalReferenceLeaf] nvarchar(max) '$.OptionalReferenceLeaf' AS JSON, + [RequiredReferenceLeaf] nvarchar(max) '$.RequiredReferenceLeaf' AS JSON +) AS [c] +"""); + } + + public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) + { + await base.SelectMany_optional_trunk_reference_branch_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [c].[Name], [c].[CollectionLeaf], [c].[OptionalReferenceLeaf], [c].[RequiredReferenceLeaf] +FROM [RootEntities] AS [r] +CROSS APPLY OPENJSON([r].[OptionalReferenceTrunk], '$.CollectionBranch') WITH ( + [Name] nvarchar(max) '$.Name', + [CollectionLeaf] nvarchar(max) '$.CollectionLeaf' AS JSON, + [OptionalReferenceLeaf] nvarchar(max) '$.OptionalReferenceLeaf' AS JSON, + [RequiredReferenceLeaf] nvarchar(max) '$.RequiredReferenceLeaf' AS JSON +) AS [c] +"""); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonProjectionSqlServerTest.cs new file mode 100644 index 00000000000..92d982fff55 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonProjectionSqlServerTest.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedJsonProjectionSqlServerTest + : OwnedJsonProjectionRelationalTestBase +{ + public OwnedJsonProjectionSqlServerTest(OwnedJsonRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override Task Select_trunk_collection(bool async) + => AssertCantTrackJson(() => base.Select_trunk_collection(async)); + + public override Task Select_branch_required_collection(bool async) + => AssertCantTrackJson(() => base.Select_branch_required_collection(async)); + + public override Task Select_branch_optional_collection(bool async) + => AssertCantTrackJson(() => base.Select_branch_optional_collection(async)); + + public override Task Project_branch_collection_element_using_indexer_constant(bool async) + => AssertCantTrackJson(() => base.Project_branch_collection_element_using_indexer_constant(async)); + + public override Task Select_multiple_branch_leaf(bool async) + => AssertCantTrackJson(() => base.Select_multiple_branch_leaf(async)); + + public override Task Select_subquery_root_set_trunk_FirstOrDefault_collection(bool async) + => AssertCantTrackJson(() => base.Select_subquery_root_set_trunk_FirstOrDefault_collection(async)); + + public override Task Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) + => AssertCantTrackJson(() => base.Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async)); + + public override Task Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) + => AssertCantTrackJson(() => base.Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async)); + + public override Task SelectMany_trunk_collection(bool async) + => AssertCantTrackJson(() => base.SelectMany_trunk_collection(async)); + + public override Task SelectMany_required_trunk_reference_branch_collection(bool async) + => AssertCantTrackJson(() => base.SelectMany_required_trunk_reference_branch_collection(async)); + + public override Task SelectMany_optional_trunk_reference_branch_collection(bool async) + => AssertCantTrackJson(() => base.SelectMany_optional_trunk_reference_branch_collection(async)); + + private async Task AssertCantTrackJson(Func test) + { + var message = (await Assert.ThrowsAsync(test)).Message; + + Assert.Equal(RelationalStrings.JsonEntityOrCollectionProjectedAtRootLevelInTrackingQuery("AsNoTracking"), message); + AssertSql(); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonReferenceNoTrackingProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonReferenceNoTrackingProjectionSqlServerTest.cs new file mode 100644 index 00000000000..87550ade35b --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonReferenceNoTrackingProjectionSqlServerTest.cs @@ -0,0 +1,181 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedJsonReferenceNoTrackingProjectionSqlServerTest + : OwnedJsonReferenceNoTrackingProjectionRelationalTestBase +{ + public OwnedJsonReferenceNoTrackingProjectionSqlServerTest(OwnedJsonRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_root(bool async) + { + await base.Select_root(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [r].[CollectionTrunk], [r].[OptionalReferenceTrunk], [r].[RequiredReferenceTrunk] +FROM [RootEntities] AS [r] +"""); + } + + public override async Task Select_trunk_optional(bool async) + { + await base.Select_trunk_optional(async); + + AssertSql( + """ +SELECT [r].[OptionalReferenceTrunk], [r].[Id] +FROM [RootEntities] AS [r] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_trunk_required(bool async) + { + await base.Select_trunk_required(async); + + AssertSql( + """ +SELECT [r].[RequiredReferenceTrunk], [r].[Id] +FROM [RootEntities] AS [r] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_required_required(bool async) + { + await base.Select_branch_required_required(async); + + AssertSql( + """ +SELECT JSON_QUERY([r].[RequiredReferenceTrunk], '$.RequiredReferenceBranch'), [r].[Id] +FROM [RootEntities] AS [r] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_required_optional(bool async) + { + await base.Select_branch_required_optional(async); + + AssertSql( + """ +SELECT JSON_QUERY([r].[RequiredReferenceTrunk], '$.OptionalReferenceBranch'), [r].[Id] +FROM [RootEntities] AS [r] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_optional_required(bool async) + { + await base.Select_branch_optional_required(async); + + AssertSql( + """ +SELECT JSON_QUERY([r].[RequiredReferenceTrunk], '$.RequiredReferenceBranch'), [r].[Id] +FROM [RootEntities] AS [r] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_branch_optional_optional(bool async) + { + await base.Select_branch_optional_optional(async); + + AssertSql( + """ +SELECT JSON_QUERY([r].[RequiredReferenceTrunk], '$.OptionalReferenceBranch'), [r].[Id] +FROM [RootEntities] AS [r] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_root_duplicated(bool async) + { + await base.Select_root_duplicated(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [r].[CollectionTrunk], [r].[OptionalReferenceTrunk], [r].[RequiredReferenceTrunk], [r].[CollectionTrunk], [r].[OptionalReferenceTrunk], [r].[RequiredReferenceTrunk] +FROM [RootEntities] AS [r] +"""); + } + + public override async Task Select_trunk_and_branch_duplicated(bool async) + { + await base.Select_trunk_and_branch_duplicated(async); + + AssertSql( + """ +SELECT [r].[OptionalReferenceTrunk], [r].[Id], JSON_QUERY([r].[OptionalReferenceTrunk], '$.RequiredReferenceBranch'), [r].[OptionalReferenceTrunk], JSON_QUERY([r].[OptionalReferenceTrunk], '$.RequiredReferenceBranch') +FROM [RootEntities] AS [r] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_trunk_and_trunk_duplicated(bool async) + { + await base.Select_trunk_and_trunk_duplicated(async); + + AssertSql( + """ +SELECT [r].[RequiredReferenceTrunk], [r].[Id], JSON_QUERY([r].[RequiredReferenceTrunk], '$.OptionalReferenceBranch.RequiredReferenceLeaf'), [r].[RequiredReferenceTrunk], JSON_QUERY([r].[RequiredReferenceTrunk], '$.OptionalReferenceBranch.RequiredReferenceLeaf') +FROM [RootEntities] AS [r] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_leaf_trunk_root(bool async) + { + await base.Select_leaf_trunk_root(async); + + AssertSql( + """ +SELECT JSON_QUERY([r].[RequiredReferenceTrunk], '$.RequiredReferenceBranch.RequiredReferenceLeaf'), [r].[Id], [r].[RequiredReferenceTrunk], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [r].[CollectionTrunk], [r].[OptionalReferenceTrunk], [r].[RequiredReferenceTrunk] +FROM [RootEntities] AS [r] +"""); + } + + public override async Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + { + await base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async); + + AssertSql( + """ +SELECT [r1].[c], [r1].[Id] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) JSON_QUERY([r0].[RequiredReferenceTrunk], '$.RequiredReferenceBranch') AS [c], [r0].[Id] + FROM [RootEntities] AS [r0] + ORDER BY [r0].[Id] +) AS [r1] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + { + await base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); + + AssertSql( + """ +SELECT [r1].[c], [r1].[Id] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) JSON_QUERY([r0].[OptionalReferenceTrunk], '$.OptionalReferenceBranch') AS [c], [r0].[Id] + FROM [RootEntities] AS [r0] + ORDER BY [r0].[Id] +) AS [r1] +ORDER BY [r].[Id] +"""); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonReferenceProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonReferenceProjectionSqlServerTest.cs new file mode 100644 index 00000000000..433358b5563 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonReferenceProjectionSqlServerTest.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedJsonReferenceProjectionSqlServerTest + : OwnedJsonReferenceProjectionRelationalTestBase +{ + public OwnedJsonReferenceProjectionSqlServerTest(OwnedJsonRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_root(bool async) + { + await base.Select_root(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [r].[CollectionTrunk], [r].[OptionalReferenceTrunk], [r].[RequiredReferenceTrunk] +FROM [RootEntities] AS [r] +"""); + } + + public override async Task Select_root_duplicated(bool async) + { + await base.Select_root_duplicated(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [r].[CollectionTrunk], [r].[OptionalReferenceTrunk], [r].[RequiredReferenceTrunk], [r].[CollectionTrunk], [r].[OptionalReferenceTrunk], [r].[RequiredReferenceTrunk] +FROM [RootEntities] AS [r] +"""); + } + + public override Task Select_trunk_optional(bool async) + => AssertCantTrackJson(() => base.Select_trunk_optional(async)); + + public override Task Select_trunk_required(bool async) + => AssertCantTrackJson(() => base.Select_trunk_required(async)); + + public override Task Select_branch_required_required(bool async) + => AssertCantTrackJson(() => base.Select_branch_required_required(async)); + + public override Task Select_branch_required_optional(bool async) + => AssertCantTrackJson(() => base.Select_branch_required_optional(async)); + + public override Task Select_branch_optional_required(bool async) + => AssertCantTrackJson(() => base.Select_branch_optional_required(async)); + + public override Task Select_branch_optional_optional(bool async) + => AssertCantTrackJson(() => base.Select_branch_optional_optional(async)); + + public override Task Select_trunk_and_branch_duplicated(bool async) + => AssertCantTrackJson(() => base.Select_trunk_and_branch_duplicated(async)); + + public override Task Select_trunk_and_trunk_duplicated(bool async) + => AssertCantTrackJson(() => base.Select_trunk_and_trunk_duplicated(async)); + + public override Task Select_leaf_trunk_root(bool async) + => AssertCantTrackJson(() => base.Select_leaf_trunk_root(async)); + + public override Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + => AssertCantTrackJson(() => base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async)); + + public override Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + => AssertCantTrackJson(() => base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async)); + + private async Task AssertCantTrackJson(Func test) + { + var message = (await Assert.ThrowsAsync(test)).Message; + + Assert.Equal(RelationalStrings.JsonEntityOrCollectionProjectedAtRootLevelInTrackingQuery("AsNoTracking"), message); + AssertSql(); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonTypeNoTrackingProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonTypeNoTrackingProjectionSqlServerTest.cs new file mode 100644 index 00000000000..5afb47eff4e --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonTypeNoTrackingProjectionSqlServerTest.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +// Only adding NoTracking version - no point to do both and most of the tests don't work in tracking (projecting without owner) +[SqlServerCondition(SqlServerCondition.SupportsJsonType)] +public class OwnedJsonTypeNoTrackingProjectionSqlServerTest + : ProjectionTestBase +{ + public OwnedJsonTypeNoTrackingProjectionSqlServerTest(OwnedJsonTypeRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonTypeReferenceNoTrackingProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonTypeReferenceNoTrackingProjectionSqlServerTest.cs new file mode 100644 index 00000000000..9ab6744ce58 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedJsonTypeReferenceNoTrackingProjectionSqlServerTest.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +// Only adding NoTracking version - no point to do both and most of the tests don't work in tracking (projecting without owner) +[SqlServerCondition(SqlServerCondition.SupportsJsonType)] +public class OwnedJsonTypeReferenceNoTrackingProjectionSqlServerTest + : ReferenceProjectionTestBase +{ + public OwnedJsonTypeReferenceNoTrackingProjectionSqlServerTest(OwnedJsonTypeRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedNoTrackingProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedNoTrackingProjectionSqlServerTest.cs new file mode 100644 index 00000000000..f1acde489dd --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedNoTrackingProjectionSqlServerTest.cs @@ -0,0 +1,228 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedNoTrackingProjectionSqlServerTest + : OwnedNoTrackingProjectionRelationalTestBase +{ + public OwnedNoTrackingProjectionSqlServerTest(OwnedRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_trunk_collection(bool async) + { + await base.Select_trunk_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [s0].[RelationshipsRootEntityId], [s0].[Id1], [s0].[Name], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsTrunkEntityId1], [s0].[Id10], [s0].[Name0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityId1], [s0].[Id100], [s0].[Name00], [s0].[OptionalReferenceLeaf_Name], [s0].[RequiredReferenceLeaf_Name], [s0].[OptionalReferenceBranch_Name], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s0].[Id11], [s0].[Name1], [s0].[OptionalReferenceBranch_OptionalReferenceLeaf_Name], [s0].[OptionalReferenceBranch_RequiredReferenceLeaf_Name], [s0].[RequiredReferenceBranch_Name], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s0].[Id12], [s0].[Name2], [s0].[RequiredReferenceBranch_OptionalReferenceLeaf_Name], [s0].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] +FROM [RootEntities] AS [r] +LEFT JOIN ( + SELECT [r0].[RelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityId1], [s].[Id1] AS [Id10], [s].[Name] AS [Name0], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s].[RelationshipsBranchEntityId1], [s].[Id10] AS [Id100], [s].[Name0] AS [Name00], [s].[OptionalReferenceLeaf_Name], [s].[RequiredReferenceLeaf_Name], [r0].[OptionalReferenceBranch_Name], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId10], [r3].[Id1] AS [Id11], [r3].[Name] AS [Name1], [r0].[OptionalReferenceBranch_OptionalReferenceLeaf_Name], [r0].[OptionalReferenceBranch_RequiredReferenceLeaf_Name], [r0].[RequiredReferenceBranch_Name], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId11], [r4].[Id1] AS [Id12], [r4].[Name] AS [Name2], [r0].[RequiredReferenceBranch_OptionalReferenceLeaf_Name], [r0].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] + FROM [Root_CollectionTrunk] AS [r0] + LEFT JOIN ( + SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityId1], [r1].[Id1], [r1].[Name], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r2].[RelationshipsBranchEntityId1], [r2].[Id1] AS [Id10], [r2].[Name] AS [Name0], [r1].[OptionalReferenceLeaf_Name], [r1].[RequiredReferenceLeaf_Name] + FROM [Root_CollectionTrunk_CollectionBranch] AS [r1] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_CollectionLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[RelationshipsTrunkEntityId1] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r1].[Id1] = [r2].[RelationshipsBranchEntityId1] + ) AS [s] ON [r0].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [s].[RelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r3] ON CASE + WHEN [r0].[OptionalReferenceBranch_Name] IS NOT NULL THEN [r0].[RelationshipsRootEntityId] + END = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND CASE + WHEN [r0].[OptionalReferenceBranch_Name] IS NOT NULL THEN [r0].[Id1] + END = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r4] ON [r0].[RelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] +) AS [s0] ON [r].[Id] = [s0].[RelationshipsRootEntityId] +ORDER BY [r].[Id], [s0].[RelationshipsRootEntityId], [s0].[Id1], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsTrunkEntityId1], [s0].[Id10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityId1], [s0].[Id100], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s0].[Id11], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId11] +"""); + } + + public override async Task Select_branch_required_collection(bool async) + { + await base.Select_branch_required_collection(async); + + AssertSql( +""" +SELECT [r].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[Id10], [s].[Name0], [s].[OptionalReferenceLeaf_Name], [s].[RequiredReferenceLeaf_Name] +FROM [RootEntities] AS [r] +LEFT JOIN ( + SELECT [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsBranchEntityId1], [r1].[Id1] AS [Id10], [r1].[Name] AS [Name0], [r0].[OptionalReferenceLeaf_Name], [r0].[RequiredReferenceLeaf_Name] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r0] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r1] ON [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [r1].[RelationshipsBranchEntityId1] +) AS [s] ON [r].[Id] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1] +"""); + } + + public override async Task Select_branch_optional_collection(bool async) + { + await base.Select_branch_optional_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[Id10], [s].[Name0], [s].[OptionalReferenceLeaf_Name], [s].[RequiredReferenceLeaf_Name] +FROM [RootEntities] AS [r] +LEFT JOIN ( + SELECT [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsBranchEntityId1], [r1].[Id1] AS [Id10], [r1].[Name] AS [Name0], [r0].[OptionalReferenceLeaf_Name], [r0].[RequiredReferenceLeaf_Name] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r0] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r1] ON [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [r1].[RelationshipsBranchEntityId1] +) AS [s] ON [r].[Id] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1] +"""); + } + + public override async Task Select_multiple_branch_leaf(bool async) + { + await base.Select_multiple_branch_leaf(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_Name], [r0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf_Name], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf_Name], [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Id1], [r1].[Name], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[Id10], [s].[Name0], [s].[OptionalReferenceLeaf_Name], [s].[RequiredReferenceLeaf_Name] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r0] ON [r].[Id] = [r0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r1] ON [r].[Id] = [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[Id1], [r2].[Name], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityId1], [r3].[Id1] AS [Id10], [r3].[Name] AS [Name0], [r2].[OptionalReferenceLeaf_Name], [r2].[RequiredReferenceLeaf_Name] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r2] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r3] ON [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r2].[Id1] = [r3].[RelationshipsBranchEntityId1] +) AS [s] ON [r].[Id] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Id1], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1] +"""); + } + + public override async Task Select_subquery_root_set_trunk_FirstOrDefault_collection(bool async) + { + await base.Select_subquery_root_set_trunk_FirstOrDefault_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [r3].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[Id10], [s].[Name0], [s].[OptionalReferenceLeaf_Name], [s].[RequiredReferenceLeaf_Name], [r3].[c] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) 1 AS [c], [r0].[Id] + FROM [RootEntities] AS [r0] + ORDER BY [r0].[Id] +) AS [r3] +LEFT JOIN ( + SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Id1], [r1].[Name], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityId1], [r2].[Id1] AS [Id10], [r2].[Name] AS [Name0], [r1].[OptionalReferenceLeaf_Name], [r1].[RequiredReferenceLeaf_Name] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r2].[RelationshipsBranchEntityId1] +) AS [s] ON [r3].[Id] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r3].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1] +"""); + } + + public override async Task Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) + { + await base.Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); + + AssertSql( + """ +SELECT [r].[Id], [r8].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[Id10], [s].[Name0], [s].[OptionalReferenceLeaf_Name], [s].[RequiredReferenceLeaf_Name], [r8].[RequiredReferenceTrunk_Name], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[Id1], [s0].[Name], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityId1], [s0].[Id10], [s0].[Name0], [s0].[OptionalReferenceLeaf_Name], [s0].[RequiredReferenceLeaf_Name], [r8].[RequiredReferenceTrunk_OptionalReferenceBranch_Name], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[Id1], [r5].[Name], [r8].[RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf_Name], [r8].[RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf_Name], [r8].[RequiredReferenceTrunk_RequiredReferenceBranch_Name], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[Id1], [r6].[Name], [r8].[RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf_Name], [r8].[RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf_Name], [r7].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[Id1], [r7].[Name], [r8].[RequiredReferenceTrunk_RequiredReferenceBranch_Name0], [r8].[c] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) [r0].[Id], [r0].[RequiredReferenceTrunk_Name], [r0].[RequiredReferenceTrunk_OptionalReferenceBranch_Name], [r0].[RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf_Name], [r0].[RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf_Name], [r0].[RequiredReferenceTrunk_RequiredReferenceBranch_Name], [r0].[RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf_Name], [r0].[RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf_Name], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_Name] AS [RequiredReferenceTrunk_RequiredReferenceBranch_Name0], 1 AS [c] + FROM [RootEntities] AS [r0] + ORDER BY [r0].[Id] +) AS [r8] +LEFT JOIN ( + SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Id1], [r1].[Name], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityId1], [r2].[Id1] AS [Id10], [r2].[Name] AS [Name0], [r1].[OptionalReferenceLeaf_Name], [r1].[RequiredReferenceLeaf_Name] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r2].[RelationshipsBranchEntityId1] +) AS [s] ON [r].[Id] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r3].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[Id1], [r3].[Name], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityId1], [r4].[Id1] AS [Id10], [r4].[Name] AS [Name0], [r3].[OptionalReferenceLeaf_Name], [r3].[RequiredReferenceLeaf_Name] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r3] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r4] ON [r3].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r3].[Id1] = [r4].[RelationshipsBranchEntityId1] +) AS [s0] ON [r8].[Id] = [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r5] ON CASE + WHEN [r8].[RequiredReferenceTrunk_OptionalReferenceBranch_Name] IS NOT NULL THEN [r8].[Id] +END = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r6] ON [r8].[Id] = [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r7] ON [r8].[Id] = [r7].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r8].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[Id10], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[Id1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityId1], [s0].[Id10], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[Id1], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[Id1], [r7].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +"""); + } + + public override async Task Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) + { + await base.Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); + + AssertSql( + """ +SELECT [r].[Id], [r3].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[Id10], [s].[Name0], [s].[OptionalReferenceLeaf_Name], [s].[RequiredReferenceLeaf_Name], [r3].[c] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) 1 AS [c], [r0].[Id] + FROM [RootEntities] AS [r0] + ORDER BY [r0].[Id] +) AS [r3] +LEFT JOIN ( + SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Id1], [r1].[Name], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityId1], [r2].[Id1] AS [Id10], [r2].[Name] AS [Name0], [r1].[OptionalReferenceLeaf_Name], [r1].[RequiredReferenceLeaf_Name] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r2].[RelationshipsBranchEntityId1] +) AS [s] ON [r].[Id] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r3].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1] +"""); + } + + public override async Task SelectMany_trunk_collection(bool async) + { + await base.SelectMany_trunk_collection(async); + + AssertSql( + """ +SELECT [r0].[RelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [r].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityId1], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s].[RelationshipsBranchEntityId1], [s].[Id10], [s].[Name0], [s].[OptionalReferenceLeaf_Name], [s].[RequiredReferenceLeaf_Name], [r0].[OptionalReferenceBranch_Name], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r3].[Id1], [r3].[Name], [r0].[OptionalReferenceBranch_OptionalReferenceLeaf_Name], [r0].[OptionalReferenceBranch_RequiredReferenceLeaf_Name], [r0].[RequiredReferenceBranch_Name], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r4].[Id1], [r4].[Name], [r0].[RequiredReferenceBranch_OptionalReferenceLeaf_Name], [r0].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] +FROM [RootEntities] AS [r] +INNER JOIN [Root_CollectionTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityId1], [r1].[Id1], [r1].[Name], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r2].[RelationshipsBranchEntityId1], [r2].[Id1] AS [Id10], [r2].[Name] AS [Name0], [r1].[OptionalReferenceLeaf_Name], [r1].[RequiredReferenceLeaf_Name] + FROM [Root_CollectionTrunk_CollectionBranch] AS [r1] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_CollectionLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[RelationshipsTrunkEntityId1] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r1].[Id1] = [r2].[RelationshipsBranchEntityId1] +) AS [s] ON [r0].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [s].[RelationshipsTrunkEntityId1] +LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r3] ON CASE + WHEN [r0].[OptionalReferenceBranch_Name] IS NOT NULL THEN [r0].[RelationshipsRootEntityId] +END = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND CASE + WHEN [r0].[OptionalReferenceBranch_Name] IS NOT NULL THEN [r0].[Id1] +END = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] +LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r4] ON [r0].[RelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r0].[Id1], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityId1], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s].[RelationshipsBranchEntityId1], [s].[Id10], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r3].[Id1], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] +"""); + } + + public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) + { + await base.SelectMany_required_trunk_reference_branch_collection(async); + + AssertSql( + """ +SELECT [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [r].[Id], [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsBranchEntityId1], [r1].[Id1], [r1].[Name], [r0].[OptionalReferenceLeaf_Name], [r0].[RequiredReferenceLeaf_Name] +FROM [RootEntities] AS [r] +INNER JOIN [Root_RequiredReferenceTrunk_CollectionBranch] AS [r0] ON [r].[Id] = [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r1] ON [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [r1].[RelationshipsBranchEntityId1] +ORDER BY [r].[Id], [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsBranchEntityId1] +"""); + } + + public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) + { + await base.SelectMany_optional_trunk_reference_branch_collection(async); + + AssertSql( + """ +SELECT [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [r].[Id], [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsBranchEntityId1], [r1].[Id1], [r1].[Name], [r0].[OptionalReferenceLeaf_Name], [r0].[RequiredReferenceLeaf_Name] +FROM [RootEntities] AS [r] +INNER JOIN [Root_OptionalReferenceTrunk_CollectionBranch] AS [r0] ON CASE + WHEN [r].[OptionalReferenceTrunk_Name] IS NOT NULL THEN [r].[Id] +END = [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r1] ON [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [r1].[RelationshipsBranchEntityId1] +ORDER BY [r].[Id], [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsBranchEntityId1] +"""); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedProjectionSqlServerTest.cs new file mode 100644 index 00000000000..0fff389e2e2 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedProjectionSqlServerTest.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedProjectionSqlServerTest + : OwnedProjectionRelationalTestBase +{ + public OwnedProjectionSqlServerTest(OwnedRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override Task Select_trunk_collection(bool async) + => AssertCantTrackOwned(() => base.Select_trunk_collection(async)); + + public override Task Select_branch_required_collection(bool async) + => AssertCantTrackOwned(() => base.Select_branch_required_collection(async)); + + public override Task Select_branch_optional_collection(bool async) + => AssertCantTrackOwned(() => base.Select_branch_optional_collection(async)); + + public override Task Select_multiple_branch_leaf(bool async) + => AssertCantTrackOwned(() => base.Select_multiple_branch_leaf(async)); + + public override Task Select_subquery_root_set_trunk_FirstOrDefault_collection(bool async) + => AssertCantTrackOwned(() => base.Select_subquery_root_set_trunk_FirstOrDefault_collection(async)); + + public override Task Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) + => AssertCantTrackOwned(() => base.Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async)); + + public override Task Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) + => AssertCantTrackOwned(() => base.Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async)); + + public override Task SelectMany_trunk_collection(bool async) + => AssertCantTrackOwned(() => base.SelectMany_trunk_collection(async)); + + public override Task SelectMany_required_trunk_reference_branch_collection(bool async) + => AssertCantTrackOwned(() => base.SelectMany_required_trunk_reference_branch_collection(async)); + + public override Task SelectMany_optional_trunk_reference_branch_collection(bool async) + => AssertCantTrackOwned(() => base.SelectMany_optional_trunk_reference_branch_collection(async)); + + private async Task AssertCantTrackOwned(Func test) + { + var message = (await Assert.ThrowsAsync(test)).Message; + + Assert.Equal(CoreStrings.OwnedEntitiesCannotBeTrackedWithoutTheirOwner, message); + AssertSql(); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedReferenceNoTrackingProjectionSqlServerTest.cs similarity index 70% rename from test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionNoTrackingQuerySqlServerTest.cs rename to test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedReferenceNoTrackingProjectionSqlServerTest.cs index b750d0debae..e7c149eb3d9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionNoTrackingQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedReferenceNoTrackingProjectionSqlServerTest.cs @@ -1,24 +1,24 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; -public class OwnedRelationshipsInProjectionNoTrackingQuerySqlServerTest - : OwnedRelationshipsInProjectionNoTrackingQueryRelationalTestBase +public class OwnedReferenceNoTrackingProjectionSqlServerTest + : OwnedReferenceNoTrackingProjectionRelationalTestBase { - public OwnedRelationshipsInProjectionNoTrackingQuerySqlServerTest(OwnedRelationshipsQuerySqlServerFixture fixture, ITestOutputHelper testOutputHelper) + public OwnedReferenceNoTrackingProjectionSqlServerTest(OwnedRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) { Fixture.TestSqlLoggerFactory.Clear(); Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - public override async Task Project_root(bool async) + public override async Task Select_root(bool async) { - await base.Project_root(async); + await base.Select_root(async); AssertSql( -""" + """ SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [s0].[RelationshipsRootEntityId], [s0].[Id1], [s0].[Name], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsTrunkEntityId1], [s0].[Id10], [s0].[Name0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityId1], [s0].[Id100], [s0].[Name00], [s0].[OptionalReferenceLeaf_Name], [s0].[RequiredReferenceLeaf_Name], [s0].[OptionalReferenceBranch_Name], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s0].[Id11], [s0].[Name1], [s0].[OptionalReferenceBranch_OptionalReferenceLeaf_Name], [s0].[OptionalReferenceBranch_RequiredReferenceLeaf_Name], [s0].[RequiredReferenceBranch_Name], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s0].[Id12], [s0].[Name2], [s0].[RequiredReferenceBranch_OptionalReferenceLeaf_Name], [s0].[RequiredReferenceBranch_RequiredReferenceLeaf_Name], [r].[OptionalReferenceTrunk_Name], [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[Id1], [s1].[Name], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[RelationshipsBranchEntityId1], [s1].[Id10], [s1].[Name0], [s1].[OptionalReferenceLeaf_Name], [s1].[RequiredReferenceLeaf_Name], [r].[OptionalReferenceTrunk_OptionalReferenceBranch_Name], [r7].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[Id1], [r7].[Name], [r].[OptionalReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf_Name], [r].[OptionalReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf_Name], [r].[OptionalReferenceTrunk_RequiredReferenceBranch_Name], [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r8].[Id1], [r8].[Name], [r].[OptionalReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf_Name], [r].[OptionalReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf_Name], [r].[RequiredReferenceTrunk_Name], [s2].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[Id1], [s2].[Name], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[RelationshipsBranchEntityId1], [s2].[Id10], [s2].[Name0], [s2].[OptionalReferenceLeaf_Name], [s2].[RequiredReferenceLeaf_Name], [r].[RequiredReferenceTrunk_OptionalReferenceBranch_Name], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[Id1], [r11].[Name], [r].[RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf_Name], [r].[RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf_Name], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_Name], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[Id1], [r12].[Name], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf_Name], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf_Name] FROM [RootEntities] AS [r] LEFT JOIN ( @@ -62,9 +62,9 @@ WHEN [r].[RequiredReferenceTrunk_OptionalReferenceBranch_Name] IS NOT NULL THEN """); } - public override async Task Project_trunk_optional(bool async) + public override async Task Select_trunk_optional(bool async) { - await base.Project_trunk_optional(async); + await base.Select_trunk_optional(async); AssertSql( """ @@ -87,9 +87,9 @@ WHEN [r].[OptionalReferenceTrunk_RequiredReferenceBranch_Name] IS NOT NULL THEN """); } - public override async Task Project_trunk_required(bool async) + public override async Task Select_trunk_required(bool async) { - await base.Project_trunk_required(async); + await base.Select_trunk_required(async); AssertSql( """ @@ -108,36 +108,9 @@ WHEN [r].[RequiredReferenceTrunk_OptionalReferenceBranch_Name] IS NOT NULL THEN """); } - public override async Task Project_trunk_collection(bool async) - { - await base.Project_trunk_collection(async); - - AssertSql( - """ -SELECT [r].[Id], [s0].[RelationshipsRootEntityId], [s0].[Id1], [s0].[Name], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsTrunkEntityId1], [s0].[Id10], [s0].[Name0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityId1], [s0].[Id100], [s0].[Name00], [s0].[OptionalReferenceLeaf_Name], [s0].[RequiredReferenceLeaf_Name], [s0].[OptionalReferenceBranch_Name], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s0].[Id11], [s0].[Name1], [s0].[OptionalReferenceBranch_OptionalReferenceLeaf_Name], [s0].[OptionalReferenceBranch_RequiredReferenceLeaf_Name], [s0].[RequiredReferenceBranch_Name], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s0].[Id12], [s0].[Name2], [s0].[RequiredReferenceBranch_OptionalReferenceLeaf_Name], [s0].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -LEFT JOIN ( - SELECT [r0].[RelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityId1], [s].[Id1] AS [Id10], [s].[Name] AS [Name0], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s].[RelationshipsBranchEntityId1], [s].[Id10] AS [Id100], [s].[Name0] AS [Name00], [s].[OptionalReferenceLeaf_Name], [s].[RequiredReferenceLeaf_Name], [r0].[OptionalReferenceBranch_Name], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId10], [r3].[Id1] AS [Id11], [r3].[Name] AS [Name1], [r0].[OptionalReferenceBranch_OptionalReferenceLeaf_Name], [r0].[OptionalReferenceBranch_RequiredReferenceLeaf_Name], [r0].[RequiredReferenceBranch_Name], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId11], [r4].[Id1] AS [Id12], [r4].[Name] AS [Name2], [r0].[RequiredReferenceBranch_OptionalReferenceLeaf_Name], [r0].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] - FROM [Root_CollectionTrunk] AS [r0] - LEFT JOIN ( - SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityId1], [r1].[Id1], [r1].[Name], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r2].[RelationshipsBranchEntityId1], [r2].[Id1] AS [Id10], [r2].[Name] AS [Name0], [r1].[OptionalReferenceLeaf_Name], [r1].[RequiredReferenceLeaf_Name] - FROM [Root_CollectionTrunk_CollectionBranch] AS [r1] - LEFT JOIN [Root_CollectionTrunk_CollectionBranch_CollectionLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[RelationshipsTrunkEntityId1] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r1].[Id1] = [r2].[RelationshipsBranchEntityId1] - ) AS [s] ON [r0].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [s].[RelationshipsTrunkEntityId1] - LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r3] ON CASE - WHEN [r0].[OptionalReferenceBranch_Name] IS NOT NULL THEN [r0].[RelationshipsRootEntityId] - END = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND CASE - WHEN [r0].[OptionalReferenceBranch_Name] IS NOT NULL THEN [r0].[Id1] - END = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] - LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r4] ON [r0].[RelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] -) AS [s0] ON [r].[Id] = [s0].[RelationshipsRootEntityId] -ORDER BY [r].[Id], [s0].[RelationshipsRootEntityId], [s0].[Id1], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsTrunkEntityId1], [s0].[Id10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityId1], [s0].[Id100], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s0].[Id11], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId11] -"""); - } - - public override async Task Project_branch_required_required(bool async) + public override async Task Select_branch_required_required(bool async) { - await base.Project_branch_required_required(async); + await base.Select_branch_required_required(async); AssertSql( """ @@ -148,9 +121,9 @@ FROM [RootEntities] AS [r] """); } - public override async Task Project_branch_required_optional(bool async) + public override async Task Select_branch_required_optional(bool async) { - await base.Project_branch_required_optional(async); + await base.Select_branch_required_optional(async); AssertSql( """ @@ -163,26 +136,9 @@ WHEN [r].[RequiredReferenceTrunk_OptionalReferenceBranch_Name] IS NOT NULL THEN """); } - public override async Task Project_branch_required_collection(bool async) + public override async Task Select_branch_optional_required(bool async) { - await base.Project_branch_required_collection(async); - - AssertSql( -""" -SELECT [r].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[Id10], [s].[Name0], [s].[OptionalReferenceLeaf_Name], [s].[RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -LEFT JOIN ( - SELECT [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsBranchEntityId1], [r1].[Id1] AS [Id10], [r1].[Name] AS [Name0], [r0].[OptionalReferenceLeaf_Name], [r0].[RequiredReferenceLeaf_Name] - FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r0] - LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r1] ON [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [r1].[RelationshipsBranchEntityId1] -) AS [s] ON [r].[Id] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] -ORDER BY [r].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1] -"""); - } - - public override async Task Project_branch_optional_required(bool async) - { - await base.Project_branch_optional_required(async); + await base.Select_branch_optional_required(async); AssertSql( """ @@ -193,12 +149,12 @@ FROM [RootEntities] AS [r] """); } - public override async Task Project_branch_optional_optional(bool async) + public override async Task Select_branch_optional_optional(bool async) { - await base.Project_branch_optional_optional(async); + await base.Select_branch_optional_optional(async); AssertSql( -""" + """ SELECT [r].[Id], [r].[RequiredReferenceTrunk_OptionalReferenceBranch_Name], [r0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [r].[RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf_Name], [r].[RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf_Name] FROM [RootEntities] AS [r] LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r0] ON CASE @@ -208,26 +164,9 @@ WHEN [r].[RequiredReferenceTrunk_OptionalReferenceBranch_Name] IS NOT NULL THEN """); } - public override async Task Project_branch_optional_collection(bool async) + public override async Task Select_root_duplicated(bool async) { - await base.Project_branch_optional_collection(async); - - AssertSql( - """ -SELECT [r].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[Id10], [s].[Name0], [s].[OptionalReferenceLeaf_Name], [s].[RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -LEFT JOIN ( - SELECT [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsBranchEntityId1], [r1].[Id1] AS [Id10], [r1].[Name] AS [Name0], [r0].[OptionalReferenceLeaf_Name], [r0].[RequiredReferenceLeaf_Name] - FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r0] - LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r1] ON [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [r1].[RelationshipsBranchEntityId1] -) AS [s] ON [r].[Id] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] -ORDER BY [r].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1] -"""); - } - - public override async Task Project_root_duplicated(bool async) - { - await base.Project_root_duplicated(async); + await base.Select_root_duplicated(async); AssertSql( """ @@ -311,9 +250,9 @@ WHEN [r].[RequiredReferenceTrunk_OptionalReferenceBranch_Name] IS NOT NULL THEN """); } - public override async Task Project_trunk_and_branch_duplicated(bool async) + public override async Task Select_trunk_and_branch_duplicated(bool async) { - await base.Project_trunk_and_branch_duplicated(async); + await base.Select_trunk_and_branch_duplicated(async); AssertSql( """ @@ -355,9 +294,9 @@ WHEN [r].[OptionalReferenceTrunk_RequiredReferenceBranch_Name] IS NOT NULL THEN """); } - public override async Task Project_trunk_and_trunk_duplicated(bool async) + public override async Task Select_trunk_and_trunk_duplicated(bool async) { - await base.Project_trunk_and_trunk_duplicated(async); + await base.Select_trunk_and_trunk_duplicated(async); AssertSql( """ @@ -385,28 +324,9 @@ WHEN [r].[RequiredReferenceTrunk_OptionalReferenceBranch_Name] IS NOT NULL THEN """); } - public override async Task Project_multiple_branch_leaf(bool async) + public override async Task Select_leaf_trunk_root(bool async) { - await base.Project_multiple_branch_leaf(async); - - AssertSql( - """ -SELECT [r].[Id], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_Name], [r0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf_Name], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf_Name], [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Id1], [r1].[Name], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[Id10], [s].[Name0], [s].[OptionalReferenceLeaf_Name], [s].[RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r0] ON [r].[Id] = [r0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] -LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r1] ON [r].[Id] = [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] -LEFT JOIN ( - SELECT [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[Id1], [r2].[Name], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityId1], [r3].[Id1] AS [Id10], [r3].[Name] AS [Name0], [r2].[OptionalReferenceLeaf_Name], [r2].[RequiredReferenceLeaf_Name] - FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r2] - LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r3] ON [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r2].[Id1] = [r3].[RelationshipsBranchEntityId1] -) AS [s] ON [r].[Id] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] -ORDER BY [r].[Id], [r0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Id1], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1] -"""); - } - - public override async Task Project_leaf_trunk_root(bool async) - { - await base.Project_leaf_trunk_root(async); + await base.Select_leaf_trunk_root(async); AssertSql( """ @@ -462,9 +382,9 @@ WHEN [r].[RequiredReferenceTrunk_OptionalReferenceBranch_Name] IS NOT NULL THEN """); } - public override async Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + public override async Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) { - await base.Project_subquery_root_set_required_trunk_FirstOrDefault_branch(async); + await base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async); AssertSql( """ @@ -480,9 +400,9 @@ ORDER BY [r0].[Id] """); } - public override async Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + public override async Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) { - await base.Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); + await base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); AssertSql( """ @@ -500,136 +420,6 @@ WHEN [r2].[OptionalReferenceTrunk_OptionalReferenceBranch_Name] IS NOT NULL THEN """); } - public override async Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - { - await base.Project_subquery_root_set_trunk_FirstOrDefault_collection(async); - - AssertSql( - """ -SELECT [r].[Id], [r3].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[Id10], [s].[Name0], [s].[OptionalReferenceLeaf_Name], [s].[RequiredReferenceLeaf_Name], [r3].[c] -FROM [RootEntities] AS [r] -OUTER APPLY ( - SELECT TOP(1) 1 AS [c], [r0].[Id] - FROM [RootEntities] AS [r0] - ORDER BY [r0].[Id] -) AS [r3] -LEFT JOIN ( - SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Id1], [r1].[Name], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityId1], [r2].[Id1] AS [Id10], [r2].[Name] AS [Name0], [r1].[OptionalReferenceLeaf_Name], [r1].[RequiredReferenceLeaf_Name] - FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r1] - LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r2].[RelationshipsBranchEntityId1] -) AS [s] ON [r3].[Id] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] -ORDER BY [r].[Id], [r3].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1] -"""); - } - - public override async Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - { - await base.Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); - - AssertSql( - """ -SELECT [r].[Id], [r8].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[Id10], [s].[Name0], [s].[OptionalReferenceLeaf_Name], [s].[RequiredReferenceLeaf_Name], [r8].[RequiredReferenceTrunk_Name], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[Id1], [s0].[Name], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityId1], [s0].[Id10], [s0].[Name0], [s0].[OptionalReferenceLeaf_Name], [s0].[RequiredReferenceLeaf_Name], [r8].[RequiredReferenceTrunk_OptionalReferenceBranch_Name], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[Id1], [r5].[Name], [r8].[RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf_Name], [r8].[RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf_Name], [r8].[RequiredReferenceTrunk_RequiredReferenceBranch_Name], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[Id1], [r6].[Name], [r8].[RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf_Name], [r8].[RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf_Name], [r7].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[Id1], [r7].[Name], [r8].[RequiredReferenceTrunk_RequiredReferenceBranch_Name0], [r8].[c] -FROM [RootEntities] AS [r] -OUTER APPLY ( - SELECT TOP(1) [r0].[Id], [r0].[RequiredReferenceTrunk_Name], [r0].[RequiredReferenceTrunk_OptionalReferenceBranch_Name], [r0].[RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf_Name], [r0].[RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf_Name], [r0].[RequiredReferenceTrunk_RequiredReferenceBranch_Name], [r0].[RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf_Name], [r0].[RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf_Name], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_Name] AS [RequiredReferenceTrunk_RequiredReferenceBranch_Name0], 1 AS [c] - FROM [RootEntities] AS [r0] - ORDER BY [r0].[Id] -) AS [r8] -LEFT JOIN ( - SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Id1], [r1].[Name], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityId1], [r2].[Id1] AS [Id10], [r2].[Name] AS [Name0], [r1].[OptionalReferenceLeaf_Name], [r1].[RequiredReferenceLeaf_Name] - FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r1] - LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r2].[RelationshipsBranchEntityId1] -) AS [s] ON [r].[Id] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] -LEFT JOIN ( - SELECT [r3].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[Id1], [r3].[Name], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityId1], [r4].[Id1] AS [Id10], [r4].[Name] AS [Name0], [r3].[OptionalReferenceLeaf_Name], [r3].[RequiredReferenceLeaf_Name] - FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r3] - LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r4] ON [r3].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r3].[Id1] = [r4].[RelationshipsBranchEntityId1] -) AS [s0] ON [r8].[Id] = [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId] -LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r5] ON CASE - WHEN [r8].[RequiredReferenceTrunk_OptionalReferenceBranch_Name] IS NOT NULL THEN [r8].[Id] -END = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] -LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r6] ON [r8].[Id] = [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] -LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r7] ON [r8].[Id] = [r7].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] -ORDER BY [r].[Id], [r8].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[Id10], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[Id1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityId1], [s0].[Id10], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[Id1], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[Id1], [r7].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] -"""); - } - - public override async Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - { - await base.Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); - - AssertSql( - """ -SELECT [r].[Id], [r3].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[Id10], [s].[Name0], [s].[OptionalReferenceLeaf_Name], [s].[RequiredReferenceLeaf_Name], [r3].[c] -FROM [RootEntities] AS [r] -OUTER APPLY ( - SELECT TOP(1) 1 AS [c], [r0].[Id] - FROM [RootEntities] AS [r0] - ORDER BY [r0].[Id] -) AS [r3] -LEFT JOIN ( - SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Id1], [r1].[Name], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityId1], [r2].[Id1] AS [Id10], [r2].[Name] AS [Name0], [r1].[OptionalReferenceLeaf_Name], [r1].[RequiredReferenceLeaf_Name] - FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r1] - LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r2].[RelationshipsBranchEntityId1] -) AS [s] ON [r].[Id] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] -ORDER BY [r].[Id], [r3].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1] -"""); - } - - public override async Task SelectMany_trunk_collection(bool async) - { - await base.SelectMany_trunk_collection(async); - - AssertSql( - """ -SELECT [r0].[RelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [r].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityId1], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s].[RelationshipsBranchEntityId1], [s].[Id10], [s].[Name0], [s].[OptionalReferenceLeaf_Name], [s].[RequiredReferenceLeaf_Name], [r0].[OptionalReferenceBranch_Name], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r3].[Id1], [r3].[Name], [r0].[OptionalReferenceBranch_OptionalReferenceLeaf_Name], [r0].[OptionalReferenceBranch_RequiredReferenceLeaf_Name], [r0].[RequiredReferenceBranch_Name], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r4].[Id1], [r4].[Name], [r0].[RequiredReferenceBranch_OptionalReferenceLeaf_Name], [r0].[RequiredReferenceBranch_RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -INNER JOIN [Root_CollectionTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] -LEFT JOIN ( - SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityId1], [r1].[Id1], [r1].[Name], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r2].[RelationshipsBranchEntityId1], [r2].[Id1] AS [Id10], [r2].[Name] AS [Name0], [r1].[OptionalReferenceLeaf_Name], [r1].[RequiredReferenceLeaf_Name] - FROM [Root_CollectionTrunk_CollectionBranch] AS [r1] - LEFT JOIN [Root_CollectionTrunk_CollectionBranch_CollectionLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[RelationshipsTrunkEntityId1] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r1].[Id1] = [r2].[RelationshipsBranchEntityId1] -) AS [s] ON [r0].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [s].[RelationshipsTrunkEntityId1] -LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r3] ON CASE - WHEN [r0].[OptionalReferenceBranch_Name] IS NOT NULL THEN [r0].[RelationshipsRootEntityId] -END = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND CASE - WHEN [r0].[OptionalReferenceBranch_Name] IS NOT NULL THEN [r0].[Id1] -END = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] -LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r4] ON [r0].[RelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] -ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r0].[Id1], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityId1], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s].[RelationshipsBranchEntityId1], [s].[Id10], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r3].[Id1], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] -"""); - } - - public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) - { - await base.SelectMany_required_trunk_reference_branch_collection(async); - - AssertSql( - """ -SELECT [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [r].[Id], [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsBranchEntityId1], [r1].[Id1], [r1].[Name], [r0].[OptionalReferenceLeaf_Name], [r0].[RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -INNER JOIN [Root_RequiredReferenceTrunk_CollectionBranch] AS [r0] ON [r].[Id] = [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId] -LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r1] ON [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [r1].[RelationshipsBranchEntityId1] -ORDER BY [r].[Id], [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsBranchEntityId1] -"""); - } - - public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) - { - await base.SelectMany_optional_trunk_reference_branch_collection(async); - - AssertSql( - """ -SELECT [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [r].[Id], [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsBranchEntityId1], [r1].[Id1], [r1].[Name], [r0].[OptionalReferenceLeaf_Name], [r0].[RequiredReferenceLeaf_Name] -FROM [RootEntities] AS [r] -INNER JOIN [Root_OptionalReferenceTrunk_CollectionBranch] AS [r0] ON CASE - WHEN [r].[OptionalReferenceTrunk_Name] IS NOT NULL THEN [r].[Id] -END = [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId] -LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r1] ON [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [r1].[RelationshipsBranchEntityId1] -ORDER BY [r].[Id], [r0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r0].[Id1], [r1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsBranchEntityId1] -"""); - } - private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedReferenceProjectionSqlServerTest.cs similarity index 88% rename from test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionQuerySqlServerTest.cs rename to test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedReferenceProjectionSqlServerTest.cs index f6fcfc67c6f..18be844f582 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedReferenceProjectionSqlServerTest.cs @@ -1,24 +1,24 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; -public class OwnedRelationshipsInProjectionQuerySqlServerTest - : OwnedRelationshipsInProjectionQueryRelationalTestBase +public class OwnedReferenceProjectionSqlServerTest + : OwnedReferenceProjectionRelationalTestBase { - public OwnedRelationshipsInProjectionQuerySqlServerTest(OwnedRelationshipsQuerySqlServerFixture fixture, ITestOutputHelper testOutputHelper) + public OwnedReferenceProjectionSqlServerTest(OwnedRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) { Fixture.TestSqlLoggerFactory.Clear(); Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - public override async Task Project_root(bool async) + public override async Task Select_root(bool async) { - await base.Project_root(async); + await base.Select_root(async); AssertSql( -""" + """ SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [s0].[RelationshipsRootEntityId], [s0].[Id1], [s0].[Name], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsTrunkEntityId1], [s0].[Id10], [s0].[Name0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityId1], [s0].[Id100], [s0].[Name00], [s0].[OptionalReferenceLeaf_Name], [s0].[RequiredReferenceLeaf_Name], [s0].[OptionalReferenceBranch_Name], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s0].[Id11], [s0].[Name1], [s0].[OptionalReferenceBranch_OptionalReferenceLeaf_Name], [s0].[OptionalReferenceBranch_RequiredReferenceLeaf_Name], [s0].[RequiredReferenceBranch_Name], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s0].[Id12], [s0].[Name2], [s0].[RequiredReferenceBranch_OptionalReferenceLeaf_Name], [s0].[RequiredReferenceBranch_RequiredReferenceLeaf_Name], [r].[OptionalReferenceTrunk_Name], [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[Id1], [s1].[Name], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[RelationshipsBranchEntityId1], [s1].[Id10], [s1].[Name0], [s1].[OptionalReferenceLeaf_Name], [s1].[RequiredReferenceLeaf_Name], [r].[OptionalReferenceTrunk_OptionalReferenceBranch_Name], [r7].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[Id1], [r7].[Name], [r].[OptionalReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf_Name], [r].[OptionalReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf_Name], [r].[OptionalReferenceTrunk_RequiredReferenceBranch_Name], [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r8].[Id1], [r8].[Name], [r].[OptionalReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf_Name], [r].[OptionalReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf_Name], [r].[RequiredReferenceTrunk_Name], [s2].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[Id1], [s2].[Name], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[RelationshipsBranchEntityId1], [s2].[Id10], [s2].[Name0], [s2].[OptionalReferenceLeaf_Name], [s2].[RequiredReferenceLeaf_Name], [r].[RequiredReferenceTrunk_OptionalReferenceBranch_Name], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[Id1], [r11].[Name], [r].[RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf_Name], [r].[RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf_Name], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_Name], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[Id1], [r12].[Name], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf_Name], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf_Name] FROM [RootEntities] AS [r] LEFT JOIN ( @@ -62,12 +62,12 @@ WHEN [r].[RequiredReferenceTrunk_OptionalReferenceBranch_Name] IS NOT NULL THEN """); } - public override async Task Project_root_duplicated(bool async) + public override async Task Select_root_duplicated(bool async) { - await base.Project_root_duplicated(async); + await base.Select_root_duplicated(async); AssertSql( -""" + """ SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [s0].[RelationshipsRootEntityId], [s0].[Id1], [s0].[Name], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsTrunkEntityId1], [s0].[Id10], [s0].[Name0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityId1], [s0].[Id100], [s0].[Name00], [s0].[OptionalReferenceLeaf_Name], [s0].[RequiredReferenceLeaf_Name], [s0].[OptionalReferenceBranch_Name], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s0].[Id11], [s0].[Name1], [s0].[OptionalReferenceBranch_OptionalReferenceLeaf_Name], [s0].[OptionalReferenceBranch_RequiredReferenceLeaf_Name], [s0].[RequiredReferenceBranch_Name], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s0].[Id12], [s0].[Name2], [s0].[RequiredReferenceBranch_OptionalReferenceLeaf_Name], [s0].[RequiredReferenceBranch_RequiredReferenceLeaf_Name], [r].[OptionalReferenceTrunk_Name], [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[Id1], [s1].[Name], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[RelationshipsBranchEntityId1], [s1].[Id10], [s1].[Name0], [s1].[OptionalReferenceLeaf_Name], [s1].[RequiredReferenceLeaf_Name], [r].[OptionalReferenceTrunk_OptionalReferenceBranch_Name], [r7].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[Id1], [r7].[Name], [r].[OptionalReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf_Name], [r].[OptionalReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf_Name], [r].[OptionalReferenceTrunk_RequiredReferenceBranch_Name], [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r8].[Id1], [r8].[Name], [r].[OptionalReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf_Name], [r].[OptionalReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf_Name], [r].[RequiredReferenceTrunk_Name], [s2].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[Id1], [s2].[Name], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[RelationshipsBranchEntityId1], [s2].[Id10], [s2].[Name0], [s2].[OptionalReferenceLeaf_Name], [s2].[RequiredReferenceLeaf_Name], [r].[RequiredReferenceTrunk_OptionalReferenceBranch_Name], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[Id1], [r11].[Name], [r].[RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf_Name], [r].[RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf_Name], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_Name], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[Id1], [r12].[Name], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf_Name], [r].[RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf_Name], [s4].[RelationshipsRootEntityId], [s4].[Id1], [s4].[Name], [s4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s4].[RelationshipsTrunkEntityId1], [s4].[Id10], [s4].[Name0], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s4].[RelationshipsBranchEntityId1], [s4].[Id100], [s4].[Name00], [s4].[OptionalReferenceLeaf_Name], [s4].[RequiredReferenceLeaf_Name], [s4].[OptionalReferenceBranch_Name], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s4].[Id11], [s4].[Name1], [s4].[OptionalReferenceBranch_OptionalReferenceLeaf_Name], [s4].[OptionalReferenceBranch_RequiredReferenceLeaf_Name], [s4].[RequiredReferenceBranch_Name], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s4].[Id12], [s4].[Name2], [s4].[RequiredReferenceBranch_OptionalReferenceLeaf_Name], [s4].[RequiredReferenceBranch_RequiredReferenceLeaf_Name], [s5].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s5].[Id1], [s5].[Name], [s5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s5].[RelationshipsBranchEntityId1], [s5].[Id10], [s5].[Name0], [s5].[OptionalReferenceLeaf_Name], [s5].[RequiredReferenceLeaf_Name], [r20].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r20].[Id1], [r20].[Name], [r21].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r21].[Id1], [r21].[Name], [s6].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s6].[Id1], [s6].[Name], [s6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s6].[RelationshipsBranchEntityId1], [s6].[Id10], [s6].[Name0], [s6].[OptionalReferenceLeaf_Name], [s6].[RequiredReferenceLeaf_Name], [r24].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r24].[Id1], [r24].[Name], [r25].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r25].[Id1], [r25].[Name] FROM [RootEntities] AS [r] LEFT JOIN ( @@ -148,99 +148,38 @@ WHEN [r].[RequiredReferenceTrunk_OptionalReferenceBranch_Name] IS NOT NULL THEN """); } - public override Task Project_trunk_optional(bool async) - => AssertCantTrackOwned(() => base.Project_trunk_optional(async)); - - public override Task Project_trunk_required(bool async) - => AssertCantTrackOwned(() => base.Project_trunk_required(async)); - - public override Task Project_trunk_collection(bool async) - => AssertCantTrackOwned(() => base.Project_trunk_collection(async)); - - public override Task Project_branch_required_required(bool async) - => AssertCantTrackOwned(() => base.Project_branch_required_required(async)); - - public override Task Project_branch_required_optional(bool async) - => AssertCantTrackOwned(() => base.Project_branch_required_optional(async)); - - public override Task Project_branch_required_collection(bool async) - => AssertCantTrackOwned(() => base.Project_branch_required_collection(async)); - - public override Task Project_branch_optional_required(bool async) - => AssertCantTrackOwned(() => base.Project_branch_optional_required(async)); - - public override Task Project_branch_optional_optional(bool async) - => AssertCantTrackOwned(() => base.Project_branch_optional_optional(async)); - - public override Task Project_branch_optional_collection(bool async) - => AssertCantTrackOwned(() => base.Project_branch_optional_collection(async)); - - public override Task Project_trunk_and_branch_duplicated(bool async) - => AssertCantTrackOwned(() => base.Project_trunk_and_branch_duplicated(async)); - - public override Task Project_trunk_and_trunk_duplicated(bool async) - => AssertCantTrackOwned(() => base.Project_trunk_and_trunk_duplicated(async)); - - public override Task Project_multiple_branch_leaf(bool async) - => AssertCantTrackOwned(() => base.Project_multiple_branch_leaf(async)); - - public override Task Project_leaf_trunk_root(bool async) - => AssertCantTrackOwned(() => base.Project_leaf_trunk_root(async)); - - public override Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) - => AssertCantTrackOwned(() => base.Project_subquery_root_set_required_trunk_FirstOrDefault_branch(async)); - - public override Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) - => AssertCantTrackOwned(() => base.Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(async)); - - public override Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - => AssertCantTrackOwned(() => base.Project_subquery_root_set_trunk_FirstOrDefault_collection(async)); - - public override Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - => AssertCantTrackOwned(() => base.Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async)); - - public override Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - => AssertCantTrackOwned(() => base.Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async)); - - public override Task SelectMany_trunk_collection(bool async) - => AssertCantTrackOwned(() => base.SelectMany_trunk_collection(async)); - - public override Task SelectMany_required_trunk_reference_branch_collection(bool async) - => AssertCantTrackOwned(() => base.SelectMany_required_trunk_reference_branch_collection(async)); - - public override Task SelectMany_optional_trunk_reference_branch_collection(bool async) - => AssertCantTrackOwned(() => base.SelectMany_optional_trunk_reference_branch_collection(async)); - - - - - - - - - - - - - - - - - - - - - + public override Task Select_trunk_optional(bool async) + => AssertCantTrackOwned(() => base.Select_trunk_optional(async)); + public override Task Select_trunk_required(bool async) + => AssertCantTrackOwned(() => base.Select_trunk_required(async)); + public override Task Select_branch_required_required(bool async) + => AssertCantTrackOwned(() => base.Select_branch_required_required(async)); + public override Task Select_branch_required_optional(bool async) + => AssertCantTrackOwned(() => base.Select_branch_required_optional(async)); + public override Task Select_branch_optional_required(bool async) + => AssertCantTrackOwned(() => base.Select_branch_optional_required(async)); + public override Task Select_branch_optional_optional(bool async) + => AssertCantTrackOwned(() => base.Select_branch_optional_optional(async)); + public override Task Select_trunk_and_branch_duplicated(bool async) + => AssertCantTrackOwned(() => base.Select_trunk_and_branch_duplicated(async)); + public override Task Select_trunk_and_trunk_duplicated(bool async) + => AssertCantTrackOwned(() => base.Select_trunk_and_trunk_duplicated(async)); + public override Task Select_leaf_trunk_root(bool async) + => AssertCantTrackOwned(() => base.Select_leaf_trunk_root(async)); + public override Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + => AssertCantTrackOwned(() => base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async)); + public override Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + => AssertCantTrackOwned(() => base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async)); private async Task AssertCantTrackOwned(Func test) { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedTableSplittingNoTrackingProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedTableSplittingNoTrackingProjectionSqlServerTest.cs new file mode 100644 index 00000000000..21bfe07c1c9 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedTableSplittingNoTrackingProjectionSqlServerTest.cs @@ -0,0 +1,269 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedTableSplittingNoTrackingProjectionSqlServerTest + : OwnedTableSplittingNoTrackingProjectionRelationalTestBase +{ + public OwnedTableSplittingNoTrackingProjectionSqlServerTest(OwnedTableSplittingRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_trunk_collection(bool async) + { + await base.Select_trunk_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [s0].[RelationshipsRootEntityId], [s0].[Id1], [s0].[Name], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsTrunkEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId12], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsTrunkEntityId11], [s0].[Id10], [s0].[Name0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId3], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId13], [s0].[RelationshipsBranchEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId00], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId100], [s0].[RelationshipsBranchEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId110], [s0].[RelationshipsBranchEntityId11], [s0].[Id100], [s0].[Name00], [s0].[Name1], [s0].[Name2], [s0].[Name3], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId4], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId14], [s0].[Id11], [s0].[Name4], [s0].[Name5], [s0].[Name6], [s0].[Name7], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId5], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId15], [s0].[Id12], [s0].[Name8], [s0].[Name9], [s0].[Name10] +FROM [RootEntities] AS [r] +LEFT JOIN ( + SELECT [r0].[RelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityId1], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId10], [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsTrunkEntityRelationshipsRootEntityId0], [r4].[RelationshipsTrunkEntityId1] AS [RelationshipsTrunkEntityId10], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId11], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId12], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsTrunkEntityId1] AS [RelationshipsTrunkEntityId11], [s].[Id1] AS [Id10], [s].[Name] AS [Name0], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId3], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId13], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId00], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId10] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId100], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId11] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId110], [s].[RelationshipsBranchEntityId11], [s].[Id10] AS [Id100], [s].[Name0] AS [Name00], [s].[Name1], [s].[Name2], [r1].[Name] AS [Name3], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId4], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId14], [r11].[Id1] AS [Id11], [r11].[Name] AS [Name4], [r2].[Name] AS [Name5], [r3].[Name] AS [Name6], [r4].[Name] AS [Name7], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId5], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId15], [r12].[Id1] AS [Id12], [r12].[Name] AS [Name8], [r5].[Name] AS [Name9], [r6].[Name] AS [Name10] + FROM [Root_CollectionTrunk] AS [r0] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [r1].[RelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[RelationshipsTrunkEntityId1] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[RelationshipsTrunkEntityId1] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch] AS [r4] ON [r0].[RelationshipsRootEntityId] = [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [r4].[RelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r5] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r4].[RelationshipsTrunkEntityId1] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r6] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r4].[RelationshipsTrunkEntityId1] = [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN ( + SELECT [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[RelationshipsTrunkEntityId1], [r7].[Id1], [r7].[Name], [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r8].[RelationshipsBranchEntityId1], [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId10], [r9].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId11], [r10].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r10].[Id1] AS [Id10], [r10].[Name] AS [Name0], [r8].[Name] AS [Name1], [r9].[Name] AS [Name2] + FROM [Root_CollectionTrunk_CollectionBranch] AS [r7] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r8] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[RelationshipsTrunkEntityId1] = [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r7].[Id1] = [r8].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r9] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[RelationshipsTrunkEntityId1] = [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r7].[Id1] = [r9].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_CollectionLeaf] AS [r10] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[RelationshipsTrunkEntityId1] = [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r7].[Id1] = [r10].[RelationshipsBranchEntityId1] + ) AS [s] ON [r0].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [s].[RelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r11] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[RelationshipsTrunkEntityId1] = [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r12] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r4].[RelationshipsTrunkEntityId1] = [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] +) AS [s0] ON [r].[Id] = [s0].[RelationshipsRootEntityId] +ORDER BY [r].[Id], [s0].[RelationshipsRootEntityId], [s0].[Id1], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsTrunkEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId12], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsTrunkEntityId11], [s0].[Id10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId3], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId13], [s0].[RelationshipsBranchEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId00], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId100], [s0].[RelationshipsBranchEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId110], [s0].[RelationshipsBranchEntityId11], [s0].[Id100], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId4], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId14], [s0].[Id11], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId5], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId15] +"""); + } + + public override async Task Select_branch_required_collection(bool async) + { + await base.Select_branch_required_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [r0].[RelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11], [s].[Id10], [s].[Name0], [s].[Name1], [s].[Name2] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_RequiredReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Id1], [r1].[Name], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityId1], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r3].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r4].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r4].[Id1] AS [Id10], [r4].[Name] AS [Name0], [r2].[Name] AS [Name1], [r3].[Name] AS [Name2] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r2].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r3].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r4] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r4].[RelationshipsBranchEntityId1] +) AS [s] ON [r0].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11] +"""); + } + + public override async Task Select_branch_optional_collection(bool async) + { + await base.Select_branch_optional_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [r0].[RelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11], [s].[Id10], [s].[Name0], [s].[Name1], [s].[Name2] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_RequiredReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Id1], [r1].[Name], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityId1], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r3].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r4].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r4].[Id1] AS [Id10], [r4].[Name] AS [Name0], [r2].[Name] AS [Name1], [r3].[Name] AS [Name2] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r2].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r3].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r4] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r4].[RelationshipsBranchEntityId1] +) AS [s] ON [r0].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11] +"""); + } + + public override async Task Select_multiple_branch_leaf(bool async) + { + await base.Select_multiple_branch_leaf(async); + + AssertSql( + """ +SELECT [r].[Id], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Name], [r0].[RelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[Id1], [r4].[Name], [r2].[Name], [r3].[Name], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[Id1], [r5].[Name], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11], [s].[Id10], [s].[Name0], [s].[Name1], [s].[Name2] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_RequiredReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r4] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r5] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r6].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[Id1], [r6].[Name], [r7].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[RelationshipsBranchEntityId1], [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r8].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r9].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r9].[Id1] AS [Id10], [r9].[Name] AS [Name0], [r7].[Name] AS [Name1], [r8].[Name] AS [Name2] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r6] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r7] ON [r6].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r7].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r6].[Id1] = [r7].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r8] ON [r6].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r6].[Id1] = [r8].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r9] ON [r6].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r6].[Id1] = [r9].[RelationshipsBranchEntityId1] +) AS [s] ON [r0].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[Id1], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[Id1], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11] +"""); + } + + public override async Task Select_subquery_root_set_trunk_FirstOrDefault_collection(bool async) + { + await base.Select_subquery_root_set_trunk_FirstOrDefault_collection(async); + + AssertSql( + """ +SELECT [r].[Id], [s].[Id], [s].[RelationshipsRootEntityId], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[Id1], [s0].[Name], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityId11], [s0].[Id10], [s0].[Name0], [s0].[Name1], [s0].[Name2], [s].[c] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) 1 AS [c], [r0].[Id], [r1].[RelationshipsRootEntityId] + FROM [RootEntities] AS [r0] + LEFT JOIN [Root_RequiredReferenceTrunk] AS [r1] ON [r0].[Id] = [r1].[RelationshipsRootEntityId] + ORDER BY [r0].[Id] +) AS [s] +LEFT JOIN ( + SELECT [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[Id1], [r2].[Name], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityId1], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r4].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r5].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r5].[Id1] AS [Id10], [r5].[Name] AS [Name0], [r3].[Name] AS [Name1], [r4].[Name] AS [Name2] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r2] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r3] ON [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r2].[Id1] = [r3].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r4] ON [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r2].[Id1] = [r4].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r5] ON [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r2].[Id1] = [r5].[RelationshipsBranchEntityId1] +) AS [s0] ON [s].[RelationshipsRootEntityId] = [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [s].[Id], [s].[RelationshipsRootEntityId], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[Id1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityId11] +"""); + } + + public override async Task Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) + { + await base.Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); + + AssertSql( + """ +SELECT [r].[Id], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id], [s].[RelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[Id1], [s0].[Name], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityId11], [s0].[Id10], [s0].[Name0], [s0].[Name1], [s0].[Name2], [s].[Name], [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[Id1], [s1].[Name], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[RelationshipsBranchEntityId1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s1].[RelationshipsBranchEntityId10], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s1].[RelationshipsBranchEntityId11], [s1].[Id10], [s1].[Name0], [s1].[Name1], [s1].[Name2], [s].[Name0], [r18].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r18].[Id1], [r18].[Name], [s].[Name1], [s].[Name2], [s].[Name3], [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r19].[Id1], [r19].[Name], [s].[Name4], [s].[Name5], [r20].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r20].[Id1], [r20].[Name], [s].[Name6], [s].[c] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_RequiredReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +OUTER APPLY ( + SELECT TOP(1) [r3].[RelationshipsRootEntityId], [r3].[Name], [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[Name] AS [Name0], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[Name] AS [Name1], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r6].[Name] AS [Name2], [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsTrunkEntityRelationshipsRootEntityId0], [r7].[Name] AS [Name3], [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r8].[Name] AS [Name4], [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [r9].[Name] AS [Name5], [r1].[Name] AS [Name6], 1 AS [c], [r2].[Id] + FROM [RootEntities] AS [r2] + LEFT JOIN [Root_RequiredReferenceTrunk] AS [r3] ON [r2].[Id] = [r3].[RelationshipsRootEntityId] + LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch] AS [r4] ON [r3].[RelationshipsRootEntityId] = [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] + LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r5] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] + LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r6] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] + LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch] AS [r7] ON [r3].[RelationshipsRootEntityId] = [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] + LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r8] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] + LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r9] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] + ORDER BY [r2].[Id] +) AS [s] +LEFT JOIN ( + SELECT [r10].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r10].[Id1], [r10].[Name], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[RelationshipsBranchEntityId1], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r12].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r13].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r13].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r13].[Id1] AS [Id10], [r13].[Name] AS [Name0], [r11].[Name] AS [Name1], [r12].[Name] AS [Name2] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r10] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r11] ON [r10].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r10].[Id1] = [r11].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r12] ON [r10].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r10].[Id1] = [r12].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r13] ON [r10].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r13].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r10].[Id1] = [r13].[RelationshipsBranchEntityId1] +) AS [s0] ON [r0].[RelationshipsRootEntityId] = [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r14].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r14].[Id1], [r14].[Name], [r15].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r15].[RelationshipsBranchEntityId1], [r16].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r16].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r17].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r17].[Id1] AS [Id10], [r17].[Name] AS [Name0], [r15].[Name] AS [Name1], [r16].[Name] AS [Name2] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r14] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r15] ON [r14].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r15].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r14].[Id1] = [r15].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r16] ON [r14].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r16].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r14].[Id1] = [r16].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r17] ON [r14].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r14].[Id1] = [r17].[RelationshipsBranchEntityId1] +) AS [s1] ON [s].[RelationshipsRootEntityId] = [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r18] ON [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r18].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r19] ON [s].[RelationshipsTrunkEntityRelationshipsRootEntityId0] = [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r20] ON [s].[RelationshipsTrunkEntityRelationshipsRootEntityId0] = [r20].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id], [s].[RelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[Id1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityId11], [s0].[Id10], [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[Id1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[RelationshipsBranchEntityId1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s1].[RelationshipsBranchEntityId10], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s1].[RelationshipsBranchEntityId11], [s1].[Id10], [r18].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r18].[Id1], [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r19].[Id1], [r20].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +"""); + } + + public override async Task Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) + { + await base.Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); + + AssertSql( + """ +SELECT [r].[Id], [r0].[RelationshipsRootEntityId], [r6].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11], [s].[Id10], [s].[Name0], [s].[Name1], [s].[Name2], [r6].[c] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_RequiredReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +OUTER APPLY ( + SELECT TOP(1) 1 AS [c], [r1].[Id] + FROM [RootEntities] AS [r1] + ORDER BY [r1].[Id] +) AS [r6] +LEFT JOIN ( + SELECT [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[Id1], [r2].[Name], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityId1], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r4].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r5].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r5].[Id1] AS [Id10], [r5].[Name] AS [Name0], [r3].[Name] AS [Name1], [r4].[Name] AS [Name2] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r2] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r3] ON [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r2].[Id1] = [r3].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r4] ON [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r2].[Id1] = [r4].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r5] ON [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r2].[Id1] = [r5].[RelationshipsBranchEntityId1] +) AS [s] ON [r0].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r6].[Id], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11] +"""); + } + + public override async Task SelectMany_trunk_collection(bool async) + { + await base.SelectMany_trunk_collection(async); + + AssertSql( + """ +SELECT [r0].[RelationshipsRootEntityId], [r0].[Id1], [r0].[Name], [r].[Id], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityId1], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsTrunkEntityId1], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityId1], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s].[RelationshipsBranchEntityId11], [s].[Id10], [s].[Name0], [s].[Name1], [s].[Name2], [r1].[Name], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r11].[Id1], [r11].[Name], [r2].[Name], [r3].[Name], [r4].[Name], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r12].[Id1], [r12].[Name], [r5].[Name], [r6].[Name] +FROM [RootEntities] AS [r] +INNER JOIN [Root_CollectionTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [r1].[RelationshipsTrunkEntityId1] +LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[RelationshipsTrunkEntityId1] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] +LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[RelationshipsTrunkEntityId1] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] +LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch] AS [r4] ON [r0].[RelationshipsRootEntityId] = [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [r4].[RelationshipsTrunkEntityId1] +LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r5] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r4].[RelationshipsTrunkEntityId1] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] +LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r6] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r4].[RelationshipsTrunkEntityId1] = [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] +LEFT JOIN ( + SELECT [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[RelationshipsTrunkEntityId1], [r7].[Id1], [r7].[Name], [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r8].[RelationshipsBranchEntityId1], [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId10], [r9].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId11], [r10].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r10].[Id1] AS [Id10], [r10].[Name] AS [Name0], [r8].[Name] AS [Name1], [r9].[Name] AS [Name2] + FROM [Root_CollectionTrunk_CollectionBranch] AS [r7] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r8] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[RelationshipsTrunkEntityId1] = [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r7].[Id1] = [r8].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r9] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[RelationshipsTrunkEntityId1] = [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r7].[Id1] = [r9].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_CollectionLeaf] AS [r10] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[RelationshipsTrunkEntityId1] = [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r7].[Id1] = [r10].[RelationshipsBranchEntityId1] +) AS [s] ON [r0].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r0].[Id1] = [s].[RelationshipsTrunkEntityId1] +LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r11] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[RelationshipsTrunkEntityId1] = [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] +LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r12] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r4].[RelationshipsTrunkEntityId1] = [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r0].[Id1], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityId1], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsTrunkEntityId1], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityId1], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s].[RelationshipsBranchEntityId11], [s].[Id10], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r11].[Id1], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] +"""); + } + + public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) + { + await base.SelectMany_required_trunk_reference_branch_collection(async); + + AssertSql( + """ +SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Id1], [r1].[Name], [r].[Id], [r0].[RelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityId1], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityId1], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityId1], [r4].[Id1], [r4].[Name], [r2].[Name], [r3].[Name] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_RequiredReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +INNER JOIN [Root_RequiredReferenceTrunk_CollectionBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r2].[RelationshipsBranchEntityId1] +LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r3].[RelationshipsBranchEntityId1] +LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r4] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r4].[RelationshipsBranchEntityId1] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Id1], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityId1], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityId1], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityId1] +"""); + } + + public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) + { + await base.SelectMany_optional_trunk_reference_branch_collection(async); + + AssertSql( + """ +SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Id1], [r1].[Name], [r].[Id], [r0].[RelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityId1], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityId1], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityId1], [r4].[Id1], [r4].[Name], [r2].[Name], [r3].[Name] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_OptionalReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +INNER JOIN [Root_OptionalReferenceTrunk_CollectionBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r2].[RelationshipsBranchEntityId1] +LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r3].[RelationshipsBranchEntityId1] +LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r4] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r1].[Id1] = [r4].[RelationshipsBranchEntityId1] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Id1], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityId1], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityId1], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityId1] +"""); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedTableSplittingReferenceProjectionNoTrackingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedTableSplittingReferenceProjectionNoTrackingSqlServerTest.cs new file mode 100644 index 00000000000..cb7bdc03dd3 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Projection/OwnedTableSplittingReferenceProjectionNoTrackingSqlServerTest.cs @@ -0,0 +1,497 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedTableSplittingReferenceProjectionNoTrackingSqlServerTest + : OwnedTableSplittingReferenceProjectionNoTrackingRelationalTestBase +{ + public OwnedTableSplittingReferenceProjectionNoTrackingSqlServerTest(OwnedTableSplittingRelationshipsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_root(bool async) + { + await base.Select_root(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[RelationshipsRootEntityId], [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r13].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsRootEntityId], [s0].[Id1], [s0].[Name], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsTrunkEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId12], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsTrunkEntityId11], [s0].[Id10], [s0].[Name0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId3], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId13], [s0].[RelationshipsBranchEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId00], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId100], [s0].[RelationshipsBranchEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId110], [s0].[RelationshipsBranchEntityId11], [s0].[Id100], [s0].[Name00], [s0].[Name1], [s0].[Name2], [s0].[Name3], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId4], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId14], [s0].[Id11], [s0].[Name4], [s0].[Name5], [s0].[Name6], [s0].[Name7], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId5], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId15], [s0].[Id12], [s0].[Name8], [s0].[Name9], [s0].[Name10], [r0].[Name], [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[Id1], [s1].[Name], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[RelationshipsBranchEntityId1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s1].[RelationshipsBranchEntityId10], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s1].[RelationshipsBranchEntityId11], [s1].[Id10], [s1].[Name0], [s1].[Name1], [s1].[Name2], [r1].[Name], [r31].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r31].[Id1], [r31].[Name], [r2].[Name], [r3].[Name], [r4].[Name], [r32].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r32].[Id1], [r32].[Name], [r5].[Name], [r6].[Name], [r7].[Name], [s2].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[Id1], [s2].[Name], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[RelationshipsBranchEntityId1], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s2].[RelationshipsBranchEntityId10], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s2].[RelationshipsBranchEntityId11], [s2].[Id10], [s2].[Name0], [s2].[Name1], [s2].[Name2], [r8].[Name], [r37].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r37].[Id1], [r37].[Name], [r9].[Name], [r10].[Name], [r11].[Name], [r38].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r38].[Id1], [r38].[Name], [r12].[Name], [r13].[Name] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_OptionalReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch] AS [r4] ON [r0].[RelationshipsRootEntityId] = [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r5] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r6] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk] AS [r7] ON [r].[Id] = [r7].[RelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch] AS [r8] ON [r7].[RelationshipsRootEntityId] = [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r9] ON [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r10] ON [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch] AS [r11] ON [r7].[RelationshipsRootEntityId] = [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r12] ON [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r13] ON [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r13].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r14].[RelationshipsRootEntityId], [r14].[Id1], [r14].[Name], [r15].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r15].[RelationshipsTrunkEntityId1], [r16].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r16].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId10], [r18].[RelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsTrunkEntityRelationshipsRootEntityId0], [r18].[RelationshipsTrunkEntityId1] AS [RelationshipsTrunkEntityId10], [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId11], [r20].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [r20].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId12], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsTrunkEntityId1] AS [RelationshipsTrunkEntityId11], [s].[Id1] AS [Id10], [s].[Name] AS [Name0], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId3], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId13], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId00], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId10] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId100], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId11] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId110], [s].[RelationshipsBranchEntityId11], [s].[Id10] AS [Id100], [s].[Name0] AS [Name00], [s].[Name1], [s].[Name2], [r15].[Name] AS [Name3], [r25].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId4], [r25].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId14], [r25].[Id1] AS [Id11], [r25].[Name] AS [Name4], [r16].[Name] AS [Name5], [r17].[Name] AS [Name6], [r18].[Name] AS [Name7], [r26].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId5], [r26].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId15], [r26].[Id1] AS [Id12], [r26].[Name] AS [Name8], [r19].[Name] AS [Name9], [r20].[Name] AS [Name10] + FROM [Root_CollectionTrunk] AS [r14] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch] AS [r15] ON [r14].[RelationshipsRootEntityId] = [r15].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r14].[Id1] = [r15].[RelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r16] ON [r15].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r16].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r15].[RelationshipsTrunkEntityId1] = [r16].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r17] ON [r15].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r15].[RelationshipsTrunkEntityId1] = [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch] AS [r18] ON [r14].[RelationshipsRootEntityId] = [r18].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r14].[Id1] = [r18].[RelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r19] ON [r18].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r18].[RelationshipsTrunkEntityId1] = [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r20] ON [r18].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r20].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r18].[RelationshipsTrunkEntityId1] = [r20].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN ( + SELECT [r21].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r21].[RelationshipsTrunkEntityId1], [r21].[Id1], [r21].[Name], [r22].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r22].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r22].[RelationshipsBranchEntityId1], [r23].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r23].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId10], [r23].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r24].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r24].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId11], [r24].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r24].[Id1] AS [Id10], [r24].[Name] AS [Name0], [r22].[Name] AS [Name1], [r23].[Name] AS [Name2] + FROM [Root_CollectionTrunk_CollectionBranch] AS [r21] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r22] ON [r21].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r22].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r21].[RelationshipsTrunkEntityId1] = [r22].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r21].[Id1] = [r22].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r23] ON [r21].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r23].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r21].[RelationshipsTrunkEntityId1] = [r23].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r21].[Id1] = [r23].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_CollectionLeaf] AS [r24] ON [r21].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r24].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r21].[RelationshipsTrunkEntityId1] = [r24].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r21].[Id1] = [r24].[RelationshipsBranchEntityId1] + ) AS [s] ON [r14].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r14].[Id1] = [s].[RelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r25] ON [r15].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r25].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r15].[RelationshipsTrunkEntityId1] = [r25].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r26] ON [r18].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r26].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r18].[RelationshipsTrunkEntityId1] = [r26].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] +) AS [s0] ON [r].[Id] = [s0].[RelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r27].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r27].[Id1], [r27].[Name], [r28].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r28].[RelationshipsBranchEntityId1], [r29].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r29].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r30].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r30].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r30].[Id1] AS [Id10], [r30].[Name] AS [Name0], [r28].[Name] AS [Name1], [r29].[Name] AS [Name2] + FROM [Root_OptionalReferenceTrunk_CollectionBranch] AS [r27] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r28] ON [r27].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r28].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r27].[Id1] = [r28].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r29] ON [r27].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r29].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r27].[Id1] = [r29].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r30] ON [r27].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r30].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r27].[Id1] = [r30].[RelationshipsBranchEntityId1] +) AS [s1] ON [r0].[RelationshipsRootEntityId] = [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r31] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r31].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r32] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r32].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r33].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r33].[Id1], [r33].[Name], [r34].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r34].[RelationshipsBranchEntityId1], [r35].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r35].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r36].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r36].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r36].[Id1] AS [Id10], [r36].[Name] AS [Name0], [r34].[Name] AS [Name1], [r35].[Name] AS [Name2] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r33] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r34] ON [r33].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r34].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r33].[Id1] = [r34].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r35] ON [r33].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r35].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r33].[Id1] = [r35].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r36] ON [r33].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r36].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r33].[Id1] = [r36].[RelationshipsBranchEntityId1] +) AS [s2] ON [r7].[RelationshipsRootEntityId] = [s2].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r37] ON [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r37].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r38] ON [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r38].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[RelationshipsRootEntityId], [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r13].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsRootEntityId], [s0].[Id1], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsTrunkEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId12], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsTrunkEntityId11], [s0].[Id10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId3], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId13], [s0].[RelationshipsBranchEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId00], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId100], [s0].[RelationshipsBranchEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId110], [s0].[RelationshipsBranchEntityId11], [s0].[Id100], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId4], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId14], [s0].[Id11], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId5], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId15], [s0].[Id12], [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[Id1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[RelationshipsBranchEntityId1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s1].[RelationshipsBranchEntityId10], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s1].[RelationshipsBranchEntityId11], [s1].[Id10], [r31].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r31].[Id1], [r32].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r32].[Id1], [s2].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[Id1], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[RelationshipsBranchEntityId1], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s2].[RelationshipsBranchEntityId10], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s2].[RelationshipsBranchEntityId11], [s2].[Id10], [r37].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r37].[Id1], [r38].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +"""); + } + + public override async Task Select_trunk_optional(bool async) + { + await base.Select_trunk_optional(async); + + AssertSql( + """ +SELECT [r0].[RelationshipsRootEntityId], [r0].[Name], [r].[Id], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11], [s].[Id10], [s].[Name0], [s].[Name1], [s].[Name2], [r1].[Name], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[Id1], [r11].[Name], [r2].[Name], [r3].[Name], [r4].[Name], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[Id1], [r12].[Name], [r5].[Name], [r6].[Name] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_OptionalReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch] AS [r4] ON [r0].[RelationshipsRootEntityId] = [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r5] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r6] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[Id1], [r7].[Name], [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r8].[RelationshipsBranchEntityId1], [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r9].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r10].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r10].[Id1] AS [Id10], [r10].[Name] AS [Name0], [r8].[Name] AS [Name1], [r9].[Name] AS [Name2] + FROM [Root_OptionalReferenceTrunk_CollectionBranch] AS [r7] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r8] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[Id1] = [r8].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r9] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[Id1] = [r9].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r10] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[Id1] = [r10].[RelationshipsBranchEntityId1] +) AS [s] ON [r0].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r11] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r12] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11], [s].[Id10], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[Id1], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +"""); + } + + public override async Task Select_trunk_required(bool async) + { + await base.Select_trunk_required(async); + + AssertSql( + """ +SELECT [r0].[RelationshipsRootEntityId], [r0].[Name], [r].[Id], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11], [s].[Id10], [s].[Name0], [s].[Name1], [s].[Name2], [r1].[Name], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[Id1], [r11].[Name], [r2].[Name], [r3].[Name], [r4].[Name], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[Id1], [r12].[Name], [r5].[Name], [r6].[Name] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_RequiredReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch] AS [r4] ON [r0].[RelationshipsRootEntityId] = [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r5] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r6] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[Id1], [r7].[Name], [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r8].[RelationshipsBranchEntityId1], [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r9].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r10].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r10].[Id1] AS [Id10], [r10].[Name] AS [Name0], [r8].[Name] AS [Name1], [r9].[Name] AS [Name2] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r7] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r8] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[Id1] = [r8].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r9] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[Id1] = [r9].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r10] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[Id1] = [r10].[RelationshipsBranchEntityId1] +) AS [s] ON [r0].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r11] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r12] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11], [s].[Id10], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[Id1], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +"""); + } + + public override async Task Select_branch_required_required(bool async) + { + await base.Select_branch_required_required(async); + + AssertSql( + """ +SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Name], [r].[Id], [r0].[RelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[Id1], [r4].[Name], [r2].[Name], [r3].[Name] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_RequiredReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r4] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +"""); + } + + public override async Task Select_branch_required_optional(bool async) + { + await base.Select_branch_required_optional(async); + + AssertSql( + """ +SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Name], [r].[Id], [r0].[RelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[Id1], [r4].[Name], [r2].[Name], [r3].[Name] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_RequiredReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r4] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +"""); + } + + public override async Task Select_branch_optional_required(bool async) + { + await base.Select_branch_optional_required(async); + + AssertSql( + """ +SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Name], [r].[Id], [r0].[RelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[Id1], [r4].[Name], [r2].[Name], [r3].[Name] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_RequiredReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r4] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +"""); + } + + public override async Task Select_branch_optional_optional(bool async) + { + await base.Select_branch_optional_optional(async); + + AssertSql( + """ +SELECT [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r1].[Name], [r].[Id], [r0].[RelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[Id1], [r4].[Name], [r2].[Name], [r3].[Name] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_RequiredReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r4] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +"""); + } + + public override async Task Select_root_duplicated(bool async) + { + await base.Select_root_duplicated(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[RelationshipsRootEntityId], [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r13].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsRootEntityId], [s0].[Id1], [s0].[Name], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsTrunkEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId12], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsTrunkEntityId11], [s0].[Id10], [s0].[Name0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId3], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId13], [s0].[RelationshipsBranchEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId00], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId100], [s0].[RelationshipsBranchEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId110], [s0].[RelationshipsBranchEntityId11], [s0].[Id100], [s0].[Name00], [s0].[Name1], [s0].[Name2], [s0].[Name3], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId4], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId14], [s0].[Id11], [s0].[Name4], [s0].[Name5], [s0].[Name6], [s0].[Name7], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId5], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId15], [s0].[Id12], [s0].[Name8], [s0].[Name9], [s0].[Name10], [r0].[Name], [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[Id1], [s1].[Name], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[RelationshipsBranchEntityId1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s1].[RelationshipsBranchEntityId10], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s1].[RelationshipsBranchEntityId11], [s1].[Id10], [s1].[Name0], [s1].[Name1], [s1].[Name2], [r1].[Name], [r31].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r31].[Id1], [r31].[Name], [r2].[Name], [r3].[Name], [r4].[Name], [r32].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r32].[Id1], [r32].[Name], [r5].[Name], [r6].[Name], [r7].[Name], [s2].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[Id1], [s2].[Name], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[RelationshipsBranchEntityId1], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s2].[RelationshipsBranchEntityId10], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s2].[RelationshipsBranchEntityId11], [s2].[Id10], [s2].[Name0], [s2].[Name1], [s2].[Name2], [r8].[Name], [r37].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r37].[Id1], [r37].[Name], [r9].[Name], [r10].[Name], [r11].[Name], [r38].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r38].[Id1], [r38].[Name], [r12].[Name], [r13].[Name], [s4].[RelationshipsRootEntityId], [s4].[Id1], [s4].[Name], [s4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s4].[RelationshipsTrunkEntityId1], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s4].[RelationshipsTrunkEntityRelationshipsRootEntityId0], [s4].[RelationshipsTrunkEntityId10], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId12], [s4].[RelationshipsTrunkEntityRelationshipsRootEntityId1], [s4].[RelationshipsTrunkEntityId11], [s4].[Id10], [s4].[Name0], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId3], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId13], [s4].[RelationshipsBranchEntityId1], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId00], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId100], [s4].[RelationshipsBranchEntityId10], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId10], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId110], [s4].[RelationshipsBranchEntityId11], [s4].[Id100], [s4].[Name00], [s4].[Name1], [s4].[Name2], [s4].[Name3], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId4], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId14], [s4].[Id11], [s4].[Name4], [s4].[Name5], [s4].[Name6], [s4].[Name7], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId5], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId15], [s4].[Id12], [s4].[Name8], [s4].[Name9], [s4].[Name10], [s5].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s5].[Id1], [s5].[Name], [s5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s5].[RelationshipsBranchEntityId1], [s5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s5].[RelationshipsBranchEntityId10], [s5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s5].[RelationshipsBranchEntityId11], [s5].[Id10], [s5].[Name0], [s5].[Name1], [s5].[Name2], [r56].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r56].[Id1], [r56].[Name], [r57].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r57].[Id1], [r57].[Name], [s6].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s6].[Id1], [s6].[Name], [s6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s6].[RelationshipsBranchEntityId1], [s6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s6].[RelationshipsBranchEntityId10], [s6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s6].[RelationshipsBranchEntityId11], [s6].[Id10], [s6].[Name0], [s6].[Name1], [s6].[Name2], [r62].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r62].[Id1], [r62].[Name], [r63].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r63].[Id1], [r63].[Name] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_OptionalReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch] AS [r4] ON [r0].[RelationshipsRootEntityId] = [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r5] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r6] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk] AS [r7] ON [r].[Id] = [r7].[RelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch] AS [r8] ON [r7].[RelationshipsRootEntityId] = [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r9] ON [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r10] ON [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch] AS [r11] ON [r7].[RelationshipsRootEntityId] = [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r12] ON [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r13] ON [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r13].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r14].[RelationshipsRootEntityId], [r14].[Id1], [r14].[Name], [r15].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r15].[RelationshipsTrunkEntityId1], [r16].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r16].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId10], [r18].[RelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsTrunkEntityRelationshipsRootEntityId0], [r18].[RelationshipsTrunkEntityId1] AS [RelationshipsTrunkEntityId10], [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId11], [r20].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [r20].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId12], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsTrunkEntityId1] AS [RelationshipsTrunkEntityId11], [s].[Id1] AS [Id10], [s].[Name] AS [Name0], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId3], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId13], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId00], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId10] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId100], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityId11] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId110], [s].[RelationshipsBranchEntityId11], [s].[Id10] AS [Id100], [s].[Name0] AS [Name00], [s].[Name1], [s].[Name2], [r15].[Name] AS [Name3], [r25].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId4], [r25].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId14], [r25].[Id1] AS [Id11], [r25].[Name] AS [Name4], [r16].[Name] AS [Name5], [r17].[Name] AS [Name6], [r18].[Name] AS [Name7], [r26].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId5], [r26].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId15], [r26].[Id1] AS [Id12], [r26].[Name] AS [Name8], [r19].[Name] AS [Name9], [r20].[Name] AS [Name10] + FROM [Root_CollectionTrunk] AS [r14] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch] AS [r15] ON [r14].[RelationshipsRootEntityId] = [r15].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r14].[Id1] = [r15].[RelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r16] ON [r15].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r16].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r15].[RelationshipsTrunkEntityId1] = [r16].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r17] ON [r15].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r15].[RelationshipsTrunkEntityId1] = [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch] AS [r18] ON [r14].[RelationshipsRootEntityId] = [r18].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r14].[Id1] = [r18].[RelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r19] ON [r18].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r18].[RelationshipsTrunkEntityId1] = [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r20] ON [r18].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r20].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r18].[RelationshipsTrunkEntityId1] = [r20].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN ( + SELECT [r21].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r21].[RelationshipsTrunkEntityId1], [r21].[Id1], [r21].[Name], [r22].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r22].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r22].[RelationshipsBranchEntityId1], [r23].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r23].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId10], [r23].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r24].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r24].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId11], [r24].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r24].[Id1] AS [Id10], [r24].[Name] AS [Name0], [r22].[Name] AS [Name1], [r23].[Name] AS [Name2] + FROM [Root_CollectionTrunk_CollectionBranch] AS [r21] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r22] ON [r21].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r22].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r21].[RelationshipsTrunkEntityId1] = [r22].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r21].[Id1] = [r22].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r23] ON [r21].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r23].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r21].[RelationshipsTrunkEntityId1] = [r23].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r21].[Id1] = [r23].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_CollectionLeaf] AS [r24] ON [r21].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r24].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r21].[RelationshipsTrunkEntityId1] = [r24].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r21].[Id1] = [r24].[RelationshipsBranchEntityId1] + ) AS [s] ON [r14].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r14].[Id1] = [s].[RelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r25] ON [r15].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r25].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r15].[RelationshipsTrunkEntityId1] = [r25].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r26] ON [r18].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r26].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r18].[RelationshipsTrunkEntityId1] = [r26].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] +) AS [s0] ON [r].[Id] = [s0].[RelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r27].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r27].[Id1], [r27].[Name], [r28].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r28].[RelationshipsBranchEntityId1], [r29].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r29].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r30].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r30].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r30].[Id1] AS [Id10], [r30].[Name] AS [Name0], [r28].[Name] AS [Name1], [r29].[Name] AS [Name2] + FROM [Root_OptionalReferenceTrunk_CollectionBranch] AS [r27] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r28] ON [r27].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r28].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r27].[Id1] = [r28].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r29] ON [r27].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r29].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r27].[Id1] = [r29].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r30] ON [r27].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r30].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r27].[Id1] = [r30].[RelationshipsBranchEntityId1] +) AS [s1] ON [r0].[RelationshipsRootEntityId] = [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r31] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r31].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r32] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r32].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r33].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r33].[Id1], [r33].[Name], [r34].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r34].[RelationshipsBranchEntityId1], [r35].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r35].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r36].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r36].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r36].[Id1] AS [Id10], [r36].[Name] AS [Name0], [r34].[Name] AS [Name1], [r35].[Name] AS [Name2] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r33] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r34] ON [r33].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r34].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r33].[Id1] = [r34].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r35] ON [r33].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r35].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r33].[Id1] = [r35].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r36] ON [r33].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r36].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r33].[Id1] = [r36].[RelationshipsBranchEntityId1] +) AS [s2] ON [r7].[RelationshipsRootEntityId] = [s2].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r37] ON [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r37].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r38] ON [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r38].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r39].[RelationshipsRootEntityId], [r39].[Id1], [r39].[Name], [r40].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r40].[RelationshipsTrunkEntityId1], [r41].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r41].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r42].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r42].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId10], [r43].[RelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsTrunkEntityRelationshipsRootEntityId0], [r43].[RelationshipsTrunkEntityId1] AS [RelationshipsTrunkEntityId10], [r44].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r44].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId11], [r45].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [r45].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId12], [s3].[RelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsTrunkEntityRelationshipsRootEntityId1], [s3].[RelationshipsTrunkEntityId1] AS [RelationshipsTrunkEntityId11], [s3].[Id1] AS [Id10], [s3].[Name] AS [Name0], [s3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId3], [s3].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId13], [s3].[RelationshipsBranchEntityId1], [s3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId00], [s3].[RelationshipsBranchEntityRelationshipsTrunkEntityId10] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId100], [s3].[RelationshipsBranchEntityId10], [s3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId10], [s3].[RelationshipsBranchEntityRelationshipsTrunkEntityId11] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId110], [s3].[RelationshipsBranchEntityId11], [s3].[Id10] AS [Id100], [s3].[Name0] AS [Name00], [s3].[Name1], [s3].[Name2], [r40].[Name] AS [Name3], [r50].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId4], [r50].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId14], [r50].[Id1] AS [Id11], [r50].[Name] AS [Name4], [r41].[Name] AS [Name5], [r42].[Name] AS [Name6], [r43].[Name] AS [Name7], [r51].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId5], [r51].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId15], [r51].[Id1] AS [Id12], [r51].[Name] AS [Name8], [r44].[Name] AS [Name9], [r45].[Name] AS [Name10] + FROM [Root_CollectionTrunk] AS [r39] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch] AS [r40] ON [r39].[RelationshipsRootEntityId] = [r40].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r39].[Id1] = [r40].[RelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r41] ON [r40].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r41].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r40].[RelationshipsTrunkEntityId1] = [r41].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r42] ON [r40].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r42].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r40].[RelationshipsTrunkEntityId1] = [r42].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch] AS [r43] ON [r39].[RelationshipsRootEntityId] = [r43].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r39].[Id1] = [r43].[RelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r44] ON [r43].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r44].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r43].[RelationshipsTrunkEntityId1] = [r44].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r45] ON [r43].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r45].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r43].[RelationshipsTrunkEntityId1] = [r45].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN ( + SELECT [r46].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r46].[RelationshipsTrunkEntityId1], [r46].[Id1], [r46].[Name], [r47].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r47].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r47].[RelationshipsBranchEntityId1], [r48].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r48].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId10], [r48].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r49].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r49].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId11], [r49].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r49].[Id1] AS [Id10], [r49].[Name] AS [Name0], [r47].[Name] AS [Name1], [r48].[Name] AS [Name2] + FROM [Root_CollectionTrunk_CollectionBranch] AS [r46] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r47] ON [r46].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r47].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r46].[RelationshipsTrunkEntityId1] = [r47].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r46].[Id1] = [r47].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r48] ON [r46].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r48].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r46].[RelationshipsTrunkEntityId1] = [r48].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r46].[Id1] = [r48].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_CollectionLeaf] AS [r49] ON [r46].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r49].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r46].[RelationshipsTrunkEntityId1] = [r49].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r46].[Id1] = [r49].[RelationshipsBranchEntityId1] + ) AS [s3] ON [r39].[RelationshipsRootEntityId] = [s3].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r39].[Id1] = [s3].[RelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r50] ON [r40].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r50].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r40].[RelationshipsTrunkEntityId1] = [r50].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r51] ON [r43].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r51].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r43].[RelationshipsTrunkEntityId1] = [r51].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] +) AS [s4] ON [r].[Id] = [s4].[RelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r52].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r52].[Id1], [r52].[Name], [r53].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r53].[RelationshipsBranchEntityId1], [r54].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r54].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r55].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r55].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r55].[Id1] AS [Id10], [r55].[Name] AS [Name0], [r53].[Name] AS [Name1], [r54].[Name] AS [Name2] + FROM [Root_OptionalReferenceTrunk_CollectionBranch] AS [r52] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r53] ON [r52].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r53].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r52].[Id1] = [r53].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r54] ON [r52].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r54].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r52].[Id1] = [r54].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r55] ON [r52].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r55].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r52].[Id1] = [r55].[RelationshipsBranchEntityId1] +) AS [s5] ON [r0].[RelationshipsRootEntityId] = [s5].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r56] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r56].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r57] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r57].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r58].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r58].[Id1], [r58].[Name], [r59].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r59].[RelationshipsBranchEntityId1], [r60].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r60].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r61].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r61].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r61].[Id1] AS [Id10], [r61].[Name] AS [Name0], [r59].[Name] AS [Name1], [r60].[Name] AS [Name2] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r58] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r59] ON [r58].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r59].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r58].[Id1] = [r59].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r60] ON [r58].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r60].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r58].[Id1] = [r60].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r61] ON [r58].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r61].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r58].[Id1] = [r61].[RelationshipsBranchEntityId1] +) AS [s6] ON [r7].[RelationshipsRootEntityId] = [s6].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r62] ON [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r62].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r63] ON [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r63].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[RelationshipsRootEntityId], [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r13].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsRootEntityId], [s0].[Id1], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsTrunkEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId12], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsTrunkEntityId11], [s0].[Id10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId3], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId13], [s0].[RelationshipsBranchEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId00], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId100], [s0].[RelationshipsBranchEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId110], [s0].[RelationshipsBranchEntityId11], [s0].[Id100], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId4], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId14], [s0].[Id11], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId5], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId15], [s0].[Id12], [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[Id1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[RelationshipsBranchEntityId1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s1].[RelationshipsBranchEntityId10], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s1].[RelationshipsBranchEntityId11], [s1].[Id10], [r31].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r31].[Id1], [r32].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r32].[Id1], [s2].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[Id1], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[RelationshipsBranchEntityId1], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s2].[RelationshipsBranchEntityId10], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s2].[RelationshipsBranchEntityId11], [s2].[Id10], [r37].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r37].[Id1], [r38].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r38].[Id1], [s4].[RelationshipsRootEntityId], [s4].[Id1], [s4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s4].[RelationshipsTrunkEntityId1], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s4].[RelationshipsTrunkEntityRelationshipsRootEntityId0], [s4].[RelationshipsTrunkEntityId10], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId12], [s4].[RelationshipsTrunkEntityRelationshipsRootEntityId1], [s4].[RelationshipsTrunkEntityId11], [s4].[Id10], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId3], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId13], [s4].[RelationshipsBranchEntityId1], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId00], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId100], [s4].[RelationshipsBranchEntityId10], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId10], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId110], [s4].[RelationshipsBranchEntityId11], [s4].[Id100], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId4], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId14], [s4].[Id11], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId5], [s4].[RelationshipsBranchEntityRelationshipsTrunkEntityId15], [s4].[Id12], [s5].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s5].[Id1], [s5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s5].[RelationshipsBranchEntityId1], [s5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s5].[RelationshipsBranchEntityId10], [s5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s5].[RelationshipsBranchEntityId11], [s5].[Id10], [r56].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r56].[Id1], [r57].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r57].[Id1], [s6].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s6].[Id1], [s6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s6].[RelationshipsBranchEntityId1], [s6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s6].[RelationshipsBranchEntityId10], [s6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s6].[RelationshipsBranchEntityId11], [s6].[Id10], [r62].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r62].[Id1], [r63].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +"""); + } + + public override async Task Select_trunk_and_branch_duplicated(bool async) + { + await base.Select_trunk_and_branch_duplicated(async); + + AssertSql( + """ +SELECT [r0].[RelationshipsRootEntityId], [r0].[Name], [r].[Id], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11], [s].[Id10], [s].[Name0], [s].[Name1], [s].[Name2], [r1].[Name], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[Id1], [r11].[Name], [r2].[Name], [r3].[Name], [r4].[Name], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[Id1], [r12].[Name], [r5].[Name], [r6].[Name], [r13].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r13].[Id1], [r13].[Name], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[Id1], [s0].[Name], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityId11], [s0].[Id10], [s0].[Name0], [s0].[Name1], [s0].[Name2], [r18].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r18].[Id1], [r18].[Name], [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r19].[Id1], [r19].[Name], [r20].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r20].[Id1], [r20].[Name] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_OptionalReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch] AS [r4] ON [r0].[RelationshipsRootEntityId] = [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r5] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r6] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[Id1], [r7].[Name], [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r8].[RelationshipsBranchEntityId1], [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r9].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r10].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r10].[Id1] AS [Id10], [r10].[Name] AS [Name0], [r8].[Name] AS [Name1], [r9].[Name] AS [Name2] + FROM [Root_OptionalReferenceTrunk_CollectionBranch] AS [r7] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r8] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[Id1] = [r8].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r9] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[Id1] = [r9].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r10] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[Id1] = [r10].[RelationshipsBranchEntityId1] +) AS [s] ON [r0].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r11] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r12] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r13] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r13].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r14].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r14].[Id1], [r14].[Name], [r15].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r15].[RelationshipsBranchEntityId1], [r16].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r16].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r17].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r17].[Id1] AS [Id10], [r17].[Name] AS [Name0], [r15].[Name] AS [Name1], [r16].[Name] AS [Name2] + FROM [Root_OptionalReferenceTrunk_CollectionBranch] AS [r14] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r15] ON [r14].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r15].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r14].[Id1] = [r15].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r16] ON [r14].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r16].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r14].[Id1] = [r16].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r17] ON [r14].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r14].[Id1] = [r17].[RelationshipsBranchEntityId1] +) AS [s0] ON [r0].[RelationshipsRootEntityId] = [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r18] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r18].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r19] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r20] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r20].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11], [s].[Id10], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[Id1], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[Id1], [r13].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r13].[Id1], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[Id1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityId11], [s0].[Id10], [r18].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r18].[Id1], [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r19].[Id1], [r20].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +"""); + } + + public override async Task Select_trunk_and_trunk_duplicated(bool async) + { + await base.Select_trunk_and_trunk_duplicated(async); + + AssertSql( + """ +SELECT [r0].[RelationshipsRootEntityId], [r0].[Name], [r].[Id], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11], [s].[Id10], [s].[Name0], [s].[Name1], [s].[Name2], [r1].[Name], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[Id1], [r11].[Name], [r2].[Name], [r3].[Name], [r4].[Name], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[Id1], [r12].[Name], [r5].[Name], [r6].[Name], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[Id1], [s0].[Name], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityId11], [s0].[Id10], [s0].[Name0], [s0].[Name1], [s0].[Name2], [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r17].[Id1], [r17].[Name], [r18].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r18].[Id1], [r18].[Name] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_RequiredReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r3] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch] AS [r4] ON [r0].[RelationshipsRootEntityId] = [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r5] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r6] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[Id1], [r7].[Name], [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r8].[RelationshipsBranchEntityId1], [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r9].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r10].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r10].[Id1] AS [Id10], [r10].[Name] AS [Name0], [r8].[Name] AS [Name1], [r9].[Name] AS [Name2] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r7] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r8] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r8].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[Id1] = [r8].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r9] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[Id1] = [r9].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r10] ON [r7].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r7].[Id1] = [r10].[RelationshipsBranchEntityId1] +) AS [s] ON [r0].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r11] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r12] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r13].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r13].[Id1], [r13].[Name], [r14].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r14].[RelationshipsBranchEntityId1], [r15].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r15].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r16].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r16].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r16].[Id1] AS [Id10], [r16].[Name] AS [Name0], [r14].[Name] AS [Name1], [r15].[Name] AS [Name2] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r13] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r14] ON [r13].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r14].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r13].[Id1] = [r14].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r15] ON [r13].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r15].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r13].[Id1] = [r15].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r16] ON [r13].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r16].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r13].[Id1] = [r16].[RelationshipsBranchEntityId1] +) AS [s0] ON [r0].[RelationshipsRootEntityId] = [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r17] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r18] ON [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r18].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11], [s].[Id10], [r11].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[Id1], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[Id1], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[Id1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s0].[RelationshipsBranchEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s0].[RelationshipsBranchEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsBranchEntityId11], [s0].[Id10], [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r17].[Id1], [r18].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +"""); + } + + public override async Task Select_leaf_trunk_root(bool async) + { + await base.Select_leaf_trunk_root(async); + + AssertSql( + """ +SELECT [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[Name], [r0].[RelationshipsRootEntityId], [r0].[Name], [r].[Id], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[RelationshipsRootEntityId], [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r13].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[Name], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11], [s].[Id10], [s].[Name0], [s].[Name1], [s].[Name2], [r3].[Name], [r18].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r18].[Id1], [r18].[Name], [r4].[Name], [r5].[Name], [r1].[Name], [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r19].[Id1], [r19].[Name], [r6].[Name], [r].[Name], [r].[OptionalReferenceTrunkId], [r].[RequiredReferenceTrunkId], [s1].[RelationshipsRootEntityId], [s1].[Id1], [s1].[Name], [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[RelationshipsTrunkEntityId1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId0], [s1].[RelationshipsTrunkEntityId10], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId12], [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId1], [s1].[RelationshipsTrunkEntityId11], [s1].[Id10], [s1].[Name0], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId3], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId13], [s1].[RelationshipsBranchEntityId1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId00], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId100], [s1].[RelationshipsBranchEntityId10], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId10], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId110], [s1].[RelationshipsBranchEntityId11], [s1].[Id100], [s1].[Name00], [s1].[Name1], [s1].[Name2], [s1].[Name3], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId4], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId14], [s1].[Id11], [s1].[Name4], [s1].[Name5], [s1].[Name6], [s1].[Name7], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId5], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId15], [s1].[Id12], [s1].[Name8], [s1].[Name9], [s1].[Name10], [r7].[Name], [s2].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[Id1], [s2].[Name], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[RelationshipsBranchEntityId1], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s2].[RelationshipsBranchEntityId10], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s2].[RelationshipsBranchEntityId11], [s2].[Id10], [s2].[Name0], [s2].[Name1], [s2].[Name2], [r8].[Name], [r37].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r37].[Id1], [r37].[Name], [r9].[Name], [r10].[Name], [r11].[Name], [r38].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r38].[Id1], [r38].[Name], [r12].[Name], [r13].[Name], [s3].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s3].[Id1], [s3].[Name], [s3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s3].[RelationshipsBranchEntityId1], [s3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s3].[RelationshipsBranchEntityId10], [s3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s3].[RelationshipsBranchEntityId11], [s3].[Id10], [s3].[Name0], [s3].[Name1], [s3].[Name2], [r43].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r43].[Id1], [r43].[Name], [r44].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r44].[Id1], [r44].[Name] +FROM [RootEntities] AS [r] +LEFT JOIN [Root_RequiredReferenceTrunk] AS [r0] ON [r].[Id] = [r0].[RelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch] AS [r1] ON [r0].[RelationshipsRootEntityId] = [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r2] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch] AS [r3] ON [r0].[RelationshipsRootEntityId] = [r3].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r4] ON [r3].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r5] ON [r3].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r6] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk] AS [r7] ON [r].[Id] = [r7].[RelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch] AS [r8] ON [r7].[RelationshipsRootEntityId] = [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r9] ON [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r10] ON [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch] AS [r11] ON [r7].[RelationshipsRootEntityId] = [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r12] ON [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r13] ON [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r13].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r14].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r14].[Id1], [r14].[Name], [r15].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r15].[RelationshipsBranchEntityId1], [r16].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r16].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r17].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r17].[Id1] AS [Id10], [r17].[Name] AS [Name0], [r15].[Name] AS [Name1], [r16].[Name] AS [Name2] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r14] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r15] ON [r14].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r15].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r14].[Id1] = [r15].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r16] ON [r14].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r16].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r14].[Id1] = [r16].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r17] ON [r14].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r17].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r14].[Id1] = [r17].[RelationshipsBranchEntityId1] +) AS [s] ON [r0].[RelationshipsRootEntityId] = [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r18] ON [r3].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r18].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r19] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r20].[RelationshipsRootEntityId], [r20].[Id1], [r20].[Name], [r21].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r21].[RelationshipsTrunkEntityId1], [r22].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r22].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r23].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r23].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId10], [r24].[RelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsTrunkEntityRelationshipsRootEntityId0], [r24].[RelationshipsTrunkEntityId1] AS [RelationshipsTrunkEntityId10], [r25].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r25].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId11], [r26].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [r26].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId12], [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsTrunkEntityRelationshipsRootEntityId1], [s0].[RelationshipsTrunkEntityId1] AS [RelationshipsTrunkEntityId11], [s0].[Id1] AS [Id10], [s0].[Name] AS [Name0], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId3], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId13], [s0].[RelationshipsBranchEntityId1], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId00], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId10] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId100], [s0].[RelationshipsBranchEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId10], [s0].[RelationshipsBranchEntityRelationshipsTrunkEntityId11] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId110], [s0].[RelationshipsBranchEntityId11], [s0].[Id10] AS [Id100], [s0].[Name0] AS [Name00], [s0].[Name1], [s0].[Name2], [r21].[Name] AS [Name3], [r31].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId4], [r31].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId14], [r31].[Id1] AS [Id11], [r31].[Name] AS [Name4], [r22].[Name] AS [Name5], [r23].[Name] AS [Name6], [r24].[Name] AS [Name7], [r32].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId5], [r32].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId15], [r32].[Id1] AS [Id12], [r32].[Name] AS [Name8], [r25].[Name] AS [Name9], [r26].[Name] AS [Name10] + FROM [Root_CollectionTrunk] AS [r20] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch] AS [r21] ON [r20].[RelationshipsRootEntityId] = [r21].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r20].[Id1] = [r21].[RelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r22] ON [r21].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r22].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r21].[RelationshipsTrunkEntityId1] = [r22].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r23] ON [r21].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r23].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r21].[RelationshipsTrunkEntityId1] = [r23].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch] AS [r24] ON [r20].[RelationshipsRootEntityId] = [r24].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r20].[Id1] = [r24].[RelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r25] ON [r24].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r25].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r24].[RelationshipsTrunkEntityId1] = [r25].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r26] ON [r24].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r26].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r24].[RelationshipsTrunkEntityId1] = [r26].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN ( + SELECT [r27].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r27].[RelationshipsTrunkEntityId1], [r27].[Id1], [r27].[Name], [r28].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r28].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [r28].[RelationshipsBranchEntityId1], [r29].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r29].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId10], [r29].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r30].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r30].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AS [RelationshipsBranchEntityRelationshipsTrunkEntityId11], [r30].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r30].[Id1] AS [Id10], [r30].[Name] AS [Name0], [r28].[Name] AS [Name1], [r29].[Name] AS [Name2] + FROM [Root_CollectionTrunk_CollectionBranch] AS [r27] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r28] ON [r27].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r28].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r27].[RelationshipsTrunkEntityId1] = [r28].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r27].[Id1] = [r28].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r29] ON [r27].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r29].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r27].[RelationshipsTrunkEntityId1] = [r29].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r27].[Id1] = [r29].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_CollectionTrunk_CollectionBranch_CollectionLeaf] AS [r30] ON [r27].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r30].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r27].[RelationshipsTrunkEntityId1] = [r30].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] AND [r27].[Id1] = [r30].[RelationshipsBranchEntityId1] + ) AS [s0] ON [r20].[RelationshipsRootEntityId] = [s0].[RelationshipsTrunkEntityRelationshipsRootEntityId] AND [r20].[Id1] = [s0].[RelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r31] ON [r21].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r31].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r21].[RelationshipsTrunkEntityId1] = [r31].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] + LEFT JOIN [Root_CollectionTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r32] ON [r24].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r32].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r24].[RelationshipsTrunkEntityId1] = [r32].[RelationshipsBranchEntityRelationshipsTrunkEntityId1] +) AS [s1] ON [r].[Id] = [s1].[RelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r33].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r33].[Id1], [r33].[Name], [r34].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r34].[RelationshipsBranchEntityId1], [r35].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r35].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r36].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r36].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r36].[Id1] AS [Id10], [r36].[Name] AS [Name0], [r34].[Name] AS [Name1], [r35].[Name] AS [Name2] + FROM [Root_OptionalReferenceTrunk_CollectionBranch] AS [r33] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r34] ON [r33].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r34].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r33].[Id1] = [r34].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r35] ON [r33].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r35].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r33].[Id1] = [r35].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_OptionalReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r36] ON [r33].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r36].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r33].[Id1] = [r36].[RelationshipsBranchEntityId1] +) AS [s2] ON [r7].[RelationshipsRootEntityId] = [s2].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r37] ON [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r37].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_OptionalReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r38] ON [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r38].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN ( + SELECT [r39].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r39].[Id1], [r39].[Name], [r40].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r40].[RelationshipsBranchEntityId1], [r41].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r41].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId10], [r42].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [r42].[RelationshipsBranchEntityId1] AS [RelationshipsBranchEntityId11], [r42].[Id1] AS [Id10], [r42].[Name] AS [Name0], [r40].[Name] AS [Name1], [r41].[Name] AS [Name2] + FROM [Root_RequiredReferenceTrunk_CollectionBranch] AS [r39] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_OptionalReferenceLeaf] AS [r40] ON [r39].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r40].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r39].[Id1] = [r40].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_RequiredReferenceLeaf] AS [r41] ON [r39].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r41].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r39].[Id1] = [r41].[RelationshipsBranchEntityId1] + LEFT JOIN [Root_RequiredReferenceTrunk_CollectionBranch_CollectionLeaf] AS [r42] ON [r39].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r42].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AND [r39].[Id1] = [r42].[RelationshipsBranchEntityId1] +) AS [s3] ON [r0].[RelationshipsRootEntityId] = [s3].[RelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r43] ON [r3].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r43].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r44] ON [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r44].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [r0].[RelationshipsRootEntityId], [r1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r6].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r7].[RelationshipsRootEntityId], [r8].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r9].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r10].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r11].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r12].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r13].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Id1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityId1], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s].[RelationshipsBranchEntityId10], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s].[RelationshipsBranchEntityId11], [s].[Id10], [r18].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r18].[Id1], [r19].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r19].[Id1], [s1].[RelationshipsRootEntityId], [s1].[Id1], [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[RelationshipsTrunkEntityId1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId10], [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId0], [s1].[RelationshipsTrunkEntityId10], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId11], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId2], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId12], [s1].[RelationshipsTrunkEntityRelationshipsRootEntityId1], [s1].[RelationshipsTrunkEntityId11], [s1].[Id10], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId3], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId13], [s1].[RelationshipsBranchEntityId1], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId00], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId100], [s1].[RelationshipsBranchEntityId10], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId10], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId110], [s1].[RelationshipsBranchEntityId11], [s1].[Id100], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId4], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId14], [s1].[Id11], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId5], [s1].[RelationshipsBranchEntityRelationshipsTrunkEntityId15], [s1].[Id12], [s2].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[Id1], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s2].[RelationshipsBranchEntityId1], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s2].[RelationshipsBranchEntityId10], [s2].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s2].[RelationshipsBranchEntityId11], [s2].[Id10], [r37].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r37].[Id1], [r38].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r38].[Id1], [s3].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s3].[Id1], [s3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s3].[RelationshipsBranchEntityId1], [s3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [s3].[RelationshipsBranchEntityId10], [s3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId1], [s3].[RelationshipsBranchEntityId11], [s3].[Id10], [r43].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r43].[Id1], [r44].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +"""); + } + + public override async Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + { + await base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async); + + AssertSql( + """ +SELECT [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Name], [r].[Id], [s].[Id], [s].[RelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[Id1], [r5].[Name], [s].[Name0], [s].[Name1] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[Name], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[Name] AS [Name0], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r4].[Name] AS [Name1], [r0].[Id], [r1].[RelationshipsRootEntityId] + FROM [RootEntities] AS [r0] + LEFT JOIN [Root_RequiredReferenceTrunk] AS [r1] ON [r0].[Id] = [r1].[RelationshipsRootEntityId] + LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch] AS [r2] ON [r1].[RelationshipsRootEntityId] = [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId] + LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_OptionalReferenceLeaf] AS [r3] ON [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] + LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_RequiredReferenceLeaf] AS [r4] ON [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] + ORDER BY [r0].[Id] +) AS [s] +LEFT JOIN [Root_RequiredReferenceTrunk_RequiredReferenceBranch_CollectionLeaf] AS [r5] ON [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [s].[Id], [s].[RelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +"""); + } + + public override async Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + { + await base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); + + AssertSql( + """ +SELECT [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[Name], [r].[Id], [s].[Id], [s].[RelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r5].[Id1], [r5].[Name], [s].[Name0], [s].[Name1] +FROM [RootEntities] AS [r] +OUTER APPLY ( + SELECT TOP(1) [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId], [r2].[Name], [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [r3].[Name] AS [Name0], [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] AS [RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r4].[Name] AS [Name1], [r0].[Id], [r1].[RelationshipsRootEntityId] + FROM [RootEntities] AS [r0] + LEFT JOIN [Root_OptionalReferenceTrunk] AS [r1] ON [r0].[Id] = [r1].[RelationshipsRootEntityId] + LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch] AS [r2] ON [r1].[RelationshipsRootEntityId] = [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId] + LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_OptionalReferenceLeaf] AS [r3] ON [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r3].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] + LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_RequiredReferenceLeaf] AS [r4] ON [r2].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r4].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] + ORDER BY [r0].[Id] +) AS [s] +LEFT JOIN [Root_OptionalReferenceTrunk_OptionalReferenceBranch_CollectionLeaf] AS [r5] ON [s].[RelationshipsTrunkEntityRelationshipsRootEntityId] = [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +ORDER BY [r].[Id], [s].[Id], [s].[RelationshipsRootEntityId], [s].[RelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId], [s].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId0], [r5].[RelationshipsBranchEntityRelationshipsTrunkEntityRelationshipsRootEntityId] +"""); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SharedTypeQuerySqlServerTest.cs index c71a5cc0058..03ec9da21a7 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SharedTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SharedTypeQuerySqlServerTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class SharedTypeQuerySqlServerTest : SharedTypeQueryRelationalTestBase +public class SharedTypeQuerySqlServerTest(NonSharedFixture fixture) : SharedTypeQueryRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalTableSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalTableSqlServerTest.cs index 65e0153b41d..fab7f5a58fa 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalTableSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalTableSqlServerTest.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable [SqlServerCondition(SqlServerCondition.SupportsTemporalTablesCascadeDelete)] -public class TemporalTableSqlServerTest : NonSharedModelTestBase +public class TemporalTableSqlServerTest(NonSharedFixture fixture) : NonSharedModelTestBase(fixture), IClassFixture { protected override string StoreName => "TemporalTableSqlServerTest"; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ToSqlQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ToSqlQuerySqlServerTest.cs index 0274d11ad50..929daea63e6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ToSqlQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ToSqlQuerySqlServerTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class ToSqlQuerySqlServerTest : ToSqlQueryTestBase +public class ToSqlQuerySqlServerTest(NonSharedFixture fixture) : ToSqlQueryTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Translations/StringTranslationsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Translations/StringTranslationsSqlServerTest.cs index c645f16d379..3681a4e4f55 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Translations/StringTranslationsSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Translations/StringTranslationsSqlServerTest.cs @@ -137,6 +137,18 @@ WHERE CAST(CHARINDEX(N'Eattl', [b].[String]) AS int) - 1 <> -1 """); } + public override async Task IndexOf_Char(bool async) + { + await base.IndexOf_Char(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE CAST(CHARINDEX('e', [b].[String]) AS int) - 1 <> -1 +"""); + } + public override async Task IndexOf_with_empty_string(bool async) { await base.IndexOf_with_empty_string(async); @@ -156,6 +168,24 @@ public override async Task IndexOf_with_one_parameter_arg(bool async) """ @pattern='Eattl' (Size = 4000) +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE CAST(CHARINDEX(@pattern, [b].[String]) AS int) - CASE + WHEN @pattern = N'' THEN 0 + ELSE 1 +END = 1 +"""); + + } + + public override async Task IndexOf_with_one_parameter_arg_char(bool async) + { + await base.IndexOf_with_one_parameter_arg_char(async); + + AssertSql( + """ +@pattern='e' (Size = 1) (DbType = String) + SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] FROM [BasicTypesEntities] AS [b] WHERE CAST(CHARINDEX(@pattern, [b].[String]) AS int) - CASE @@ -178,6 +208,18 @@ WHERE CAST(LEN([b].[String]) AS int) > 2 AND CAST(CHARINDEX(N'e', [b].[String], """); } + public override async Task IndexOf_with_constant_starting_position_char(bool async) + { + await base.IndexOf_with_constant_starting_position_char(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE CAST(LEN([b].[String]) AS int) > 2 AND CAST(CHARINDEX('e', [b].[String], 3) AS int) - 1 = 6 +"""); + } + public override async Task IndexOf_with_parameter_starting_position(bool async) { await base.IndexOf_with_parameter_starting_position(async); @@ -192,6 +234,20 @@ WHERE CAST(LEN([b].[String]) AS int) > 2 AND CAST(CHARINDEX(N'E', [b].[String], """); } + public override async Task IndexOf_with_parameter_starting_position_char(bool async) + { + await base.IndexOf_with_parameter_starting_position_char(async); + + AssertSql( + """ +@start='2' + +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE CAST(LEN([b].[String]) AS int) > 2 AND CAST(CHARINDEX('e', [b].[String], @start + 1) AS int) - 1 = 6 +"""); + } + public override async Task IndexOf_after_ToString(bool async) { await base.IndexOf_after_ToString(async); @@ -235,6 +291,18 @@ WHERE REPLACE([b].[String], N'sea', N'rea') = N'reattle' """); } + public override async Task Replace_Char(bool async) + { + await base.Replace_Char(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE REPLACE([b].[String], 'S', 'R') = N'Reattle' +"""); + } + public override async Task Replace_with_empty_string(bool async) { await base.Replace_with_empty_string(async); @@ -435,6 +503,18 @@ WHERE [b].[String] LIKE N'se%' """); } + public override async Task StartsWith_Literal_Char(bool async) + { + await base.StartsWith_Literal_Char(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE [b].[String] LIKE N'S%' +"""); + } + public override async Task StartsWith_Parameter(bool async) { await base.StartsWith_Parameter(async); @@ -443,6 +523,20 @@ public override async Task StartsWith_Parameter(bool async) """ @pattern_startswith='se%' (Size = 4000) +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE [b].[String] LIKE @pattern_startswith ESCAPE N'\' +"""); + } + + public override async Task StartsWith_Parameter_Char(bool async) + { + await base.StartsWith_Parameter_Char(async); + + AssertSql( + """ +@pattern_startswith='S%' (Size = 4000) + SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] FROM [BasicTypesEntities] AS [b] WHERE [b].[String] LIKE @pattern_startswith ESCAPE N'\' @@ -498,6 +592,18 @@ WHERE [b].[String] LIKE N'%Le' """); } + public override async Task EndsWith_Literal_Char(bool async) + { + await base.EndsWith_Literal_Char(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE [b].[String] LIKE N'%e' +"""); + } + public override async Task EndsWith_Parameter(bool async) { await base.EndsWith_Parameter(async); @@ -506,6 +612,20 @@ public override async Task EndsWith_Parameter(bool async) """ @pattern_endswith='%LE' (Size = 4000) +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE [b].[String] LIKE @pattern_endswith ESCAPE N'\' +"""); + } + + public override async Task EndsWith_Parameter_Char(bool async) + { + await base.EndsWith_Parameter_Char(async); + + AssertSql( + """ +@pattern_endswith='%e' (Size = 4000) + SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] FROM [BasicTypesEntities] AS [b] WHERE [b].[String] LIKE @pattern_endswith ESCAPE N'\' @@ -568,6 +688,20 @@ WHERE [b].[String] LIKE N'%eattl%' """); } + public override async Task Contains_Literal_Char(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(c => c.String.Contains('e'))); + + AssertSql( + """ +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE [b].[String] LIKE N'%e%' +"""); + } + public override async Task Contains_Column(bool async) { await base.Contains_Column(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/DbContextModelBuilder.cs b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/DbContextModelBuilder.cs index 77a88d2d54b..012b81141ff 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/DbContextModelBuilder.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/DbContextModelBuilder.cs @@ -1,12 +1,12 @@ // using System; using System.Collections.Generic; -using System.Text.Json; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Update.Internal; #pragma warning disable 219, 612, 618 @@ -2258,10 +2258,10 @@ private IRelationalModel CreateRelationalModel() IsNullable = true }; principalBaseTable.Columns.Add("ManyOwned", manyOwnedColumn); - manyOwnedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric(manyOwnedColumn); + manyOwnedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric(manyOwnedColumn); var ownedColumn = new JsonColumn("Owned", "nvarchar(max)", principalBaseTable); principalBaseTable.Columns.Add("Owned", ownedColumn); - ownedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric(ownedColumn); + ownedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric(ownedColumn); var refTypeArrayColumn = new Column("RefTypeArray", "nvarchar(max)", principalBaseTable) { IsNullable = true diff --git a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/CompiledModelSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/CompiledModelSqlServerTest.cs index 2503b3bacdb..0cad6ecae34 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/CompiledModelSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/CompiledModelSqlServerTest.cs @@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Scaffolding; -public class CompiledModelSqlServerTest : CompiledModelRelationalTestBase +public class CompiledModelSqlServerTest(NonSharedFixture fixture) : CompiledModelRelationalTestBase(fixture) { protected override void BuildBigModel(ModelBuilder modelBuilder, bool jsonColumns) { diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTestBase.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTestBase.cs index da75efae8f5..9a808470ef9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTestBase.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTestBase.cs @@ -1767,5 +1767,5 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) b => b.UseNetTopologySuite().ApplyConfiguration()); } - public static IEnumerable IsAsyncData = new object[][] { [false], [true] }; + public static readonly IEnumerable IsAsyncData = [[false], [true]]; } diff --git a/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs index 608604fb55f..904b9e0df1d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore; #nullable disable -public class TPTTableSplittingSqlServerTest(ITestOutputHelper testOutputHelper) : TPTTableSplittingTestBase(testOutputHelper) +public class TPTTableSplittingSqlServerTest(NonSharedFixture fixture, ITestOutputHelper testOutputHelper) : TPTTableSplittingTestBase(fixture, testOutputHelper) { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs index 3f21d64b816..ef483ac1230 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore; #nullable disable -public class TableSplittingSqlServerTest(ITestOutputHelper testOutputHelper) : TableSplittingTestBase(testOutputHelper) +public class TableSplittingSqlServerTest(NonSharedFixture fixture, ITestOutputHelper testOutputHelper) : TableSplittingTestBase(fixture, testOutputHelper) { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/AzureSynapseTestStore.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/AzureSynapseTestStore.cs index 3b7a628a676..1f07a753dff 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/AzureSynapseTestStore.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/AzureSynapseTestStore.cs @@ -27,8 +27,8 @@ public class AzureSynapseTestStore : SqlServerTestStore bool shared = true) => new(name, scriptPath: scriptPath, multipleActiveResultSets: multipleActiveResultSets, shared: shared); - public new static AzureSynapseTestStore Create(string name, bool useFileName = false) - => new(name, useFileName, shared: false); + public new static AzureSynapseTestStore Create(string name, bool useFileName = false, bool? multipleActiveResultSets = null) + => new(name, useFileName, shared: false, multipleActiveResultSets: multipleActiveResultSets); public new static async Task CreateInitializedAsync( string name, diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerTestStore.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerTestStore.cs index 3ff4faabd17..d7c3b1f8112 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerTestStore.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerTestStore.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Data; -using System.Text.RegularExpressions; using Microsoft.Data.SqlClient; #pragma warning disable IDE0022 // Use block body for methods @@ -36,8 +35,11 @@ public static SqlServerTestStore GetOrCreateWithScriptPath( bool shared = true) => new(name, scriptPath: scriptPath, multipleActiveResultSets: multipleActiveResultSets, shared: shared); - public static SqlServerTestStore Create(string name, bool useFileName = false) - => new(name, useFileName, shared: false); + public static SqlServerTestStore Create( + string name, + bool useFileName = false, + bool? multipleActiveResultSets = null) + => new(name, useFileName, shared: false, multipleActiveResultSets: multipleActiveResultSets); public static async Task CreateInitializedAsync( string name, @@ -86,26 +88,28 @@ public async Task InitializeSqlServerAsync( protected override async Task InitializeAsync(Func createContext, Func? seed, Func? clean) { - if (await CreateDatabaseAsync(clean)) + if (!await CleanDatabaseAsync(clean)) + { + return; + } + + if (_scriptPath != null) + { + ExecuteScript(await File.ReadAllTextAsync(_scriptPath)); + } + else { - if (_scriptPath != null) + using var context = createContext(); + await context.Database.EnsureCreatedResilientlyAsync(); + + if (_initScript != null) { - ExecuteScript(await File.ReadAllTextAsync(_scriptPath)); + ExecuteScript(_initScript); } - else - { - using var context = createContext(); - await context.Database.EnsureCreatedResilientlyAsync(); - - if (_initScript != null) - { - ExecuteScript(_initScript); - } - if (seed != null) - { - await seed(context); - } + if (seed != null) + { + await seed(context); } } } @@ -116,7 +120,7 @@ public override DbContextOptionsBuilder AddProviderOptions(DbContextOptionsBuild : builder.UseSqlServer(Connection, b => b.ApplyConfiguration())) .ConfigureWarnings(b => b.Ignore(SqlServerEventId.SavepointsDisabledBecauseOfMARS)); - private async Task CreateDatabaseAsync(Func? clean) + private async Task CleanDatabaseAsync(Func? clean) { await using var master = new SqlConnection(CreateConnectionString("master", fileName: null, multipleActiveResultSets: false)); diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs index 754e1670159..3edadd89ff2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs @@ -45,16 +45,17 @@ public IRelationalCommand Build() => new TestRelationalCommand( Dependencies, Instance.ToString(), + Instance.ToString(), Parameters); - public IRelationalCommandBuilder Append(string value) + public IRelationalCommandBuilder Append(string value, bool redact = false) { Instance.Append(value); return this; } - public IRelationalCommandBuilder Append(FormattableString value) + public IRelationalCommandBuilder Append(FormattableString value, bool redact = false) { Instance.Append(value); @@ -89,13 +90,17 @@ public int CommandTextLength private class TestRelationalCommand( RelationalCommandBuilderDependencies dependencies, string commandText, + string logCommandText, IReadOnlyList parameters) : IRelationalCommand { - private readonly RelationalCommand _realRelationalCommand = new(dependencies, commandText, parameters); + private readonly RelationalCommand _realRelationalCommand = new(dependencies, commandText, logCommandText, parameters); public string CommandText => _realRelationalCommand.CommandText; + public string LogCommandText + => _realRelationalCommand.LogCommandText; + public IReadOnlyList Parameters => _realRelationalCommand.Parameters; diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/NonSharedModelUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/NonSharedModelUpdatesSqlServerTest.cs index 4b7780efc13..348d5b919dd 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Update/NonSharedModelUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Update/NonSharedModelUpdatesSqlServerTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Update; #nullable disable -public class NonSharedModelUpdatesSqlServerTest : NonSharedModelUpdatesTestBase +public class NonSharedModelUpdatesSqlServerTest(NonSharedFixture fixture) : NonSharedModelUpdatesTestBase(fixture) { public override async Task Principal_and_dependent_roundtrips_with_cycle_breaking(bool async) { @@ -95,11 +95,10 @@ public virtual async Task Bulk_insert_result_set_mapping() mb.Entity().ToTable("Users"); mb.Entity().ToTable("DailyDigests"); }, - createTestStore: () => Task.FromResult( - SqlServerTestStore.GetOrCreateWithScriptPath( + createTestStore: () => SqlServerTestStore.GetOrCreateWithScriptPath( "Issue29502", Path.Combine("Update", "Issue29502.sql"), - shared: false))); + shared: false)); await ExecuteWithStrategyInTransactionAsync( contextFactory, diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/StoredProcedureUpdateSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/StoredProcedureUpdateSqlServerTest.cs index 8ff25c423c6..413b86e73ad 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Update/StoredProcedureUpdateSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Update/StoredProcedureUpdateSqlServerTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Update; #nullable disable -public class StoredProcedureUpdateSqlServerTest : StoredProcedureUpdateTestBase +public class StoredProcedureUpdateSqlServerTest(NonSharedFixture fixture) : StoredProcedureUpdateTestBase(fixture) { public override async Task Insert_with_output_parameter(bool async) { diff --git a/test/EFCore.SqlServer.Tests/Storage/Internal/SqlServerConnectionTest.cs b/test/EFCore.SqlServer.Tests/Storage/Internal/SqlServerConnectionTest.cs index 37353c7efe6..53b6ca920eb 100644 --- a/test/EFCore.SqlServer.Tests/Storage/Internal/SqlServerConnectionTest.cs +++ b/test/EFCore.SqlServer.Tests/Storage/Internal/SqlServerConnectionTest.cs @@ -88,7 +88,9 @@ public static RelationalConnectionDependencies CreateDependencies(DbContextOptio new TestRelationalTypeMappingSource( TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()), - new SqlServerExceptionDetector()))); + new SqlServerExceptionDetector(), + new LoggingOptions())), + new SqlServerExceptionDetector()); } private const string ConnectionString = "Fake Connection String"; diff --git a/test/EFCore.SqlServer.Tests/Storage/Internal/SqlServerDatabaseCreatorTest.cs b/test/EFCore.SqlServer.Tests/Storage/Internal/SqlServerDatabaseCreatorTest.cs index 6100f41307d..db2402e8335 100644 --- a/test/EFCore.SqlServer.Tests/Storage/Internal/SqlServerDatabaseCreatorTest.cs +++ b/test/EFCore.SqlServer.Tests/Storage/Internal/SqlServerDatabaseCreatorTest.cs @@ -191,14 +191,14 @@ public IRelationalTypeMappingSource TypeMappingSource public IRelationalCommand Build() => new FakeRelationalCommand(); - public IRelationalCommandBuilder Append(string value) + public IRelationalCommandBuilder Append(string value, bool redact = false) { Instance.Append(value); return this; } - public IRelationalCommandBuilder Append(FormattableString value) + public IRelationalCommandBuilder Append(FormattableString value, bool redact = false) { Instance.Append(value); @@ -234,6 +234,8 @@ private class FakeRelationalCommand : IRelationalCommand { public string CommandText { get; } + public string LogCommandText { get; } + public IReadOnlyList Parameters { get; } public IReadOnlyDictionary ParameterValues diff --git a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs index cc2fe94bdef..5a7bb018800 100644 --- a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs +++ b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Update.Internal; @@ -28,7 +29,8 @@ public void Uses_MaxBatchSize_specified_in_SqlServerOptionsExtension() new RelationalCommandBuilderFactory( new RelationalCommandBuilderDependencies( typeMapper, - new SqlServerExceptionDetector())), + new SqlServerExceptionDetector(), + new LoggingOptions())), new SqlServerSqlGenerationHelper( new RelationalSqlGenerationHelperDependencies()), new SqlServerUpdateSqlGenerator( @@ -64,7 +66,8 @@ public void MaxBatchSize_is_optional() new RelationalCommandBuilderFactory( new RelationalCommandBuilderDependencies( typeMapper, - new SqlServerExceptionDetector())), + new SqlServerExceptionDetector(), + new LoggingOptions())), new SqlServerSqlGenerationHelper( new RelationalSqlGenerationHelperDependencies()), new SqlServerUpdateSqlGenerator( diff --git a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs index 1eb35674bee..d5e005b0faf 100644 --- a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs +++ b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Update.Internal; @@ -136,7 +137,8 @@ private static TestSqlServerModificationCommandBatch CreateBatch(int maxBatchSiz new RelationalCommandBuilderFactory( new RelationalCommandBuilderDependencies( typeMapper, - new SqlServerExceptionDetector())), + new SqlServerExceptionDetector(), + new LoggingOptions())), new SqlServerSqlGenerationHelper( new RelationalSqlGenerationHelperDependencies()), new SqlServerUpdateSqlGenerator( diff --git a/test/EFCore.SqlServer.Tests/ValueGeneration/SqlServerSequenceValueGeneratorTest.cs b/test/EFCore.SqlServer.Tests/ValueGeneration/SqlServerSequenceValueGeneratorTest.cs index d4f4d20894c..5be0526e345 100644 --- a/test/EFCore.SqlServer.Tests/ValueGeneration/SqlServerSequenceValueGeneratorTest.cs +++ b/test/EFCore.SqlServer.Tests/ValueGeneration/SqlServerSequenceValueGeneratorTest.cs @@ -220,6 +220,9 @@ private class FakeRelationalCommand(FakeRawSqlCommandBuilder commandBuilder) : I public string CommandText => throw new NotImplementedException(); + public string LogCommandText + => throw new NotImplementedException(); + public IReadOnlyList Parameters => throw new NotImplementedException(); diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs index 9cb9d6c6eb2..afd77f4ef2a 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public class NonSharedModelBulkUpdatesSqliteTest : NonSharedModelBulkUpdatesRelationalTestBase +public class NonSharedModelBulkUpdatesSqliteTest(NonSharedFixture fixture) : NonSharedModelBulkUpdatesRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/EntitySplittingSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/EntitySplittingSqliteTest.cs index 7299a4102e9..fdecf50ef8b 100644 --- a/test/EFCore.Sqlite.FunctionalTests/EntitySplittingSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/EntitySplittingSqliteTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore; #nullable disable -public class EntitySplittingSqliteTest(ITestOutputHelper testOutputHelper) : EntitySplittingTestBase(testOutputHelper) +public class EntitySplittingSqliteTest(NonSharedFixture fixture, ITestOutputHelper testOutputHelper) : EntitySplittingTestBase(fixture, testOutputHelper) { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/JsonTypesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/JsonTypesSqliteTest.cs index b3c437accc4..15d5603bc3c 100644 --- a/test/EFCore.Sqlite.FunctionalTests/JsonTypesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/JsonTypesSqliteTest.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore; -public class JsonTypesSqliteTest : JsonTypesRelationalTestBase +public class JsonTypesSqliteTest(NonSharedFixture fixture) : JsonTypesRelationalTestBase(fixture) { public override Task Can_read_write_array_of_list_of_GUID_JSON_values(string expected) => base.Can_read_write_array_of_list_of_GUID_JSON_values( diff --git a/test/EFCore.Sqlite.FunctionalTests/MaterializationInterceptionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/MaterializationInterceptionSqliteTest.cs index eb3fbda1cc5..49e5f4fc148 100644 --- a/test/EFCore.Sqlite.FunctionalTests/MaterializationInterceptionSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/MaterializationInterceptionSqliteTest.cs @@ -5,8 +5,8 @@ namespace Microsoft.EntityFrameworkCore; -public class MaterializationInterceptionSqliteTest : - MaterializationInterceptionTestBase +public class MaterializationInterceptionSqliteTest(NonSharedFixture fixture) : + MaterializationInterceptionTestBase(fixture) { public override async Task Intercept_query_materialization_with_owned_types_projecting_collection(bool async, bool usePooling) => Assert.Equal( diff --git a/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs index 79529ed9b1d..e3a82cc168d 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs @@ -2070,6 +2070,11 @@ public override Task Rename_sequence() public override Task Move_sequence() => AssertNotSupportedAsync(base.Move_sequence, SqliteStrings.SequencesNotSupported); + public override Task Multiop_rename_table_and_drop() + => AssertNotSupportedAsync( + base.Multiop_rename_table_and_drop, + SqliteStrings.InvalidMigrationOperation(nameof(DropPrimaryKeyOperation))); + [ConditionalFact] public override async Task Add_required_primitve_collection_to_existing_table() { diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocAdvancedMappingsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocAdvancedMappingsQuerySqliteTest.cs index d42b66ac875..71a98a4e5cc 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocAdvancedMappingsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocAdvancedMappingsQuerySqliteTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class AdHocAdvancedMappingsQuerySqliteTest : AdHocAdvancedMappingsQueryRelationalTestBase +public class AdHocAdvancedMappingsQuerySqliteTest(NonSharedFixture fixture) : AdHocAdvancedMappingsQueryRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocComplexTypeQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocComplexTypeQuerySqliteTest.cs index f9aa99e7c4a..af3de65c81b 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocComplexTypeQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocComplexTypeQuerySqliteTest.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query; -public class AdHocComplexTypeQuerySqliteTest : AdHocComplexTypeQueryTestBase +public class AdHocComplexTypeQuerySqliteTest(NonSharedFixture fixture) : AdHocComplexTypeQueryTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocJsonQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocJsonQuerySqliteTest.cs index 55f245a3f94..5aa4bb84dd1 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocJsonQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocJsonQuerySqliteTest.cs @@ -5,34 +5,79 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class AdHocJsonQuerySqliteTest : AdHocJsonQueryTestBase +public class AdHocJsonQuerySqliteTest(NonSharedFixture fixture) : AdHocJsonQueryRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; + protected override async Task Seed21006(Context21006 context) + { + await base.Seed21006(context); + + // missing scalar on top level + await context.Database.ExecuteSqlAsync( + $$$""" +INSERT INTO "Entities" ("Collection", "OptionalReference", "RequiredReference", "Id", "Name") +VALUES ( +'[{"Text":"e2 c1","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e2 c1 c1"},{"DoB":"2000-01-01T00:00:00","Text":"e2 c1 c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e2 c1 nor"},"NestedRequiredReference":{"DoB":"2000-01-01T00:00:00","Text":"e2 c1 nrr"}},{"Text":"e2 c2","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e2 c2 c1"},{"DoB":"2000-01-01T00:00:00","Text":"e2 c2 c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e2 c2 nor"},"NestedRequiredReference":{"DoB":"2000-01-01T00:00:00","Text":"e2 c2 nrr"}}]', +'{"Text":"e2 or","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e2 or c1"},{"DoB":"2000-01-01T00:00:00","Text":"e2 or c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e2 or nor"},"NestedRequiredReference":{"DoB":"2000-01-01T00:00:00","Text":"e2 or nrr"}}', +'{"Text":"e2 rr","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e2 rr c1"},{"DoB":"2000-01-01T00:00:00","Text":"e2 rr c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e2 rr nor"},"NestedRequiredReference":{"DoB":"2000-01-01T00:00:00","Text":"e2 rr nrr"}}', +2, +'e2') +"""); + + // missing scalar on nested level + await context.Database.ExecuteSqlAsync( + $$$""" +INSERT INTO "Entities" ("Collection", "OptionalReference", "RequiredReference", "Id", "Name") +VALUES ( +'[{"Number":7,"Text":"e3 c1","NestedCollection":[{"Text":"e3 c1 c1"},{"Text":"e3 c1 c2"}],"NestedOptionalReference":{"Text":"e3 c1 nor"},"NestedRequiredReference":{"Text":"e3 c1 nrr"}},{"Number":7,"Text":"e3 c2","NestedCollection":[{"Text":"e3 c2 c1"},{"Text":"e3 c2 c2"}],"NestedOptionalReference":{"Text":"e3 c2 nor"},"NestedRequiredReference":{"Text":"e3 c2 nrr"}}]', +'{"Number":7,"Text":"e3 or","NestedCollection":[{"Text":"e3 or c1"},{"Text":"e3 or c2"}],"NestedOptionalReference":{"Text":"e3 or nor"},"NestedRequiredReference":{"Text":"e3 or nrr"}}', +'{"Number":7,"Text":"e3 rr","NestedCollection":[{"Text":"e3 rr c1"},{"Text":"e3 rr c2"}],"NestedOptionalReference":{"Text":"e3 rr nor"},"NestedRequiredReference":{"Text":"e3 rr nrr"}}', +3, +'e3') +"""); + + // null scalar on top level + await context.Database.ExecuteSqlAsync( + $$$""" +INSERT INTO [Entities] ("Collection", "OptionalReference", "RequiredReference", "Id", "Name") +VALUES ( +'[{"Number":null,"Text":"e4 c1","NestedCollection":[{"Text":"e4 c1 c1"},{"Text":"e4 c1 c2"}],"NestedOptionalReference":{"Text":"e4 c1 nor"},"NestedRequiredReference":{"Text":"e4 c1 nrr"}},{"Number":null,"Text":"e4 c2","NestedCollection":[{"Text":"e4 c2 c1"},{"Text":"e4 c2 c2"}],"NestedOptionalReference":{"Text":"e4 c2 nor"},"NestedRequiredReference":{"Text":"e4 c2 nrr"}}]', +'{"Number":null,"Text":"e4 or","NestedCollection":[{"Text":"e4 or c1"},{"Text":"e4 or c2"}],"NestedOptionalReference":{"Text":"e4 or nor"},"NestedRequiredReference":{"Text":"e4 or nrr"}}', +'{"Number":null,"Text":"e4 rr","NestedCollection":[{"Text":"e4 rr c1"},{"Text":"e4 rr c2"}],"NestedOptionalReference":{"Text":"e4 rr nor"},"NestedRequiredReference":{"Text":"e4 rr nrr"}}', +4, +'e4') +"""); + + // missing required navigation + await context.Database.ExecuteSqlAsync( + $$$""" +INSERT INTO "Entities" ("Collection", "OptionalReference", "RequiredReference", "Id", "Name") +VALUES ( +'[{"Number":7,"Text":"e5 c1","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e5 c1 c1"},{"DoB":"2000-01-01T00:00:00","Text":"e5 c1 c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e5 c1 nor"}},{"Number":7,"Text":"e5 c2","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e5 c2 c1"},{"DoB":"2000-01-01T00:00:00","Text":"e5 c2 c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e5 c2 nor"}}]', +'{"Number":7,"Text":"e5 or","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e5 or c1"},{"DoB":"2000-01-01T00:00:00","Text":"e5 or c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e5 or nor"}}', +'{"Number":7,"Text":"e5 rr","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e5 rr c1"},{"DoB":"2000-01-01T00:00:00","Text":"e5 rr c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e5 rr nor"}}', +5, +'e5') +"""); + + // null required navigation + await context.Database.ExecuteSqlAsync( + $$$""" +INSERT INTO "Entities" ("Collection", "OptionalReference", "RequiredReference", "Id", "Name") +VALUES ( +'[{"Number":7,"Text":"e6 c1","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e6 c1 c1"},{"DoB":"2000-01-01T00:00:00","Text":"e6 c1 c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e6 c1 nor"},"NestedRequiredReference":null},{"Number":7,"Text":"e6 c2","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e6 c2 c1"},{"DoB":"2000-01-01T00:00:00","Text":"e6 c2 c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e6 c2 nor"},"NestedRequiredReference":null}]', +'{"Number":7,"Text":"e6 or","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e6 or c1"},{"DoB":"2000-01-01T00:00:00","Text":"e6 or c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e6 or nor"},"NestedRequiredReference":null}', +'{"Number":7,"Text":"e6 rr","NestedCollection":[{"DoB":"2000-01-01T00:00:00","Text":"e6 rr c1"},{"DoB":"2000-01-01T00:00:00","Text":"e6 rr c2"}],"NestedOptionalReference":{"DoB":"2000-01-01T00:00:00","Text":"e6 rr nor"},"NestedRequiredReference":null}', +6, +'e6') +"""); + } + protected override async Task Seed29219(DbContext ctx) { - var entity1 = new MyEntity29219 - { - Id = 1, - Reference = new MyJsonEntity29219 { NonNullableScalar = 10, NullableScalar = 11 }, - Collection = - [ - new MyJsonEntity29219 { NonNullableScalar = 100, NullableScalar = 101 }, - new MyJsonEntity29219 { NonNullableScalar = 200, NullableScalar = 201 }, - new MyJsonEntity29219 { NonNullableScalar = 300, NullableScalar = null } - ] - }; - - var entity2 = new MyEntity29219 - { - Id = 2, - Reference = new MyJsonEntity29219 { NonNullableScalar = 20, NullableScalar = null }, - Collection = [new MyJsonEntity29219 { NonNullableScalar = 1001, NullableScalar = null }] - }; - - ctx.Set().AddRange(entity1, entity2); - await ctx.SaveChangesAsync(); + await base.Seed29219(ctx); await ctx.Database.ExecuteSqlAsync( $$""" @@ -122,52 +167,6 @@ await ctx.Database.ExecuteSqlAsync( """); } - protected override Task SeedArrayOfPrimitives(DbContext ctx) - { - var entity1 = new MyEntityArrayOfPrimitives - { - Id = 1, - Reference = new MyJsonEntityArrayOfPrimitives - { - IntArray = [1, 2, 3], - ListOfString = - [ - "Foo", - "Bar", - "Baz" - ] - }, - Collection = - [ - new MyJsonEntityArrayOfPrimitives { IntArray = [111, 112, 113], ListOfString = ["Foo11", "Bar11"] }, - new MyJsonEntityArrayOfPrimitives { IntArray = [211, 212, 213], ListOfString = ["Foo12", "Bar12"] } - ] - }; - - var entity2 = new MyEntityArrayOfPrimitives - { - Id = 2, - Reference = new MyJsonEntityArrayOfPrimitives - { - IntArray = [10, 20, 30], - ListOfString = - [ - "A", - "B", - "C" - ] - }, - Collection = - [ - new MyJsonEntityArrayOfPrimitives { IntArray = [110, 120, 130], ListOfString = ["A1", "Z1"] }, - new MyJsonEntityArrayOfPrimitives { IntArray = [210, 220, 230], ListOfString = ["A2", "Z2"] } - ] - }; - - ctx.Set().AddRange(entity1, entity2); - return ctx.SaveChangesAsync(); - } - protected override Task SeedJunkInJson(DbContext ctx) => ctx.Database.ExecuteSqlAsync( $$$""" diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocManyToManyQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocManyToManyQuerySqliteTest.cs index 2fbb3333ba6..630292d817e 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocManyToManyQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocManyToManyQuerySqliteTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class AdHocManyToManyQuerySqliteTest : AdHocManyToManyQueryRelationalTestBase +public class AdHocManyToManyQuerySqliteTest(NonSharedFixture fixture) : AdHocManyToManyQueryRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocMiscellaneousQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocMiscellaneousQuerySqliteTest.cs index 2fa464a2554..9bc746ce897 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocMiscellaneousQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocMiscellaneousQuerySqliteTest.cs @@ -5,11 +5,18 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class AdHocMiscellaneousQuerySqliteTest : AdHocMiscellaneousQueryRelationalTestBase +public class AdHocMiscellaneousQuerySqliteTest(NonSharedFixture fixture) : AdHocMiscellaneousQueryRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; + protected override DbContextOptionsBuilder SetTranslateParameterizedCollectionsToConstants(DbContextOptionsBuilder optionsBuilder) + { + new SqliteDbContextOptionsBuilder(optionsBuilder).TranslateParameterizedCollectionsToConstants(); + + return optionsBuilder; + } + protected override Task Seed2951(Context2951 context) => context.Database.ExecuteSqlRawAsync( """ @@ -82,4 +89,58 @@ SELECT ef_avg("p"."NullableDecimalColumn") FROM "Prices" AS "p" """); } + + public override async Task Check_inlined_constants_redacting(bool async, bool enableSensitiveDataLogging) + { + await base.Check_inlined_constants_redacting(async, enableSensitiveDataLogging); + + if (!enableSensitiveDataLogging) + { + AssertSql( + """ +SELECT "t"."Id", "t"."Name" +FROM "TestEntities" AS "t" +WHERE "t"."Id" IN (?, ?, ?) +""", + // + """ +SELECT "t"."Id", "t"."Name" +FROM "TestEntities" AS "t" +WHERE EXISTS ( + SELECT 1 + FROM (SELECT ? AS "Value" UNION ALL VALUES (?), (?)) AS "i" + WHERE "i"."Value" = "t"."Id") +""", + // + """ +SELECT "t"."Id", "t"."Name" +FROM "TestEntities" AS "t" +WHERE ? = "t"."Id" +"""); + } + else + { + AssertSql( + """ +SELECT "t"."Id", "t"."Name" +FROM "TestEntities" AS "t" +WHERE "t"."Id" IN (1, 2, 3) +""", + // + """ +SELECT "t"."Id", "t"."Name" +FROM "TestEntities" AS "t" +WHERE EXISTS ( + SELECT 1 + FROM (SELECT 1 AS "Value" UNION ALL VALUES (2), (3)) AS "i" + WHERE "i"."Value" = "t"."Id") +""", + // + """ +SELECT "t"."Id", "t"."Name" +FROM "TestEntities" AS "t" +WHERE 1 = "t"."Id" +"""); + } + } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocNavigationsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocNavigationsQuerySqliteTest.cs index c49b9e3b3a7..dbd9b9c2d2b 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocNavigationsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocNavigationsQuerySqliteTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class AdHocNavigationsQuerySqliteTest : AdHocNavigationsQueryRelationalTestBase +public class AdHocNavigationsQuerySqliteTest(NonSharedFixture fixture) : AdHocNavigationsQueryRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocPrecompiledQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocPrecompiledQuerySqliteTest.cs index 076e532fb33..46d4a96625c 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocPrecompiledQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocPrecompiledQuerySqliteTest.cs @@ -3,8 +3,8 @@ namespace Microsoft.EntityFrameworkCore.Query; -public class AdHocPrecompiledQuerySqliteTest(ITestOutputHelper testOutputHelper) - : AdHocPrecompiledQueryRelationalTestBase(testOutputHelper) +public class AdHocPrecompiledQuerySqliteTest(NonSharedFixture fixture, ITestOutputHelper testOutputHelper) + : AdHocPrecompiledQueryRelationalTestBase(fixture, testOutputHelper) { protected override bool AlwaysPrintGeneratedSources => false; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocQueryFiltersQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocQueryFiltersQuerySqliteTest.cs index 82dcc7104f3..9916840f733 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocQueryFiltersQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocQueryFiltersQuerySqliteTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class AdHocQueryFiltersQuerySqliteTest : AdHocQueryFiltersQueryRelationalTestBase +public class AdHocQueryFiltersQuerySqliteTest(NonSharedFixture fixture) : AdHocQueryFiltersQueryRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocQuerySplittingQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocQuerySplittingQuerySqliteTest.cs index 0889d679be4..904282336db 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocQuerySplittingQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocQuerySplittingQuerySqliteTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class AdHocQuerySplittingQuerySqliteTest : AdHocQuerySplittingQueryTestBase +public class AdHocQuerySplittingQuerySqliteTest(NonSharedFixture fixture) : AdHocQuerySplittingQueryTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/BadDataSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/BadDataSqliteTest.cs index 7739782e86e..d7f89d46f82 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/BadDataSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/BadDataSqliteTest.cs @@ -130,13 +130,14 @@ private class BadDataRelationalCommandBuilder( private readonly object[] _values = values; public override IRelationalCommand Build() - => new BadDataRelationalCommand(Dependencies, ToString(), Parameters, _values); + => new BadDataRelationalCommand(Dependencies, ToString(), ToString(), Parameters, _values); private class BadDataRelationalCommand( RelationalCommandBuilderDependencies dependencies, string commandText, + string logCommandText, IReadOnlyList parameters, - object[] values) : RelationalCommand(dependencies, commandText, parameters) + object[] values) : RelationalCommand(dependencies, commandText, logCommandText, parameters) { private object[] _values = values; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/EntitySplittingQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/EntitySplittingQuerySqliteTest.cs index 9d5014176d2..40fa28d41c7 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/EntitySplittingQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/EntitySplittingQuerySqliteTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class EntitySplittingQuerySqliteTest : EntitySplittingQueryTestBase +public class EntitySplittingQuerySqliteTest(NonSharedFixture fixture) : EntitySplittingQueryTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs index 36c401f268e..ebfc82e21ec 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class NonSharedPrimitiveCollectionsQuerySqliteTest : NonSharedPrimitiveCollectionsQueryRelationalTestBase +public class NonSharedPrimitiveCollectionsQuerySqliteTest(NonSharedFixture fixture) : NonSharedPrimitiveCollectionsQueryRelationalTestBase(fixture) { protected override DbContextOptionsBuilder SetTranslateParameterizedCollectionsToConstants(DbContextOptionsBuilder optionsBuilder) { diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/OperatorsProceduralSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/OperatorsProceduralSqliteTest.cs index be993ca09b0..1bbbb354b43 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/OperatorsProceduralSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/OperatorsProceduralSqliteTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class OperatorsProceduralSqliteTest : OperatorsProceduralQueryTestBase +public class OperatorsProceduralSqliteTest(NonSharedFixture fixture) : OperatorsProceduralQueryTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/OperatorsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/OperatorsQuerySqliteTest.cs index 321d440275b..9b7d7e3957f 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/OperatorsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/OperatorsQuerySqliteTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class OperatorsQuerySqliteTest : OperatorsQueryTestBase +public class OperatorsQuerySqliteTest(NonSharedFixture fixture) : OperatorsQueryTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/OwnedEntityQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/OwnedEntityQuerySqliteTest.cs index 9090b685cd9..d9f43403d3d 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/OwnedEntityQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/OwnedEntityQuerySqliteTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class OwnedEntityQuerySqliteTest : OwnedEntityQueryRelationalTestBase +public class OwnedEntityQuerySqliteTest(NonSharedFixture fixture) : OwnedEntityQueryRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/JsonRelationshipsQuerySqliteFixture.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexRelationshipsSqliteFixture.cs similarity index 77% rename from test/EFCore.Sqlite.FunctionalTests/Query/Relationships/JsonRelationshipsQuerySqliteFixture.cs rename to test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexRelationshipsSqliteFixture.cs index e6ac7ee8cde..60cb1fb42b3 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/JsonRelationshipsQuerySqliteFixture.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexRelationshipsSqliteFixture.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public class JsonRelationshipsQuerySqliteFixture : JsonRelationshipsQueryRelationalFixtureBase, ITestSqlLoggerFactory +public class ComplexRelationshipsSqliteFixture : ComplexRelationsjipsRelationalFixtureBase, ITestSqlLoggerFactory { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionNoTrackingQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionNoTrackingQuerySqliteTest.cs deleted file mode 100644 index 0e1b771a6ef..00000000000 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionNoTrackingQuerySqliteTest.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public class ComplexRelationshipsInProjectionNoTrackingQuerySqliteTest - : ComplexRelationshipsInProjectionNoTrackingQueryRelationalTestBase -{ - public ComplexRelationshipsInProjectionNoTrackingQuerySqliteTest(ComplexRelationshipsQuerySqliteFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - private void AssertSql(params string[] expected) - => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); -} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionQuerySqliteTest.cs deleted file mode 100644 index f72c0a8b826..00000000000 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/ComplexRelationshipsInProjectionQuerySqliteTest.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public class ComplexRelationshipsInProjectionQuerySqliteTest - : ComplexRelationshipsInProjectionQueryRelationalTestBase -{ - public ComplexRelationshipsInProjectionQuerySqliteTest(ComplexRelationshipsQuerySqliteFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - private void AssertSql(params string[] expected) - => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); -} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/EntityRelationshipsInProjectionNoTrackingQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/EntityRelationshipsInProjectionNoTrackingQuerySqliteTest.cs deleted file mode 100644 index f3713f77b58..00000000000 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/EntityRelationshipsInProjectionNoTrackingQuerySqliteTest.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.EntityFrameworkCore.Sqlite.Internal; - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public class EntityRelationshipsInProjectionNoTrackingQuerySqliteTest - : EntityRelationshipsInProjectionNoTrackingQueryRelationalTestBase -{ - public EntityRelationshipsInProjectionNoTrackingQuerySqliteTest(EntityRelationshipsQuerySqliteFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - public override async Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async))) - .Message); - - public override async Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async))) - .Message); - - public override async Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(async))) - .Message); - - public override async Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_required_trunk_FirstOrDefault_branch(async))) - .Message); - - public override async Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_trunk_FirstOrDefault_collection(async))) - .Message); -} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/EntityRelationshipsInProjectionQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/EntityRelationshipsInProjectionQuerySqliteTest.cs deleted file mode 100644 index a469c8be6fb..00000000000 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/EntityRelationshipsInProjectionQuerySqliteTest.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.EntityFrameworkCore.Sqlite.Internal; - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public class EntityRelationshipsInProjectionQuerySqliteTest - : EntityRelationshipsInProjectionQueryRelationalTestBase -{ - public EntityRelationshipsInProjectionQuerySqliteTest(EntityRelationshipsQuerySqliteFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - public override async Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async))) - .Message); - - public override async Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async))) - .Message); - - public override async Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(async))) - .Message); - - public override async Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_required_trunk_FirstOrDefault_branch(async))) - .Message); - - public override async Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_trunk_FirstOrDefault_collection(async))) - .Message); -} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/JsonRelationshipsInProjectionNoTrackingQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/JsonRelationshipsInProjectionNoTrackingQuerySqliteTest.cs deleted file mode 100644 index 77d93601165..00000000000 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/JsonRelationshipsInProjectionNoTrackingQuerySqliteTest.cs +++ /dev/null @@ -1,254 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.EntityFrameworkCore.Sqlite.Internal; - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public class JsonRelationshipsInProjectionNoTrackingQuerySqliteTest - : JsonRelationshipsInProjectionNoTrackingQueryRelationalTestBase -{ - public JsonRelationshipsInProjectionNoTrackingQuerySqliteTest(JsonRelationshipsQuerySqliteFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - public override async Task Project_root(bool async) - { - await base.Project_root(async); - - AssertSql( - """ -SELECT "r"."Id", "r"."Name", "r"."OptionalReferenceTrunkId", "r"."RequiredReferenceTrunkId", "r"."CollectionTrunk", "r"."OptionalReferenceTrunk", "r"."RequiredReferenceTrunk" -FROM "RootEntities" AS "r" -"""); - } - - public override async Task Project_trunk_optional(bool async) - { - await base.Project_trunk_optional(async); - - AssertSql( - """ -SELECT "r"."OptionalReferenceTrunk", "r"."Id" -FROM "RootEntities" AS "r" -ORDER BY "r"."Id" -"""); - } - - public override async Task Project_trunk_required(bool async) - { - await base.Project_trunk_required(async); - - AssertSql( - """ -SELECT "r"."RequiredReferenceTrunk", "r"."Id" -FROM "RootEntities" AS "r" -ORDER BY "r"."Id" -"""); - } - - public override async Task Project_trunk_collection(bool async) - { - await base.Project_trunk_collection(async); - - AssertSql( - """ -SELECT "r"."CollectionTrunk", "r"."Id" -FROM "RootEntities" AS "r" -ORDER BY "r"."Id" -"""); - } - - public override async Task Project_branch_required_required(bool async) - { - await base.Project_branch_required_required(async); - - AssertSql( - """ -SELECT "r"."RequiredReferenceTrunk" ->> 'RequiredReferenceBranch', "r"."Id" -FROM "RootEntities" AS "r" -ORDER BY "r"."Id" -"""); - } - - public override async Task Project_branch_required_optional(bool async) - { - await base.Project_branch_required_optional(async); - - AssertSql( - """ -SELECT "r"."RequiredReferenceTrunk" ->> 'OptionalReferenceBranch', "r"."Id" -FROM "RootEntities" AS "r" -ORDER BY "r"."Id" -"""); - } - - public override async Task Project_branch_required_collection(bool async) - { - await base.Project_branch_required_collection(async); - - AssertSql( - """ -SELECT "r"."RequiredReferenceTrunk" ->> 'CollectionBranch', "r"."Id" -FROM "RootEntities" AS "r" -ORDER BY "r"."Id" -"""); - } - - public override async Task Project_branch_optional_required(bool async) - { - await base.Project_branch_optional_required(async); - - AssertSql( - """ -SELECT "r"."RequiredReferenceTrunk" ->> 'RequiredReferenceBranch', "r"."Id" -FROM "RootEntities" AS "r" -ORDER BY "r"."Id" -"""); - } - - public override async Task Project_branch_optional_optional(bool async) - { - await base.Project_branch_optional_optional(async); - - AssertSql( - """ -SELECT "r"."RequiredReferenceTrunk" ->> 'OptionalReferenceBranch', "r"."Id" -FROM "RootEntities" AS "r" -ORDER BY "r"."Id" -"""); - } - - public override async Task Project_branch_optional_collection(bool async) - { - await base.Project_branch_optional_collection(async); - - AssertSql( - """ -SELECT "r"."RequiredReferenceTrunk" ->> 'CollectionBranch', "r"."Id" -FROM "RootEntities" AS "r" -ORDER BY "r"."Id" -"""); - } - - public override async Task Project_root_duplicated(bool async) - { - await base.Project_root_duplicated(async); - - AssertSql( - """ -SELECT "r"."Id", "r"."Name", "r"."OptionalReferenceTrunkId", "r"."RequiredReferenceTrunkId", "r"."CollectionTrunk", "r"."OptionalReferenceTrunk", "r"."RequiredReferenceTrunk", "r"."CollectionTrunk", "r"."OptionalReferenceTrunk", "r"."RequiredReferenceTrunk" -FROM "RootEntities" AS "r" -"""); - } - - public override async Task Project_trunk_and_branch_duplicated(bool async) - { - await base.Project_trunk_and_branch_duplicated(async); - - AssertSql( - """ -SELECT "r"."OptionalReferenceTrunk", "r"."Id", "r"."OptionalReferenceTrunk" ->> 'RequiredReferenceBranch', "r"."OptionalReferenceTrunk", "r"."OptionalReferenceTrunk" ->> 'RequiredReferenceBranch' -FROM "RootEntities" AS "r" -ORDER BY "r"."Id" -"""); - } - - public override async Task Project_trunk_and_trunk_duplicated(bool async) - { - await base.Project_trunk_and_trunk_duplicated(async); - - AssertSql( - """ -SELECT "r"."RequiredReferenceTrunk", "r"."Id", "r"."RequiredReferenceTrunk" ->> '$.OptionalReferenceBranch.RequiredReferenceLeaf', "r"."RequiredReferenceTrunk", "r"."RequiredReferenceTrunk" ->> '$.OptionalReferenceBranch.RequiredReferenceLeaf' -FROM "RootEntities" AS "r" -ORDER BY "r"."Id" -"""); - } - - public override async Task Project_multiple_branch_leaf(bool async) - { - await base.Project_multiple_branch_leaf(async); - - AssertSql( - """ -SELECT "r"."Id", "r"."RequiredReferenceTrunk" ->> 'RequiredReferenceBranch', "r"."RequiredReferenceTrunk" ->> '$.RequiredReferenceBranch.OptionalReferenceLeaf', "r"."RequiredReferenceTrunk" ->> '$.RequiredReferenceBranch.CollectionLeaf', "r"."RequiredReferenceTrunk" ->> 'CollectionBranch', "r"."RequiredReferenceTrunk" ->> '$.RequiredReferenceBranch.OptionalReferenceLeaf.Name' -FROM "RootEntities" AS "r" -"""); - } - - public override async Task Project_leaf_trunk_root(bool async) - { - await base.Project_leaf_trunk_root(async); - - AssertSql( - """ -SELECT "r"."RequiredReferenceTrunk" ->> '$.RequiredReferenceBranch.RequiredReferenceLeaf', "r"."Id", "r"."RequiredReferenceTrunk", "r"."Name", "r"."OptionalReferenceTrunkId", "r"."RequiredReferenceTrunkId", "r"."CollectionTrunk", "r"."OptionalReferenceTrunk", "r"."RequiredReferenceTrunk" -FROM "RootEntities" AS "r" -"""); - } - - public override async Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async))) - .Message); - - public override async Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async))) - .Message); - - public override async Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(async))) - .Message); - - public override async Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_required_trunk_FirstOrDefault_branch(async))) - .Message); - - public override async Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_trunk_FirstOrDefault_collection(async))) - .Message); - - public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.SelectMany_optional_trunk_reference_branch_collection(async))) - .Message); - - - public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.SelectMany_required_trunk_reference_branch_collection(async))) - .Message); - - - public override async Task SelectMany_trunk_collection(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.SelectMany_trunk_collection(async))) - .Message); - - private void AssertSql(params string[] expected) - => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); -} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/JsonRelationshipsInProjectionQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/JsonRelationshipsInProjectionQuerySqliteTest.cs deleted file mode 100644 index 9345b9c0eca..00000000000 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/JsonRelationshipsInProjectionQuerySqliteTest.cs +++ /dev/null @@ -1,150 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.EntityFrameworkCore.Sqlite.Internal; - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public class JsonRelationshipsInProjectionQuerySqliteTest - : JsonRelationshipsInProjectionQueryRelationalTestBase -{ - public JsonRelationshipsInProjectionQuerySqliteTest(JsonRelationshipsQuerySqliteFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - public override async Task Project_root(bool async) - { - await base.Project_root(async); - - AssertSql( - """ -SELECT "r"."Id", "r"."Name", "r"."OptionalReferenceTrunkId", "r"."RequiredReferenceTrunkId", "r"."CollectionTrunk", "r"."OptionalReferenceTrunk", "r"."RequiredReferenceTrunk" -FROM "RootEntities" AS "r" -"""); - } - - public override async Task Project_root_duplicated(bool async) - { - await base.Project_root_duplicated(async); - - AssertSql( - """ -SELECT "r"."Id", "r"."Name", "r"."OptionalReferenceTrunkId", "r"."RequiredReferenceTrunkId", "r"."CollectionTrunk", "r"."OptionalReferenceTrunk", "r"."RequiredReferenceTrunk", "r"."CollectionTrunk", "r"."OptionalReferenceTrunk", "r"."RequiredReferenceTrunk" -FROM "RootEntities" AS "r" -"""); - } - - public override Task Project_trunk_optional(bool async) - => AssertCantTrackJson(() => base.Project_trunk_optional(async)); - - public override Task Project_trunk_required(bool async) - => AssertCantTrackJson(() => base.Project_trunk_required(async)); - - public override Task Project_trunk_collection(bool async) - => AssertCantTrackJson(() => base.Project_trunk_collection(async)); - - public override Task Project_branch_required_required(bool async) - => AssertCantTrackJson(() => base.Project_branch_required_required(async)); - - public override Task Project_branch_required_optional(bool async) - => AssertCantTrackJson(() => base.Project_branch_required_optional(async)); - - public override Task Project_branch_required_collection(bool async) - => AssertCantTrackJson(() => base.Project_branch_required_collection(async)); - - public override Task Project_branch_optional_required(bool async) - => AssertCantTrackJson(() => base.Project_branch_optional_required(async)); - - public override Task Project_branch_optional_optional(bool async) - => AssertCantTrackJson(() => base.Project_branch_optional_optional(async)); - - public override Task Project_branch_optional_collection(bool async) - => AssertCantTrackJson(() => base.Project_branch_optional_collection(async)); - - public override Task Project_branch_collection_element_using_indexer_constant(bool async) - => AssertCantTrackJson(() => base.Project_branch_collection_element_using_indexer_constant(async)); - - public override Task Project_leaf_trunk_root(bool async) - => AssertCantTrackJson(() => base.Project_leaf_trunk_root(async)); - - public override Task Project_multiple_branch_leaf(bool async) - => AssertCantTrackJson(() => base.Project_multiple_branch_leaf(async)); - - public override Task Project_trunk_and_branch_duplicated(bool async) - => AssertCantTrackJson(() => base.Project_trunk_and_branch_duplicated(async)); - - public override Task Project_trunk_and_trunk_duplicated(bool async) - => AssertCantTrackJson(() => base.Project_trunk_and_trunk_duplicated(async)); - - public override async Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async))) - .Message); - - public override async Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async))) - .Message); - - public override async Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(async))) - .Message); - - public override async Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_required_trunk_FirstOrDefault_branch(async))) - .Message); - - public override async Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.Project_subquery_root_set_trunk_FirstOrDefault_collection(async))) - .Message); - - public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.SelectMany_optional_trunk_reference_branch_collection(async))) - .Message); - - - public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.SelectMany_required_trunk_reference_branch_collection(async))) - .Message); - - - public override async Task SelectMany_trunk_collection(bool async) - => Assert.Equal( - SqliteStrings.ApplyNotSupported, - (await Assert.ThrowsAsync( - () => base.SelectMany_trunk_collection(async))) - .Message); - - private async Task AssertCantTrackJson(Func test) - { - var message = (await Assert.ThrowsAsync(test)).Message; - - Assert.Equal(RelationalStrings.JsonEntityOrCollectionProjectedAtRootLevelInTrackingQuery("AsNoTracking"), message); - AssertSql(); - } - - private void AssertSql(params string[] expected) - => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); -} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionNoTrackingQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionNoTrackingQuerySqliteTest.cs deleted file mode 100644 index e290a4d7040..00000000000 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionNoTrackingQuerySqliteTest.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public class OwnedRelationshipsInProjectionNoTrackingQuerySqliteTest - : OwnedRelationshipsInProjectionNoTrackingQueryRelationalTestBase -{ - public OwnedRelationshipsInProjectionNoTrackingQuerySqliteTest(OwnedRelationshipsQuerySqliteFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_root(bool async) - => base.Project_root(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_trunk_optional(bool async) - => base.Project_trunk_optional(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_trunk_required(bool async) - => base.Project_trunk_required(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_trunk_collection(bool async) - => base.Project_trunk_collection(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_branch_required_required(bool async) - => base.Project_branch_required_required(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_branch_required_optional(bool async) - => base.Project_branch_required_optional(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_branch_required_collection(bool async) - => base.Project_branch_required_collection(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_branch_optional_required(bool async) - => base.Project_branch_optional_required(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_branch_optional_optional(bool async) - => base.Project_branch_optional_optional(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_branch_optional_collection(bool async) - => base.Project_branch_optional_collection(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_root_duplicated(bool async) - => base.Project_root_duplicated(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_trunk_and_branch_duplicated(bool async) - => base.Project_trunk_and_branch_duplicated(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_trunk_and_trunk_duplicated(bool async) - => base.Project_trunk_and_trunk_duplicated(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_multiple_branch_leaf(bool async) - => base.Project_multiple_branch_leaf(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_leaf_trunk_root(bool async) - => base.Project_leaf_trunk_root(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) - => base.Project_subquery_root_set_required_trunk_FirstOrDefault_branch(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) - => base.Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - => base.Project_subquery_root_set_trunk_FirstOrDefault_collection(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - => base.Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - => base.Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task SelectMany_trunk_collection(bool async) - => base.SelectMany_trunk_collection(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task SelectMany_required_trunk_reference_branch_collection(bool async) - => base.SelectMany_required_trunk_reference_branch_collection(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task SelectMany_optional_trunk_reference_branch_collection(bool async) - => base.SelectMany_optional_trunk_reference_branch_collection(async); -} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionQuerySqliteTest.cs deleted file mode 100644 index bb3c6082424..00000000000 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/InProjection/OwnedRelationshipsInProjectionQuerySqliteTest.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Query.Relationships.InProjection; - -public class OwnedRelationshipsInProjectionQuerySqliteTest - : OwnedRelationshipsInProjectionQueryRelationalTestBase -{ - public OwnedRelationshipsInProjectionQuerySqliteTest(OwnedRelationshipsQuerySqliteFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_root(bool async) - => base.Project_root(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_trunk_optional(bool async) - => base.Project_trunk_optional(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_trunk_required(bool async) - => base.Project_trunk_required(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_trunk_collection(bool async) - => base.Project_trunk_collection(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_branch_required_required(bool async) - => base.Project_branch_required_required(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_branch_required_optional(bool async) - => base.Project_branch_required_optional(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_branch_required_collection(bool async) - => base.Project_branch_required_collection(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_branch_optional_required(bool async) - => base.Project_branch_optional_required(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_branch_optional_optional(bool async) - => base.Project_branch_optional_optional(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_branch_optional_collection(bool async) - => base.Project_branch_optional_collection(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_root_duplicated(bool async) - => base.Project_root_duplicated(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_trunk_and_branch_duplicated(bool async) - => base.Project_trunk_and_branch_duplicated(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_trunk_and_trunk_duplicated(bool async) - => base.Project_trunk_and_trunk_duplicated(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_multiple_branch_leaf(bool async) - => base.Project_multiple_branch_leaf(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_leaf_trunk_root(bool async) - => base.Project_leaf_trunk_root(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) - => base.Project_subquery_root_set_required_trunk_FirstOrDefault_branch(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) - => base.Project_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_subquery_root_set_trunk_FirstOrDefault_collection(bool async) - => base.Project_subquery_root_set_trunk_FirstOrDefault_collection(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) - => base.Project_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) - => base.Project_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task SelectMany_trunk_collection(bool async) - => base.SelectMany_trunk_collection(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task SelectMany_required_trunk_reference_branch_collection(bool async) - => base.SelectMany_required_trunk_reference_branch_collection(async); - - [ConditionalTheory(Skip = "issue 26708")] - public override Task SelectMany_optional_trunk_reference_branch_collection(bool async) - => base.SelectMany_optional_trunk_reference_branch_collection(async); -} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Include/EntityRelationshipsIncludeQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Include/NavigationIncludeSqliteTest.cs similarity index 56% rename from test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Include/EntityRelationshipsIncludeQuerySqliteTest.cs rename to test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Include/NavigationIncludeSqliteTest.cs index 6215b083956..7e0896d0b47 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Include/EntityRelationshipsIncludeQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Include/NavigationIncludeSqliteTest.cs @@ -3,10 +3,10 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships.Include; -public class EntityRelationshipsIncludeQuerySqliteTest - : EntityRelationshipsIncludeQueryRelationalTestBase +public class NavigationIncludeSqliteTest + : NavigationIncludeRelationalTestBase { - public EntityRelationshipsIncludeQuerySqliteTest(EntityRelationshipsQuerySqliteFixture fixture, ITestOutputHelper testOutputHelper) + public NavigationIncludeSqliteTest(NavigationRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) { Fixture.TestSqlLoggerFactory.Clear(); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/EntityRelationshipsQuerySqliteFixture.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/NavigationRelationshipsSqliteFixture.cs similarity index 77% rename from test/EFCore.Sqlite.FunctionalTests/Query/Relationships/EntityRelationshipsQuerySqliteFixture.cs rename to test/EFCore.Sqlite.FunctionalTests/Query/Relationships/NavigationRelationshipsSqliteFixture.cs index f322b0ae778..54f04a66274 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/EntityRelationshipsQuerySqliteFixture.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/NavigationRelationshipsSqliteFixture.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public class EntityRelationshipsQuerySqliteFixture : EntityRelationshipsQueryRelationalFixtureBase, ITestSqlLoggerFactory +public class NavigationRelationshipsSqliteFixture : NavigationRelationshipsRelationalFixtureBase, ITestSqlLoggerFactory { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexRelationshipsQuerySqliteFixture.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedJsonRelationshipsSqliteFixture.cs similarity index 76% rename from test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexRelationshipsQuerySqliteFixture.cs rename to test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedJsonRelationshipsSqliteFixture.cs index 0da93356f17..fb692c7111b 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexRelationshipsQuerySqliteFixture.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedJsonRelationshipsSqliteFixture.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public class ComplexRelationshipsQuerySqliteFixture : ComplexRelationshipsQueryRelationalFixtureBase, ITestSqlLoggerFactory +public class OwnedJsonRelationshipsSqliteFixture : OwnedJsonRelationshipsRelationalFixtureBase, ITestSqlLoggerFactory { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedRelationshipsQuerySqliteFixture.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedRelationshipsSqliteFixture.cs similarity index 83% rename from test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedRelationshipsQuerySqliteFixture.cs rename to test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedRelationshipsSqliteFixture.cs index 733591a11ae..7015a6ff4df 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedRelationshipsQuerySqliteFixture.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedRelationshipsSqliteFixture.cs @@ -4,7 +4,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; -public class OwnedRelationshipsQuerySqliteFixture : OwnedRelationshipsQueryRelationalFixtureBase, ITestSqlLoggerFactory +public class OwnedRelationshipsSqliteFixture : OwnedRelationshipsRelationalFixtureBase, ITestSqlLoggerFactory { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedTableSplittingRelationshipsSqliteFixture.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedTableSplittingRelationshipsSqliteFixture.cs new file mode 100644 index 00000000000..dac7a914345 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedTableSplittingRelationshipsSqliteFixture.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships; + +public class OwnedTableSplittingRelationshipsSqliteFixture : OwnedTableSplittingRelationshipsRelationalFixtureBase, ITestSqlLoggerFactory +{ + protected override ITestStoreFactory TestStoreFactory + => SqliteTestStoreFactory.Instance; + + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder.ConfigureWarnings(b => b.Ignore(SqliteEventId.CompositeKeyWithValueGeneration))); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/ComplexNoTrackingProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/ComplexNoTrackingProjectionSqliteTest.cs new file mode 100644 index 00000000000..20c2ab98928 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/ComplexNoTrackingProjectionSqliteTest.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class ComplexNoTrackingProjectionSqliteTest + : ComplexNoTrackingProjectionRelationalTestBase +{ + public ComplexNoTrackingProjectionSqliteTest(ComplexRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/ComplexProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/ComplexProjectionSqliteTest.cs new file mode 100644 index 00000000000..9f4eeb9c8e8 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/ComplexProjectionSqliteTest.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class ComplexProjectionSqliteTest + : ComplexProjectionRelationalTestBase +{ + public ComplexProjectionSqliteTest(ComplexRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/NavigationNoTrackingProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/NavigationNoTrackingProjectionSqliteTest.cs new file mode 100644 index 00000000000..c2eb5c7a7f2 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/NavigationNoTrackingProjectionSqliteTest.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Sqlite.Internal; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class NavigationNoTrackingProjectionSqliteTest + : NavigationNoTrackingProjectionRelationalTestBase +{ + public NavigationNoTrackingProjectionSqliteTest(NavigationRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async))) + .Message); + + public override async Task Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async))) + .Message); + + public override async Task Select_subquery_root_set_trunk_FirstOrDefault_collection(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_trunk_FirstOrDefault_collection(async))) + .Message); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/NavigationProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/NavigationProjectionSqliteTest.cs new file mode 100644 index 00000000000..c2cc4657d10 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/NavigationProjectionSqliteTest.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Sqlite.Internal; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class NavigationProjectionSqliteTest + : NavigationProjectionRelationalTestBase +{ + public NavigationProjectionSqliteTest(NavigationRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async))) + .Message); + + public override async Task Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async))) + .Message); + + public override async Task Select_subquery_root_set_trunk_FirstOrDefault_collection(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_trunk_FirstOrDefault_collection(async))) + .Message); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/NavigationReferenceNoTrackingProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/NavigationReferenceNoTrackingProjectionSqliteTest.cs new file mode 100644 index 00000000000..2e65e6cbabc --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/NavigationReferenceNoTrackingProjectionSqliteTest.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Sqlite.Internal; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class NavigationReferenceNoTrackingProjectionSqliteTest + : NavigationReferenceNoTrackingProjectionRelationalTestBase +{ + public NavigationReferenceNoTrackingProjectionSqliteTest(NavigationRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async))) + .Message); + + public override async Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async))) + .Message); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/NavigationReferenceProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/NavigationReferenceProjectionSqliteTest.cs new file mode 100644 index 00000000000..71fa016f661 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/NavigationReferenceProjectionSqliteTest.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Sqlite.Internal; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class NavigationReferenceProjectionSqliteTest + : NavigationReferenceProjectionRelationalTestBase +{ + public NavigationReferenceProjectionSqliteTest(NavigationRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async))) + .Message); + + public override async Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async))) + .Message); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedJsonNoTrackingProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedJsonNoTrackingProjectionSqliteTest.cs new file mode 100644 index 00000000000..c2742daecfa --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedJsonNoTrackingProjectionSqliteTest.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Sqlite.Internal; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedJsonNoTrackingProjectionSqliteTest + : OwnedJsonNoTrackingProjectionRelationalTestBase +{ + public OwnedJsonNoTrackingProjectionSqliteTest(OwnedJsonRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_trunk_collection(bool async) + { + await base.Select_trunk_collection(async); + + AssertSql( + """ +SELECT "r"."CollectionTrunk", "r"."Id" +FROM "RootEntities" AS "r" +ORDER BY "r"."Id" +"""); + } + + public override async Task Select_branch_required_collection(bool async) + { + await base.Select_branch_required_collection(async); + + AssertSql( + """ +SELECT "r"."RequiredReferenceTrunk" ->> 'CollectionBranch', "r"."Id" +FROM "RootEntities" AS "r" +ORDER BY "r"."Id" +"""); + } + + public override async Task Select_branch_optional_collection(bool async) + { + await base.Select_branch_optional_collection(async); + + AssertSql( + """ +SELECT "r"."RequiredReferenceTrunk" ->> 'CollectionBranch', "r"."Id" +FROM "RootEntities" AS "r" +ORDER BY "r"."Id" +"""); + } + + public override async Task Select_multiple_branch_leaf(bool async) + { + await base.Select_multiple_branch_leaf(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."RequiredReferenceTrunk" ->> 'RequiredReferenceBranch', "r"."RequiredReferenceTrunk" ->> '$.RequiredReferenceBranch.OptionalReferenceLeaf', "r"."RequiredReferenceTrunk" ->> '$.RequiredReferenceBranch.CollectionLeaf', "r"."RequiredReferenceTrunk" ->> 'CollectionBranch', "r"."RequiredReferenceTrunk" ->> '$.RequiredReferenceBranch.OptionalReferenceLeaf.Name' +FROM "RootEntities" AS "r" +"""); + } + + public override async Task Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async))) + .Message); + + public override async Task Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async))) + .Message); + + public override async Task Select_subquery_root_set_trunk_FirstOrDefault_collection(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_trunk_FirstOrDefault_collection(async))) + .Message); + + public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.SelectMany_optional_trunk_reference_branch_collection(async))) + .Message); + + public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.SelectMany_required_trunk_reference_branch_collection(async))) + .Message); + + public override async Task SelectMany_trunk_collection(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.SelectMany_trunk_collection(async))) + .Message); + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedJsonProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedJsonProjectionSqliteTest.cs new file mode 100644 index 00000000000..265accb6ee8 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedJsonProjectionSqliteTest.cs @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Sqlite.Internal; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedJsonProjectionSqliteTest + : OwnedJsonProjectionRelationalTestBase +{ + public OwnedJsonProjectionSqliteTest(OwnedJsonRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override Task Select_trunk_collection(bool async) + => AssertCantTrackJson(() => base.Select_trunk_collection(async)); + + public override Task Select_branch_required_collection(bool async) + => AssertCantTrackJson(() => base.Select_branch_required_collection(async)); + + public override Task Select_branch_optional_collection(bool async) + => AssertCantTrackJson(() => base.Select_branch_optional_collection(async)); + + public override Task Project_branch_collection_element_using_indexer_constant(bool async) + => AssertCantTrackJson(() => base.Project_branch_collection_element_using_indexer_constant(async)); + + public override Task Select_multiple_branch_leaf(bool async) + => AssertCantTrackJson(() => base.Select_multiple_branch_leaf(async)); + + public override async Task Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async))) + .Message); + + public override async Task Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async))) + .Message); + + public override async Task Select_subquery_root_set_trunk_FirstOrDefault_collection(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_trunk_FirstOrDefault_collection(async))) + .Message); + + public override async Task SelectMany_optional_trunk_reference_branch_collection(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.SelectMany_optional_trunk_reference_branch_collection(async))) + .Message); + + public override async Task SelectMany_required_trunk_reference_branch_collection(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.SelectMany_required_trunk_reference_branch_collection(async))) + .Message); + + public override async Task SelectMany_trunk_collection(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.SelectMany_trunk_collection(async))) + .Message); + + private async Task AssertCantTrackJson(Func test) + { + var message = (await Assert.ThrowsAsync(test)).Message; + + Assert.Equal(RelationalStrings.JsonEntityOrCollectionProjectedAtRootLevelInTrackingQuery("AsNoTracking"), message); + AssertSql(); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedJsonReferenceNoTrackingProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedJsonReferenceNoTrackingProjectionSqliteTest.cs new file mode 100644 index 00000000000..eb307a883bf --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedJsonReferenceNoTrackingProjectionSqliteTest.cs @@ -0,0 +1,163 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Sqlite.Internal; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedJsonReferenceNoTrackingProjectionSqliteTest + : OwnedJsonReferenceNoTrackingProjectionRelationalTestBase +{ + public OwnedJsonReferenceNoTrackingProjectionSqliteTest(OwnedJsonRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_root(bool async) + { + await base.Select_root(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalReferenceTrunkId", "r"."RequiredReferenceTrunkId", "r"."CollectionTrunk", "r"."OptionalReferenceTrunk", "r"."RequiredReferenceTrunk" +FROM "RootEntities" AS "r" +"""); + } + + public override async Task Select_trunk_optional(bool async) + { + await base.Select_trunk_optional(async); + + AssertSql( + """ +SELECT "r"."OptionalReferenceTrunk", "r"."Id" +FROM "RootEntities" AS "r" +ORDER BY "r"."Id" +"""); + } + + public override async Task Select_trunk_required(bool async) + { + await base.Select_trunk_required(async); + + AssertSql( + """ +SELECT "r"."RequiredReferenceTrunk", "r"."Id" +FROM "RootEntities" AS "r" +ORDER BY "r"."Id" +"""); + } + + public override async Task Select_branch_required_required(bool async) + { + await base.Select_branch_required_required(async); + + AssertSql( + """ +SELECT "r"."RequiredReferenceTrunk" ->> 'RequiredReferenceBranch', "r"."Id" +FROM "RootEntities" AS "r" +ORDER BY "r"."Id" +"""); + } + + public override async Task Select_branch_required_optional(bool async) + { + await base.Select_branch_required_optional(async); + + AssertSql( + """ +SELECT "r"."RequiredReferenceTrunk" ->> 'OptionalReferenceBranch', "r"."Id" +FROM "RootEntities" AS "r" +ORDER BY "r"."Id" +"""); + } + + public override async Task Select_branch_optional_required(bool async) + { + await base.Select_branch_optional_required(async); + + AssertSql( + """ +SELECT "r"."RequiredReferenceTrunk" ->> 'RequiredReferenceBranch', "r"."Id" +FROM "RootEntities" AS "r" +ORDER BY "r"."Id" +"""); + } + + public override async Task Select_branch_optional_optional(bool async) + { + await base.Select_branch_optional_optional(async); + + AssertSql( + """ +SELECT "r"."RequiredReferenceTrunk" ->> 'OptionalReferenceBranch', "r"."Id" +FROM "RootEntities" AS "r" +ORDER BY "r"."Id" +"""); + } + + public override async Task Select_root_duplicated(bool async) + { + await base.Select_root_duplicated(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalReferenceTrunkId", "r"."RequiredReferenceTrunkId", "r"."CollectionTrunk", "r"."OptionalReferenceTrunk", "r"."RequiredReferenceTrunk", "r"."CollectionTrunk", "r"."OptionalReferenceTrunk", "r"."RequiredReferenceTrunk" +FROM "RootEntities" AS "r" +"""); + } + + public override async Task Select_trunk_and_branch_duplicated(bool async) + { + await base.Select_trunk_and_branch_duplicated(async); + + AssertSql( + """ +SELECT "r"."OptionalReferenceTrunk", "r"."Id", "r"."OptionalReferenceTrunk" ->> 'RequiredReferenceBranch', "r"."OptionalReferenceTrunk", "r"."OptionalReferenceTrunk" ->> 'RequiredReferenceBranch' +FROM "RootEntities" AS "r" +ORDER BY "r"."Id" +"""); + } + + public override async Task Select_trunk_and_trunk_duplicated(bool async) + { + await base.Select_trunk_and_trunk_duplicated(async); + + AssertSql( + """ +SELECT "r"."RequiredReferenceTrunk", "r"."Id", "r"."RequiredReferenceTrunk" ->> '$.OptionalReferenceBranch.RequiredReferenceLeaf', "r"."RequiredReferenceTrunk", "r"."RequiredReferenceTrunk" ->> '$.OptionalReferenceBranch.RequiredReferenceLeaf' +FROM "RootEntities" AS "r" +ORDER BY "r"."Id" +"""); + } + + public override async Task Select_leaf_trunk_root(bool async) + { + await base.Select_leaf_trunk_root(async); + + AssertSql( + """ +SELECT "r"."RequiredReferenceTrunk" ->> '$.RequiredReferenceBranch.RequiredReferenceLeaf', "r"."Id", "r"."RequiredReferenceTrunk", "r"."Name", "r"."OptionalReferenceTrunkId", "r"."RequiredReferenceTrunkId", "r"."CollectionTrunk", "r"."OptionalReferenceTrunk", "r"."RequiredReferenceTrunk" +FROM "RootEntities" AS "r" +"""); + } + + public override async Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async))) + .Message); + + public override async Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async))) + .Message); + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedJsonReferenceProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedJsonReferenceProjectionSqliteTest.cs new file mode 100644 index 00000000000..0ceba3f140d --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedJsonReferenceProjectionSqliteTest.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Sqlite.Internal; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedJsonReferenceProjectionSqliteTest + : OwnedJsonReferenceProjectionRelationalTestBase +{ + public OwnedJsonReferenceProjectionSqliteTest(OwnedJsonRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Select_root(bool async) + { + await base.Select_root(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalReferenceTrunkId", "r"."RequiredReferenceTrunkId", "r"."CollectionTrunk", "r"."OptionalReferenceTrunk", "r"."RequiredReferenceTrunk" +FROM "RootEntities" AS "r" +"""); + } + + public override async Task Select_root_duplicated(bool async) + { + await base.Select_root_duplicated(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalReferenceTrunkId", "r"."RequiredReferenceTrunkId", "r"."CollectionTrunk", "r"."OptionalReferenceTrunk", "r"."RequiredReferenceTrunk", "r"."CollectionTrunk", "r"."OptionalReferenceTrunk", "r"."RequiredReferenceTrunk" +FROM "RootEntities" AS "r" +"""); + } + + public override Task Select_trunk_optional(bool async) + => AssertCantTrackJson(() => base.Select_trunk_optional(async)); + + public override Task Select_trunk_required(bool async) + => AssertCantTrackJson(() => base.Select_trunk_required(async)); + + public override Task Select_branch_required_required(bool async) + => AssertCantTrackJson(() => base.Select_branch_required_required(async)); + + public override Task Select_branch_required_optional(bool async) + => AssertCantTrackJson(() => base.Select_branch_required_optional(async)); + + public override Task Select_branch_optional_required(bool async) + => AssertCantTrackJson(() => base.Select_branch_optional_required(async)); + + public override Task Select_branch_optional_optional(bool async) + => AssertCantTrackJson(() => base.Select_branch_optional_optional(async)); + + public override Task Select_leaf_trunk_root(bool async) + => AssertCantTrackJson(() => base.Select_leaf_trunk_root(async)); + + public override Task Select_trunk_and_branch_duplicated(bool async) + => AssertCantTrackJson(() => base.Select_trunk_and_branch_duplicated(async)); + + public override Task Select_trunk_and_trunk_duplicated(bool async) + => AssertCantTrackJson(() => base.Select_trunk_and_trunk_duplicated(async)); + + public override async Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async))) + .Message); + + public override async Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async))) + .Message); + + private async Task AssertCantTrackJson(Func test) + { + var message = (await Assert.ThrowsAsync(test)).Message; + + Assert.Equal(RelationalStrings.JsonEntityOrCollectionProjectedAtRootLevelInTrackingQuery("AsNoTracking"), message); + AssertSql(); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedNoTrackingProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedNoTrackingProjectionSqliteTest.cs new file mode 100644 index 00000000000..d4abbae84d6 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedNoTrackingProjectionSqliteTest.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedNoTrackingProjectionSqliteTest + : OwnedNoTrackingProjectionRelationalTestBase +{ + public OwnedNoTrackingProjectionSqliteTest(OwnedRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_trunk_collection(bool async) + => base.Select_trunk_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_required_collection(bool async) + => base.Select_branch_required_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_optional_collection(bool async) + => base.Select_branch_optional_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_multiple_branch_leaf(bool async) + => base.Select_multiple_branch_leaf(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_subquery_root_set_trunk_FirstOrDefault_collection(bool async) + => base.Select_subquery_root_set_trunk_FirstOrDefault_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) + => base.Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) + => base.Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task SelectMany_trunk_collection(bool async) + => base.SelectMany_trunk_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task SelectMany_required_trunk_reference_branch_collection(bool async) + => base.SelectMany_required_trunk_reference_branch_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task SelectMany_optional_trunk_reference_branch_collection(bool async) + => base.SelectMany_optional_trunk_reference_branch_collection(async); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedProjectionSqliteTest.cs new file mode 100644 index 00000000000..58204ff9f2c --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedProjectionSqliteTest.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedProjectionSqliteTest + : OwnedProjectionRelationalTestBase +{ + public OwnedProjectionSqliteTest(OwnedRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_trunk_collection(bool async) + => base.Select_trunk_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_required_collection(bool async) + => base.Select_branch_required_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_optional_collection(bool async) + => base.Select_branch_optional_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_multiple_branch_leaf(bool async) + => base.Select_multiple_branch_leaf(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_subquery_root_set_trunk_FirstOrDefault_collection(bool async) + => base.Select_subquery_root_set_trunk_FirstOrDefault_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) + => base.Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) + => base.Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task SelectMany_trunk_collection(bool async) + => base.SelectMany_trunk_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task SelectMany_required_trunk_reference_branch_collection(bool async) + => base.SelectMany_required_trunk_reference_branch_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task SelectMany_optional_trunk_reference_branch_collection(bool async) + => base.SelectMany_optional_trunk_reference_branch_collection(async); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedReferenceNoTrackingProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedReferenceNoTrackingProjectionSqliteTest.cs new file mode 100644 index 00000000000..e9aa3b7256b --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedReferenceNoTrackingProjectionSqliteTest.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedReferenceNoTrackingProjectionSqliteTest + : OwnedReferenceNoTrackingProjectionRelationalTestBase +{ + public OwnedReferenceNoTrackingProjectionSqliteTest(OwnedRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_root(bool async) + => base.Select_root(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_trunk_optional(bool async) + => base.Select_trunk_optional(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_trunk_required(bool async) + => base.Select_trunk_required(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_required_required(bool async) + => base.Select_branch_required_required(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_required_optional(bool async) + => base.Select_branch_required_optional(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_optional_required(bool async) + => base.Select_branch_optional_required(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_optional_optional(bool async) + => base.Select_branch_optional_optional(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_root_duplicated(bool async) + => base.Select_root_duplicated(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_trunk_and_branch_duplicated(bool async) + => base.Select_trunk_and_branch_duplicated(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_trunk_and_trunk_duplicated(bool async) + => base.Select_trunk_and_trunk_duplicated(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_leaf_trunk_root(bool async) + => base.Select_leaf_trunk_root(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + => base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + => base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedReferenceProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedReferenceProjectionSqliteTest.cs new file mode 100644 index 00000000000..380c7fa854d --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedReferenceProjectionSqliteTest.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedReferenceProjectionSqliteTest + : OwnedReferenceProjectionRelationalTestBase +{ + public OwnedReferenceProjectionSqliteTest(OwnedRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_root(bool async) + => base.Select_root(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_trunk_optional(bool async) + => base.Select_trunk_optional(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_trunk_required(bool async) + => base.Select_trunk_required(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_required_required(bool async) + => base.Select_branch_required_required(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_required_optional(bool async) + => base.Select_branch_required_optional(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_optional_required(bool async) + => base.Select_branch_optional_required(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_optional_optional(bool async) + => base.Select_branch_optional_optional(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_root_duplicated(bool async) + => base.Select_root_duplicated(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_trunk_and_branch_duplicated(bool async) + => base.Select_trunk_and_branch_duplicated(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_trunk_and_trunk_duplicated(bool async) + => base.Select_trunk_and_trunk_duplicated(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_leaf_trunk_root(bool async) + => base.Select_leaf_trunk_root(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + => base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + => base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedTableSplittingNoTrackingProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedTableSplittingNoTrackingProjectionSqliteTest.cs new file mode 100644 index 00000000000..75b85b0c71e --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedTableSplittingNoTrackingProjectionSqliteTest.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedTableSplittingNoTrackingProjectionSqliteTest + : OwnedTableSplittingNoTrackingProjectionRelationalTestBase +{ + public OwnedTableSplittingNoTrackingProjectionSqliteTest(OwnedTableSplittingRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_trunk_collection(bool async) + => base.Select_trunk_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_required_collection(bool async) + => base.Select_branch_required_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_optional_collection(bool async) + => base.Select_branch_optional_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_multiple_branch_leaf(bool async) + => base.Select_multiple_branch_leaf(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_subquery_root_set_trunk_FirstOrDefault_collection(bool async) + => base.Select_subquery_root_set_trunk_FirstOrDefault_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(bool async) + => base.Select_subquery_root_set_complex_projection_including_references_to_outer_FirstOrDefault(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(bool async) + => base.Select_subquery_root_set_complex_projection_FirstOrDefault_project_reference_to_outer(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task SelectMany_trunk_collection(bool async) + => base.SelectMany_trunk_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task SelectMany_required_trunk_reference_branch_collection(bool async) + => base.SelectMany_required_trunk_reference_branch_collection(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task SelectMany_optional_trunk_reference_branch_collection(bool async) + => base.SelectMany_optional_trunk_reference_branch_collection(async); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedTableSplittingReferenceProjectionNoTrackingSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedTableSplittingReferenceProjectionNoTrackingSqliteTest.cs new file mode 100644 index 00000000000..7f7068f18cb --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Projection/OwnedTableSplittingReferenceProjectionNoTrackingSqliteTest.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Projection; + +public class OwnedTableSplittingReferenceProjectionNoTrackingSqliteTest + : OwnedTableSplittingReferenceProjectionNoTrackingRelationalTestBase +{ + public OwnedTableSplittingReferenceProjectionNoTrackingSqliteTest(OwnedTableSplittingRelationshipsSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_root(bool async) + => base.Select_root(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_trunk_optional(bool async) + => base.Select_trunk_optional(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_trunk_required(bool async) + => base.Select_trunk_required(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_required_required(bool async) + => base.Select_branch_required_required(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_required_optional(bool async) + => base.Select_branch_required_optional(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_optional_required(bool async) + => base.Select_branch_optional_required(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_branch_optional_optional(bool async) + => base.Select_branch_optional_optional(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_root_duplicated(bool async) + => base.Select_root_duplicated(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_trunk_and_branch_duplicated(bool async) + => base.Select_trunk_and_branch_duplicated(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_trunk_and_trunk_duplicated(bool async) + => base.Select_trunk_and_trunk_duplicated(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_leaf_trunk_root(bool async) + => base.Select_leaf_trunk_root(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_subquery_root_set_required_trunk_FirstOrDefault_branch(bool async) + => base.Select_subquery_root_set_required_trunk_FirstOrDefault_branch(async); + + [ConditionalTheory(Skip = "issue 26708")] + public override Task Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(bool async) + => base.Select_subquery_root_set_optional_trunk_FirstOrDefault_branch(async); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/SharedTypeQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/SharedTypeQuerySqliteTest.cs index 48dd1c39d96..dda374a5b82 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/SharedTypeQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/SharedTypeQuerySqliteTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class SharedTypeQuerySqliteTest : SharedTypeQueryRelationalTestBase +public class SharedTypeQuerySqliteTest(NonSharedFixture fixture) : SharedTypeQueryRelationalTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ToSqlQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ToSqlQuerySqliteTest.cs index c87ab0aec5a..2a2c6730b71 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/ToSqlQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ToSqlQuerySqliteTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query; #nullable disable -public class ToSqlQuerySqliteTest : ToSqlQueryTestBase +public class ToSqlQuerySqliteTest(NonSharedFixture fixture) : ToSqlQueryTestBase(fixture) { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Translations/StringTranslationsSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Translations/StringTranslationsSqliteTest.cs index 58a5dc22eb1..d5b76fa9426 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Translations/StringTranslationsSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Translations/StringTranslationsSqliteTest.cs @@ -134,6 +134,18 @@ WHERE instr("b"."String", 'eattl') - 1 <> -1 """); } + public override async Task IndexOf_Char(bool async) + { + await base.IndexOf_Char(async); + + AssertSql( + """ +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE instr("b"."String", 'e') - 1 <> -1 +"""); + } + public override async Task IndexOf_with_empty_string(bool async) { await base.IndexOf_with_empty_string(async); @@ -160,11 +172,71 @@ WHERE instr("b"."String", @pattern) - 1 = 1 """); } - public override Task IndexOf_with_constant_starting_position(bool async) - => AssertTranslationFailed(() => base.IndexOf_with_constant_starting_position(async)); + public override async Task IndexOf_with_one_parameter_arg_char(bool async) + { + await base.IndexOf_with_one_parameter_arg_char(async); + + AssertSql( + """ +@pattern='e' (DbType = String) + +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE instr("b"."String", @pattern) - 1 = 1 +"""); + } + + public override async Task IndexOf_with_constant_starting_position(bool async) + { + await base.IndexOf_with_constant_starting_position(async); + + AssertSql( + """ +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE length("b"."String") > 2 AND (instr(substr("b"."String", 2 + 1), 'e') - 1) + 2 = 6 +"""); + } + + public override async Task IndexOf_with_constant_starting_position_char(bool async) + { + await base.IndexOf_with_constant_starting_position_char(async); + + AssertSql( + """ +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE length("b"."String") > 2 AND (instr(substr("b"."String", 2 + 1), 'e') - 1) + 2 = 6 +"""); + } - public override Task IndexOf_with_parameter_starting_position(bool async) - => AssertTranslationFailed(() => base.IndexOf_with_parameter_starting_position(async)); + public override async Task IndexOf_with_parameter_starting_position(bool async) + { + await base.IndexOf_with_parameter_starting_position(async); + + AssertSql( + """ +@start='2' + +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE length("b"."String") > 2 AND (instr(substr("b"."String", @start + 1), 'e') - 1) + @start = 6 +"""); + } + + public override async Task IndexOf_with_parameter_starting_position_char(bool async) + { + await base.IndexOf_with_parameter_starting_position_char(async); + + AssertSql( + """ +@start='2' + +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE length("b"."String") > 2 AND (instr(substr("b"."String", @start + 1), 'e') - 1) + @start = 6 +"""); + } public override async Task IndexOf_after_ToString(bool async) { @@ -206,6 +278,18 @@ WHERE replace("b"."String", 'Sea', 'Rea') = 'Reattle' """); } + public override async Task Replace_Char(bool async) + { + await base.Replace_Char(async); + + AssertSql( + """ +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE replace("b"."String", 'S', 'R') = 'Reattle' +"""); + } + public override async Task Replace_with_empty_string(bool async) { await base.Replace_with_empty_string(async); @@ -395,6 +479,18 @@ public override async Task StartsWith_Literal(bool async) """); } + public override async Task StartsWith_Literal_Char(bool async) + { + await base.StartsWith_Literal_Char(async); + + AssertSql( + """ +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE "b"."String" LIKE 'S%' +"""); + } + public override async Task StartsWith_Parameter(bool async) { await base.StartsWith_Parameter(async); @@ -403,6 +499,20 @@ public override async Task StartsWith_Parameter(bool async) """ @pattern_startswith='Se%' (Size = 3) +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE "b"."String" LIKE @pattern_startswith ESCAPE '\' +"""); + } + + public override async Task StartsWith_Parameter_Char(bool async) + { + await base.StartsWith_Parameter_Char(async); + + AssertSql( + """ +@pattern_startswith='S%' (Size = 2) + SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" FROM "BasicTypesEntities" AS "b" WHERE "b"."String" LIKE @pattern_startswith ESCAPE '\' @@ -458,6 +568,18 @@ public override async Task EndsWith_Literal(bool async) """); } + public override async Task EndsWith_Literal_Char(bool async) + { + await base.EndsWith_Literal_Char(async); + + AssertSql( + """ +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE "b"."String" LIKE '%e' +"""); + } + public override async Task EndsWith_Parameter(bool async) { await base.EndsWith_Parameter(async); @@ -466,6 +588,20 @@ public override async Task EndsWith_Parameter(bool async) """ @pattern_endswith='%le' (Size = 3) +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE "b"."String" LIKE @pattern_endswith ESCAPE '\' +"""); + } + + public override async Task EndsWith_Parameter_Char(bool async) + { + await base.EndsWith_Parameter_Char(async); + + AssertSql( + """ +@pattern_endswith='%e' (Size = 2) + SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" FROM "BasicTypesEntities" AS "b" WHERE "b"."String" LIKE @pattern_endswith ESCAPE '\' @@ -521,6 +657,18 @@ WHERE instr("b"."String", 'eattl') > 0 """); } + public override async Task Contains_Literal_Char(bool async) + { + await base.Contains_Literal_Char(async); + + AssertSql( + """ +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE instr("b"."String", 'e') > 0 +"""); + } + public override async Task Contains_Column(bool async) { await base.Contains_Column(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/DbContextModelBuilder.cs b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/DbContextModelBuilder.cs index 358b107b0dd..20ada7f7fb9 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/DbContextModelBuilder.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/DbContextModelBuilder.cs @@ -1,12 +1,12 @@ // using System; using System.Collections.Generic; -using System.Text.Json; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Update.Internal; using NetTopologySuite.Geometries; @@ -2325,10 +2325,10 @@ private IRelationalModel CreateRelationalModel() IsNullable = true }; principalBaseTable.Columns.Add("ManyOwned", manyOwnedColumn); - manyOwnedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric(manyOwnedColumn); + manyOwnedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric(manyOwnedColumn); var ownedColumn = new JsonColumn("Owned", "TEXT", principalBaseTable); principalBaseTable.Columns.Add("Owned", ownedColumn); - ownedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric(ownedColumn); + ownedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric(ownedColumn); var pointColumn0 = new Column("Point", "geometry", principalBaseTable) { IsNullable = true diff --git a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/CompiledModelSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/CompiledModelSqliteTest.cs index 010499c6527..25cbd0a988c 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/CompiledModelSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/CompiledModelSqliteTest.cs @@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Scaffolding; [SpatialiteRequired] -public class CompiledModelSqliteTest : CompiledModelRelationalTestBase +public class CompiledModelSqliteTest(NonSharedFixture fixture) : CompiledModelRelationalTestBase(fixture) { protected override void BuildBigModel(ModelBuilder modelBuilder, bool jsonColumns) { diff --git a/test/EFCore.Sqlite.FunctionalTests/TPTTableSplittingSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/TPTTableSplittingSqliteTest.cs index c84e139264b..48d7a494cb5 100644 --- a/test/EFCore.Sqlite.FunctionalTests/TPTTableSplittingSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/TPTTableSplittingSqliteTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore; #nullable disable -public class TPTTableSplittingSqliteTest(ITestOutputHelper testOutputHelper) : TPTTableSplittingTestBase(testOutputHelper) +public class TPTTableSplittingSqliteTest(NonSharedFixture fixture, ITestOutputHelper testOutputHelper) : TPTTableSplittingTestBase(fixture, testOutputHelper) { public override Task Can_insert_dependent_with_just_one_parent() // This scenario is not valid for TPT diff --git a/test/EFCore.Sqlite.FunctionalTests/TableSplittingSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/TableSplittingSqliteTest.cs index 5ec0bc1369d..df606d2d943 100644 --- a/test/EFCore.Sqlite.FunctionalTests/TableSplittingSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/TableSplittingSqliteTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore; #nullable disable -public class TableSplittingSqliteTest(ITestOutputHelper testOutputHelper) : TableSplittingTestBase(testOutputHelper) +public class TableSplittingSqliteTest(NonSharedFixture fixture, ITestOutputHelper testOutputHelper) : TableSplittingTestBase(fixture, testOutputHelper) { public override async Task ExecuteUpdate_works_for_table_sharing(bool async) { diff --git a/test/EFCore.Sqlite.FunctionalTests/TestUtilities/SqliteTestStore.cs b/test/EFCore.Sqlite.FunctionalTests/TestUtilities/SqliteTestStore.cs index f53602725cf..dc63c4f3290 100644 --- a/test/EFCore.Sqlite.FunctionalTests/TestUtilities/SqliteTestStore.cs +++ b/test/EFCore.Sqlite.FunctionalTests/TestUtilities/SqliteTestStore.cs @@ -72,7 +72,9 @@ protected override async Task InitializeAsync(Func createContext, Fun } using var context = createContext(); - if (!await context.Database.EnsureCreatedResilientlyAsync()) + + var databaseCreator = context.GetService(); + if (await databaseCreator.ExistsAsync()) { if (clean != null) { @@ -80,11 +82,11 @@ protected override async Task InitializeAsync(Func createContext, Fun } await CleanAsync(context); - - // Run context seeding - await context.Database.EnsureCreatedResilientlyAsync(); } + // Run context seeding + await context.Database.EnsureCreatedResilientlyAsync(); + if (seed != null) { await seed(context); diff --git a/test/EFCore.Sqlite.FunctionalTests/Update/NonSharedModelUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Update/NonSharedModelUpdatesSqliteTest.cs index f56072ffe01..bcbead6b6bb 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Update/NonSharedModelUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Update/NonSharedModelUpdatesSqliteTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Update; #nullable disable -public class NonSharedModelUpdatesSqliteTest : NonSharedModelUpdatesTestBase +public class NonSharedModelUpdatesSqliteTest(NonSharedFixture fixture) : NonSharedModelUpdatesTestBase(fixture) { public override async Task Principal_and_dependent_roundtrips_with_cycle_breaking(bool async) { diff --git a/test/EFCore.Tests/ApiConsistencyTest.cs b/test/EFCore.Tests/ApiConsistencyTest.cs index c01f993d370..df6a8f5b112 100644 --- a/test/EFCore.Tests/ApiConsistencyTest.cs +++ b/test/EFCore.Tests/ApiConsistencyTest.cs @@ -79,7 +79,7 @@ protected override void Initialize() typeof(EntityFrameworkServiceCollectionExtensions) ]; - public override HashSet NonVirtualMethods { get; } = + public override HashSet VirtualMethodExceptions { get; } = [ typeof(CompiledQueryCacheKeyGenerator).GetMethod("GenerateCacheKeyCore", AnyInstance), typeof(InternalEntityEntry).GetMethod("get_Item"), @@ -168,37 +168,14 @@ protected override void Initialize() typeof(IConventionModelBuilder).GetMethod(nameof(IConventionModelBuilder.ComplexType)), typeof(IReadOnlyEntityType).GetMethod(nameof(IReadOnlyEntityType.GetConcreteDerivedTypesInclusive)), typeof(IMutableEntityType).GetMethod(nameof(IMutableEntityType.AddData)), + typeof(IConventionEntityType).GetMethod(nameof(IConventionEntityType.SetBaseType)), typeof(IReadOnlyNavigationBase).GetMethod("get_DeclaringEntityType"), typeof(IReadOnlyNavigationBase).GetMethod("get_TargetEntityType"), typeof(IReadOnlyNavigationBase).GetMethod("get_Inverse"), typeof(IConventionAnnotatableBuilder).GetMethod(nameof(IConventionAnnotatableBuilder.HasNonNullAnnotation)), typeof(IConventionEntityTypeBuilder).GetMethod(nameof(IConventionEntityTypeBuilder.RemoveUnusedImplicitProperties)), typeof(IConventionTypeBaseBuilder).GetMethod(nameof(IConventionTypeBaseBuilder.RemoveUnusedImplicitProperties)), - typeof(IConventionEntityTypeBuilder).GetMethod(nameof(IConventionEntityTypeBuilder.GetTargetEntityTypeBuilder)), - typeof(IConventionPropertyBuilder).GetMethod( - nameof(IConventionPropertyBuilder.HasField), [typeof(string), typeof(bool)]), - typeof(IConventionPropertyBuilder).GetMethod( - nameof(IConventionPropertyBuilder.HasField), [typeof(FieldInfo), typeof(bool)]), - typeof(IConventionPropertyBuilder).GetMethod( - nameof(IConventionPropertyBuilder.UsePropertyAccessMode), [typeof(PropertyAccessMode), typeof(bool)]), - typeof(IConventionServicePropertyBuilder).GetMethod( - nameof(IConventionServicePropertyBuilder.HasField), [typeof(string), typeof(bool)]), - typeof(IConventionServicePropertyBuilder).GetMethod( - nameof(IConventionServicePropertyBuilder.HasField), [typeof(FieldInfo), typeof(bool)]), - typeof(IConventionServicePropertyBuilder).GetMethod( - nameof(IConventionServicePropertyBuilder.UsePropertyAccessMode), [typeof(PropertyAccessMode), typeof(bool)]), - typeof(IConventionNavigationBuilder).GetMethod( - nameof(IConventionNavigationBuilder.HasField), [typeof(string), typeof(bool)]), - typeof(IConventionNavigationBuilder).GetMethod( - nameof(IConventionNavigationBuilder.HasField), [typeof(FieldInfo), typeof(bool)]), - typeof(IConventionNavigationBuilder).GetMethod( - nameof(IConventionNavigationBuilder.UsePropertyAccessMode), [typeof(PropertyAccessMode), typeof(bool)]), - typeof(IConventionSkipNavigationBuilder).GetMethod( - nameof(IConventionSkipNavigationBuilder.HasField), [typeof(string), typeof(bool)]), - typeof(IConventionSkipNavigationBuilder).GetMethod( - nameof(IConventionSkipNavigationBuilder.HasField), [typeof(FieldInfo), typeof(bool)]), - typeof(IConventionSkipNavigationBuilder).GetMethod( - nameof(IConventionSkipNavigationBuilder.UsePropertyAccessMode), [typeof(PropertyAccessMode), typeof(bool)]) + typeof(IConventionEntityTypeBuilder).GetMethod(nameof(IConventionEntityTypeBuilder.GetTargetEntityTypeBuilder)) ]; public override HashSet MetadataMethodExceptions { get; } = @@ -208,7 +185,6 @@ protected override void Initialize() typeof(IConventionAnnotatable).GetMethod(nameof(IConventionAnnotatable.AddAnnotations)), typeof(IMutableAnnotatable).GetMethod(nameof(IMutableAnnotatable.AddAnnotations)), typeof(IConventionModel).GetMethod(nameof(IConventionModel.IsIgnoredType)), - typeof(IConventionModel).GetMethod(nameof(IConventionModel.IsShared)), typeof(IConventionModel).GetMethod(nameof(IConventionModel.AddOwned)), typeof(IConventionModel).GetMethod(nameof(IConventionModel.AddShared)), typeof(IMutableModel).GetMethod(nameof(IMutableModel.AddOwned)), diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index 643ca9cc5df..cab44b3e42e 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -1919,6 +1919,28 @@ public virtual void Detects_missing_non_string_discriminator_values() VerifyError(CoreStrings.NoDiscriminatorValue(typeof(C).Name), modelBuilder); } + [ConditionalFact] + public virtual void Detects_missing_complex_type_discriminator_values() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Entity().ComplexProperty(b => b.A) + .HasDiscriminator("Type"); + + VerifyError(CoreStrings.NoDiscriminatorValue("B.A#A"), modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_incompatible_complex_type_discriminator_value() + { + var modelBuilder = CreateConventionModelBuilder(); + var complexPropertyBuilder = modelBuilder.Entity().ComplexProperty(b => b.A); + complexPropertyBuilder.HasDiscriminator("Type"); + + complexPropertyBuilder.Metadata.ComplexType.SetDiscriminatorValue("1"); + + VerifyError(CoreStrings.DiscriminatorValueIncompatible("1", "B.A#A", "byte"), modelBuilder); + } + [ConditionalFact] public virtual void Detects_duplicate_discriminator_values() { diff --git a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs index c6236e957ae..de9b13c99df 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs @@ -762,11 +762,11 @@ private class DiscriminatorPropertySetConvention(bool terminate) : IDiscriminato public readonly List Calls = []; public void ProcessDiscriminatorPropertySet( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder structuralTypeBuilder, string name, IConventionContext context) { - Assert.True(entityTypeBuilder.Metadata.IsInModel); + Assert.True(structuralTypeBuilder.Metadata.IsInModel); Calls.Add(name); diff --git a/test/EFCore.Tests/Metadata/Conventions/ConventionSetBuilderTests.cs b/test/EFCore.Tests/Metadata/Conventions/ConventionSetBuilderTests.cs index 5eb545a8495..c959423f25f 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ConventionSetBuilderTests.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ConventionSetBuilderTests.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel.DataAnnotations.Schema; -using Microsoft.EntityFrameworkCore.InMemory.Metadata.Conventions; // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; diff --git a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.Tests.csproj b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.Tests.csproj index 4decb62d26f..7c19ff01620 100644 --- a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.Tests.csproj +++ b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.Tests.csproj @@ -1,7 +1,7 @@ - $(DefaultNetCoreTargetFramework);net462 + $(DefaultNetCoreTargetFramework);$(NetFrameworkCurrent) $(DefineConstants);E_SQLITE3 enable diff --git a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.e_sqlcipher.Tests.csproj b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.e_sqlcipher.Tests.csproj index 675c49e1c0d..29447db7f09 100644 --- a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.e_sqlcipher.Tests.csproj +++ b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.e_sqlcipher.Tests.csproj @@ -1,7 +1,7 @@ - $(DefaultNetCoreTargetFramework);net462 + $(DefaultNetCoreTargetFramework);$(NetFrameworkCurrent) $(DefineConstants);E_SQLCIPHER enable diff --git a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.e_sqlite3mc.Tests.csproj b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.e_sqlite3mc.Tests.csproj index 5695d730aff..33eff6e0c39 100644 --- a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.e_sqlite3mc.Tests.csproj +++ b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.e_sqlite3mc.Tests.csproj @@ -1,7 +1,7 @@ - $(DefaultNetCoreTargetFramework);net462 + $(DefaultNetCoreTargetFramework);$(NetFrameworkCurrent) $(DefineConstants);E_SQLITE3MC enable diff --git a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.sqlite3.Tests.csproj b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.sqlite3.Tests.csproj index c057b2cf32a..58047b3caef 100644 --- a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.sqlite3.Tests.csproj +++ b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.sqlite3.Tests.csproj @@ -1,7 +1,7 @@ - $(DefaultNetCoreTargetFramework);net462 + $(DefaultNetCoreTargetFramework);$(NetFrameworkCurrent) $(DefineConstants);SQLITE3 enable diff --git a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.winsqlite3.Tests.csproj b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.winsqlite3.Tests.csproj index d5a11003545..e0a71beed0f 100644 --- a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.winsqlite3.Tests.csproj +++ b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.winsqlite3.Tests.csproj @@ -1,7 +1,7 @@ - $(DefaultNetCoreTargetFramework);net462 + $(DefaultNetCoreTargetFramework);$(NetFrameworkCurrent) $(DefineConstants);WINSQLITE3 enable