diff --git a/Directory.Packages.props b/Directory.Packages.props
index 5975e2b9a33..c04cc84129e 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -36,7 +36,7 @@
-
+
diff --git a/NuGet.config b/NuGet.config
index b24702052c1..4b75d96c4fa 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -4,7 +4,7 @@
-
+
@@ -21,7 +21,7 @@
-
+
diff --git a/azure-pipelines-public.yml b/azure-pipelines-public.yml
index 85b9fbab9e4..c42e450fb7e 100644
--- a/azure-pipelines-public.yml
+++ b/azure-pipelines-public.yml
@@ -151,7 +151,7 @@ stages:
- name: _HelixBuildConfig
value: $(_BuildConfig)
- name: HelixTargetQueues
- value: Windows.10.Amd64.Open;OSX.1200.Amd64.Open;OSX.1200.ARM64.Open;Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
+ value: Windows.10.Amd64.Open;OSX.13.ARM64.Open;Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
- name: _HelixAccessToken
value: '' # Needed for public queues
steps:
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 530b57da6a1..78837f93284 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -170,36 +170,11 @@ extends:
- _runCounter: $[counter(variables['Build.Reason'], 0)]
# Rely on task Arcade injects, not auto-injected build step.
- skipComponentGovernanceDetection: true
- - ${{ if notin(variables['Build.Reason'], 'PullRequest', 'Schedule') }}:
- - _CosmosConnectionUrl: 'true'
steps:
- - bash: |
- echo "##vso[task.setvariable variable=_CosmosConnectionUrl]https://ef-nightly-test.documents.azure.com:443/"
- displayName: Prepare to run Cosmos tests on ef-nightly-test
- condition: and(eq(variables['_CosmosConnectionUrl'], 'true'), or(endsWith(variables['_runCounter'], '0'), endsWith(variables['_runCounter'], '2'), endsWith(variables['_runCounter'], '4'), endsWith(variables['_runCounter'], '6'), endsWith(variables['_runCounter'], '8')))
- - bash: |
- echo "##vso[task.setvariable variable=_CosmosConnectionUrl]https://ef-pr-test.documents.azure.com:443/"
- displayName: Prepare to run Cosmos tests on ef-pr-test
- 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)
displayName: Build
- - task: AzureCLI@2
- displayName: Run Cosmos tests
- condition: notin(variables['Build.Reason'], 'PullRequest', 'Schedule')
- inputs:
- azureSubscription: EFCosmosTesting
- scriptType: bash
- scriptLocation: 'inlineScript'
- inlineScript: |
- ./test.sh --ci --configuration $(_BuildConfig) --projects $(Build.SourcesDirectory)/test/EFCore.Cosmos.FunctionalTests/EFCore.Cosmos.FunctionalTests.csproj
- env:
- Test__Cosmos__DefaultConnection: $(_CosmosConnectionUrl)
- Test__Cosmos__UseTokenCredential: true
- Test__Cosmos__SubscriptionId: d709b837-4a74-4aec-addc-b6e4b9b23e7e
- Test__Cosmos__ResourceGroup: efcosmosci
- name: Build
templateContext:
outputs:
- output: pipelineArtifact
@@ -219,7 +194,7 @@ extends:
- name: _HelixBuildConfig
value: $(_BuildConfig)
- name: HelixTargetQueues
- value: Windows.10.Amd64;OSX.1200.Amd64;OSX.1200.ARM64;Ubuntu.2204.Amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
+ value: Windows.10.Amd64;OSX.13.ARM64;Ubuntu.2204.Amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
- name: _HelixAccessToken
# Needed for internal queues
value: $(HelixApiAccessToken)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 3fff94bc548..6fd90654018 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -1,83 +1,83 @@
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- c8acea22626efab11c13778c028975acdc34678f
+ 3c298d9f00936d651cc47d221762474e25277672
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- c8acea22626efab11c13778c028975acdc34678f
+ 3c298d9f00936d651cc47d221762474e25277672
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- c8acea22626efab11c13778c028975acdc34678f
+ 3c298d9f00936d651cc47d221762474e25277672
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- c8acea22626efab11c13778c028975acdc34678f
+ 3c298d9f00936d651cc47d221762474e25277672
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- c8acea22626efab11c13778c028975acdc34678f
+ 3c298d9f00936d651cc47d221762474e25277672
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- c8acea22626efab11c13778c028975acdc34678f
+ 3c298d9f00936d651cc47d221762474e25277672
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- c8acea22626efab11c13778c028975acdc34678f
+ 3c298d9f00936d651cc47d221762474e25277672
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- c8acea22626efab11c13778c028975acdc34678f
+ 3c298d9f00936d651cc47d221762474e25277672
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- c8acea22626efab11c13778c028975acdc34678f
+ 3c298d9f00936d651cc47d221762474e25277672
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- c8acea22626efab11c13778c028975acdc34678f
+ 3c298d9f00936d651cc47d221762474e25277672
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- c8acea22626efab11c13778c028975acdc34678f
+ 3c298d9f00936d651cc47d221762474e25277672
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- c8acea22626efab11c13778c028975acdc34678f
+ 3c298d9f00936d651cc47d221762474e25277672
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- c8acea22626efab11c13778c028975acdc34678f
+ 3c298d9f00936d651cc47d221762474e25277672
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- c8acea22626efab11c13778c028975acdc34678f
+ 3c298d9f00936d651cc47d221762474e25277672
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- c8acea22626efab11c13778c028975acdc34678f
+ 3c298d9f00936d651cc47d221762474e25277672
-
+
https://github.com/dotnet/arcade
- b41381d5cd633471265e9cd72e933a7048e03062
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- b41381d5cd633471265e9cd72e933a7048e03062
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- b41381d5cd633471265e9cd72e933a7048e03062
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
diff --git a/eng/Versions.props b/eng/Versions.props
index dc4ee10c83a..e9d675db6df 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -1,6 +1,6 @@
- 9.0.1
+ 9.0.7
rtm
@@ -17,24 +17,24 @@
False
- 9.0.1
- 9.0.1
- 9.0.1
- 9.0.1
- 9.0.1
- 9.0.1
- 9.0.1
- 9.0.1-servicing.24610.10
- 9.0.1
- 9.0.1
- 9.0.1
- 9.0.1-servicing.24610.10
- 9.0.1
- 9.0.1
- 9.0.1
+ 9.0.7
+ 9.0.7
+ 9.0.7
+ 9.0.7
+ 9.0.7
+ 9.0.7
+ 9.0.7
+ 9.0.7-servicing.25316.16
+ 9.0.7
+ 9.0.7
+ 9.0.7
+ 9.0.7-servicing.25316.16
+ 9.0.7
+ 9.0.7
+ 9.0.7
- 9.0.0-beta.24572.2
+ 9.0.0-beta.25302.2
17.8.3
diff --git a/eng/common/core-templates/job/source-build.yml b/eng/common/core-templates/job/source-build.yml
index c4713c8b6ed..d47f09d58fd 100644
--- a/eng/common/core-templates/job/source-build.yml
+++ b/eng/common/core-templates/job/source-build.yml
@@ -26,6 +26,8 @@ parameters:
# Specifies the build script to invoke to perform the build in the repo. The default
# './build.sh' should work for typical Arcade repositories, but this is customizable for
# difficult situations.
+ # buildArguments: ''
+ # Specifies additional build arguments to pass to the build script.
# jobProperties: {}
# A list of job properties to inject at the top level, for potential extensibility beyond
# container and pool.
diff --git a/eng/common/core-templates/job/source-index-stage1.yml b/eng/common/core-templates/job/source-index-stage1.yml
index 205fb5b3a39..8b833332b3e 100644
--- a/eng/common/core-templates/job/source-index-stage1.yml
+++ b/eng/common/core-templates/job/source-index-stage1.yml
@@ -1,7 +1,7 @@
parameters:
runAsPublic: false
- sourceIndexUploadPackageVersion: 2.0.0-20240522.1
- sourceIndexProcessBinlogPackageVersion: 1.0.1-20240522.1
+ sourceIndexUploadPackageVersion: 2.0.0-20250425.2
+ sourceIndexProcessBinlogPackageVersion: 1.0.1-20250425.2
sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json
sourceIndexBuildCommand: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "eng/common/build.ps1 -restore -build -binarylog -ci"
preSteps: []
diff --git a/eng/common/core-templates/post-build/post-build.yml b/eng/common/core-templates/post-build/post-build.yml
index 454fd75c7af..a8c0bd3b921 100644
--- a/eng/common/core-templates/post-build/post-build.yml
+++ b/eng/common/core-templates/post-build/post-build.yml
@@ -44,6 +44,11 @@ parameters:
displayName: Publish installers and checksums
type: boolean
default: true
+
+ - name: requireDefaultChannels
+ displayName: Fail the build if there are no default channel(s) registrations for the current build
+ type: boolean
+ default: false
- name: SDLValidationParameters
type: object
@@ -312,5 +317,6 @@ stages:
-PublishingInfraVersion ${{ parameters.publishingInfraVersion }}
-AzdoToken '$(System.AccessToken)'
-WaitPublishingFinish true
+ -RequireDefaultChannels ${{ parameters.requireDefaultChannels }}
-ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}'
-SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}'
diff --git a/eng/common/core-templates/steps/generate-sbom.yml b/eng/common/core-templates/steps/generate-sbom.yml
index d938b60e1bb..56a09009482 100644
--- a/eng/common/core-templates/steps/generate-sbom.yml
+++ b/eng/common/core-templates/steps/generate-sbom.yml
@@ -38,7 +38,7 @@ steps:
PackageName: ${{ parameters.packageName }}
BuildDropPath: ${{ parameters.buildDropPath }}
PackageVersion: ${{ parameters.packageVersion }}
- ManifestDirPath: ${{ parameters.manifestDirPath }}
+ ManifestDirPath: ${{ parameters.manifestDirPath }}/$(ARTIFACT_NAME)
${{ if ne(parameters.IgnoreDirectories, '') }}:
AdditionalComponentDetectorArgs: '--IgnoreDirectories ${{ parameters.IgnoreDirectories }}'
diff --git a/eng/common/core-templates/steps/source-build.yml b/eng/common/core-templates/steps/source-build.yml
index 2915d29bb7f..37133b55b75 100644
--- a/eng/common/core-templates/steps/source-build.yml
+++ b/eng/common/core-templates/steps/source-build.yml
@@ -79,6 +79,7 @@ steps:
${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \
--configuration $buildConfig \
--restore --build --pack $publishArgs -bl \
+ ${{ parameters.platform.buildArguments }} \
$officialBuildArgs \
$internalRuntimeDownloadArgs \
$internalRestoreArgs \
diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake
index 9a4e285a5ae..9a7ecfbd42c 100644
--- a/eng/common/cross/toolchain.cmake
+++ b/eng/common/cross/toolchain.cmake
@@ -40,7 +40,7 @@ if(TARGET_ARCH_NAME STREQUAL "arm")
set(TOOLCHAIN "arm-linux-gnueabihf")
endif()
if(TIZEN)
- set(TIZEN_TOOLCHAIN "armv7hl-tizen-linux-gnueabihf/9.2.0")
+ set(TIZEN_TOOLCHAIN "armv7hl-tizen-linux-gnueabihf")
endif()
elseif(TARGET_ARCH_NAME STREQUAL "arm64")
set(CMAKE_SYSTEM_PROCESSOR aarch64)
@@ -49,7 +49,7 @@ elseif(TARGET_ARCH_NAME STREQUAL "arm64")
elseif(LINUX)
set(TOOLCHAIN "aarch64-linux-gnu")
if(TIZEN)
- set(TIZEN_TOOLCHAIN "aarch64-tizen-linux-gnu/9.2.0")
+ set(TIZEN_TOOLCHAIN "aarch64-tizen-linux-gnu")
endif()
elseif(FREEBSD)
set(triple "aarch64-unknown-freebsd12")
@@ -58,7 +58,7 @@ elseif(TARGET_ARCH_NAME STREQUAL "armel")
set(CMAKE_SYSTEM_PROCESSOR armv7l)
set(TOOLCHAIN "arm-linux-gnueabi")
if(TIZEN)
- set(TIZEN_TOOLCHAIN "armv7l-tizen-linux-gnueabi/9.2.0")
+ set(TIZEN_TOOLCHAIN "armv7l-tizen-linux-gnueabi")
endif()
elseif(TARGET_ARCH_NAME STREQUAL "armv6")
set(CMAKE_SYSTEM_PROCESSOR armv6l)
@@ -81,7 +81,7 @@ elseif(TARGET_ARCH_NAME STREQUAL "riscv64")
else()
set(TOOLCHAIN "riscv64-linux-gnu")
if(TIZEN)
- set(TIZEN_TOOLCHAIN "riscv64-tizen-linux-gnu/13.1.0")
+ set(TIZEN_TOOLCHAIN "riscv64-tizen-linux-gnu")
endif()
endif()
elseif(TARGET_ARCH_NAME STREQUAL "s390x")
@@ -98,7 +98,7 @@ elseif(TARGET_ARCH_NAME STREQUAL "x64")
elseif(LINUX)
set(TOOLCHAIN "x86_64-linux-gnu")
if(TIZEN)
- set(TIZEN_TOOLCHAIN "x86_64-tizen-linux-gnu/9.2.0")
+ set(TIZEN_TOOLCHAIN "x86_64-tizen-linux-gnu")
endif()
elseif(FREEBSD)
set(triple "x86_64-unknown-freebsd12")
@@ -115,7 +115,7 @@ elseif(TARGET_ARCH_NAME STREQUAL "x86")
set(TOOLCHAIN "i686-linux-gnu")
endif()
if(TIZEN)
- set(TIZEN_TOOLCHAIN "i586-tizen-linux-gnu/9.2.0")
+ set(TIZEN_TOOLCHAIN "i586-tizen-linux-gnu")
endif()
else()
message(FATAL_ERROR "Arch is ${TARGET_ARCH_NAME}. Only arm, arm64, armel, armv6, ppc64le, riscv64, s390x, x64 and x86 are supported!")
@@ -127,30 +127,25 @@ endif()
# Specify include paths
if(TIZEN)
- if(TARGET_ARCH_NAME STREQUAL "arm")
- include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/)
- include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/armv7hl-tizen-linux-gnueabihf)
- endif()
- if(TARGET_ARCH_NAME STREQUAL "armel")
- include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/)
- include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/armv7l-tizen-linux-gnueabi)
- endif()
- if(TARGET_ARCH_NAME STREQUAL "arm64")
- include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/)
- include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/aarch64-tizen-linux-gnu)
- endif()
- if(TARGET_ARCH_NAME STREQUAL "x86")
- include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/)
- include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/i586-tizen-linux-gnu)
- endif()
- if(TARGET_ARCH_NAME STREQUAL "x64")
- include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/)
- include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/x86_64-tizen-linux-gnu)
- endif()
- if(TARGET_ARCH_NAME STREQUAL "riscv64")
- include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/)
- include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/riscv64-tizen-linux-gnu)
+ function(find_toolchain_dir prefix)
+ # Dynamically find the version subdirectory
+ file(GLOB DIRECTORIES "${prefix}/*")
+ list(GET DIRECTORIES 0 FIRST_MATCH)
+ get_filename_component(TOOLCHAIN_VERSION ${FIRST_MATCH} NAME)
+
+ set(TIZEN_TOOLCHAIN_PATH "${prefix}/${TOOLCHAIN_VERSION}" PARENT_SCOPE)
+ endfunction()
+
+ if(TARGET_ARCH_NAME MATCHES "^(arm|armel|x86)$")
+ find_toolchain_dir("${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}")
+ else()
+ find_toolchain_dir("${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}")
endif()
+
+ message(STATUS "TIZEN_TOOLCHAIN_PATH set to: ${TIZEN_TOOLCHAIN_PATH}")
+
+ include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++)
+ include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++/${TIZEN_TOOLCHAIN})
endif()
if(ANDROID)
@@ -272,21 +267,21 @@ endif()
if(TARGET_ARCH_NAME MATCHES "^(arm|armel)$")
if(TIZEN)
- add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}")
+ add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}")
add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib")
add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib")
- add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}")
+ add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}")
endif()
elseif(TARGET_ARCH_NAME MATCHES "^(arm64|x64|riscv64)$")
if(TIZEN)
- add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}")
+ add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}")
add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib64")
add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib64")
- add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}")
+ add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}")
add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/lib64")
add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib64")
- add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}")
+ add_toolchain_linker_flag("-Wl,--rpath-link=${TIZEN_TOOLCHAIN_PATH}")
endif()
elseif(TARGET_ARCH_NAME STREQUAL "s390x")
add_toolchain_linker_flag("--target=${TOOLCHAIN}")
@@ -297,10 +292,10 @@ elseif(TARGET_ARCH_NAME STREQUAL "x86")
endif()
add_toolchain_linker_flag(-m32)
if(TIZEN)
- add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}")
+ add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}")
add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib")
add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib")
- add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}")
+ add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}")
endif()
elseif(ILLUMOS)
add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib/amd64")
diff --git a/eng/common/generate-sbom-prep.ps1 b/eng/common/generate-sbom-prep.ps1
index 3e5c1c74a1c..a0c7d792a76 100644
--- a/eng/common/generate-sbom-prep.ps1
+++ b/eng/common/generate-sbom-prep.ps1
@@ -4,18 +4,26 @@ Param(
. $PSScriptRoot\pipeline-logging-functions.ps1
+# Normally - we'd listen to the manifest path given, but 1ES templates will overwrite if this level gets uploaded directly
+# with their own overwriting ours. So we create it as a sub directory of the requested manifest path.
+$ArtifactName = "${env:SYSTEM_STAGENAME}_${env:AGENT_JOBNAME}_SBOM"
+$SafeArtifactName = $ArtifactName -replace '["/:<>\\|?@*"() ]', '_'
+$SbomGenerationDir = Join-Path $ManifestDirPath $SafeArtifactName
+
+Write-Host "Artifact name before : $ArtifactName"
+Write-Host "Artifact name after : $SafeArtifactName"
+
Write-Host "Creating dir $ManifestDirPath"
+
# create directory for sbom manifest to be placed
-if (!(Test-Path -path $ManifestDirPath))
+if (!(Test-Path -path $SbomGenerationDir))
{
- New-Item -ItemType Directory -path $ManifestDirPath
- Write-Host "Successfully created directory $ManifestDirPath"
+ New-Item -ItemType Directory -path $SbomGenerationDir
+ Write-Host "Successfully created directory $SbomGenerationDir"
}
else{
Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder."
}
Write-Host "Updating artifact name"
-$artifact_name = "${env:SYSTEM_STAGENAME}_${env:AGENT_JOBNAME}_SBOM" -replace '["/:<>\\|?@*"() ]', '_'
-Write-Host "Artifact name $artifact_name"
-Write-Host "##vso[task.setvariable variable=ARTIFACT_NAME]$artifact_name"
+Write-Host "##vso[task.setvariable variable=ARTIFACT_NAME]$SafeArtifactName"
diff --git a/eng/common/generate-sbom-prep.sh b/eng/common/generate-sbom-prep.sh
index d5c76dc827b..b8ecca72bbf 100644
--- a/eng/common/generate-sbom-prep.sh
+++ b/eng/common/generate-sbom-prep.sh
@@ -14,19 +14,24 @@ done
scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
. $scriptroot/pipeline-logging-functions.sh
+
+# replace all special characters with _, some builds use special characters like : in Agent.Jobname, that is not a permissible name while uploading artifacts.
+artifact_name=$SYSTEM_STAGENAME"_"$AGENT_JOBNAME"_SBOM"
+safe_artifact_name="${artifact_name//["/:<>\\|?@*$" ]/_}"
manifest_dir=$1
-if [ ! -d "$manifest_dir" ] ; then
- mkdir -p "$manifest_dir"
- echo "Sbom directory created." $manifest_dir
+# Normally - we'd listen to the manifest path given, but 1ES templates will overwrite if this level gets uploaded directly
+# with their own overwriting ours. So we create it as a sub directory of the requested manifest path.
+sbom_generation_dir="$manifest_dir/$safe_artifact_name"
+
+if [ ! -d "$sbom_generation_dir" ] ; then
+ mkdir -p "$sbom_generation_dir"
+ echo "Sbom directory created." $sbom_generation_dir
else
Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder."
fi
-artifact_name=$SYSTEM_STAGENAME"_"$AGENT_JOBNAME"_SBOM"
echo "Artifact name before : "$artifact_name
-# replace all special characters with _, some builds use special characters like : in Agent.Jobname, that is not a permissible name while uploading artifacts.
-safe_artifact_name="${artifact_name//["/:<>\\|?@*$" ]/_}"
echo "Artifact name after : "$safe_artifact_name
export ARTIFACT_NAME=$safe_artifact_name
echo "##vso[task.setvariable variable=ARTIFACT_NAME]$safe_artifact_name"
diff --git a/eng/common/internal/Tools.csproj b/eng/common/internal/Tools.csproj
index 32f79dfb340..feaa6d20812 100644
--- a/eng/common/internal/Tools.csproj
+++ b/eng/common/internal/Tools.csproj
@@ -15,16 +15,6 @@
-
-
-
- https://devdiv.pkgs.visualstudio.com/_packaging/dotnet-core-internal-tooling/nuget/v3/index.json;
-
-
- $(RestoreSources);
- https://devdiv.pkgs.visualstudio.com/_packaging/VS/nuget/v3/index.json;
-
-
diff --git a/eng/common/post-build/publish-using-darc.ps1 b/eng/common/post-build/publish-using-darc.ps1
index 90b58e32a87..a261517ef90 100644
--- a/eng/common/post-build/publish-using-darc.ps1
+++ b/eng/common/post-build/publish-using-darc.ps1
@@ -5,7 +5,8 @@ param(
[Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro.dot.net',
[Parameter(Mandatory=$true)][string] $WaitPublishingFinish,
[Parameter(Mandatory=$false)][string] $ArtifactsPublishingAdditionalParameters,
- [Parameter(Mandatory=$false)][string] $SymbolPublishingAdditionalParameters
+ [Parameter(Mandatory=$false)][string] $SymbolPublishingAdditionalParameters,
+ [Parameter(Mandatory=$false)][string] $RequireDefaultChannels
)
try {
@@ -33,6 +34,10 @@ try {
if ("false" -eq $WaitPublishingFinish) {
$optionalParams.Add("--no-wait") | Out-Null
}
+
+ if ("true" -eq $RequireDefaultChannels) {
+ $optionalParams.Add("--default-channels-required") | Out-Null
+ }
& $darc add-build-to-channel `
--id $buildId `
diff --git a/eng/common/template-guidance.md b/eng/common/template-guidance.md
index 5ef6c30ba92..98bbc1ded0b 100644
--- a/eng/common/template-guidance.md
+++ b/eng/common/template-guidance.md
@@ -57,7 +57,7 @@ extends:
Note: Multiple outputs are ONLY applicable to 1ES PT publishing (only usable when referencing `templates-official`).
-# Development notes
+## Development notes
**Folder / file structure**
diff --git a/eng/common/templates-official/job/job.yml b/eng/common/templates-official/job/job.yml
index 605692d2fb7..817555505aa 100644
--- a/eng/common/templates-official/job/job.yml
+++ b/eng/common/templates-official/job/job.yml
@@ -16,6 +16,7 @@ jobs:
parameters:
PackageVersion: ${{ parameters.packageVersion }}
BuildDropPath: ${{ parameters.buildDropPath }}
+ ManifestDirPath: $(Build.ArtifactStagingDirectory)/sbom
publishArtifacts: false
# publish artifacts
diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1
index aa94fb17459..22b49e09d09 100644
--- a/eng/common/tools.ps1
+++ b/eng/common/tools.ps1
@@ -42,7 +42,7 @@
[bool]$useInstalledDotNetCli = if (Test-Path variable:useInstalledDotNetCli) { $useInstalledDotNetCli } else { $true }
# Enable repos to use a particular version of the on-line dotnet-install scripts.
-# default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.ps1
+# default URL: https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.ps1
[string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { 'v1' }
# True to use global NuGet cache instead of restoring packages to repository-local directory.
@@ -262,7 +262,7 @@ function GetDotNetInstallScript([string] $dotnetRoot) {
if (!(Test-Path $installScript)) {
Create-Directory $dotnetRoot
$ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit
- $uri = "https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1"
+ $uri = "https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1"
Retry({
Write-Host "GET $uri"
@@ -320,7 +320,7 @@ function InstallDotNet([string] $dotnetRoot,
$variations += @($installParameters)
$dotnetBuilds = $installParameters.Clone()
- $dotnetbuilds.AzureFeed = "https://dotnetbuilds.azureedge.net/public"
+ $dotnetbuilds.AzureFeed = "https://ci.dot.net/public"
$variations += @($dotnetBuilds)
if ($runtimeSourceFeed) {
diff --git a/eng/common/tools.sh b/eng/common/tools.sh
index 00473c9f918..01b09b65796 100755
--- a/eng/common/tools.sh
+++ b/eng/common/tools.sh
@@ -54,7 +54,7 @@ warn_as_error=${warn_as_error:-true}
use_installed_dotnet_cli=${use_installed_dotnet_cli:-true}
# Enable repos to use a particular version of the on-line dotnet-install scripts.
-# default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh
+# default URL: https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh
dotnetInstallScriptVersion=${dotnetInstallScriptVersion:-'v1'}
# True to use global NuGet cache instead of restoring packages to repository-local directory.
@@ -232,7 +232,7 @@ function InstallDotNet {
local public_location=("${installParameters[@]}")
variations+=(public_location)
- local dotnetbuilds=("${installParameters[@]}" --azure-feed "https://dotnetbuilds.azureedge.net/public")
+ local dotnetbuilds=("${installParameters[@]}" --azure-feed "https://ci.dot.net/public")
variations+=(dotnetbuilds)
if [[ -n "${6:-}" ]]; then
@@ -295,7 +295,7 @@ function with_retries {
function GetDotNetInstallScript {
local root=$1
local install_script="$root/dotnet-install.sh"
- local install_script_url="https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh"
+ local install_script_url="https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh"
if [[ ! -a "$install_script" ]]; then
mkdir -p "$root"
diff --git a/global.json b/global.json
index 90650c26b0b..3edced2da7a 100644
--- a/global.json
+++ b/global.json
@@ -1,11 +1,11 @@
{
"sdk": {
- "version": "9.0.100",
+ "version": "9.0.106",
"allowPrerelease": true,
"rollForward": "latestMajor"
},
"tools": {
- "dotnet": "9.0.100",
+ "dotnet": "9.0.106",
"runtimes": {
"dotnet": [
"$(MicrosoftNETCoreBrowserDebugHostTransportVersion)"
@@ -13,7 +13,7 @@
}
},
"msbuild-sdks": {
- "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.24572.2",
- "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.24572.2"
+ "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25302.2",
+ "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.25302.2"
}
}
diff --git a/src/EFCore.Analyzers/EFDiagnostics.cs b/src/EFCore.Analyzers/EFDiagnostics.cs
index c3021424089..13d885f600c 100644
--- a/src/EFCore.Analyzers/EFDiagnostics.cs
+++ b/src/EFCore.Analyzers/EFDiagnostics.cs
@@ -19,4 +19,5 @@ public static class EFDiagnostics
public const string MetricsExperimental = "EF9101";
public const string PagingExperimental = "EF9102";
public const string CosmosVectorSearchExperimental = "EF9103";
+ public const string CosmosFullTextSearchExperimental = "EF9104";
}
diff --git a/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs b/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs
index aa4589d54b8..096ebc3b278 100644
--- a/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs
+++ b/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs
@@ -14,13 +14,25 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal;
///
public sealed class StringDictionaryComparer : ValueComparer
@@ -49,6 +50,8 @@
+
+
diff --git a/src/EFCore.Cosmos/Extensions/CosmosDbFunctionsExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosDbFunctionsExtensions.cs
index 3dc681450be..c34b68cd07b 100644
--- a/src/EFCore.Cosmos/Extensions/CosmosDbFunctionsExtensions.cs
+++ b/src/EFCore.Cosmos/Extensions/CosmosDbFunctionsExtensions.cs
@@ -52,6 +52,60 @@ public static T CoalesceUndefined(
T expression2)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(CoalesceUndefined)));
+ ///
+ /// Checks if the specified property contains the given keyword using full-text search.
+ ///
+ /// The instance.
+ /// The property to search.
+ /// The keyword to search for.
+ /// if the property contains the keyword; otherwise, .
+ [Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
+ public static bool FullTextContains(this DbFunctions _, string property, string keyword)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FullTextContains)));
+
+ ///
+ /// Checks if the specified property contains all the given keywords using full-text search.
+ ///
+ /// The instance.
+ /// The property to search.
+ /// The keywords to search for.
+ /// if the property contains all the keywords; otherwise, .
+ [Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
+ public static bool FullTextContainsAll(this DbFunctions _, string property, params string[] keywords)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FullTextContainsAll)));
+
+ ///
+ /// Checks if the specified property contains any of the given keywords using full-text search.
+ ///
+ /// The instance.
+ /// The property to search.
+ /// The keywords to search for.
+ /// if the property contains any of the keywords; otherwise, .
+ [Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
+ public static bool FullTextContainsAny(this DbFunctions _, string property, params string[] keywords)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FullTextContainsAny)));
+
+ ///
+ /// Returns the full-text search score for the specified property and keywords.
+ ///
+ /// The instance.
+ /// The property to score.
+ /// The keywords to score by.
+ /// The full-text search score.
+ [Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
+ public static double FullTextScore(this DbFunctions _, string property, params string[] keywords)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FullTextScore)));
+
+ ///
+ /// Combines scores provided by two or more specified functions.
+ ///
+ /// The instance.
+ /// The functions to compute the score for.
+ /// The combined score.
+ [Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
+ public static double Rrf(this DbFunctions _, params double[] functions)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Rrf)));
+
///
/// Returns the distance between two vectors, using the distance function and data type defined using
///
+ /// Ordering based on scoring function is not supported inside '{orderByDescending}'. Use '{orderBy}' instead.
+ ///
+ public static string OrderByDescendingScoringFunction(object? orderByDescending, object? orderBy)
+ => string.Format(
+ GetString("OrderByDescendingScoringFunction", nameof(orderByDescending), nameof(orderBy)),
+ orderByDescending, orderBy);
+
+ ///
+ /// Only one ordering using scoring function is allowed. Use 'EF.Functions.{rrf}' method to combine multiple scoring functions.
+ ///
+ public static string OrderByMultipleScoringFunctionWithoutRrf(object? rrf)
+ => string.Format(
+ GetString("OrderByMultipleScoringFunctionWithoutRrf", nameof(rrf)),
+ rrf);
+
+ ///
+ /// Ordering using a scoring function is mutually exclusive with other forms of ordering.
+ ///
+ public static string OrderByScoringFunctionMixedWithRegularOrderby
+ => GetString("OrderByScoringFunctionMixedWithRegularOrderby");
+
///
/// The entity of type '{entityType}' is mapped as a part of the document mapped to '{missingEntityType}', but there is no tracked entity of this type with the corresponding key value. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values.
///
diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.resx b/src/EFCore.Cosmos/Properties/CosmosStrings.resx
index 8f9a875524b..09182e651e6 100644
--- a/src/EFCore.Cosmos/Properties/CosmosStrings.resx
+++ b/src/EFCore.Cosmos/Properties/CosmosStrings.resx
@@ -283,6 +283,15 @@
Exactly one of '{param1}' or '{param2}' must be set.
+
+ Ordering based on scoring function is not supported inside '{orderByDescending}'. Use '{orderBy}' instead.
+
+
+ Only one ordering using scoring function is allowed. Use 'EF.Functions.{rrf}' method to combine multiple scoring functions.
+
+
+ Ordering using a scoring function is mutually exclusive with other forms of ordering.
+
The entity of type '{entityType}' is mapped as a part of the document mapped to '{missingEntityType}', but there is no tracked entity of this type with the corresponding key value. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values.
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosMethodCallTranslatorProvider.cs b/src/EFCore.Cosmos/Query/Internal/CosmosMethodCallTranslatorProvider.cs
index 352e3d443e1..000c449a7c0 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosMethodCallTranslatorProvider.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosMethodCallTranslatorProvider.cs
@@ -36,7 +36,8 @@ public CosmosMethodCallTranslatorProvider(
new CosmosRegexTranslator(sqlExpressionFactory),
new CosmosStringMethodTranslator(sqlExpressionFactory),
new CosmosTypeCheckingTranslator(sqlExpressionFactory),
- new CosmosVectorSearchTranslator(sqlExpressionFactory, typeMappingSource)
+ new CosmosVectorSearchTranslator(sqlExpressionFactory, typeMappingSource),
+ new CosmosFullTextSearchTranslator(sqlExpressionFactory, typeMappingSource)
//new LikeTranslator(sqlExpressionFactory),
//new EnumHasFlagTranslator(sqlExpressionFactory),
//new GetValueOrDefaultTranslator(sqlExpressionFactory),
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs
index 67b1437208a..5da126d0827 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs
@@ -14,6 +14,9 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
///
public class CosmosQuerySqlGenerator(ITypeMappingSource typeMappingSource) : SqlExpressionVisitor
{
+ private static readonly bool UseOldBehavior35476 =
+ AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35476", out var enabled35476) && enabled35476;
+
private readonly IndentedStringBuilder _sqlBuilder = new();
private IReadOnlyDictionary _parameterValues = null!;
private List _sqlParameters = null!;
@@ -341,6 +344,15 @@ protected override Expression VisitSelect(SelectExpression selectExpression)
{
_sqlBuilder.AppendLine().Append("ORDER BY ");
+ var orderByScoringFunction = selectExpression.Orderings is [{ Expression: SqlFunctionExpression { IsScoringFunction: true } }];
+ if (!UseOldBehavior35476 && orderByScoringFunction)
+ {
+ _sqlBuilder.Append("RANK ");
+ }
+
+ Check.DebugAssert(UseOldBehavior35476 || orderByScoringFunction || selectExpression.Orderings.All(x => x.Expression is not SqlFunctionExpression { IsScoringFunction: true }),
+ "Scoring function can only appear as first (and only) ordering, or not at all.");
+
GenerateList(selectExpression.Orderings, e => Visit(e));
}
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs
index d349d73e609..63553c5eba8 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs
@@ -20,6 +20,9 @@ private abstract class CosmosProjectionBindingRemovingExpressionVisitorBase(
bool trackQueryResults)
: ExpressionVisitor
{
+ private static readonly bool UseOldBehavior21006 =
+ AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue21006", out var enabled21006) && enabled21006;
+
private static readonly MethodInfo GetItemMethodInfo
= typeof(JObject).GetRuntimeProperties()
.Single(pi => pi.Name == "Item" && pi.GetIndexParameters()[0].ParameterType == typeof(string))
@@ -691,7 +694,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 +719,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 +734,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 +778,7 @@ var body
Constant(CosmosClientWrapper.Serializer)),
converter.ConvertFromProviderExpression.Body);
+ var originalBodyType = body.Type;
if (body.Type != type)
{
body = Convert(body, type);
@@ -783,7 +799,11 @@ var body
}
else
{
- replaceExpression = Default(type);
+ replaceExpression = isNonNullableScalar && !UseOldBehavior21006
+ ? Expression.Convert(
+ Default(originalBodyType),
+ type)
+ : Default(type);
}
body = Condition(
@@ -799,7 +819,11 @@ var body
}
else
{
- valueExpression = ConvertJTokenToType(jTokenExpression, typeMapping?.ClrType.MakeNullable() ?? type);
+ valueExpression = ConvertJTokenToType(
+ jTokenExpression,
+ (isNonNullableScalar && !UseOldBehavior21006
+ ? typeMapping?.ClrType
+ : typeMapping?.ClrType.MakeNullable()) ?? type);
if (valueExpression.Type != type)
{
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.InExpressionValuesExpandingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.InExpressionValuesExpandingExpressionVisitor.cs
index 98563fd0a2a..a3cdfa86608 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.InExpressionValuesExpandingExpressionVisitor.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.InExpressionValuesExpandingExpressionVisitor.cs
@@ -9,48 +9,156 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
public partial class CosmosShapedQueryCompilingExpressionVisitor
{
- private sealed class InExpressionValuesExpandingExpressionVisitor(
+ private static readonly bool UseOldBehavior35476 =
+ AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35476", out var enabled35476) && enabled35476;
+
+ private static readonly bool UseOldBehavior35983 =
+ AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35983", out var enabled35983) && enabled35983;
+
+ private sealed class ParameterInliner(
ISqlExpressionFactory sqlExpressionFactory,
IReadOnlyDictionary parametersValues)
: ExpressionVisitor
{
protected override Expression VisitExtension(Expression expression)
{
- if (expression is InExpression inExpression)
+ if (!UseOldBehavior35476)
{
- IReadOnlyList values;
+ expression = base.VisitExtension(expression);
+ }
- switch (inExpression)
+ switch (expression)
+ {
+ // Inlines array parameter of InExpression, transforming: 'item IN (@valuesArray)' to: 'item IN (value1, value2)'
+ case InExpression inExpression:
{
- case { Values: IReadOnlyList values2 }:
- values = values2;
- break;
-
- // TODO: IN with subquery (return immediately, nothing to do here)
+ IReadOnlyList values;
- case { ValuesParameter: SqlParameterExpression valuesParameter }:
+ switch (inExpression)
{
- var typeMapping = valuesParameter.TypeMapping;
- var mutableValues = new List();
- foreach (var value in (IEnumerable)parametersValues[valuesParameter.Name])
+ case { Values: IReadOnlyList values2 }:
+ values = values2;
+ break;
+
+ // TODO: IN with subquery (return immediately, nothing to do here)
+
+ case { ValuesParameter: SqlParameterExpression valuesParameter }:
{
- mutableValues.Add(sqlExpressionFactory.Constant(value, value?.GetType() ?? typeof(object), typeMapping));
+ var typeMapping = valuesParameter.TypeMapping;
+ var mutableValues = new List();
+ foreach (var value in (IEnumerable)parametersValues[valuesParameter.Name])
+ {
+ mutableValues.Add(sqlExpressionFactory.Constant(value, value?.GetType() ?? typeof(object), typeMapping));
+ }
+
+ values = mutableValues;
+ break;
}
- values = mutableValues;
- break;
+ default:
+ throw new UnreachableException();
}
- default:
- throw new UnreachableException();
+ return values.Count == 0
+ ? sqlExpressionFactory.ApplyDefaultTypeMapping(sqlExpressionFactory.Constant(false))
+ : sqlExpressionFactory.In((SqlExpression)Visit(inExpression.Item), values);
}
- return values.Count == 0
- ? sqlExpressionFactory.ApplyDefaultTypeMapping(sqlExpressionFactory.Constant(false))
- : sqlExpressionFactory.In((SqlExpression)Visit(inExpression.Item), values);
- }
+ // Converts Offset and Limit parameters to constants when ORDER BY RANK is detected in the SelectExpression (i.e. we order by scoring function)
+ // Cosmos only supports constants in Offset and Limit for this scenario currently (ORDER BY RANK limitation)
+ case SelectExpression { Orderings: [{ Expression: SqlFunctionExpression { IsScoringFunction: true } }], Limit: var limit, Offset: var offset } hybridSearch
+ when !UseOldBehavior35476 && (limit is SqlParameterExpression || offset is SqlParameterExpression):
+ {
+ if (hybridSearch.Limit is SqlParameterExpression limitPrm)
+ {
+ hybridSearch.ApplyLimit(
+ sqlExpressionFactory.Constant(
+ parametersValues[limitPrm.Name],
+ limitPrm.TypeMapping));
+ }
+
+ if (hybridSearch.Offset is SqlParameterExpression offsetPrm)
+ {
+ hybridSearch.ApplyOffset(
+ sqlExpressionFactory.Constant(
+ parametersValues[offsetPrm.Name],
+ offsetPrm.TypeMapping));
+ }
- return base.VisitExtension(expression);
+ return base.VisitExtension(expression);
+ }
+
+ // Inlines array parameter of full-text functions, transforming FullTextContainsAll(x, @keywordsArray) to FullTextContainsAll(x, keyword1, keyword2)
+ case SqlFunctionExpression
+ {
+ Name: "FullTextContainsAny" or "FullTextContainsAll",
+ Arguments: [var property, SqlParameterExpression { TypeMapping: { ElementTypeMapping: var elementTypeMapping }, Type: Type type } keywords]
+ } fullTextContainsAllAnyFunction
+ when !UseOldBehavior35476 && type == typeof(string[]):
+ {
+ var keywordValues = new List();
+ foreach (var value in (IEnumerable)parametersValues[keywords.Name])
+ {
+ keywordValues.Add(sqlExpressionFactory.Constant(value, typeof(string), elementTypeMapping));
+ }
+
+ return sqlExpressionFactory.Function(
+ fullTextContainsAllAnyFunction.Name,
+ [property, .. keywordValues],
+ fullTextContainsAllAnyFunction.Type,
+ fullTextContainsAllAnyFunction.TypeMapping);
+ }
+
+ // Inlines array parameter of full-text score, transforming FullTextScore(x, @keywordsArray) to FullTextScore(x, keyword1, keyword2)
+ case SqlFunctionExpression
+ {
+ Name: "FullTextScore",
+ IsScoringFunction: true,
+ Arguments: [var property, SqlParameterExpression { TypeMapping: { ElementTypeMapping: var elementTypeMapping }, Type: Type type } keywords]
+ } fullTextScoreFunction
+ when !UseOldBehavior35476 && !UseOldBehavior35983 && type == typeof(string[]):
+ {
+ var keywordValues = new List();
+ foreach (var value in (IEnumerable)parametersValues[keywords.Name])
+ {
+ keywordValues.Add(sqlExpressionFactory.Constant(value, typeof(string), elementTypeMapping));
+ }
+
+ return new SqlFunctionExpression(
+ fullTextScoreFunction.Name,
+ isScoringFunction: true,
+ [property, .. keywordValues],
+ fullTextScoreFunction.Type,
+ fullTextScoreFunction.TypeMapping);
+ }
+
+ // Legacy path for #35983
+ // Inlines array parameter of full-text score, transforming FullTextScore(x, @keywordsArray) to FullTextScore(x, [keyword1, keyword2])
+ case SqlFunctionExpression
+ {
+ Name: "FullTextScore",
+ IsScoringFunction: true,
+ Arguments: [var property, SqlParameterExpression { TypeMapping: { ElementTypeMapping: not null } typeMapping } keywords]
+ } fullTextScoreFunction
+ when !UseOldBehavior35476 && UseOldBehavior35983:
+ {
+ var keywordValues = new List();
+ foreach (var value in (IEnumerable)parametersValues[keywords.Name])
+ {
+ keywordValues.Add((string)value);
+ }
+
+ return new SqlFunctionExpression(
+ fullTextScoreFunction.Name,
+ isScoringFunction: true,
+ [property, sqlExpressionFactory.Constant(keywordValues, typeMapping)],
+ fullTextScoreFunction.Type,
+ fullTextScoreFunction.TypeMapping);
+ }
+
+ default:
+ return expression;
+ }
}
}
}
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.PagingQueryingEnumerable.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.PagingQueryingEnumerable.cs
index e90c24664a5..6e84ffa7827 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.PagingQueryingEnumerable.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.PagingQueryingEnumerable.cs
@@ -75,7 +75,7 @@ public IAsyncEnumerator> GetAsyncEnumerator(CancellationToken canc
private CosmosSqlQuery GenerateQuery()
=> _querySqlGeneratorFactory.Create().GetSqlQuery(
- (SelectExpression)new InExpressionValuesExpandingExpressionVisitor(
+ (SelectExpression)new ParameterInliner(
_sqlExpressionFactory,
_cosmosQueryContext.ParameterValues)
.Visit(_selectExpression),
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs
index 07b0c22115c..26c7d885cdc 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs
@@ -71,7 +71,7 @@ IEnumerator IEnumerable.GetEnumerator()
private CosmosSqlQuery GenerateQuery()
=> _querySqlGeneratorFactory.Create().GetSqlQuery(
- (SelectExpression)new InExpressionValuesExpandingExpressionVisitor(
+ (SelectExpression)new ParameterInliner(
_sqlExpressionFactory,
_cosmosQueryContext.ParameterValues)
.Visit(_selectExpression),
diff --git a/src/EFCore.Cosmos/Query/Internal/Expressions/FragmentExpression.cs b/src/EFCore.Cosmos/Query/Internal/Expressions/FragmentExpression.cs
index cdb0bbff323..3efd431ca2f 100644
--- a/src/EFCore.Cosmos/Query/Internal/Expressions/FragmentExpression.cs
+++ b/src/EFCore.Cosmos/Query/Internal/Expressions/FragmentExpression.cs
@@ -23,6 +23,14 @@ public class FragmentExpression(string fragment) : Expression, IPrintableExpress
///
public virtual string Fragment { get; } = fragment;
+ ///
+ public override ExpressionType NodeType
+ => base.NodeType;
+
+ ///
+ public override Type Type
+ => typeof(object);
+
///
protected override Expression VisitChildren(ExpressionVisitor visitor)
=> this;
diff --git a/src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs b/src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs
index 3f31abdf5a8..5b735680b43 100644
--- a/src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs
+++ b/src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.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.Cosmos.Extensions;
using Microsoft.EntityFrameworkCore.Cosmos.Internal;
using Microsoft.EntityFrameworkCore.Internal;
@@ -16,6 +17,9 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
[DebuggerDisplay("{PrintShortSql(), nq}")]
public sealed class SelectExpression : Expression, IPrintableExpression
{
+ private static readonly bool UseOldBehavior35476 =
+ AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35476", out var enabled35476) && enabled35476;
+
private IDictionary _projectionMapping = new Dictionary();
private readonly List _sources = [];
private readonly List _projection = [];
@@ -381,6 +385,12 @@ public void ApplyOffset(SqlExpression sqlExpression)
///
public void ApplyOrdering(OrderingExpression orderingExpression)
{
+ if (!UseOldBehavior35476 && orderingExpression is { Expression: SqlFunctionExpression { IsScoringFunction: true }, IsAscending: false })
+ {
+ throw new InvalidOperationException(
+ CosmosStrings.OrderByDescendingScoringFunction(nameof(Queryable.OrderByDescending), nameof(Queryable.OrderBy)));
+ }
+
_orderings.Clear();
_orderings.Add(orderingExpression);
}
@@ -393,6 +403,19 @@ public void ApplyOrdering(OrderingExpression orderingExpression)
///
public void AppendOrdering(OrderingExpression orderingExpression)
{
+ if (!UseOldBehavior35476 && _orderings.Count > 0)
+ {
+ var existingScoringFunctionOrdering = _orderings is [{ Expression: SqlFunctionExpression { IsScoringFunction: true } }];
+ var appendingScoringFunctionOrdering = orderingExpression.Expression is SqlFunctionExpression { IsScoringFunction: true };
+ if (appendingScoringFunctionOrdering || existingScoringFunctionOrdering)
+ {
+ throw new InvalidOperationException(
+ appendingScoringFunctionOrdering && existingScoringFunctionOrdering
+ ? CosmosStrings.OrderByMultipleScoringFunctionWithoutRrf(nameof(CosmosDbFunctionsExtensions.Rrf))
+ : CosmosStrings.OrderByScoringFunctionMixedWithRegularOrderby);
+ }
+ }
+
if (_orderings.FirstOrDefault(o => o.Expression.Equals(orderingExpression.Expression)) == null)
{
_orderings.Add(orderingExpression);
@@ -752,6 +775,11 @@ private void PrintSql(ExpressionPrinter expressionPrinter, bool withTags = true)
if (Orderings.Any())
{
expressionPrinter.AppendLine().Append("ORDER BY ");
+ if (!UseOldBehavior35476 && Orderings is [{ Expression: SqlFunctionExpression { IsScoringFunction: true } }])
+ {
+ expressionPrinter.Append("RANK ");
+ }
+
expressionPrinter.VisitCollection(Orderings);
}
diff --git a/src/EFCore.Cosmos/Query/Internal/Expressions/SqlFunctionExpression.cs b/src/EFCore.Cosmos/Query/Internal/Expressions/SqlFunctionExpression.cs
index 91b53ca7039..960b2c6f0eb 100644
--- a/src/EFCore.Cosmos/Query/Internal/Expressions/SqlFunctionExpression.cs
+++ b/src/EFCore.Cosmos/Query/Internal/Expressions/SqlFunctionExpression.cs
@@ -3,6 +3,8 @@
// ReSharper disable once CheckNamespace
+using System.Diagnostics.CodeAnalysis;
+
namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
///
@@ -24,10 +26,28 @@ public SqlFunctionExpression(
IEnumerable arguments,
Type type,
CoreTypeMapping? typeMapping)
+ : this(name, isScoringFunction: false, arguments, type, typeMapping)
+ {
+ }
+
+ ///
+ /// 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.
+ ///
+ [Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
+ public SqlFunctionExpression(
+ string name,
+ bool isScoringFunction,
+ IEnumerable arguments,
+ Type type,
+ CoreTypeMapping? typeMapping)
: base(type, typeMapping)
{
Name = name;
Arguments = arguments.ToList();
+ IsScoringFunction = isScoringFunction;
}
///
@@ -38,6 +58,15 @@ public SqlFunctionExpression(
///
public virtual string Name { get; }
+ ///
+ /// 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.
+ ///
+ [Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
+ public virtual bool IsScoringFunction { get; }
+
///
/// 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
@@ -63,7 +92,7 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
}
return changed
- ? new SqlFunctionExpression(Name, arguments, Type, TypeMapping)
+ ? new SqlFunctionExpression(Name, IsScoringFunction, arguments, Type, TypeMapping)
: this;
}
@@ -74,7 +103,7 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual SqlFunctionExpression ApplyTypeMapping(CoreTypeMapping? typeMapping)
- => new(Name, Arguments, Type, typeMapping ?? TypeMapping);
+ => new(Name, IsScoringFunction, Arguments, Type, typeMapping ?? TypeMapping);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -85,7 +114,7 @@ public virtual SqlFunctionExpression ApplyTypeMapping(CoreTypeMapping? typeMappi
public virtual SqlFunctionExpression Update(IReadOnlyList arguments)
=> arguments.SequenceEqual(Arguments)
? this
- : new SqlFunctionExpression(Name, arguments, Type, TypeMapping);
+ : new SqlFunctionExpression(Name, IsScoringFunction, arguments, Type, TypeMapping);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore.Cosmos/Query/Internal/Translators/CosmosFullTextSearchTranslator.cs b/src/EFCore.Cosmos/Query/Internal/Translators/CosmosFullTextSearchTranslator.cs
new file mode 100644
index 00000000000..db9995c074e
--- /dev/null
+++ b/src/EFCore.Cosmos/Query/Internal/Translators/CosmosFullTextSearchTranslator.cs
@@ -0,0 +1,142 @@
+// 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.Extensions;
+
+namespace Microsoft.EntityFrameworkCore.Cosmos.Query.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 class CosmosFullTextSearchTranslator(ISqlExpressionFactory sqlExpressionFactory, ITypeMappingSource typeMappingSource)
+ : IMethodCallTranslator
+{
+ private static readonly bool UseOldBehavior35476 =
+ AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35476", out var enabled35476) && enabled35476;
+
+ private static readonly bool UseOldBehavior35983 =
+ AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35983", out var enabled35983) && enabled35983;
+
+ ///
+ /// 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 SqlExpression? Translate(
+ SqlExpression? instance,
+ MethodInfo method,
+ IReadOnlyList arguments,
+ IDiagnosticsLogger logger)
+ {
+ if (UseOldBehavior35476 || method.DeclaringType != typeof(CosmosDbFunctionsExtensions))
+ {
+ return null;
+ }
+
+ return method.Name switch
+ {
+ nameof(CosmosDbFunctionsExtensions.FullTextContains)
+ when arguments is [_, var property, var keyword] => sqlExpressionFactory.Function(
+ "FullTextContains",
+ [
+ property,
+ keyword,
+ ],
+ typeof(bool),
+ typeMappingSource.FindMapping(typeof(bool))),
+
+ nameof(CosmosDbFunctionsExtensions.FullTextScore)
+ when !UseOldBehavior35983 && arguments is [_, SqlExpression property, SqlConstantExpression { Type: var keywordClrType, Value: string[] values } keywords]
+ && keywordClrType == typeof(string[]) => BuildScoringFunction(
+ sqlExpressionFactory,
+ "FullTextScore",
+ [property, .. values.Select(x => sqlExpressionFactory.Constant(x))],
+ typeof(double),
+ typeMappingSource.FindMapping(typeof(double))),
+
+ nameof(CosmosDbFunctionsExtensions.FullTextScore)
+ when !UseOldBehavior35983 && arguments is [_, SqlExpression property, SqlParameterExpression { Type: var keywordClrType } keywords]
+ && keywordClrType == typeof(string[]) => BuildScoringFunction(
+ sqlExpressionFactory,
+ "FullTextScore",
+ [property, keywords],
+ typeof(double),
+ typeMappingSource.FindMapping(typeof(double))),
+
+ nameof(CosmosDbFunctionsExtensions.FullTextScore)
+ when !UseOldBehavior35983 && arguments is [_, SqlExpression property, ArrayConstantExpression keywords] => BuildScoringFunction(
+ sqlExpressionFactory,
+ "FullTextScore",
+ [property, .. keywords.Items],
+ typeof(double),
+ typeMappingSource.FindMapping(typeof(double))),
+
+ nameof(CosmosDbFunctionsExtensions.FullTextScore)
+ when UseOldBehavior35983 && arguments is [_, var property, var keywords] => BuildScoringFunction(
+ sqlExpressionFactory,
+ "FullTextScore",
+ [property, keywords],
+ typeof(double),
+ typeMappingSource.FindMapping(typeof(double))),
+
+ nameof(CosmosDbFunctionsExtensions.Rrf)
+ when arguments is [_, ArrayConstantExpression functions] => BuildScoringFunction(
+ sqlExpressionFactory,
+ "RRF",
+ functions.Items,
+ typeof(double),
+ typeMappingSource.FindMapping(typeof(double))),
+
+ nameof(CosmosDbFunctionsExtensions.FullTextContainsAny) or nameof(CosmosDbFunctionsExtensions.FullTextContainsAll)
+ when arguments is [_, SqlExpression property, SqlConstantExpression { Type: var keywordClrType, Value: string[] values } keywords]
+ && keywordClrType == typeof(string[]) => sqlExpressionFactory.Function(
+ method.Name == nameof(CosmosDbFunctionsExtensions.FullTextContainsAny) ? "FullTextContainsAny" : "FullTextContainsAll",
+ [property, .. values.Select(x => sqlExpressionFactory.Constant(x))],
+ typeof(bool),
+ typeMappingSource.FindMapping(typeof(bool))),
+
+ nameof(CosmosDbFunctionsExtensions.FullTextContainsAny) or nameof(CosmosDbFunctionsExtensions.FullTextContainsAll)
+ when arguments is [_, SqlExpression property, SqlParameterExpression { Type: var keywordClrType } keywords]
+ && keywordClrType == typeof(string[]) => sqlExpressionFactory.Function(
+ method.Name == nameof(CosmosDbFunctionsExtensions.FullTextContainsAny) ? "FullTextContainsAny" : "FullTextContainsAll",
+ [property, keywords],
+ typeof(bool),
+ typeMappingSource.FindMapping(typeof(bool))),
+
+ nameof(CosmosDbFunctionsExtensions.FullTextContainsAny) or nameof(CosmosDbFunctionsExtensions.FullTextContainsAll)
+ when arguments is [_, SqlExpression property, ArrayConstantExpression keywords] => sqlExpressionFactory.Function(
+ method.Name == nameof(CosmosDbFunctionsExtensions.FullTextContainsAny) ? "FullTextContainsAny" : "FullTextContainsAll",
+ [property, .. keywords.Items],
+ typeof(bool),
+ typeMappingSource.FindMapping(typeof(bool))),
+
+ _ => null
+ };
+ }
+
+ private SqlExpression BuildScoringFunction(
+ ISqlExpressionFactory sqlExpressionFactory,
+ string functionName,
+ IEnumerable arguments,
+ Type returnType,
+ CoreTypeMapping? typeMapping = null)
+ {
+ var typeMappedArguments = new List();
+
+ foreach (var argument in arguments)
+ {
+ typeMappedArguments.Add(argument is SqlExpression sqlArgument ? sqlExpressionFactory.ApplyDefaultTypeMapping(sqlArgument) : argument);
+ }
+
+ return new SqlFunctionExpression(
+ functionName,
+ isScoringFunction: true,
+ typeMappedArguments,
+ returnType,
+ typeMapping);
+ }
+}
diff --git a/src/EFCore.Cosmos/Query/Internal/Translators/CosmosVectorSearchTranslator.cs b/src/EFCore.Cosmos/Query/Internal/Translators/CosmosVectorSearchTranslator.cs
index 9c6e62d02a2..d77ddbf4373 100644
--- a/src/EFCore.Cosmos/Query/Internal/Translators/CosmosVectorSearchTranslator.cs
+++ b/src/EFCore.Cosmos/Query/Internal/Translators/CosmosVectorSearchTranslator.cs
@@ -18,6 +18,9 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
public class CosmosVectorSearchTranslator(ISqlExpressionFactory sqlExpressionFactory, ITypeMappingSource typeMappingSource)
: IMethodCallTranslator
{
+ private static readonly bool UseOldBehavior35853 =
+ AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35853", out var enabled35853) && enabled35853;
+
///
/// 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
@@ -30,7 +33,15 @@ public class CosmosVectorSearchTranslator(ISqlExpressionFactory sqlExpressionFac
IReadOnlyList arguments,
IDiagnosticsLogger logger)
{
- if (method.DeclaringType != typeof(CosmosDbFunctionsExtensions)
+ if (!UseOldBehavior35853)
+ {
+ if (method.DeclaringType != typeof(CosmosDbFunctionsExtensions)
+ || method.Name != nameof(CosmosDbFunctionsExtensions.VectorDistance))
+ {
+ return null;
+ }
+ }
+ else if (method.DeclaringType != typeof(CosmosDbFunctionsExtensions)
&& method.Name != nameof(CosmosDbFunctionsExtensions.VectorDistance))
{
return null;
diff --git a/src/EFCore.Design/Design/Internal/AppServiceProviderFactory.cs b/src/EFCore.Design/Design/Internal/AppServiceProviderFactory.cs
index b797f8a4add..456184ec46a 100644
--- a/src/EFCore.Design/Design/Internal/AppServiceProviderFactory.cs
+++ b/src/EFCore.Design/Design/Internal/AppServiceProviderFactory.cs
@@ -89,4 +89,30 @@ private IServiceProvider CreateEmptyServiceProvider()
return new ServiceCollection().BuildServiceProvider();
}
+
+ ///
+ /// 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 void SetEnvironment(IOperationReporter reporter)
+ {
+ var aspnetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
+ var dotnetEnvironment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
+ var environment = aspnetCoreEnvironment
+ ?? dotnetEnvironment
+ ?? "Development";
+ if (aspnetCoreEnvironment == null)
+ {
+ Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", environment);
+ }
+
+ if (dotnetEnvironment == null)
+ {
+ Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", environment);
+ }
+
+ reporter.WriteVerbose(DesignStrings.UsingEnvironment(environment));
+ }
}
diff --git a/src/EFCore.Design/Design/Internal/DatabaseOperations.cs b/src/EFCore.Design/Design/Internal/DatabaseOperations.cs
index d27a2532b3c..f02f26bf4ed 100644
--- a/src/EFCore.Design/Design/Internal/DatabaseOperations.cs
+++ b/src/EFCore.Design/Design/Internal/DatabaseOperations.cs
@@ -11,6 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Design.Internal;
///
public class DatabaseOperations
{
+ private readonly IOperationReporter _reporter;
private readonly string _projectDir;
private readonly string? _rootNamespace;
private readonly string? _language;
@@ -34,6 +35,7 @@ public DatabaseOperations(
bool nullable,
string[]? args)
{
+ _reporter = reporter;
_projectDir = projectDir;
_rootNamespace = rootNamespace;
_language = language;
@@ -73,6 +75,7 @@ public virtual SavedModelFiles ScaffoldContext(
? Path.GetFullPath(Path.Combine(_projectDir, outputContextDir))
: outputDir;
+ AppServiceProviderFactory.SetEnvironment(_reporter);
var services = _servicesBuilder.Build(provider);
using var scope = services.CreateScope();
diff --git a/src/EFCore.Design/Design/Internal/DbContextOperations.cs b/src/EFCore.Design/Design/Internal/DbContextOperations.cs
index 761579c8bdb..f430f9892b0 100644
--- a/src/EFCore.Design/Design/Internal/DbContextOperations.cs
+++ b/src/EFCore.Design/Design/Internal/DbContextOperations.cs
@@ -503,22 +503,7 @@ private IDictionary> FindContextTypes(string? name = null,
{
_reporter.WriteVerbose(DesignStrings.FindingContexts);
- var aspnetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
- var dotnetEnvironment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
- var environment = aspnetCoreEnvironment
- ?? dotnetEnvironment
- ?? "Development";
- if (aspnetCoreEnvironment == null)
- {
- Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", environment);
- }
-
- if (dotnetEnvironment == null)
- {
- Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", environment);
- }
-
- _reporter.WriteVerbose(DesignStrings.UsingEnvironment(environment));
+ AppServiceProviderFactory.SetEnvironment(_reporter);
var contexts = new Dictionary?>();
diff --git a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs
index a47c283c472..30b89651e13 100644
--- a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs
+++ b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs
@@ -52,7 +52,9 @@ public SnapshotModelProcessor(
///
public virtual IModel? Process(IReadOnlyModel? model, bool resetVersion = false)
{
- if (model == null)
+ if (model == null
+ || model is not Model mutableModel
+ || mutableModel.IsReadOnly)
{
return null;
}
@@ -79,13 +81,10 @@ public SnapshotModelProcessor(
}
}
- if (model is IMutableModel mutableModel)
+ mutableModel.RemoveAnnotation("ChangeDetector.SkipDetectChanges");
+ if (resetVersion)
{
- mutableModel.RemoveAnnotation("ChangeDetector.SkipDetectChanges");
- if (resetVersion)
- {
- mutableModel.SetProductVersion(ProductInfo.GetVersion());
- }
+ mutableModel.SetProductVersion(ProductInfo.GetVersion());
}
return _modelRuntimeInitializer.Initialize((IModel)model, designTime: true, validationLogger: null);
diff --git a/src/EFCore.Relational/Migrations/Internal/Migrator.cs b/src/EFCore.Relational/Migrations/Internal/Migrator.cs
index ea758d2c634..2d5989257aa 100644
--- a/src/EFCore.Relational/Migrations/Internal/Migrator.cs
+++ b/src/EFCore.Relational/Migrations/Internal/Migrator.cs
@@ -3,7 +3,6 @@
using System.Transactions;
using Microsoft.EntityFrameworkCore.Diagnostics.Internal;
-using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace Microsoft.EntityFrameworkCore.Migrations.Internal;
@@ -94,7 +93,7 @@ public Migrator(
public virtual void Migrate(string? targetMigration)
{
var useTransaction = _connection.CurrentTransaction is null;
- ValidateMigrations(useTransaction);
+ ValidateMigrations(useTransaction, targetMigration);
using var transactionScope = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled);
@@ -219,7 +218,7 @@ public virtual async Task MigrateAsync(
CancellationToken cancellationToken = default)
{
var useTransaction = _connection.CurrentTransaction is null;
- ValidateMigrations(useTransaction);
+ ValidateMigrations(useTransaction, targetMigration);
using var transactionScope = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled);
@@ -349,7 +348,7 @@ await _migrationCommandExecutor.ExecuteNonQueryAsync(
}
}
- private void ValidateMigrations(bool useTransaction)
+ private void ValidateMigrations(bool useTransaction, string? targetMigration)
{
if (!useTransaction
&& _executionStrategy.RetriesOnFailure)
@@ -365,7 +364,8 @@ private void ValidateMigrations(bool useTransaction)
{
_logger.ModelSnapshotNotFound(this, _migrationsAssembly);
}
- else if (RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore
+ else if (targetMigration == null
+ && RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore
&& HasPendingModelChanges())
{
var modelSource = (ModelSource)_currentContext.Context.GetService();
diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs
index 1906d587a40..c18fe6a3ffd 100644
--- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs
+++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs
@@ -22,6 +22,9 @@ public class QuerySqlGenerator : SqlExpressionVisitor
private IRelationalCommandBuilder _relationalCommandBuilder;
private Dictionary? _repeatedParameterCounts;
+ private static readonly bool UseOldBehavior36105 =
+ AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue36105", out var enabled36105) && enabled36105;
+
///
/// Creates a new instance of the class.
///
@@ -1382,9 +1385,16 @@ static string GetSetOperation(SetOperationBase operation)
protected virtual void GenerateSetOperationOperand(SetOperationBase setOperation, SelectExpression operand)
{
// INTERSECT has higher precedence over UNION and EXCEPT, but otherwise evaluation is left-to-right.
- // To preserve meaning, add parentheses whenever a set operation is nested within a different set operation.
+ // To preserve evaluation order, add parentheses whenever a set operation is nested within a different set operation
+ // - including different distinctness.
+ // In addition, EXCEPT is non-commutative (unlike UNION/INTERSECT), so add parentheses for that case too (see #36105).
if (IsNonComposedSetOperation(operand)
- && operand.Tables[0].GetType() != setOperation.GetType())
+ && ((UseOldBehavior36105 && operand.Tables[0].GetType() != setOperation.GetType())
+ || (!UseOldBehavior36105
+ && operand.Tables[0] is SetOperationBase nestedSetOperation
+ && (nestedSetOperation is ExceptExpression
+ || nestedSetOperation.GetType() != setOperation.GetType()
+ || nestedSetOperation.IsDistinct != setOperation.IsDistinct))))
{
_relationalCommandBuilder.AppendLine("(");
using (_relationalCommandBuilder.Indent())
diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs
index 47f94f1d78b..31553d0e9db 100644
--- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs
+++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs
@@ -9,6 +9,9 @@ namespace Microsoft.EntityFrameworkCore.Query;
///
public class SqlExpressionFactory : ISqlExpressionFactory
{
+ private static readonly bool UseOldBehavior35393 =
+ AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35393", out var enabled35393) && enabled35393;
+
private readonly IRelationalTypeMappingSource _typeMappingSource;
private readonly RelationalTypeMapping _boolTypeMapping;
@@ -660,20 +663,45 @@ private SqlExpression Not(SqlExpression operand, SqlExpression? existingExpressi
SqlBinaryExpression { OperatorType: ExpressionType.OrElse } binary
=> AndAlso(Not(binary.Left), Not(binary.Right)),
- // use equality where possible
+ // use equality where possible - we can only do this when we know a is not null
+ // at this point we are limited to constants, parameters and columns
+ // see issue #35393
// !(a == true) -> a == false
// !(a == false) -> a == true
SqlBinaryExpression { OperatorType: ExpressionType.Equal, Right: SqlConstantExpression { Value: bool } } binary
+ when UseOldBehavior35393
+ => Equal(binary.Left, Not(binary.Right)),
+
+ SqlBinaryExpression
+ {
+ OperatorType: ExpressionType.Equal,
+ Right: SqlConstantExpression { Value: bool },
+ Left: SqlConstantExpression { Value: bool }
+ or SqlParameterExpression { IsNullable: false }
+ or ColumnExpression { IsNullable: false }
+ } binary
=> Equal(binary.Left, Not(binary.Right)),
// !(true == a) -> false == a
// !(false == a) -> true == a
SqlBinaryExpression { OperatorType: ExpressionType.Equal, Left: SqlConstantExpression { Value: bool } } binary
+ when UseOldBehavior35393
+ => Equal(Not(binary.Left), binary.Right),
+
+ SqlBinaryExpression
+ {
+ OperatorType: ExpressionType.Equal,
+ Left: SqlConstantExpression { Value: bool },
+ Right: SqlConstantExpression { Value: bool }
+ or SqlParameterExpression { IsNullable: false }
+ or ColumnExpression { IsNullable: false }
+ } binary
=> Equal(Not(binary.Left), binary.Right),
// !(a == b) -> a != b
SqlBinaryExpression { OperatorType: ExpressionType.Equal } sqlBinaryOperand => NotEqual(
sqlBinaryOperand.Left, sqlBinaryOperand.Right),
+
// !(a != b) -> a == b
SqlBinaryExpression { OperatorType: ExpressionType.NotEqual } sqlBinaryOperand => Equal(
sqlBinaryOperand.Left, sqlBinaryOperand.Right),
diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
index 407826342f2..95b79bfe9a7 100644
--- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
+++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
@@ -19,6 +19,9 @@ namespace Microsoft.EntityFrameworkCore.Query;
///
public class SqlNullabilityProcessor
{
+ private static readonly bool UseOldBehavior35393 =
+ AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35393", out var enabled35393) && enabled35393;
+
private readonly List _nonNullableColumns;
private readonly List _nullValueColumns;
private readonly ISqlExpressionFactory _sqlExpressionFactory;
@@ -1343,7 +1346,7 @@ protected virtual SqlExpression VisitSqlBinary(
// we assume that NullSemantics rewrite is only needed (on the current level)
// if the optimization didn't make any changes.
// Reason is that optimization can/will change the nullability of the resulting expression
- // and that inforation is not tracked/stored anywhere
+ // and that information is not tracked/stored anywhere
// so we can no longer rely on nullabilities that we computed earlier (leftNullable, rightNullable)
// when performing null semantics rewrite.
// It should be fine because current optimizations *radically* change the expression
diff --git a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
index 28362b77761..613f391200a 100644
--- a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
+++ b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.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 System.Collections;
using System.Globalization;
using System.Text;
using Microsoft.EntityFrameworkCore.SqlServer.Internal;
@@ -1599,17 +1600,6 @@ protected override void ColumnDefinition(
var isPeriodStartColumn = operation[SqlServerAnnotationNames.TemporalIsPeriodStartColumn] as bool? == true;
var isPeriodEndColumn = operation[SqlServerAnnotationNames.TemporalIsPeriodEndColumn] as bool? == true;
- // falling back to legacy annotations, in case the migration was generated using pre-9.0 bits
- if (!isPeriodStartColumn && !isPeriodEndColumn)
- {
- if (operation[SqlServerAnnotationNames.TemporalPeriodStartColumnName] is string periodStartColumnName
- && operation[SqlServerAnnotationNames.TemporalPeriodEndColumnName] is string periodEndColumnName)
- {
- isPeriodStartColumn = operation.Name == periodStartColumnName;
- isPeriodEndColumn = operation.Name == periodEndColumnName;
- }
- }
-
if (isPeriodStartColumn || isPeriodEndColumn)
{
builder.Append(" GENERATED ALWAYS AS ROW ");
@@ -2363,11 +2353,140 @@ private string Uniquify(string variableName, bool increase = true)
return _variableCounter == 0 ? variableName : variableName + _variableCounter;
}
+ private IReadOnlyList FixLegacyTemporalAnnotations(IReadOnlyList migrationOperations)
+ {
+ // short-circuit for non-temporal migrations (which is the majority)
+ if (migrationOperations.All(o => o[SqlServerAnnotationNames.IsTemporal] as bool? != true))
+ {
+ return migrationOperations;
+ }
+
+ var resultOperations = new List(migrationOperations.Count);
+ foreach (var migrationOperation in migrationOperations)
+ {
+ var isTemporal = migrationOperation[SqlServerAnnotationNames.IsTemporal] as bool? == true;
+ if (!isTemporal)
+ {
+ resultOperations.Add(migrationOperation);
+ continue;
+ }
+
+ switch (migrationOperation)
+ {
+ case CreateTableOperation createTableOperation:
+
+ foreach (var column in createTableOperation.Columns)
+ {
+ NormalizeTemporalAnnotationsForAddColumnOperation(column);
+ }
+
+ resultOperations.Add(migrationOperation);
+ break;
+
+ case AddColumnOperation addColumnOperation:
+ NormalizeTemporalAnnotationsForAddColumnOperation(addColumnOperation);
+ resultOperations.Add(addColumnOperation);
+ break;
+
+ case AlterColumnOperation alterColumnOperation:
+ RemoveLegacyTemporalColumnAnnotations(alterColumnOperation);
+ RemoveLegacyTemporalColumnAnnotations(alterColumnOperation.OldColumn);
+ if (!CanSkipAlterColumnOperation(alterColumnOperation, alterColumnOperation.OldColumn))
+ {
+ resultOperations.Add(alterColumnOperation);
+ }
+
+ break;
+
+ case DropColumnOperation dropColumnOperation:
+ RemoveLegacyTemporalColumnAnnotations(dropColumnOperation);
+ resultOperations.Add(dropColumnOperation);
+ break;
+
+ case RenameColumnOperation renameColumnOperation:
+ RemoveLegacyTemporalColumnAnnotations(renameColumnOperation);
+ resultOperations.Add(renameColumnOperation);
+ break;
+
+ default:
+ resultOperations.Add(migrationOperation);
+ break;
+ }
+ }
+
+ return resultOperations;
+
+ static void NormalizeTemporalAnnotationsForAddColumnOperation(AddColumnOperation addColumnOperation)
+ {
+ var periodStartColumnName = addColumnOperation[SqlServerAnnotationNames.TemporalPeriodStartColumnName] as string;
+ var periodEndColumnName = addColumnOperation[SqlServerAnnotationNames.TemporalPeriodEndColumnName] as string;
+ if (periodStartColumnName == addColumnOperation.Name)
+ {
+ addColumnOperation.AddAnnotation(SqlServerAnnotationNames.TemporalIsPeriodStartColumn, true);
+ }
+ else if (periodEndColumnName == addColumnOperation.Name)
+ {
+ addColumnOperation.AddAnnotation(SqlServerAnnotationNames.TemporalIsPeriodEndColumn, true);
+ }
+
+ RemoveLegacyTemporalColumnAnnotations(addColumnOperation);
+ }
+
+ static void RemoveLegacyTemporalColumnAnnotations(MigrationOperation operation)
+ {
+ operation.RemoveAnnotation(SqlServerAnnotationNames.IsTemporal);
+ operation.RemoveAnnotation(SqlServerAnnotationNames.TemporalHistoryTableName);
+ operation.RemoveAnnotation(SqlServerAnnotationNames.TemporalHistoryTableSchema);
+ operation.RemoveAnnotation(SqlServerAnnotationNames.TemporalPeriodStartColumnName);
+ operation.RemoveAnnotation(SqlServerAnnotationNames.TemporalPeriodEndColumnName);
+ }
+
+ static bool CanSkipAlterColumnOperation(ColumnOperation column, ColumnOperation oldColumn)
+ => ColumnPropertiesAreTheSame(column, oldColumn) && AnnotationsAreTheSame(column, oldColumn);
+
+ // don't compare name, table or schema - they are not being set in the model differ (since they should always be the same)
+ static bool ColumnPropertiesAreTheSame(ColumnOperation column, ColumnOperation oldColumn)
+ => column.ClrType == oldColumn.ClrType
+ && column.Collation == oldColumn.Collation
+ && column.ColumnType == oldColumn.ColumnType
+ && column.Comment == oldColumn.Comment
+ && column.ComputedColumnSql == oldColumn.ComputedColumnSql
+ && Equals(column.DefaultValue, oldColumn.DefaultValue)
+ && column.DefaultValueSql == oldColumn.DefaultValueSql
+ && column.IsDestructiveChange == oldColumn.IsDestructiveChange
+ && column.IsFixedLength == oldColumn.IsFixedLength
+ && column.IsNullable == oldColumn.IsNullable
+ && column.IsReadOnly == oldColumn.IsReadOnly
+ && column.IsRowVersion == oldColumn.IsRowVersion
+ && column.IsStored == oldColumn.IsStored
+ && column.IsUnicode == oldColumn.IsUnicode
+ && column.MaxLength == oldColumn.MaxLength
+ && column.Precision == oldColumn.Precision
+ && column.Scale == oldColumn.Scale;
+
+ static bool AnnotationsAreTheSame(ColumnOperation column, ColumnOperation oldColumn)
+ {
+ var columnAnnotations = column.GetAnnotations().ToList();
+ var oldColumnAnnotations = oldColumn.GetAnnotations().ToList();
+
+ if (columnAnnotations.Count != oldColumnAnnotations.Count)
+ {
+ return false;
+ }
+
+ return columnAnnotations.Zip(oldColumnAnnotations)
+ .All(x => x.First.Name == x.Second.Name
+ && StructuralComparisons.StructuralEqualityComparer.Equals(x.First.Value, x.Second.Value));
+ }
+ }
+
private IReadOnlyList RewriteOperations(
IReadOnlyList migrationOperations,
IModel? model,
MigrationsSqlGenerationOptions options)
{
+ migrationOperations = FixLegacyTemporalAnnotations(migrationOperations);
+
var operations = new List();
var availableSchemas = new List();
@@ -2430,10 +2549,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;
}
@@ -2528,10 +2653,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.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs
index ad1c62dc00e..f761526cf12 100644
--- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs
+++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.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 System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal;
@@ -13,6 +14,9 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal;
///
public class SqliteQuerySqlGenerator : QuerySqlGenerator
{
+ private static readonly bool UseOldBehavior36112 =
+ AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue36112", out var enabled36112) && enabled36112;
+
///
/// 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
@@ -97,8 +101,63 @@ protected override void GenerateLimitOffset(SelectExpression selectExpression)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
protected override void GenerateSetOperationOperand(SetOperationBase setOperation, SelectExpression operand)
- // Sqlite doesn't support parentheses around set operation operands
- => Visit(operand);
+ {
+ // Most databases support parentheses around set operations to determine evaluation order, but SQLite does not;
+ // however, we can instead wrap the nested set operation in a SELECT * FROM () to achieve the same effect.
+ // The following is a copy-paste of the base implementation from QuerySqlGenerator, adding the SELECT.
+
+ // INTERSECT has higher precedence over UNION and EXCEPT, but otherwise evaluation is left-to-right.
+ // To preserve evaluation order, add parentheses whenever a set operation is nested within a different set operation
+ // - including different distinctness.
+ // In addition, EXCEPT is non-commutative (unlike UNION/INTERSECT), so add parentheses for that case too (see #36105).
+ if (!UseOldBehavior36112
+ && TryUnwrapBareSetOperation(operand, out var nestedSetOperation)
+ && (nestedSetOperation is ExceptExpression
+ || nestedSetOperation.GetType() != setOperation.GetType()
+ || nestedSetOperation.IsDistinct != setOperation.IsDistinct))
+ {
+ Sql.AppendLine("SELECT * FROM (");
+
+ using (Sql.Indent())
+ {
+ Visit(operand);
+ }
+
+ Sql.AppendLine().Append(")");
+ }
+ else
+ {
+ Visit(operand);
+ }
+
+ static bool TryUnwrapBareSetOperation(SelectExpression selectExpression, [NotNullWhen(true)] out SetOperationBase? setOperation)
+ {
+ if (selectExpression is
+ {
+ Tables: [SetOperationBase s],
+ Predicate: null,
+ Orderings: [],
+ Offset: null,
+ Limit: null,
+ IsDistinct: false,
+ Having: null,
+ GroupBy: []
+ }
+ && selectExpression.Projection.Count == s.Source1.Projection.Count
+ && selectExpression.Projection.Select(
+ (pe, index) => pe.Expression is ColumnExpression column
+ && column.TableAlias == s.Alias
+ && column.Name == s.Source1.Projection[index].Alias)
+ .All(e => e))
+ {
+ setOperation = s;
+ return true;
+ }
+
+ setOperation = null;
+ return false;
+ }
+ }
private void GenerateGlob(GlobExpression globExpression, bool negated = false)
{
diff --git a/src/EFCore/ChangeTracking/ListOfNullableValueTypesComparer.cs b/src/EFCore/ChangeTracking/ListOfNullableValueTypesComparer.cs
index d95a40877db..a8c08979f18 100644
--- a/src/EFCore/ChangeTracking/ListOfNullableValueTypesComparer.cs
+++ b/src/EFCore/ChangeTracking/ListOfNullableValueTypesComparer.cs
@@ -25,6 +25,9 @@ public sealed class ListOfNullableValueTypesComparer :
IInfrastructure
where TElement : struct
{
+ private static readonly bool UseOldBehavior35239 =
+ AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35239", out var enabled35239) && enabled35239;
+
private static readonly bool IsArray = typeof(TConcreteList).IsArray;
private static readonly bool IsReadOnly = IsArray
@@ -32,14 +35,26 @@ public sealed class ListOfNullableValueTypesComparer :
&& typeof(TConcreteList).GetGenericTypeDefinition() == typeof(ReadOnlyCollection<>));
private static readonly MethodInfo CompareMethod = typeof(ListOfNullableValueTypesComparer).GetMethod(
+ nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic,
+ [typeof(IEnumerable), typeof(IEnumerable), typeof(Func)])!;
+
+ private static readonly MethodInfo LegacyCompareMethod = typeof(ListOfNullableValueTypesComparer).GetMethod(
nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic,
[typeof(IEnumerable), typeof(IEnumerable), typeof(ValueComparer)])!;
private static readonly MethodInfo GetHashCodeMethod = typeof(ListOfNullableValueTypesComparer).GetMethod(
+ nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic,
+ [typeof(IEnumerable), typeof(Func)])!;
+
+ private static readonly MethodInfo LegacyGetHashCodeMethod = typeof(ListOfNullableValueTypesComparer).GetMethod(
nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic,
[typeof(IEnumerable), typeof(ValueComparer)])!;
private static readonly MethodInfo SnapshotMethod = typeof(ListOfNullableValueTypesComparer).GetMethod(
+ nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic,
+ [typeof(IEnumerable), typeof(Func)])!;
+
+ private static readonly MethodInfo LegacySnapshotMethod = typeof(ListOfNullableValueTypesComparer).GetMethod(
nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic,
[typeof(IEnumerable), typeof(ValueComparer)])!;
@@ -67,10 +82,23 @@ ValueComparer IInfrastructure.Instance
var prm1 = Expression.Parameter(typeof(IEnumerable), "a");
var prm2 = Expression.Parameter(typeof(IEnumerable), "b");
+ if (elementComparer is ValueComparer && !UseOldBehavior35239)
+ {
+ //(a, b) => Compare(a, b, elementComparer.Equals)
+ return Expression.Lambda?, IEnumerable?, bool>>(
+ Expression.Call(
+ CompareMethod,
+ prm1,
+ prm2,
+ elementComparer.EqualsExpression),
+ prm1,
+ prm2);
+ }
+
//(a, b) => Compare(a, b, (ValueComparer)elementComparer)
return Expression.Lambda?, IEnumerable?, bool>>(
Expression.Call(
- CompareMethod,
+ LegacyCompareMethod,
prm1,
prm2,
Expression.Convert(
@@ -84,10 +112,21 @@ ValueComparer IInfrastructure.Instance
{
var prm = Expression.Parameter(typeof(IEnumerable), "o");
+ if (elementComparer is ValueComparer && !UseOldBehavior35239)
+ {
+ //o => GetHashCode(o, elementComparer.GetHashCode)
+ return Expression.Lambda, int>>(
+ Expression.Call(
+ GetHashCodeMethod,
+ prm,
+ elementComparer.HashCodeExpression),
+ prm);
+ }
+
//o => GetHashCode(o, (ValueComparer)elementComparer)
return Expression.Lambda, int>>(
Expression.Call(
- GetHashCodeMethod,
+ LegacyGetHashCodeMethod,
prm,
Expression.Convert(
elementComparer.ConstructorExpression,
@@ -98,11 +137,21 @@ ValueComparer IInfrastructure.Instance
private static Expression, IEnumerable>> SnapshotLambda(ValueComparer elementComparer)
{
var prm = Expression.Parameter(typeof(IEnumerable), "source");
+ if (elementComparer is ValueComparer && !UseOldBehavior35239)
+ {
+ //source => Snapshot(source, elementComparer.Snapshot)
+ return Expression.Lambda, IEnumerable>>(
+ Expression.Call(
+ SnapshotMethod,
+ prm,
+ elementComparer.SnapshotExpression),
+ prm);
+ }
//source => Snapshot(source, (ValueComparer)elementComparer)
return Expression.Lambda, IEnumerable>>(
Expression.Call(
- SnapshotMethod,
+ LegacySnapshotMethod,
prm,
Expression.Convert(
elementComparer.ConstructorExpression,
@@ -110,6 +159,63 @@ ValueComparer IInfrastructure.Instance
prm);
}
+ private static bool Compare(IEnumerable? a, IEnumerable? b, Func elementCompare)
+ {
+ if (ReferenceEquals(a, b))
+ {
+ return true;
+ }
+
+ if (a is null)
+ {
+ return b is null;
+ }
+
+ if (b is null)
+ {
+ return false;
+ }
+
+ if (a is IList aList && b is IList bList)
+ {
+ if (aList.Count != bList.Count)
+ {
+ return false;
+ }
+
+ for (var i = 0; i < aList.Count; i++)
+ {
+ var (el1, el2) = (aList[i], bList[i]);
+ if (el1 is null)
+ {
+ if (el2 is null)
+ {
+ continue;
+ }
+
+ return false;
+ }
+
+ if (el2 is null)
+ {
+ return false;
+ }
+
+ if (!elementCompare(el1, el2))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ throw new InvalidOperationException(
+ CoreStrings.BadListType(
+ (a is IList ? b : a).GetType().ShortDisplayName(),
+ typeof(IList<>).MakeGenericType(typeof(TElement).MakeNullable()).ShortDisplayName()));
+ }
+
private static bool Compare(IEnumerable? a, IEnumerable? b, ValueComparer elementComparer)
{
if (ReferenceEquals(a, b))
@@ -167,6 +273,18 @@ private static bool Compare(IEnumerable? a, IEnumerable? b
typeof(IList<>).MakeGenericType(elementComparer.Type.MakeNullable()).ShortDisplayName()));
}
+ private static int GetHashCode(IEnumerable source, Func elementGetHashCode)
+ {
+ var hash = new HashCode();
+
+ foreach (var el in source)
+ {
+ hash.Add(el == null ? 0 : elementGetHashCode(el));
+ }
+
+ return hash.ToHashCode();
+ }
+
private static int GetHashCode(IEnumerable source, ValueComparer elementComparer)
{
var hash = new HashCode();
@@ -179,6 +297,41 @@ private static int GetHashCode(IEnumerable source, ValueComparer Snapshot(IEnumerable source, Func elementSnapshot)
+ {
+ if (source is not IList sourceList)
+ {
+ throw new InvalidOperationException(
+ CoreStrings.BadListType(
+ source.GetType().ShortDisplayName(),
+ typeof(IList<>).MakeGenericType(typeof(TElement).MakeNullable()).ShortDisplayName()));
+ }
+
+ if (IsArray)
+ {
+ var snapshot = new TElement?[sourceList.Count];
+ for (var i = 0; i < sourceList.Count; i++)
+ {
+ var instance = sourceList[i];
+ snapshot[i] = instance == null ? null : elementSnapshot(instance);
+ }
+
+ return snapshot;
+ }
+ else
+ {
+ var snapshot = IsReadOnly ? new List() : (IList)Activator.CreateInstance()!;
+ foreach (var e in sourceList)
+ {
+ snapshot.Add(e == null ? null : elementSnapshot(e));
+ }
+
+ return IsReadOnly
+ ? (IList)Activator.CreateInstance(typeof(TConcreteList), snapshot)!
+ : snapshot;
+ }
+ }
+
private static IList Snapshot(IEnumerable source, ValueComparer elementComparer)
{
if (source is not IList sourceList)
diff --git a/src/EFCore/ChangeTracking/ListOfReferenceTypesComparer.cs b/src/EFCore/ChangeTracking/ListOfReferenceTypesComparer.cs
index e0e77528d87..f24a6f95aa2 100644
--- a/src/EFCore/ChangeTracking/ListOfReferenceTypesComparer.cs
+++ b/src/EFCore/ChangeTracking/ListOfReferenceTypesComparer.cs
@@ -23,6 +23,9 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking;
public sealed class ListOfReferenceTypesComparer : ValueComparer