diff --git a/.config/guardian/.gdnbaselines b/.config/guardian/.gdnbaselines new file mode 100644 index 00000000000..f8d9ac1b5c3 --- /dev/null +++ b/.config/guardian/.gdnbaselines @@ -0,0 +1,33 @@ +{ + "hydrated": false, + "properties": { + "helpUri": "https://eng.ms/docs/microsoft-security/security/azure-security/cloudai-security-fundamentals-engineering/security-integration/guardian-wiki/microsoft-guardian/general/baselines", + "hydrationStatus": "This file does not contain identifying data. It is safe to check into your repo. To hydrate this file with identifying data, run `guardian hydrate --help` and follow the guidance." + }, + "version": "1.0.0", + "baselines": { + "default": { + "name": "default", + "createdDate": "2024-03-26 04:31:45Z", + "lastUpdatedDate": "2024-03-26 04:31:45Z" + } + }, + "results": { + "669098c2981b719b462da19de85b79975f42c84324e2a97495a071d3be667770": { + "signature": "669098c2981b719b462da19de85b79975f42c84324e2a97495a071d3be667770", + "alternativeSignatures": [], + "memberOf": [ + "default" + ], + "createdDate": "2024-03-26 04:31:45Z" + }, + "07b47302c50eb284f09501218f012eb0ea54edf3d8ae1a3737f1bd5083da699a": { + "signature": "07b47302c50eb284f09501218f012eb0ea54edf3d8ae1a3737f1bd5083da699a", + "alternativeSignatures": [], + "memberOf": [ + "default" + ], + "createdDate": "2024-03-26 04:31:45Z" + } + } +} \ No newline at end of file diff --git a/.config/tsaoptions.json b/.config/tsaoptions.json index a0aae9cb757..f4ade5f9fe7 100644 --- a/.config/tsaoptions.json +++ b/.config/tsaoptions.json @@ -4,7 +4,7 @@ "instanceUrl": "https://devdiv.visualstudio.com/", "iterationPath": "DevDiv", "notificationAliases": [ - "aspnetcore-build@microsoft.com" + "efdatateam@microsoft.com" ], "projectName": "DEVDIV", "repositoryName": "efcore", diff --git a/Directory.Build.targets b/Directory.Build.targets index 58605e9ceb0..4aec99516ab 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -2,18 +2,16 @@ + PACKAGE.md + + + + - - - - - + @@ -19,7 +19,7 @@ - + diff --git a/azure-pipelines-public.yml b/azure-pipelines-public.yml new file mode 100644 index 00000000000..aa704ddcde6 --- /dev/null +++ b/azure-pipelines-public.yml @@ -0,0 +1,316 @@ +schedules: +- cron: 0 9 * * 1 + displayName: "Run CodeQL3000 weekly, Monday at 2:00 AM PDT" + branches: + include: + - release/2.1 + - release/6.0 + - release/7.0 + - main + always: true + +parameters: + # Parameter below is ignored in public builds. + # + # Choose whether to run the CodeQL3000 tasks. + # Manual builds align w/ official builds unless this parameter is true. + - name: runCodeQL3000 + default: false + displayName: Run CodeQL3000 tasks + type: boolean + +variables: + - name: _BuildConfig + value: Release + - name: _TeamName + value: AspNetCore + - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + value: true + - name: _PublishUsingPipelines + value: true + - name: _CosmosConnectionUrl + value: https://localhost:8081 + - name: _CosmosToken + value: C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw== + - ${{ if or(startswith(variables['Build.SourceBranch'], 'refs/heads/release/'), startswith(variables['Build.SourceBranch'], 'refs/heads/internal/release/'), eq(variables['Build.Reason'], 'Manual')) }}: + - name: PostBuildSign + value: false + - ${{ else }}: + - name: PostBuildSign + value: true + - ${{ if ne(variables['System.TeamProject'], 'public') }}: + - group: DotNet-HelixApi-Access + - group: DotNetBuilds storage account read tokens + - group: AzureDevOps-Artifact-Feeds-Pats + - name: _InternalRuntimeDownloadArgs + value: /p:DotNetRuntimeSourceFeed=https://dotnetbuilds.blob.core.windows.net/internal + /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) + - ${{ if eq(variables['System.TeamProject'], 'public') }}: + - name: _InternalRuntimeDownloadArgs + value: '' + - name: LC_ALL + value: 'en_US.UTF-8' + - name: LANG + value: 'en_US.UTF-8' + - name: LANGUAGE + value: 'en_US.UTF-8' + - name: runCodeQL3000 + value: ${{ and(ne(variables['System.TeamProject'], 'public'), or(eq(variables['Build.Reason'], 'Schedule'), and(eq(variables['Build.Reason'], 'Manual'), eq(parameters.runCodeQL3000, 'true')))) }} + - template: /eng/common/templates/variables/pool-providers.yml + +trigger: + batch: true + branches: + include: + - main + - release/* + - feature/* + - internal/release/* + +pr: ['*'] + +stages: +- stage: build + displayName: Build + jobs: + - template: eng/common/templates/jobs/jobs.yml + parameters: + enableMicrobuild: ${{ ne(variables.runCodeQL3000, 'true') }} + enablePublishBuildArtifacts: true + enablePublishBuildAssets: ${{ ne(variables.runCodeQL3000, 'true') }} + enablePublishUsingPipelines: ${{ variables._PublishUsingPipelines }} + publishAssetsImmediately: true + enableSourceIndex: ${{ and(ne(variables['System.TeamProject'], 'public'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) }} + enableTelemetry: true + helixRepo: dotnet/efcore + jobs: + - job: Windows + enablePublishTestResults: ${{ ne(variables.runCodeQL3000, 'true') }} + pool: + ${{ if eq(variables['System.TeamProject'], 'public') }}: + name: $(DncEngPublicBuildPool) + demands: ImageOverride -equals 1es-windows-2019-open + ${{ if ne(variables['System.TeamProject'], 'public') }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals 1es-windows-2019 + ${{ if eq(variables.runCodeQL3000, 'true') }}: + # Component governance and SBOM creation are not needed here. Disable what Arcade would inject. + disableComponentGovernance: true + enableSbom: false + # CodeQL3000 extends build duration. + timeoutInMinutes: 180 + ${{ else }}: + timeoutInMinutes: 90 + variables: + - _InternalBuildArgs: '' + # Rely on task Arcade injects, not auto-injected build step. + - skipComponentGovernanceDetection: true + - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - _SignType: real + - _InternalBuildArgs: /p:DotNetSignType=$(_SignType) /p:TeamName=$(_TeamName) /p:DotNetPublishUsingPipelines=$(_PublishUsingPipelines) /p:OfficialBuildId=$(BUILD.BUILDNUMBER) + - ${{ if eq(variables.runCodeQL3000, 'true') }}: + - _AdditionalBuildArgs: /p:Test=false /p:Sign=false /p:Pack=false /p:Publish=false /p:UseSharedCompilation=false + # Security analysis is included in normal runs. Disable its auto-injection. + - skipNugetSecurityAnalysis: true + # Do not let CodeQL3000 Extension gate scan frequency. + - Codeql.Cadence: 0 + # Enable CodeQL3000 unconditionally so it may be run on any branch. + - Codeql.Enabled: true + # Ignore test and infrastructure code. + - Codeql.SourceRoot: src + # CodeQL3000 needs this plumbed along as a variable to enable TSA. + - Codeql.TSAEnabled: ${{ eq(variables['Build.Reason'], 'Schedule') }} + # Default expects tsaoptions.json under SourceRoot. + - Codeql.TSAOptionsPath: '$(Build.SourcesDirectory)/.config/tsaoptions.json' + - ${{ else }}: + - _AdditionalBuildArgs: '' + steps: + - task: NuGetCommand@2 + displayName: 'Clear NuGet caches' + condition: succeeded() + inputs: + command: custom + arguments: 'locals all -clear' + - script: "echo ##vso[build.addbuildtag]daily-build" + condition: and(notin(variables['Build.Reason'], 'PullRequest'), ne(variables['IsFinalBuild'], 'true')) + displayName: 'Set CI tags' + - script: "echo ##vso[build.addbuildtag]release-candidate" + condition: and(notin(variables['Build.Reason'], 'PullRequest'), eq(variables['IsFinalBuild'], 'true')) + displayName: 'Set CI tags' + - powershell: SqlLocalDB start + displayName: Start LocalDB + - ${{ if ne(variables['System.TeamProject'], 'public') }}: + - task: PowerShell@2 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 + arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - ${{ if eq(variables.runCodeQL3000, 'true') }}: + - task: CodeQL3000Init@0 + displayName: CodeQL Initialize + - script: "echo ##vso[build.addbuildtag]CodeQL3000" + displayName: 'Set CI CodeQL3000 tag' + condition: ne(variables.CODEQL_DIST,'') + - script: eng\common\cibuild.cmd -configuration $(_BuildConfig) -prepareMachine $(_InternalBuildArgs) + $(_InternalRuntimeDownloadArgs) $(_AdditionalBuildArgs) + env: + Test__Cosmos__DefaultConnection: $(_CosmosConnectionUrl) + name: Build + - ${{ if eq(variables.runCodeQL3000, 'true') }}: + - task: CodeQL3000Finalize@0 + displayName: CodeQL Finalize + - ${{ else }}: + - task: PublishBuildArtifacts@1 + displayName: Upload TestResults + condition: always() + continueOnError: true + inputs: + pathtoPublish: artifacts/TestResults/$(_BuildConfig)/ + artifactName: $(Agent.Os)_$(Agent.JobName) TestResults + artifactType: Container + parallel: true + + - ${{ if ne(variables.runCodeQL3000, 'true') }}: + - job: macOS + enablePublishTestResults: true + pool: + vmImage: macOS-11 + variables: + # Rely on task Arcade injects, not auto-injected build step. + - skipComponentGovernanceDetection: true + steps: + - ${{ if ne(variables['System.TeamProject'], 'public') }}: + - task: Bash@3 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh + arguments: $(Build.SourcesDirectory)/NuGet.config $Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - script: eng/common/cibuild.sh --configuration $(_BuildConfig) --prepareMachine $(_InternalRuntimeDownloadArgs) + env: + Test__Cosmos__DefaultConnection: $(_CosmosConnectionUrl) + COMPlus_EnableWriteXorExecute: 0 # Work-around for https://github.com/dotnet/runtime/issues/70758 + name: Build + - task: PublishBuildArtifacts@1 + displayName: Upload TestResults + condition: always() + continueOnError: true + inputs: + pathtoPublish: artifacts/TestResults/$(_BuildConfig)/ + artifactName: $(Agent.Os)_$(Agent.JobName) TestResults + artifactType: Container + parallel: true + + - job: Linux + timeoutInMinutes: 120 + enablePublishTestResults: true + pool: + ${{ if or(ne(variables['System.TeamProject'], 'internal'), in(variables['Build.Reason'], 'Manual', 'PullRequest', 'Schedule')) }}: + vmImage: ubuntu-22.04 + ${{ if and(eq(variables['System.TeamProject'], 'internal'), notin(variables['Build.Reason'], 'Manual', 'PullRequest', 'Schedule')) }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals Build.Ubuntu.2204.Amd64 + variables: + - _runCounter: $[counter(variables['Build.Reason'], 0)] + # Rely on task Arcade injects, not auto-injected build step. + - skipComponentGovernanceDetection: true + - ${{ if and(eq(variables['System.TeamProject'], 'internal'), notin(variables['Build.Reason'], 'PullRequest', 'Schedule')) }}: + - _CosmosConnectionUrl: 'true' + steps: + - bash: | + echo "##vso[task.setvariable variable=_CosmosConnectionUrl]https://ef-nightly-test.documents.azure.com:443/" + echo "##vso[task.setvariable variable=_CosmosToken]$(ef-nightly-cosmos-key)" + 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/" + echo "##vso[task.setvariable variable=_CosmosToken]$(ef-pr-cosmos-test)" + 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'))) + - ${{ if ne(variables['System.TeamProject'], 'public') }}: + - task: Bash@3 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh + arguments: $(Build.SourcesDirectory)/NuGet.config $Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - script: eng/common/cibuild.sh --configuration $(_BuildConfig) --prepareMachine $(_InternalRuntimeDownloadArgs) + env: + Test__Cosmos__DefaultConnection: $(_CosmosConnectionUrl) + Test__Cosmos__AuthToken: $(_CosmosToken) + name: Build + - task: PublishBuildArtifacts@1 + displayName: Upload TestResults + condition: always() + continueOnError: true + inputs: + pathtoPublish: artifacts/TestResults/$(_BuildConfig)/ + artifactName: $(Agent.Os)_$(Agent.JobName) TestResults + artifactType: Container + parallel: true + + - job: Helix + timeoutInMinutes: 180 + pool: + ${{ if eq(variables['System.TeamProject'], 'public') }}: + name: $(DncEngPublicBuildPool) + demands: ImageOverride -equals 1es-windows-2019-open + ${{ if ne(variables['System.TeamProject'], 'public') }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals 1es-windows-2019 + variables: + # Rely on task Arcade injects, not auto-injected build step. + - skipComponentGovernanceDetection: true + - name: _HelixBuildConfig + value: $(_BuildConfig) + - ${{ if eq(variables['System.TeamProject'], 'public') }}: + - name: HelixTargetQueues + value: OSX.1100.Amd64.Open;(Ubuntu.2004.Amd64.SqlServer)Ubuntu.2004.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64 + - name: _HelixAccessToken + value: '' # Needed for public queues + - ${{ if ne(variables['System.TeamProject'], 'public') }}: + - name: HelixTargetQueues + value: OSX.1100.Amd64;(Ubuntu.2004.Amd64.SqlServer)Ubuntu.2004.Amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64 + - name: _HelixAccessToken + value: $(HelixApiAccessToken) # Needed for internal queues + steps: + - task: NuGetCommand@2 + displayName: 'Clear NuGet caches' + condition: succeeded() + inputs: + command: custom + arguments: 'locals all -clear' + - ${{ if ne(variables['System.TeamProject'], 'public') }}: + - task: PowerShell@2 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 + arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - script: restore.cmd -ci /p:configuration=$(_BuildConfig) $(_InternalRuntimeDownloadArgs) + displayName: Restore packages + - script: .dotnet\dotnet build eng\helix.proj /restore /t:Test /p:configuration=$(_BuildConfig) /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog $(_InternalRuntimeDownloadArgs) + displayName: Send job to helix + env: + HelixAccessToken: $(_HelixAccessToken) + SYSTEM_ACCESSTOKEN: $(System.AccessToken) # We need to set this env var to publish helix results to Azure Dev Ops + MSSQL_SA_PASSWORD: "Password12!" + COMPlus_EnableWriteXorExecute: 0 # Work-around for https://github.com/dotnet/runtime/issues/70758 + DotNetBuildsInternalReadSasToken: $(dotnetbuilds-internal-container-read-token) + +- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), ne(variables.runCodeQL3000, 'true')) }}: + - template: eng\common\templates\post-build\post-build.yml + parameters: + publishingInfraVersion: 3 + # Symbol validation isn't being very reliable lately. This should be enabled back + # once this issue is resolved: https://github.com/dotnet/arcade/issues/2871 + enableSymbolValidation: false + enableSigningValidation: false + enableNugetValidation: false + enableSourceLinkValidation: false + publishAssetsImmediately: true diff --git a/azure-pipelines.yml b/azure-pipelines.yml index aa704ddcde6..e169cc05bcf 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,24 +1,3 @@ -schedules: -- cron: 0 9 * * 1 - displayName: "Run CodeQL3000 weekly, Monday at 2:00 AM PDT" - branches: - include: - - release/2.1 - - release/6.0 - - release/7.0 - - main - always: true - -parameters: - # Parameter below is ignored in public builds. - # - # Choose whether to run the CodeQL3000 tasks. - # Manual builds align w/ official builds unless this parameter is true. - - name: runCodeQL3000 - default: false - displayName: Run CodeQL3000 tasks - type: boolean - variables: - name: _BuildConfig value: Release @@ -38,26 +17,17 @@ variables: - ${{ else }}: - name: PostBuildSign value: true - - ${{ if ne(variables['System.TeamProject'], 'public') }}: - - group: DotNet-HelixApi-Access - - group: DotNetBuilds storage account read tokens - - group: AzureDevOps-Artifact-Feeds-Pats - - name: _InternalRuntimeDownloadArgs - value: /p:DotNetRuntimeSourceFeed=https://dotnetbuilds.blob.core.windows.net/internal - /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) - - ${{ if eq(variables['System.TeamProject'], 'public') }}: - - name: _InternalRuntimeDownloadArgs - value: '' + - group: DotNet-HelixApi-Access + - group: DotNetBuilds storage account read tokens + - group: AzureDevOps-Artifact-Feeds-Pats + - name: _InternalRuntimeDownloadArgs + value: /p:DotNetRuntimeSourceFeed=https://dotnetbuilds.blob.core.windows.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) - name: LC_ALL value: 'en_US.UTF-8' - name: LANG value: 'en_US.UTF-8' - name: LANGUAGE value: 'en_US.UTF-8' - - name: runCodeQL3000 - value: ${{ and(ne(variables['System.TeamProject'], 'public'), or(eq(variables['Build.Reason'], 'Schedule'), and(eq(variables['Build.Reason'], 'Manual'), eq(parameters.runCodeQL3000, 'true')))) }} - - template: /eng/common/templates/variables/pool-providers.yml - trigger: batch: true branches: @@ -66,64 +36,65 @@ trigger: - release/* - feature/* - internal/release/* - pr: ['*'] - -stages: -- stage: build - displayName: Build - jobs: - - template: eng/common/templates/jobs/jobs.yml - parameters: - enableMicrobuild: ${{ ne(variables.runCodeQL3000, 'true') }} - enablePublishBuildArtifacts: true - enablePublishBuildAssets: ${{ ne(variables.runCodeQL3000, 'true') }} - enablePublishUsingPipelines: ${{ variables._PublishUsingPipelines }} - publishAssetsImmediately: true - enableSourceIndex: ${{ and(ne(variables['System.TeamProject'], 'public'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) }} - enableTelemetry: true - helixRepo: dotnet/efcore - jobs: +resources: + repositories: + - repository: 1ESPipelineTemplates + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + parameters: + featureFlags: + autoBaseline: false + sdl: + sourceAnalysisPool: + name: NetCore1ESPool-Svc-Internal + image: 1es-windows-2022 + os: windows + baseline: + baselineFile: $(Build.SourcesDirectory)\.config\guardian\.gdnbaselines + binskim: + scanOutputDirectoryOnly: true + customBuildTags: + - ES365AIMigrationTooling + stages: + - stage: build + displayName: Build + jobs: + - template: /eng/common/templates-official/jobs/jobs.yml@self + parameters: + enableMicrobuild: true + enablePublishBuildArtifacts: true + enablePublishBuildAssets: true + enablePublishTestResults: true + enablePublishUsingPipelines: ${{ variables._PublishUsingPipelines }} + publishAssetsImmediately: true + enableSourceIndex: ${{ eq(variables['Build.SourceBranch'], 'refs/heads/main') }} + enableTelemetry: true + helixRepo: dotnet/efcore + jobs: - job: Windows - enablePublishTestResults: ${{ ne(variables.runCodeQL3000, 'true') }} pool: - ${{ if eq(variables['System.TeamProject'], 'public') }}: - name: $(DncEngPublicBuildPool) - demands: ImageOverride -equals 1es-windows-2019-open - ${{ if ne(variables['System.TeamProject'], 'public') }}: - name: $(DncEngInternalBuildPool) - demands: ImageOverride -equals 1es-windows-2019 - ${{ if eq(variables.runCodeQL3000, 'true') }}: - # Component governance and SBOM creation are not needed here. Disable what Arcade would inject. - disableComponentGovernance: true - enableSbom: false - # CodeQL3000 extends build duration. - timeoutInMinutes: 180 - ${{ else }}: - timeoutInMinutes: 90 + name: NetCore1ESPool-Svc-Internal + demands: ImageOverride -equals 1es-windows-2022 + os: windows + timeoutInMinutes: 180 variables: + - _AdditionalBuildArgs: '' - _InternalBuildArgs: '' # Rely on task Arcade injects, not auto-injected build step. - skipComponentGovernanceDetection: true - - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if notin(variables['Build.Reason'], 'PullRequest') }}: - _SignType: real - _InternalBuildArgs: /p:DotNetSignType=$(_SignType) /p:TeamName=$(_TeamName) /p:DotNetPublishUsingPipelines=$(_PublishUsingPipelines) /p:OfficialBuildId=$(BUILD.BUILDNUMBER) - - ${{ if eq(variables.runCodeQL3000, 'true') }}: - - _AdditionalBuildArgs: /p:Test=false /p:Sign=false /p:Pack=false /p:Publish=false /p:UseSharedCompilation=false - # Security analysis is included in normal runs. Disable its auto-injection. - - skipNugetSecurityAnalysis: true - # Do not let CodeQL3000 Extension gate scan frequency. - - Codeql.Cadence: 0 - # Enable CodeQL3000 unconditionally so it may be run on any branch. - - Codeql.Enabled: true - # Ignore test and infrastructure code. - - Codeql.SourceRoot: src - # CodeQL3000 needs this plumbed along as a variable to enable TSA. - - Codeql.TSAEnabled: ${{ eq(variables['Build.Reason'], 'Schedule') }} - # Default expects tsaoptions.json under SourceRoot. - - Codeql.TSAOptionsPath: '$(Build.SourcesDirectory)/.config/tsaoptions.json' - - ${{ else }}: - - _AdditionalBuildArgs: '' + # Ignore test and infrastructure code. + - Codeql.SourceRoot: src + # CodeQL3000 needs this plumbed along as a variable to enable TSA. + - Codeql.TSAEnabled: true + # Default expects tsaoptions.json under SourceRoot. + - Codeql.TSAOptionsPath: '$(Build.SourcesDirectory)/.config/tsaoptions.json' steps: - task: NuGetCommand@2 displayName: 'Clear NuGet caches' @@ -139,178 +110,146 @@ stages: displayName: 'Set CI tags' - powershell: SqlLocalDB start displayName: Start LocalDB - - ${{ if ne(variables['System.TeamProject'], 'public') }}: - - task: PowerShell@2 - displayName: Setup Private Feeds Credentials - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 - arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token - env: - Token: $(dn-bot-dnceng-artifact-feeds-rw) - - ${{ if eq(variables.runCodeQL3000, 'true') }}: - - task: CodeQL3000Init@0 - displayName: CodeQL Initialize - - script: "echo ##vso[build.addbuildtag]CodeQL3000" - displayName: 'Set CI CodeQL3000 tag' - condition: ne(variables.CODEQL_DIST,'') - - script: eng\common\cibuild.cmd -configuration $(_BuildConfig) -prepareMachine $(_InternalBuildArgs) - $(_InternalRuntimeDownloadArgs) $(_AdditionalBuildArgs) + - task: PowerShell@2 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 + arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - script: eng\common\cibuild.cmd -configuration $(_BuildConfig) -prepareMachine $(_InternalBuildArgs) $(_InternalRuntimeDownloadArgs) $(_AdditionalBuildArgs) + env: + Test__Cosmos__DefaultConnection: $(_CosmosConnectionUrl) + name: Build + templateContext: + outputs: + - output: pipelineArtifact + displayName: Upload TestResults + condition: always() + path: artifacts/TestResults/$(_BuildConfig)/ + artifact: $(Agent.Os)_$(Agent.JobName) TestResults + - job: macOS + pool: + name: Azure Pipelines + image: macOS-11 + os: macOS + variables: + # Rely on task Arcade injects, not auto-injected build step. + - skipComponentGovernanceDetection: true + steps: + - task: Bash@3 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh + arguments: $(Build.SourcesDirectory)/NuGet.config $Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - script: eng/common/cibuild.sh --configuration $(_BuildConfig) --prepareMachine $(_InternalRuntimeDownloadArgs) + env: + Test__Cosmos__DefaultConnection: $(_CosmosConnectionUrl) + # Work-around for https://github.com/dotnet/runtime/issues/70758 + COMPlus_EnableWriteXorExecute: 0 + name: Build + templateContext: + outputs: + - output: pipelineArtifact + displayName: Upload TestResults + condition: always() + path: artifacts/TestResults/$(_BuildConfig)/ + artifact: $(Agent.Os)_$(Agent.JobName) TestResults + - job: Linux + timeoutInMinutes: 120 + pool: + name: NetCore1ESPool-Svc-Internal + demands: ImageOverride -equals 1es-ubuntu-2204 + os: linux + variables: + - _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/" + echo "##vso[task.setvariable variable=_CosmosToken]$(ef-nightly-cosmos-key)" + 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/" + echo "##vso[task.setvariable variable=_CosmosToken]$(ef-pr-cosmos-test)" + 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'))) + - task: Bash@3 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh + arguments: $(Build.SourcesDirectory)/NuGet.config $Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - script: eng/common/cibuild.sh --configuration $(_BuildConfig) --prepareMachine $(_InternalRuntimeDownloadArgs) env: Test__Cosmos__DefaultConnection: $(_CosmosConnectionUrl) + Test__Cosmos__AuthToken: $(_CosmosToken) name: Build - - ${{ if eq(variables.runCodeQL3000, 'true') }}: - - task: CodeQL3000Finalize@0 - displayName: CodeQL Finalize - - ${{ else }}: - - task: PublishBuildArtifacts@1 - displayName: Upload TestResults - condition: always() - continueOnError: true - inputs: - pathtoPublish: artifacts/TestResults/$(_BuildConfig)/ - artifactName: $(Agent.Os)_$(Agent.JobName) TestResults - artifactType: Container - parallel: true - - - ${{ if ne(variables.runCodeQL3000, 'true') }}: - - job: macOS - enablePublishTestResults: true - pool: - vmImage: macOS-11 - variables: - # Rely on task Arcade injects, not auto-injected build step. - - skipComponentGovernanceDetection: true - steps: - - ${{ if ne(variables['System.TeamProject'], 'public') }}: - - task: Bash@3 - displayName: Setup Private Feeds Credentials - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh - arguments: $(Build.SourcesDirectory)/NuGet.config $Token - env: - Token: $(dn-bot-dnceng-artifact-feeds-rw) - - script: eng/common/cibuild.sh --configuration $(_BuildConfig) --prepareMachine $(_InternalRuntimeDownloadArgs) - env: - Test__Cosmos__DefaultConnection: $(_CosmosConnectionUrl) - COMPlus_EnableWriteXorExecute: 0 # Work-around for https://github.com/dotnet/runtime/issues/70758 - name: Build - - task: PublishBuildArtifacts@1 - displayName: Upload TestResults - condition: always() - continueOnError: true - inputs: - pathtoPublish: artifacts/TestResults/$(_BuildConfig)/ - artifactName: $(Agent.Os)_$(Agent.JobName) TestResults - artifactType: Container - parallel: true - - - job: Linux - timeoutInMinutes: 120 - enablePublishTestResults: true - pool: - ${{ if or(ne(variables['System.TeamProject'], 'internal'), in(variables['Build.Reason'], 'Manual', 'PullRequest', 'Schedule')) }}: - vmImage: ubuntu-22.04 - ${{ if and(eq(variables['System.TeamProject'], 'internal'), notin(variables['Build.Reason'], 'Manual', 'PullRequest', 'Schedule')) }}: - name: $(DncEngInternalBuildPool) - demands: ImageOverride -equals Build.Ubuntu.2204.Amd64 - variables: - - _runCounter: $[counter(variables['Build.Reason'], 0)] - # Rely on task Arcade injects, not auto-injected build step. - - skipComponentGovernanceDetection: true - - ${{ if and(eq(variables['System.TeamProject'], 'internal'), notin(variables['Build.Reason'], 'PullRequest', 'Schedule')) }}: - - _CosmosConnectionUrl: 'true' - steps: - - bash: | - echo "##vso[task.setvariable variable=_CosmosConnectionUrl]https://ef-nightly-test.documents.azure.com:443/" - echo "##vso[task.setvariable variable=_CosmosToken]$(ef-nightly-cosmos-key)" - 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/" - echo "##vso[task.setvariable variable=_CosmosToken]$(ef-pr-cosmos-test)" - 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'))) - - ${{ if ne(variables['System.TeamProject'], 'public') }}: - - task: Bash@3 - displayName: Setup Private Feeds Credentials - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh - arguments: $(Build.SourcesDirectory)/NuGet.config $Token - env: - Token: $(dn-bot-dnceng-artifact-feeds-rw) - - script: eng/common/cibuild.sh --configuration $(_BuildConfig) --prepareMachine $(_InternalRuntimeDownloadArgs) - env: - Test__Cosmos__DefaultConnection: $(_CosmosConnectionUrl) - Test__Cosmos__AuthToken: $(_CosmosToken) - name: Build - - task: PublishBuildArtifacts@1 - displayName: Upload TestResults - condition: always() - continueOnError: true - inputs: - pathtoPublish: artifacts/TestResults/$(_BuildConfig)/ - artifactName: $(Agent.Os)_$(Agent.JobName) TestResults - artifactType: Container - parallel: true - - - job: Helix - timeoutInMinutes: 180 - pool: - ${{ if eq(variables['System.TeamProject'], 'public') }}: - name: $(DncEngPublicBuildPool) - demands: ImageOverride -equals 1es-windows-2019-open - ${{ if ne(variables['System.TeamProject'], 'public') }}: - name: $(DncEngInternalBuildPool) - demands: ImageOverride -equals 1es-windows-2019 - variables: - # Rely on task Arcade injects, not auto-injected build step. - - skipComponentGovernanceDetection: true - - name: _HelixBuildConfig - value: $(_BuildConfig) - - ${{ if eq(variables['System.TeamProject'], 'public') }}: - - name: HelixTargetQueues - value: OSX.1100.Amd64.Open;(Ubuntu.2004.Amd64.SqlServer)Ubuntu.2004.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64 - - name: _HelixAccessToken - value: '' # Needed for public queues - - ${{ if ne(variables['System.TeamProject'], 'public') }}: - - name: HelixTargetQueues - value: OSX.1100.Amd64;(Ubuntu.2004.Amd64.SqlServer)Ubuntu.2004.Amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64 - - name: _HelixAccessToken - value: $(HelixApiAccessToken) # Needed for internal queues - steps: - - task: NuGetCommand@2 - displayName: 'Clear NuGet caches' - condition: succeeded() - inputs: - command: custom - arguments: 'locals all -clear' - - ${{ if ne(variables['System.TeamProject'], 'public') }}: - - task: PowerShell@2 - displayName: Setup Private Feeds Credentials - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 - arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token - env: - Token: $(dn-bot-dnceng-artifact-feeds-rw) - - script: restore.cmd -ci /p:configuration=$(_BuildConfig) $(_InternalRuntimeDownloadArgs) - displayName: Restore packages - - script: .dotnet\dotnet build eng\helix.proj /restore /t:Test /p:configuration=$(_BuildConfig) /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog $(_InternalRuntimeDownloadArgs) - displayName: Send job to helix - env: - HelixAccessToken: $(_HelixAccessToken) - SYSTEM_ACCESSTOKEN: $(System.AccessToken) # We need to set this env var to publish helix results to Azure Dev Ops - MSSQL_SA_PASSWORD: "Password12!" - COMPlus_EnableWriteXorExecute: 0 # Work-around for https://github.com/dotnet/runtime/issues/70758 - DotNetBuildsInternalReadSasToken: $(dotnetbuilds-internal-container-read-token) - -- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), ne(variables.runCodeQL3000, 'true')) }}: - - template: eng\common\templates\post-build\post-build.yml - parameters: - publishingInfraVersion: 3 - # Symbol validation isn't being very reliable lately. This should be enabled back - # once this issue is resolved: https://github.com/dotnet/arcade/issues/2871 - enableSymbolValidation: false - enableSigningValidation: false - enableNugetValidation: false - enableSourceLinkValidation: false - publishAssetsImmediately: true + templateContext: + outputs: + - output: pipelineArtifact + displayName: Upload TestResults + condition: always() + path: artifacts/TestResults/$(_BuildConfig)/ + artifact: $(Agent.Os)_$(Agent.JobName) TestResults + - job: Helix + timeoutInMinutes: 180 + pool: + name: NetCore1ESPool-Svc-Internal + demands: ImageOverride -equals 1es-windows-2022 + os: windows + variables: + # Rely on task Arcade injects, not auto-injected build step. + - skipComponentGovernanceDetection: true + - name: _HelixBuildConfig + value: $(_BuildConfig) + - name: HelixTargetQueues + value: OSX.1100.Amd64;(Ubuntu.2004.Amd64.SqlServer)Ubuntu.2004.Amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64 + - name: _HelixAccessToken + # Needed for internal queues + value: $(HelixApiAccessToken) + steps: + - task: NuGetCommand@2 + displayName: 'Clear NuGet caches' + condition: succeeded() + inputs: + command: custom + arguments: 'locals all -clear' + - task: PowerShell@2 + displayName: Setup Private Feeds Credentials + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 + arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token + env: + Token: $(dn-bot-dnceng-artifact-feeds-rw) + - script: restore.cmd -ci /p:configuration=$(_BuildConfig) $(_InternalRuntimeDownloadArgs) + displayName: Restore packages + - script: .dotnet\dotnet build eng\helix.proj /restore /t:Test /p:configuration=$(_BuildConfig) /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog $(_InternalRuntimeDownloadArgs) + displayName: Send job to helix + env: + HelixAccessToken: $(_HelixAccessToken) + # We need to set this env var to publish helix results to Azure Dev Ops + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + MSSQL_SA_PASSWORD: "Password12!" + # Work-around for https://github.com/dotnet/runtime/issues/70758 + COMPlus_EnableWriteXorExecute: 0 + DotNetBuildsInternalReadSasToken: $(dotnetbuilds-internal-container-read-token) + - ${{ if notin(variables['Build.Reason'], 'PullRequest') }}: + - template: /eng/common/templates-official/post-build/post-build.yml@self + parameters: + publishingInfraVersion: 3 + # Symbol validation isn't being very reliable lately. This should be enabled back + # once this issue is resolved: https://github.com/dotnet/arcade/issues/2871 + enableSymbolValidation: false + enableSigningValidation: false + enableNugetValidation: false + enableSourceLinkValidation: false + SDLValidationParameters: + enable: false \ No newline at end of file diff --git a/eng/Publishing.props b/eng/Publishing.props index 1da7cb4e448..6f77090ffcd 100644 --- a/eng/Publishing.props +++ b/eng/Publishing.props @@ -5,5 +5,6 @@ false + true diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index d5aec717843..bd37246d819 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -29,44 +29,44 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 5535e31a712343a63f5d7d796cd874e563e5ac14 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1381d5ebd2ab1f292848d5b19b80cf71ac332508 + 087e15321bb712ef6fe8b0ba6f8bd12facf92629 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 5535e31a712343a63f5d7d796cd874e563e5ac14 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1381d5ebd2ab1f292848d5b19b80cf71ac332508 + 9f4b1f5d664afdfc80e1508ab7ed099dff210fbd - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1381d5ebd2ab1f292848d5b19b80cf71ac332508 + 087e15321bb712ef6fe8b0ba6f8bd12facf92629 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1381d5ebd2ab1f292848d5b19b80cf71ac332508 + 087e15321bb712ef6fe8b0ba6f8bd12facf92629 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1381d5ebd2ab1f292848d5b19b80cf71ac332508 + 087e15321bb712ef6fe8b0ba6f8bd12facf92629 - + https://github.com/dotnet/arcade - 61ae141d2bf3534619265c8f691fd55dc3e75147 + 188340e12c0a372b1681ad6a5e72c608021efdba - + https://github.com/dotnet/arcade - 61ae141d2bf3534619265c8f691fd55dc3e75147 + 188340e12c0a372b1681ad6a5e72c608021efdba - + https://github.com/dotnet/arcade - 61ae141d2bf3534619265c8f691fd55dc3e75147 + 188340e12c0a372b1681ad6a5e72c608021efdba diff --git a/eng/Versions.props b/eng/Versions.props index ccfe6d5b20d..4340235984a 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -1,6 +1,6 @@ - 8.0.2 + 8.0.5 servicing @@ -24,19 +24,19 @@ 8.0.0 8.0.0 8.0.0 - 8.0.2-servicing.24067.11 + 8.0.5-servicing.24216.15 8.0.0 - 8.0.2 - 8.0.2 - 8.0.2 - 8.0.2-servicing.24067.11 + 8.0.3 + 8.0.5 + 8.0.5 + 8.0.5-servicing.24216.15 - 8.0.0-beta.24059.4 + 8.0.0-beta.24204.3 4.5.0 - 1.1.2-beta1.23371.1 + 1.1.2-beta1.24121.1 diff --git a/eng/common/SetupNugetSources.ps1 b/eng/common/SetupNugetSources.ps1 index 6c65e81925f..efa2fd72bfa 100644 --- a/eng/common/SetupNugetSources.ps1 +++ b/eng/common/SetupNugetSources.ps1 @@ -35,7 +35,7 @@ Set-StrictMode -Version 2.0 . $PSScriptRoot\tools.ps1 # Add source entry to PackageSources -function AddPackageSource($sources, $SourceName, $SourceEndPoint, $creds, $Username, $Password) { +function AddPackageSource($sources, $SourceName, $SourceEndPoint, $creds, $Username, $pwd) { $packageSource = $sources.SelectSingleNode("add[@key='$SourceName']") if ($packageSource -eq $null) @@ -48,12 +48,11 @@ function AddPackageSource($sources, $SourceName, $SourceEndPoint, $creds, $Usern else { Write-Host "Package source $SourceName already present." } - - AddCredential -Creds $creds -Source $SourceName -Username $Username -Password $Password + AddCredential -Creds $creds -Source $SourceName -Username $Username -pwd $pwd } # Add a credential node for the specified source -function AddCredential($creds, $source, $username, $password) { +function AddCredential($creds, $source, $username, $pwd) { # Looks for credential configuration for the given SourceName. Create it if none is found. $sourceElement = $creds.SelectSingleNode($Source) if ($sourceElement -eq $null) @@ -82,17 +81,18 @@ function AddCredential($creds, $source, $username, $password) { $passwordElement.SetAttribute("key", "ClearTextPassword") $sourceElement.AppendChild($passwordElement) | Out-Null } - $passwordElement.SetAttribute("value", $Password) + + $passwordElement.SetAttribute("value", $pwd) } -function InsertMaestroPrivateFeedCredentials($Sources, $Creds, $Username, $Password) { +function InsertMaestroPrivateFeedCredentials($Sources, $Creds, $Username, $pwd) { $maestroPrivateSources = $Sources.SelectNodes("add[contains(@key,'darc-int')]") Write-Host "Inserting credentials for $($maestroPrivateSources.Count) Maestro's private feeds." ForEach ($PackageSource in $maestroPrivateSources) { Write-Host "`tInserting credential for Maestro's feed:" $PackageSource.Key - AddCredential -Creds $creds -Source $PackageSource.Key -Username $Username -Password $Password + AddCredential -Creds $creds -Source $PackageSource.Key -Username $Username -pwd $pwd } } @@ -144,13 +144,13 @@ if ($disabledSources -ne $null) { $userName = "dn-bot" # Insert credential nodes for Maestro's private feeds -InsertMaestroPrivateFeedCredentials -Sources $sources -Creds $creds -Username $userName -Password $Password +InsertMaestroPrivateFeedCredentials -Sources $sources -Creds $creds -Username $userName -pwd $Password # 3.1 uses a different feed url format so it's handled differently here $dotnet31Source = $sources.SelectSingleNode("add[@key='dotnet3.1']") if ($dotnet31Source -ne $null) { - AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v2" -Creds $creds -Username $userName -Password $Password - AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v2" -Creds $creds -Username $userName -Password $Password + AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v2" -Creds $creds -Username $userName -pwd $Password + AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v2" -Creds $creds -Username $userName -pwd $Password } $dotnetVersions = @('5','6','7','8') @@ -159,9 +159,9 @@ foreach ($dotnetVersion in $dotnetVersions) { $feedPrefix = "dotnet" + $dotnetVersion; $dotnetSource = $sources.SelectSingleNode("add[@key='$feedPrefix']") if ($dotnetSource -ne $null) { - AddPackageSource -Sources $sources -SourceName "$feedPrefix-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal/nuget/v2" -Creds $creds -Username $userName -Password $Password - AddPackageSource -Sources $sources -SourceName "$feedPrefix-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal-transport/nuget/v2" -Creds $creds -Username $userName -Password $Password + AddPackageSource -Sources $sources -SourceName "$feedPrefix-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal/nuget/v2" -Creds $creds -Username $userName -pwd $Password + AddPackageSource -Sources $sources -SourceName "$feedPrefix-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal-transport/nuget/v2" -Creds $creds -Username $userName -pwd $Password } } -$doc.Save($filename) +$doc.Save($filename) \ No newline at end of file diff --git a/eng/common/native/init-compiler.sh b/eng/common/native/init-compiler.sh index f5c1ec7eafe..2d5660642b8 100644 --- a/eng/common/native/init-compiler.sh +++ b/eng/common/native/init-compiler.sh @@ -63,7 +63,7 @@ if [ -z "$CLR_CC" ]; then # Set default versions if [ -z "$majorVersion" ]; then # note: gcc (all versions) and clang versions higher than 6 do not have minor version in file name, if it is zero. - if [ "$compiler" = "clang" ]; then versions="17 16 15 14 13 12 11 10 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5" + if [ "$compiler" = "clang" ]; then versions="18 17 16 15 14 13 12 11 10 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5" elif [ "$compiler" = "gcc" ]; then versions="13 12 11 10 9 8 7 6 5 4.9"; fi for version in $versions; do diff --git a/eng/common/post-build/publish-using-darc.ps1 b/eng/common/post-build/publish-using-darc.ps1 index 1e779fec4dd..5a3a32ea8d7 100644 --- a/eng/common/post-build/publish-using-darc.ps1 +++ b/eng/common/post-build/publish-using-darc.ps1 @@ -12,7 +12,7 @@ param( try { . $PSScriptRoot\post-build-utils.ps1 - $darc = Get-Darc + $darc = Get-Darc $optionalParams = [System.Collections.ArrayList]::new() @@ -46,7 +46,7 @@ try { } Write-Host 'done.' -} +} catch { Write-Host $_ Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "There was an error while trying to publish build '$BuildId' to default channels." diff --git a/eng/common/templates-official/job/job.yml b/eng/common/templates-official/job/job.yml new file mode 100644 index 00000000000..1f035fee73f --- /dev/null +++ b/eng/common/templates-official/job/job.yml @@ -0,0 +1,264 @@ +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + +parameters: +# Job schema parameters - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + cancelTimeoutInMinutes: '' + condition: '' + container: '' + continueOnError: false + dependsOn: '' + displayName: '' + pool: '' + steps: [] + strategy: '' + timeoutInMinutes: '' + variables: [] + workspace: '' + templateContext: '' + +# Job base template specific parameters + # See schema documentation - https://github.com/dotnet/arcade/blob/master/Documentation/AzureDevOps/TemplateSchema.md + artifacts: '' + enableMicrobuild: false + enablePublishBuildArtifacts: false + enablePublishBuildAssets: false + enablePublishTestResults: false + enablePublishUsingPipelines: false + enableBuildRetry: false + disableComponentGovernance: '' + componentGovernanceIgnoreDirectories: '' + mergeTestResults: false + testRunTitle: '' + testResultsFormat: '' + name: '' + preSteps: [] + runAsPublic: false +# Sbom related params + enableSbom: true + PackageVersion: 7.0.0 + BuildDropPath: '$(Build.SourcesDirectory)/artifacts' + +jobs: +- job: ${{ parameters.name }} + + ${{ if ne(parameters.cancelTimeoutInMinutes, '') }}: + cancelTimeoutInMinutes: ${{ parameters.cancelTimeoutInMinutes }} + + ${{ if ne(parameters.condition, '') }}: + condition: ${{ parameters.condition }} + + ${{ if ne(parameters.container, '') }}: + container: ${{ parameters.container }} + + ${{ if ne(parameters.continueOnError, '') }}: + continueOnError: ${{ parameters.continueOnError }} + + ${{ if ne(parameters.dependsOn, '') }}: + dependsOn: ${{ parameters.dependsOn }} + + ${{ if ne(parameters.displayName, '') }}: + displayName: ${{ parameters.displayName }} + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + + ${{ if ne(parameters.strategy, '') }}: + strategy: ${{ parameters.strategy }} + + ${{ if ne(parameters.timeoutInMinutes, '') }}: + timeoutInMinutes: ${{ parameters.timeoutInMinutes }} + + ${{ if ne(parameters.templateContext, '') }}: + templateContext: ${{ parameters.templateContext }} + + variables: + - ${{ if ne(parameters.enableTelemetry, 'false') }}: + - name: DOTNET_CLI_TELEMETRY_PROFILE + value: '$(Build.Repository.Uri)' + - ${{ if eq(parameters.enableRichCodeNavigation, 'true') }}: + - name: EnableRichCodeNavigation + value: 'true' + # Retry signature validation up to three times, waiting 2 seconds between attempts. + # See https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu3028#retry-untrusted-root-failures + - name: NUGET_EXPERIMENTAL_CHAIN_BUILD_RETRY_POLICY + value: 3,2000 + - ${{ each variable in parameters.variables }}: + # handle name-value variable syntax + # example: + # - name: [key] + # value: [value] + - ${{ if ne(variable.name, '') }}: + - name: ${{ variable.name }} + value: ${{ variable.value }} + + # handle variable groups + - ${{ if ne(variable.group, '') }}: + - group: ${{ variable.group }} + + # handle template variable syntax + # example: + # - template: path/to/template.yml + # parameters: + # [key]: [value] + - ${{ if ne(variable.template, '') }}: + - template: ${{ variable.template }} + ${{ if ne(variable.parameters, '') }}: + parameters: ${{ variable.parameters }} + + # handle key-value variable syntax. + # example: + # - [key]: [value] + - ${{ if and(eq(variable.name, ''), eq(variable.group, ''), eq(variable.template, '')) }}: + - ${{ each pair in variable }}: + - name: ${{ pair.key }} + value: ${{ pair.value }} + + # DotNet-HelixApi-Access provides 'HelixApiAccessToken' for internal builds + - ${{ if and(eq(parameters.enableTelemetry, 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: DotNet-HelixApi-Access + + ${{ if ne(parameters.workspace, '') }}: + workspace: ${{ parameters.workspace }} + + steps: + - ${{ if ne(parameters.preSteps, '') }}: + - ${{ each preStep in parameters.preSteps }}: + - ${{ preStep }} + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - task: MicroBuildSigningPlugin@4 + displayName: Install MicroBuild plugin + inputs: + signType: $(_SignType) + zipSources: false + feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json + env: + TeamName: $(_TeamName) + MicroBuildOutputFolderOverride: '$(Agent.TempDirectory)' + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + + - ${{ if and(eq(parameters.runAsPublic, 'false'), eq(variables['System.TeamProject'], 'internal')) }}: + - task: NuGetAuthenticate@1 + + - ${{ if and(ne(parameters.artifacts.download, 'false'), ne(parameters.artifacts.download, '')) }}: + - task: DownloadPipelineArtifact@2 + inputs: + buildType: current + artifactName: ${{ coalesce(parameters.artifacts.download.name, 'Artifacts_$(Agent.OS)_$(_BuildConfig)') }} + targetPath: ${{ coalesce(parameters.artifacts.download.path, 'artifacts') }} + itemPattern: ${{ coalesce(parameters.artifacts.download.pattern, '**') }} + + - ${{ each step in parameters.steps }}: + - ${{ step }} + + - ${{ if eq(parameters.enableRichCodeNavigation, true) }}: + - task: RichCodeNavIndexer@0 + displayName: RichCodeNav Upload + inputs: + languages: ${{ coalesce(parameters.richCodeNavigationLanguage, 'csharp') }} + environment: ${{ coalesce(parameters.richCodeNavigationEnvironment, 'production') }} + richNavLogOutputDirectory: $(Build.SourcesDirectory)/artifacts/bin + uploadRichNavArtifacts: ${{ coalesce(parameters.richCodeNavigationUploadArtifacts, false) }} + continueOnError: true + + - template: /eng/common/templates-official/steps/component-governance.yml + parameters: + ${{ if eq(parameters.disableComponentGovernance, '') }}: + ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.runAsPublic, 'false'), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/dotnet/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/microsoft/'), eq(variables['Build.SourceBranch'], 'refs/heads/main'))) }}: + disableComponentGovernance: false + ${{ else }}: + disableComponentGovernance: true + ${{ else }}: + disableComponentGovernance: ${{ parameters.disableComponentGovernance }} + componentGovernanceIgnoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} + + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: MicroBuildCleanup@1 + displayName: Execute Microbuild cleanup tasks + condition: and(always(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} + env: + TeamName: $(_TeamName) + + - ${{ if ne(parameters.artifacts.publish, '') }}: + - ${{ if and(ne(parameters.artifacts.publish.artifacts, 'false'), ne(parameters.artifacts.publish.artifacts, '')) }}: + - task: CopyFiles@2 + displayName: Gather binaries for publish to artifacts + inputs: + SourceFolder: 'artifacts/bin' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/bin' + - task: CopyFiles@2 + displayName: Gather packages for publish to artifacts + inputs: + SourceFolder: 'artifacts/packages' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/packages' + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish pipeline artifacts + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/artifacts' + PublishLocation: Container + ArtifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }} + continueOnError: true + condition: always() + - ${{ if and(ne(parameters.artifacts.publish.logs, 'false'), ne(parameters.artifacts.publish.logs, '')) }}: + - task: 1ES.PublishPipelineArtifact@1 + inputs: + targetPath: 'artifacts/log' + artifactName: ${{ coalesce(parameters.artifacts.publish.logs.name, 'Logs_Build_$(Agent.Os)_$(_BuildConfig)') }} + displayName: 'Publish logs' + continueOnError: true + condition: always() + + - ${{ if ne(parameters.enablePublishBuildArtifacts, 'false') }}: + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)' + PublishLocation: Container + ArtifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)' ) }} + continueOnError: true + condition: always() + + - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'xunit')) }}: + - task: PublishTestResults@2 + displayName: Publish XUnit Test Results + inputs: + testResultsFormat: 'xUnit' + testResultsFiles: '*.xml' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-xunit + mergeTestResults: ${{ parameters.mergeTestResults }} + continueOnError: true + condition: always() + - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'vstest')) }}: + - task: PublishTestResults@2 + displayName: Publish TRX Test Results + inputs: + testResultsFormat: 'VSTest' + testResultsFiles: '*.trx' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-trx + mergeTestResults: ${{ parameters.mergeTestResults }} + continueOnError: true + condition: always() + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.enableSbom, 'true')) }}: + - template: /eng/common/templates-official/steps/generate-sbom.yml + parameters: + PackageVersion: ${{ parameters.packageVersion}} + BuildDropPath: ${{ parameters.buildDropPath }} + IgnoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} + + - ${{ if eq(parameters.enableBuildRetry, 'true') }}: + - task: 1ES.PublishPipelineArtifact@1 + inputs: + targetPath: '$(Build.SourcesDirectory)\eng\common\BuildConfiguration' + artifactName: 'BuildConfiguration' + displayName: 'Publish build retry configuration' + continueOnError: true \ No newline at end of file diff --git a/eng/common/templates-official/job/onelocbuild.yml b/eng/common/templates-official/job/onelocbuild.yml new file mode 100644 index 00000000000..52b4d05d3f8 --- /dev/null +++ b/eng/common/templates-official/job/onelocbuild.yml @@ -0,0 +1,112 @@ +parameters: + # Optional: dependencies of the job + dependsOn: '' + + # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool + pool: '' + + CeapexPat: $(dn-bot-ceapex-package-r) # PAT for the loc AzDO instance https://dev.azure.com/ceapex + GithubPat: $(BotAccount-dotnet-bot-repo-PAT) + + SourcesDirectory: $(Build.SourcesDirectory) + CreatePr: true + AutoCompletePr: false + ReusePr: true + UseLfLineEndings: true + UseCheckedInLocProjectJson: false + SkipLocProjectJsonGeneration: false + LanguageSet: VS_Main_Languages + LclSource: lclFilesInRepo + LclPackageId: '' + RepoType: gitHub + GitHubOrg: dotnet + MirrorRepo: '' + MirrorBranch: main + condition: '' + JobNameSuffix: '' + +jobs: +- job: OneLocBuild${{ parameters.JobNameSuffix }} + + dependsOn: ${{ parameters.dependsOn }} + + displayName: OneLocBuild${{ parameters.JobNameSuffix }} + + variables: + - group: OneLocBuildVariables # Contains the CeapexPat and GithubPat + - name: _GenerateLocProjectArguments + value: -SourcesDirectory ${{ parameters.SourcesDirectory }} + -LanguageSet "${{ parameters.LanguageSet }}" + -CreateNeutralXlfs + - ${{ if eq(parameters.UseCheckedInLocProjectJson, 'true') }}: + - name: _GenerateLocProjectArguments + value: ${{ variables._GenerateLocProjectArguments }} -UseCheckedInLocProjectJson + - template: /eng/common/templates-official/variables/pool-providers.yml + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + ${{ if eq(parameters.pool, '') }}: + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + name: $(DncEngInternalBuildPool) + image: 1es-windows-2022 + os: windows + + steps: + - ${{ if ne(parameters.SkipLocProjectJsonGeneration, 'true') }}: + - task: Powershell@2 + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/generate-locproject.ps1 + arguments: $(_GenerateLocProjectArguments) + displayName: Generate LocProject.json + condition: ${{ parameters.condition }} + + - task: OneLocBuild@2 + displayName: OneLocBuild + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + inputs: + locProj: eng/Localize/LocProject.json + outDir: $(Build.ArtifactStagingDirectory) + lclSource: ${{ parameters.LclSource }} + lclPackageId: ${{ parameters.LclPackageId }} + isCreatePrSelected: ${{ parameters.CreatePr }} + isAutoCompletePrSelected: ${{ parameters.AutoCompletePr }} + ${{ if eq(parameters.CreatePr, true) }}: + isUseLfLineEndingsSelected: ${{ parameters.UseLfLineEndings }} + ${{ if eq(parameters.RepoType, 'gitHub') }}: + isShouldReusePrSelected: ${{ parameters.ReusePr }} + packageSourceAuth: patAuth + patVariable: ${{ parameters.CeapexPat }} + ${{ if eq(parameters.RepoType, 'gitHub') }}: + repoType: ${{ parameters.RepoType }} + gitHubPatVariable: "${{ parameters.GithubPat }}" + ${{ if ne(parameters.MirrorRepo, '') }}: + isMirrorRepoSelected: true + gitHubOrganization: ${{ parameters.GitHubOrg }} + mirrorRepo: ${{ parameters.MirrorRepo }} + mirrorBranch: ${{ parameters.MirrorBranch }} + condition: ${{ parameters.condition }} + + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish Localization Files + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/loc' + PublishLocation: Container + ArtifactName: Loc + condition: ${{ parameters.condition }} + + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish LocProject.json + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/eng/Localize/' + PublishLocation: Container + ArtifactName: Loc + condition: ${{ parameters.condition }} \ No newline at end of file diff --git a/eng/common/templates-official/job/publish-build-assets.yml b/eng/common/templates-official/job/publish-build-assets.yml new file mode 100644 index 00000000000..589ac80a18b --- /dev/null +++ b/eng/common/templates-official/job/publish-build-assets.yml @@ -0,0 +1,155 @@ +parameters: + configuration: 'Debug' + + # Optional: condition for the job to run + condition: '' + + # Optional: 'true' if future jobs should run even if this job fails + continueOnError: false + + # Optional: dependencies of the job + dependsOn: '' + + # Optional: Include PublishBuildArtifacts task + enablePublishBuildArtifacts: false + + # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool + pool: {} + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + + # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing + publishUsingPipelines: false + + # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing + publishAssetsImmediately: false + + artifactsPublishingAdditionalParameters: '' + + signingValidationAdditionalParameters: '' + +jobs: +- job: Asset_Registry_Publish + + dependsOn: ${{ parameters.dependsOn }} + timeoutInMinutes: 150 + + ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + displayName: Publish Assets + ${{ else }}: + displayName: Publish to Build Asset Registry + + variables: + - template: /eng/common/templates-official/variables/pool-providers.yml + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: Publish-Build-Assets + - group: AzureDevOps-Artifact-Feeds-Pats + - name: runCodesignValidationInjection + value: false + - ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + - template: /eng/common/templates-official/post-build/common-variables.yml + + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + name: NetCore1ESPool-Publishing-Internal + image: windows.vs2019.amd64 + os: windows + steps: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download artifact + inputs: + artifactName: AssetManifests + downloadPath: '$(Build.StagingDirectory)/Download' + checkDownloadedFiles: true + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + + - task: NuGetAuthenticate@1 + + - task: PowerShell@2 + displayName: Publish Build Assets + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task PublishBuildAssets -restore -msbuildEngine dotnet + /p:ManifestsPath='$(Build.StagingDirectory)/Download/AssetManifests' + /p:BuildAssetRegistryToken=$(MaestroAccessToken) + /p:MaestroApiEndpoint=https://maestro-prod.westus2.cloudapp.azure.com + /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} + /p:OfficialBuildId=$(Build.BuildNumber) + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + + - task: powershell@2 + displayName: Create ReleaseConfigs Artifact + inputs: + targetType: inline + script: | + New-Item -Path "$(Build.StagingDirectory)/ReleaseConfigs" -ItemType Directory -Force + $filePath = "$(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt" + Add-Content -Path $filePath -Value $(BARBuildId) + Add-Content -Path $filePath -Value "$(DefaultChannels)" + Add-Content -Path $filePath -Value $(IsStableBuild) + + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish ReleaseConfigs Artifact + inputs: + PathtoPublish: '$(Build.StagingDirectory)/ReleaseConfigs' + PublishLocation: Container + ArtifactName: ReleaseConfigs + + - task: powershell@2 + displayName: Check if SymbolPublishingExclusionsFile.txt exists + inputs: + targetType: inline + script: | + $symbolExclusionfile = "$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt" + if(Test-Path -Path $symbolExclusionfile) + { + Write-Host "SymbolExclusionFile exists" + Write-Host "##vso[task.setvariable variable=SymbolExclusionFile]true" + } + else{ + Write-Host "Symbols Exclusion file does not exists" + Write-Host "##vso[task.setvariable variable=SymbolExclusionFile]false" + } + + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish SymbolPublishingExclusionsFile Artifact + condition: eq(variables['SymbolExclusionFile'], 'true') + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt' + PublishLocation: Container + ArtifactName: ReleaseConfigs + + - ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + - template: /eng/common/templates-official/post-build/setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: PowerShell@2 + displayName: Publish Using Darc + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 + arguments: -BuildId $(BARBuildId) + -PublishingInfraVersion 3 + -AzdoToken '$(publishing-dnceng-devdiv-code-r-build-re)' + -MaestroToken '$(MaestroApiAccessToken)' + -WaitPublishingFinish true + -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' + -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' + + - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: + - template: /eng/common/templates-official/steps/publish-logs.yml + parameters: + JobLabel: 'Publish_Artifacts_Logs' diff --git a/eng/common/templates-official/job/source-build.yml b/eng/common/templates-official/job/source-build.yml new file mode 100644 index 00000000000..f193dfbe236 --- /dev/null +++ b/eng/common/templates-official/job/source-build.yml @@ -0,0 +1,67 @@ +parameters: + # This template adds arcade-powered source-build to CI. The template produces a server job with a + # default ID 'Source_Build_Complete' to put in a dependency list if necessary. + + # Specifies the prefix for source-build jobs added to pipeline. Use this if disambiguation needed. + jobNamePrefix: 'Source_Build' + + # Defines the platform on which to run the job. By default, a linux-x64 machine, suitable for + # managed-only repositories. This is an object with these properties: + # + # name: '' + # The name of the job. This is included in the job ID. + # targetRID: '' + # The name of the target RID to use, instead of the one auto-detected by Arcade. + # nonPortable: false + # Enables non-portable mode. This means a more specific RID (e.g. fedora.32-x64 rather than + # linux-x64), and compiling against distro-provided packages rather than portable ones. + # skipPublishValidation: false + # Disables publishing validation. By default, a check is performed to ensure no packages are + # published by source-build. + # container: '' + # A container to use. Runs in docker. + # pool: {} + # A pool to use. Runs directly on an agent. + # buildScript: '' + # 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. + # jobProperties: {} + # A list of job properties to inject at the top level, for potential extensibility beyond + # container and pool. + platform: {} + +jobs: +- job: ${{ parameters.jobNamePrefix }}_${{ parameters.platform.name }} + displayName: Source-Build (${{ parameters.platform.name }}) + + ${{ each property in parameters.platform.jobProperties }}: + ${{ property.key }}: ${{ property.value }} + + ${{ if ne(parameters.platform.container, '') }}: + container: ${{ parameters.platform.container }} + + ${{ if eq(parameters.platform.pool, '') }}: + # The default VM host AzDO pool. This should be capable of running Docker containers: almost all + # source-build builds run in Docker, including the default managed platform. + # /eng/common/templates-official/variables/pool-providers.yml can't be used here (some customers declare variables already), so duplicate its logic + pool: + ${{ if eq(variables['System.TeamProject'], 'public') }}: + name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore-Svc-Public' ), False, 'NetCore-Public')] + demands: ImageOverride -equals Build.Ubuntu.1804.Amd64.Open + + ${{ if eq(variables['System.TeamProject'], 'internal') }}: + name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] + image: 1es-mariner-2 + os: linux + + ${{ if ne(parameters.platform.pool, '') }}: + pool: ${{ parameters.platform.pool }} + + workspace: + clean: all + + steps: + - template: /eng/common/templates-official/steps/source-build.yml + parameters: + platform: ${{ parameters.platform }} diff --git a/eng/common/templates-official/job/source-index-stage1.yml b/eng/common/templates-official/job/source-index-stage1.yml new file mode 100644 index 00000000000..f0513aee5b0 --- /dev/null +++ b/eng/common/templates-official/job/source-index-stage1.yml @@ -0,0 +1,68 @@ +parameters: + runAsPublic: false + sourceIndexPackageVersion: 1.0.1-20230228.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: [] + binlogPath: artifacts/log/Debug/Build.binlog + condition: '' + dependsOn: '' + pool: '' + +jobs: +- job: SourceIndexStage1 + dependsOn: ${{ parameters.dependsOn }} + condition: ${{ parameters.condition }} + variables: + - name: SourceIndexPackageVersion + value: ${{ parameters.sourceIndexPackageVersion }} + - name: SourceIndexPackageSource + value: ${{ parameters.sourceIndexPackageSource }} + - name: BinlogPath + value: ${{ parameters.binlogPath }} + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: source-dot-net stage1 variables + - template: /eng/common/templates-official/variables/pool-providers.yml + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + ${{ if eq(parameters.pool, '') }}: + pool: + ${{ if eq(variables['System.TeamProject'], 'public') }}: + name: $(DncEngPublicBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64.open + ${{ if eq(variables['System.TeamProject'], 'internal') }}: + name: $(DncEngInternalBuildPool) + image: windows.vs2022.amd64 + os: windows + + steps: + - ${{ each preStep in parameters.preSteps }}: + - ${{ preStep }} + + - task: UseDotNet@2 + displayName: Use .NET Core SDK 6 + inputs: + packageType: sdk + version: 6.0.x + installationPath: $(Agent.TempDirectory)/dotnet + workingDirectory: $(Agent.TempDirectory) + + - script: | + $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version $(SourceIndexPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version $(SourceIndexPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools + displayName: Download Tools + # Set working directory to temp directory so 'dotnet' doesn't try to use global.json and use the repo's sdk. + workingDirectory: $(Agent.TempDirectory) + + - script: ${{ parameters.sourceIndexBuildCommand }} + displayName: Build Repository + + - script: $(Agent.TempDirectory)/.source-index/tools/BinLogToSln -i $(BinlogPath) -r $(Build.SourcesDirectory) -n $(Build.Repository.Name) -o .source-index/stage1output + displayName: Process Binlog into indexable sln + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - script: $(Agent.TempDirectory)/.source-index/tools/UploadIndexStage1 -i .source-index/stage1output -n $(Build.Repository.Name) + displayName: Upload stage1 artifacts to source index + env: + BLOB_CONTAINER_URL: $(source-dot-net-stage1-blob-container-url) diff --git a/eng/common/templates-official/jobs/codeql-build.yml b/eng/common/templates-official/jobs/codeql-build.yml new file mode 100644 index 00000000000..b68d3c2f319 --- /dev/null +++ b/eng/common/templates-official/jobs/codeql-build.yml @@ -0,0 +1,31 @@ +parameters: + # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md + continueOnError: false + # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + jobs: [] + # Optional: if specified, restore and use this version of Guardian instead of the default. + overrideGuardianVersion: '' + +jobs: +- template: /eng/common/templates-official/jobs/jobs.yml + parameters: + enableMicrobuild: false + enablePublishBuildArtifacts: false + enablePublishTestResults: false + enablePublishBuildAssets: false + enablePublishUsingPipelines: false + enableTelemetry: true + + variables: + - group: Publish-Build-Assets + # The Guardian version specified in 'eng/common/sdl/packages.config'. This value must be kept in + # sync with the packages.config file. + - name: DefaultGuardianVersion + value: 0.109.0 + - name: GuardianPackagesConfigFile + value: $(Build.SourcesDirectory)\eng\common\sdl\packages.config + - name: GuardianVersion + value: ${{ coalesce(parameters.overrideGuardianVersion, '$(DefaultGuardianVersion)') }} + + jobs: ${{ parameters.jobs }} + diff --git a/eng/common/templates-official/jobs/jobs.yml b/eng/common/templates-official/jobs/jobs.yml new file mode 100644 index 00000000000..857a0f8ba43 --- /dev/null +++ b/eng/common/templates-official/jobs/jobs.yml @@ -0,0 +1,97 @@ +parameters: + # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md + continueOnError: false + + # Optional: Include PublishBuildArtifacts task + enablePublishBuildArtifacts: false + + # Optional: Enable publishing using release pipelines + enablePublishUsingPipelines: false + + # Optional: Enable running the source-build jobs to build repo from source + enableSourceBuild: false + + # Optional: Parameters for source-build template. + # See /eng/common/templates-official/jobs/source-build.yml for options + sourceBuildParameters: [] + + graphFileGeneration: + # Optional: Enable generating the graph files at the end of the build + enabled: false + # Optional: Include toolset dependencies in the generated graph files + includeToolset: false + + # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + jobs: [] + + # Optional: Override automatically derived dependsOn value for "publish build assets" job + publishBuildAssetsDependsOn: '' + + # Optional: Publish the assets as soon as the publish to BAR stage is complete, rather doing so in a separate stage. + publishAssetsImmediately: false + + # Optional: If using publishAssetsImmediately and additional parameters are needed, can be used to send along additional parameters (normally sent to post-build.yml) + artifactsPublishingAdditionalParameters: '' + signingValidationAdditionalParameters: '' + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + + enableSourceIndex: false + sourceIndexParams: {} + +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + +jobs: +- ${{ each job in parameters.jobs }}: + - template: ../job/job.yml + parameters: + # pass along parameters + ${{ each parameter in parameters }}: + ${{ if ne(parameter.key, 'jobs') }}: + ${{ parameter.key }}: ${{ parameter.value }} + + # pass along job properties + ${{ each property in job }}: + ${{ if ne(property.key, 'job') }}: + ${{ property.key }}: ${{ property.value }} + + name: ${{ job.job }} + +- ${{ if eq(parameters.enableSourceBuild, true) }}: + - template: /eng/common/templates-official/jobs/source-build.yml + parameters: + allCompletedJobId: Source_Build_Complete + ${{ each parameter in parameters.sourceBuildParameters }}: + ${{ parameter.key }}: ${{ parameter.value }} + +- ${{ if eq(parameters.enableSourceIndex, 'true') }}: + - template: ../job/source-index-stage1.yml + parameters: + runAsPublic: ${{ parameters.runAsPublic }} + ${{ each parameter in parameters.sourceIndexParams }}: + ${{ parameter.key }}: ${{ parameter.value }} + +- ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if or(eq(parameters.enablePublishBuildAssets, true), eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, '')) }}: + - template: ../job/publish-build-assets.yml + parameters: + continueOnError: ${{ parameters.continueOnError }} + dependsOn: + - ${{ if ne(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.publishBuildAssetsDependsOn }}: + - ${{ job.job }} + - ${{ if eq(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.jobs }}: + - ${{ job.job }} + - ${{ if eq(parameters.enableSourceBuild, true) }}: + - Source_Build_Complete + + runAsPublic: ${{ parameters.runAsPublic }} + publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} + publishAssetsImmediately: ${{ parameters.publishAssetsImmediately }} + enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + signingValidationAdditionalParameters: ${{ parameters.signingValidationAdditionalParameters }} diff --git a/eng/common/templates-official/jobs/source-build.yml b/eng/common/templates-official/jobs/source-build.yml new file mode 100644 index 00000000000..08e5db9bb11 --- /dev/null +++ b/eng/common/templates-official/jobs/source-build.yml @@ -0,0 +1,46 @@ +parameters: + # This template adds arcade-powered source-build to CI. A job is created for each platform, as + # well as an optional server job that completes when all platform jobs complete. + + # The name of the "join" job for all source-build platforms. If set to empty string, the job is + # not included. Existing repo pipelines can use this job depend on all source-build jobs + # completing without maintaining a separate list of every single job ID: just depend on this one + # server job. By default, not included. Recommended name if used: 'Source_Build_Complete'. + allCompletedJobId: '' + + # See /eng/common/templates-official/job/source-build.yml + jobNamePrefix: 'Source_Build' + + # This is the default platform provided by Arcade, intended for use by a managed-only repo. + defaultManagedPlatform: + name: 'Managed' + container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream8' + + # Defines the platforms on which to run build jobs. One job is created for each platform, and the + # object in this array is sent to the job template as 'platform'. If no platforms are specified, + # one job runs on 'defaultManagedPlatform'. + platforms: [] + +jobs: + +- ${{ if ne(parameters.allCompletedJobId, '') }}: + - job: ${{ parameters.allCompletedJobId }} + displayName: Source-Build Complete + pool: server + dependsOn: + - ${{ each platform in parameters.platforms }}: + - ${{ parameters.jobNamePrefix }}_${{ platform.name }} + - ${{ if eq(length(parameters.platforms), 0) }}: + - ${{ parameters.jobNamePrefix }}_${{ parameters.defaultManagedPlatform.name }} + +- ${{ each platform in parameters.platforms }}: + - template: /eng/common/templates-official/job/source-build.yml + parameters: + jobNamePrefix: ${{ parameters.jobNamePrefix }} + platform: ${{ platform }} + +- ${{ if eq(length(parameters.platforms), 0) }}: + - template: /eng/common/templates-official/job/source-build.yml + parameters: + jobNamePrefix: ${{ parameters.jobNamePrefix }} + platform: ${{ parameters.defaultManagedPlatform }} diff --git a/eng/common/templates-official/post-build/common-variables.yml b/eng/common/templates-official/post-build/common-variables.yml new file mode 100644 index 00000000000..c24193acfc9 --- /dev/null +++ b/eng/common/templates-official/post-build/common-variables.yml @@ -0,0 +1,22 @@ +variables: + - group: Publish-Build-Assets + + # Whether the build is internal or not + - name: IsInternalBuild + value: ${{ and(ne(variables['System.TeamProject'], 'public'), contains(variables['Build.SourceBranch'], 'internal')) }} + + # Default Maestro++ API Endpoint and API Version + - name: MaestroApiEndPoint + value: "https://maestro-prod.westus2.cloudapp.azure.com" + - name: MaestroApiAccessToken + value: $(MaestroAccessToken) + - name: MaestroApiVersion + value: "2020-02-20" + + - name: SourceLinkCLIVersion + value: 3.0.0 + - name: SymbolToolVersion + value: 1.0.1 + + - name: runCodesignValidationInjection + value: false diff --git a/eng/common/templates-official/post-build/post-build.yml b/eng/common/templates-official/post-build/post-build.yml new file mode 100644 index 00000000000..da1f40958b4 --- /dev/null +++ b/eng/common/templates-official/post-build/post-build.yml @@ -0,0 +1,285 @@ +parameters: + # Which publishing infra should be used. THIS SHOULD MATCH THE VERSION ON THE BUILD MANIFEST. + # Publishing V1 is no longer supported + # Publishing V2 is no longer supported + # Publishing V3 is the default + - name: publishingInfraVersion + displayName: Which version of publishing should be used to promote the build definition? + type: number + default: 3 + values: + - 3 + + - name: BARBuildId + displayName: BAR Build Id + type: number + default: 0 + + - name: PromoteToChannelIds + displayName: Channel to promote BARBuildId to + type: string + default: '' + + - name: enableSourceLinkValidation + displayName: Enable SourceLink validation + type: boolean + default: false + + - name: enableSigningValidation + displayName: Enable signing validation + type: boolean + default: true + + - name: enableSymbolValidation + displayName: Enable symbol validation + type: boolean + default: false + + - name: enableNugetValidation + displayName: Enable NuGet validation + type: boolean + default: true + + - name: publishInstallersAndChecksums + displayName: Publish installers and checksums + type: boolean + default: true + + - name: SDLValidationParameters + type: object + default: + enable: false + publishGdn: false + continueOnError: false + params: '' + artifactNames: '' + downloadArtifacts: true + + # These parameters let the user customize the call to sdk-task.ps1 for publishing + # symbols & general artifacts as well as for signing validation + - name: symbolPublishingAdditionalParameters + displayName: Symbol publishing additional parameters + type: string + default: '' + + - name: artifactsPublishingAdditionalParameters + displayName: Artifact publishing additional parameters + type: string + default: '' + + - name: signingValidationAdditionalParameters + displayName: Signing validation additional parameters + type: string + default: '' + + # Which stages should finish execution before post-build stages start + - name: validateDependsOn + type: object + default: + - build + + - name: publishDependsOn + type: object + default: + - Validate + + # Optional: Call asset publishing rather than running in a separate stage + - name: publishAssetsImmediately + type: boolean + default: false + +stages: +- ${{ if or(eq( parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: + - stage: Validate + dependsOn: ${{ parameters.validateDependsOn }} + displayName: Validate Build Assets + variables: + - template: common-variables.yml + - template: /eng/common/templates-official/variables/pool-providers.yml + jobs: + - job: + displayName: NuGet Validation + condition: and(succeededOrFailed(), eq( ${{ parameters.enableNugetValidation }}, 'true')) + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ else }}: + name: $(DncEngInternalBuildPool) + image: 1es-windows-2022 + os: windows + + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/nuget-validation.ps1 + arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ + -ToolDestinationPath $(Agent.BuildDirectory)/Extract/ + + - job: + displayName: Signing Validation + condition: and( eq( ${{ parameters.enableSigningValidation }}, 'true'), ne( variables['PostBuildSign'], 'true')) + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ else }}: + name: $(DncEngInternalBuildPool) + image: 1es-windows-2022 + os: windows + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + itemPattern: | + ** + !**/Microsoft.SourceBuild.Intermediate.*.nupkg + + # This is necessary whenever we want to publish/restore to an AzDO private feed + # Since sdk-task.ps1 tries to restore packages we need to do this authentication here + # otherwise it'll complain about accessing a private feed. + - task: NuGetAuthenticate@1 + displayName: 'Authenticate to AzDO Feeds' + + # Signing validation will optionally work with the buildmanifest file which is downloaded from + # Azure DevOps above. + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task SigningValidation -restore -msbuildEngine vs + /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' + /p:SignCheckExclusionsFile='$(Build.SourcesDirectory)/eng/SignCheckExclusionsFile.txt' + ${{ parameters.signingValidationAdditionalParameters }} + + - template: ../steps/publish-logs.yml + parameters: + StageLabel: 'Validation' + JobLabel: 'Signing' + BinlogToolVersion: $(BinlogToolVersion) + + - job: + displayName: SourceLink Validation + condition: eq( ${{ parameters.enableSourceLinkValidation }}, 'true') + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ else }}: + name: $(DncEngInternalBuildPool) + image: 1es-windows-2022 + os: windows + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: DownloadBuildArtifacts@0 + displayName: Download Blob Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: BlobArtifacts + checkDownloadedFiles: true + + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/sourcelink-validation.ps1 + arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ + -ExtractPath $(Agent.BuildDirectory)/Extract/ + -GHRepoName $(Build.Repository.Name) + -GHCommit $(Build.SourceVersion) + -SourcelinkCliVersion $(SourceLinkCLIVersion) + continueOnError: true + +- ${{ if ne(parameters.publishAssetsImmediately, 'true') }}: + - stage: publish_using_darc + ${{ if or(eq(parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: + dependsOn: ${{ parameters.publishDependsOn }} + ${{ else }}: + dependsOn: ${{ parameters.validateDependsOn }} + displayName: Publish using Darc + variables: + - template: common-variables.yml + - template: /eng/common/templates-official/variables/pool-providers.yml + jobs: + - job: + displayName: Publish Using Darc + timeoutInMinutes: 120 + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ else }}: + name: NetCore1ESPool-Publishing-Internal + image: windows.vs2019.amd64 + os: windows + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: NuGetAuthenticate@1 + + - task: PowerShell@2 + displayName: Publish Using Darc + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 + arguments: -BuildId $(BARBuildId) + -PublishingInfraVersion ${{ parameters.publishingInfraVersion }} + -AzdoToken '$(publishing-dnceng-devdiv-code-r-build-re)' + -MaestroToken '$(MaestroApiAccessToken)' + -WaitPublishingFinish true + -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' + -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' diff --git a/eng/common/templates-official/post-build/setup-maestro-vars.yml b/eng/common/templates-official/post-build/setup-maestro-vars.yml new file mode 100644 index 00000000000..0c87f149a4a --- /dev/null +++ b/eng/common/templates-official/post-build/setup-maestro-vars.yml @@ -0,0 +1,70 @@ +parameters: + BARBuildId: '' + PromoteToChannelIds: '' + +steps: + - ${{ if eq(coalesce(parameters.PromoteToChannelIds, 0), 0) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Release Configs + inputs: + buildType: current + artifactName: ReleaseConfigs + checkDownloadedFiles: true + + - task: PowerShell@2 + name: setReleaseVars + displayName: Set Release Configs Vars + inputs: + targetType: inline + pwsh: true + script: | + try { + if (!$Env:PromoteToMaestroChannels -or $Env:PromoteToMaestroChannels.Trim() -eq '') { + $Content = Get-Content $(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt + + $BarId = $Content | Select -Index 0 + $Channels = $Content | Select -Index 1 + $IsStableBuild = $Content | Select -Index 2 + + $AzureDevOpsProject = $Env:System_TeamProject + $AzureDevOpsBuildDefinitionId = $Env:System_DefinitionId + $AzureDevOpsBuildId = $Env:Build_BuildId + } + else { + $buildApiEndpoint = "${Env:MaestroApiEndPoint}/api/builds/${Env:BARBuildId}?api-version=${Env:MaestroApiVersion}" + + $apiHeaders = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]' + $apiHeaders.Add('Accept', 'application/json') + $apiHeaders.Add('Authorization',"Bearer ${Env:MAESTRO_API_TOKEN}") + + $buildInfo = try { Invoke-WebRequest -Method Get -Uri $buildApiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } + + $BarId = $Env:BARBuildId + $Channels = $Env:PromoteToMaestroChannels -split "," + $Channels = $Channels -join "][" + $Channels = "[$Channels]" + + $IsStableBuild = $buildInfo.stable + $AzureDevOpsProject = $buildInfo.azureDevOpsProject + $AzureDevOpsBuildDefinitionId = $buildInfo.azureDevOpsBuildDefinitionId + $AzureDevOpsBuildId = $buildInfo.azureDevOpsBuildId + } + + Write-Host "##vso[task.setvariable variable=BARBuildId]$BarId" + Write-Host "##vso[task.setvariable variable=TargetChannels]$Channels" + Write-Host "##vso[task.setvariable variable=IsStableBuild]$IsStableBuild" + + Write-Host "##vso[task.setvariable variable=AzDOProjectName]$AzureDevOpsProject" + Write-Host "##vso[task.setvariable variable=AzDOPipelineId]$AzureDevOpsBuildDefinitionId" + Write-Host "##vso[task.setvariable variable=AzDOBuildId]$AzureDevOpsBuildId" + } + catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + exit 1 + } + env: + MAESTRO_API_TOKEN: $(MaestroApiAccessToken) + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToMaestroChannels: ${{ parameters.PromoteToChannelIds }} diff --git a/eng/common/templates-official/post-build/trigger-subscription.yml b/eng/common/templates-official/post-build/trigger-subscription.yml new file mode 100644 index 00000000000..da669030daf --- /dev/null +++ b/eng/common/templates-official/post-build/trigger-subscription.yml @@ -0,0 +1,13 @@ +parameters: + ChannelId: 0 + +steps: +- task: PowerShell@2 + displayName: Triggering subscriptions + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/trigger-subscriptions.ps1 + arguments: -SourceRepo $(Build.Repository.Uri) + -ChannelId ${{ parameters.ChannelId }} + -MaestroApiAccessToken $(MaestroAccessToken) + -MaestroApiEndPoint $(MaestroApiEndPoint) + -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates-official/steps/add-build-to-channel.yml b/eng/common/templates-official/steps/add-build-to-channel.yml new file mode 100644 index 00000000000..f67a210d62f --- /dev/null +++ b/eng/common/templates-official/steps/add-build-to-channel.yml @@ -0,0 +1,13 @@ +parameters: + ChannelId: 0 + +steps: +- task: PowerShell@2 + displayName: Add Build to Channel + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/add-build-to-channel.ps1 + arguments: -BuildId $(BARBuildId) + -ChannelId ${{ parameters.ChannelId }} + -MaestroApiAccessToken $(MaestroApiAccessToken) + -MaestroApiEndPoint $(MaestroApiEndPoint) + -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates-official/steps/build-reason.yml b/eng/common/templates-official/steps/build-reason.yml new file mode 100644 index 00000000000..eba58109b52 --- /dev/null +++ b/eng/common/templates-official/steps/build-reason.yml @@ -0,0 +1,12 @@ +# build-reason.yml +# Description: runs steps if build.reason condition is valid. conditions is a string of valid build reasons +# to include steps (',' separated). +parameters: + conditions: '' + steps: [] + +steps: + - ${{ if and( not(startsWith(parameters.conditions, 'not')), contains(parameters.conditions, variables['build.reason'])) }}: + - ${{ parameters.steps }} + - ${{ if and( startsWith(parameters.conditions, 'not'), not(contains(parameters.conditions, variables['build.reason']))) }}: + - ${{ parameters.steps }} diff --git a/eng/common/templates-official/steps/component-governance.yml b/eng/common/templates-official/steps/component-governance.yml new file mode 100644 index 00000000000..cbba0596709 --- /dev/null +++ b/eng/common/templates-official/steps/component-governance.yml @@ -0,0 +1,13 @@ +parameters: + disableComponentGovernance: false + componentGovernanceIgnoreDirectories: '' + +steps: +- ${{ if eq(parameters.disableComponentGovernance, 'true') }}: + - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true" + displayName: Set skipComponentGovernanceDetection variable +- ${{ if ne(parameters.disableComponentGovernance, 'true') }}: + - task: ComponentGovernanceComponentDetection@0 + continueOnError: true + inputs: + ignoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} \ No newline at end of file diff --git a/eng/common/templates-official/steps/execute-codeql.yml b/eng/common/templates-official/steps/execute-codeql.yml new file mode 100644 index 00000000000..9b4a5ffa30a --- /dev/null +++ b/eng/common/templates-official/steps/execute-codeql.yml @@ -0,0 +1,32 @@ +parameters: + # Language that should be analyzed. Defaults to csharp + language: csharp + # Build Commands + buildCommands: '' + overrideParameters: '' # Optional: to override values for parameters. + additionalParameters: '' # Optional: parameters that need user specific values eg: '-SourceToolsList @("abc","def") -ArtifactToolsList @("ghi","jkl")' + # Optional: if specified, restore and use this version of Guardian instead of the default. + overrideGuardianVersion: '' + # Optional: if true, publish the '.gdn' folder as a pipeline artifact. This can help with in-depth + # diagnosis of problems with specific tool configurations. + publishGuardianDirectoryToPipeline: false + # The script to run to execute all SDL tools. Use this if you want to use a script to define SDL + # parameters rather than relying on YAML. It may be better to use a local script, because you can + # reproduce results locally without piecing together a command based on the YAML. + executeAllSdlToolsScript: 'eng/common/sdl/execute-all-sdl-tools.ps1' + # There is some sort of bug (has been reported) in Azure DevOps where if this parameter is named + # 'continueOnError', the parameter value is not correctly picked up. + # This can also be remedied by the caller (post-build.yml) if it does not use a nested parameter + # optional: determines whether to continue the build if the step errors; + sdlContinueOnError: false + +steps: +- template: /eng/common/templates-official/steps/execute-sdl.yml + parameters: + overrideGuardianVersion: ${{ parameters.overrideGuardianVersion }} + executeAllSdlToolsScript: ${{ parameters.executeAllSdlToolsScript }} + overrideParameters: ${{ parameters.overrideParameters }} + additionalParameters: '${{ parameters.additionalParameters }} + -CodeQLAdditionalRunConfigParams @("BuildCommands < ${{ parameters.buildCommands }}", "Language < ${{ parameters.language }}")' + publishGuardianDirectoryToPipeline: ${{ parameters.publishGuardianDirectoryToPipeline }} + sdlContinueOnError: ${{ parameters.sdlContinueOnError }} \ No newline at end of file diff --git a/eng/common/templates-official/steps/execute-sdl.yml b/eng/common/templates-official/steps/execute-sdl.yml new file mode 100644 index 00000000000..07426fde05d --- /dev/null +++ b/eng/common/templates-official/steps/execute-sdl.yml @@ -0,0 +1,88 @@ +parameters: + overrideGuardianVersion: '' + executeAllSdlToolsScript: '' + overrideParameters: '' + additionalParameters: '' + publishGuardianDirectoryToPipeline: false + sdlContinueOnError: false + condition: '' + +steps: +- task: NuGetAuthenticate@1 + inputs: + nuGetServiceConnections: GuardianConnect + +- task: NuGetToolInstaller@1 + displayName: 'Install NuGet.exe' + +- ${{ if ne(parameters.overrideGuardianVersion, '') }}: + - pwsh: | + Set-Location -Path $(Build.SourcesDirectory)\eng\common\sdl + . .\sdl.ps1 + $guardianCliLocation = Install-Gdn -Path $(Build.SourcesDirectory)\.artifacts -Version ${{ parameters.overrideGuardianVersion }} + Write-Host "##vso[task.setvariable variable=GuardianCliLocation]$guardianCliLocation" + displayName: Install Guardian (Overridden) + +- ${{ if eq(parameters.overrideGuardianVersion, '') }}: + - pwsh: | + Set-Location -Path $(Build.SourcesDirectory)\eng\common\sdl + . .\sdl.ps1 + $guardianCliLocation = Install-Gdn -Path $(Build.SourcesDirectory)\.artifacts + Write-Host "##vso[task.setvariable variable=GuardianCliLocation]$guardianCliLocation" + displayName: Install Guardian + +- ${{ if ne(parameters.overrideParameters, '') }}: + - powershell: ${{ parameters.executeAllSdlToolsScript }} ${{ parameters.overrideParameters }} + displayName: Execute SDL (Overridden) + continueOnError: ${{ parameters.sdlContinueOnError }} + condition: ${{ parameters.condition }} + +- ${{ if eq(parameters.overrideParameters, '') }}: + - powershell: ${{ parameters.executeAllSdlToolsScript }} + -GuardianCliLocation $(GuardianCliLocation) + -NugetPackageDirectory $(Build.SourcesDirectory)\.packages + -AzureDevOpsAccessToken $(dn-bot-dotnet-build-rw-code-rw) + ${{ parameters.additionalParameters }} + displayName: Execute SDL + continueOnError: ${{ parameters.sdlContinueOnError }} + condition: ${{ parameters.condition }} + +- ${{ if ne(parameters.publishGuardianDirectoryToPipeline, 'false') }}: + # We want to publish the Guardian results and configuration for easy diagnosis. However, the + # '.gdn' dir is a mix of configuration, results, extracted dependencies, and Guardian default + # tooling files. Some of these files are large and aren't useful during an investigation, so + # exclude them by simply deleting them before publishing. (As of writing, there is no documented + # way to selectively exclude a dir from the pipeline artifact publish task.) + - task: DeleteFiles@1 + displayName: Delete Guardian dependencies to avoid uploading + inputs: + SourceFolder: $(Agent.BuildDirectory)/.gdn + Contents: | + c + i + condition: succeededOrFailed() + + - publish: $(Agent.BuildDirectory)/.gdn + artifact: GuardianConfiguration + displayName: Publish GuardianConfiguration + condition: succeededOrFailed() + + # Publish the SARIF files in a container named CodeAnalysisLogs to enable integration + # with the "SARIF SAST Scans Tab" Azure DevOps extension + - task: CopyFiles@2 + displayName: Copy SARIF files + inputs: + flattenFolders: true + sourceFolder: $(Agent.BuildDirectory)/.gdn/rc/ + contents: '**/*.sarif' + targetFolder: $(Build.SourcesDirectory)/CodeAnalysisLogs + condition: succeededOrFailed() + + # Use PublishBuildArtifacts because the SARIF extension only checks this case + # see microsoft/sarif-azuredevops-extension#4 + - task: PublishBuildArtifacts@1 + displayName: Publish SARIF files to CodeAnalysisLogs container + inputs: + pathToPublish: $(Build.SourcesDirectory)/CodeAnalysisLogs + artifactName: CodeAnalysisLogs + condition: succeededOrFailed() \ No newline at end of file diff --git a/eng/common/templates-official/steps/generate-sbom.yml b/eng/common/templates-official/steps/generate-sbom.yml new file mode 100644 index 00000000000..1bf43bf807a --- /dev/null +++ b/eng/common/templates-official/steps/generate-sbom.yml @@ -0,0 +1,48 @@ +# BuildDropPath - The root folder of the drop directory for which the manifest file will be generated. +# PackageName - The name of the package this SBOM represents. +# PackageVersion - The version of the package this SBOM represents. +# ManifestDirPath - The path of the directory where the generated manifest files will be placed +# IgnoreDirectories - Directories to ignore for SBOM generation. This will be passed through to the CG component detector. + +parameters: + PackageVersion: 8.0.0 + BuildDropPath: '$(Build.SourcesDirectory)/artifacts' + PackageName: '.NET' + ManifestDirPath: $(Build.ArtifactStagingDirectory)/sbom + IgnoreDirectories: '' + sbomContinueOnError: true + +steps: +- task: PowerShell@2 + displayName: Prep for SBOM generation in (Non-linux) + condition: or(eq(variables['Agent.Os'], 'Windows_NT'), eq(variables['Agent.Os'], 'Darwin')) + inputs: + filePath: ./eng/common/generate-sbom-prep.ps1 + arguments: ${{parameters.manifestDirPath}} + +# Chmodding is a workaround for https://github.com/dotnet/arcade/issues/8461 +- script: | + chmod +x ./eng/common/generate-sbom-prep.sh + ./eng/common/generate-sbom-prep.sh ${{parameters.manifestDirPath}} + displayName: Prep for SBOM generation in (Linux) + condition: eq(variables['Agent.Os'], 'Linux') + continueOnError: ${{ parameters.sbomContinueOnError }} + +- task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: 'Generate SBOM manifest' + continueOnError: ${{ parameters.sbomContinueOnError }} + inputs: + PackageName: ${{ parameters.packageName }} + BuildDropPath: ${{ parameters.buildDropPath }} + PackageVersion: ${{ parameters.packageVersion }} + ManifestDirPath: ${{ parameters.manifestDirPath }} + ${{ if ne(parameters.IgnoreDirectories, '') }}: + AdditionalComponentDetectorArgs: '--IgnoreDirectories ${{ parameters.IgnoreDirectories }}' + +- task: 1ES.PublishPipelineArtifact@1 + displayName: Publish SBOM manifest + continueOnError: ${{parameters.sbomContinueOnError}} + inputs: + targetPath: '${{parameters.manifestDirPath}}' + artifactName: $(ARTIFACT_NAME) + diff --git a/eng/common/templates-official/steps/publish-logs.yml b/eng/common/templates-official/steps/publish-logs.yml new file mode 100644 index 00000000000..04012fed182 --- /dev/null +++ b/eng/common/templates-official/steps/publish-logs.yml @@ -0,0 +1,23 @@ +parameters: + StageLabel: '' + JobLabel: '' + +steps: +- task: Powershell@2 + displayName: Prepare Binlogs to Upload + inputs: + targetType: inline + script: | + New-Item -ItemType Directory $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + Move-Item -Path $(Build.SourcesDirectory)/artifacts/log/Debug/* $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + continueOnError: true + condition: always() + +- task: 1ES.PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/PostBuildLogs' + PublishLocation: Container + ArtifactName: PostBuildLogs + continueOnError: true + condition: always() diff --git a/eng/common/templates-official/steps/retain-build.yml b/eng/common/templates-official/steps/retain-build.yml new file mode 100644 index 00000000000..83d97a26a01 --- /dev/null +++ b/eng/common/templates-official/steps/retain-build.yml @@ -0,0 +1,28 @@ +parameters: + # Optional azure devops PAT with build execute permissions for the build's organization, + # only needed if the build that should be retained ran on a different organization than + # the pipeline where this template is executing from + Token: '' + # Optional BuildId to retain, defaults to the current running build + BuildId: '' + # Azure devops Organization URI for the build in the https://dev.azure.com/ format. + # Defaults to the organization the current pipeline is running on + AzdoOrgUri: '$(System.CollectionUri)' + # Azure devops project for the build. Defaults to the project the current pipeline is running on + AzdoProject: '$(System.TeamProject)' + +steps: + - task: powershell@2 + inputs: + targetType: 'filePath' + filePath: eng/common/retain-build.ps1 + pwsh: true + arguments: > + -AzdoOrgUri: ${{parameters.AzdoOrgUri}} + -AzdoProject ${{parameters.AzdoProject}} + -Token ${{coalesce(parameters.Token, '$env:SYSTEM_ACCESSTOKEN') }} + -BuildId ${{coalesce(parameters.BuildId, '$env:BUILD_ID')}} + displayName: Enable permanent build retention + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + BUILD_ID: $(Build.BuildId) \ No newline at end of file diff --git a/eng/common/templates-official/steps/send-to-helix.yml b/eng/common/templates-official/steps/send-to-helix.yml new file mode 100644 index 00000000000..3eb7e2d5f84 --- /dev/null +++ b/eng/common/templates-official/steps/send-to-helix.yml @@ -0,0 +1,91 @@ +# Please remember to update the documentation if you make changes to these parameters! +parameters: + HelixSource: 'pr/default' # required -- sources must start with pr/, official/, prodcon/, or agent/ + HelixType: 'tests/default/' # required -- Helix telemetry which identifies what type of data this is; should include "test" for clarity and must end in '/' + HelixBuild: $(Build.BuildNumber) # required -- the build number Helix will use to identify this -- automatically set to the AzDO build number + HelixTargetQueues: '' # required -- semicolon-delimited list of Helix queues to test on; see https://helix.dot.net/ for a list of queues + HelixAccessToken: '' # required -- access token to make Helix API requests; should be provided by the appropriate variable group + HelixConfiguration: '' # optional -- additional property attached to a job + HelixPreCommands: '' # optional -- commands to run before Helix work item execution + HelixPostCommands: '' # optional -- commands to run after Helix work item execution + WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects + WorkItemCommand: '' # optional -- a command to execute on the payload; requires WorkItemDirectory; incompatible with XUnitProjects + WorkItemTimeout: '' # optional -- a timeout in TimeSpan.Parse-ready value (e.g. 00:02:00) for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects + CorrelationPayloadDirectory: '' # optional -- a directory to zip up and send to Helix as a correlation payload + XUnitProjects: '' # optional -- semicolon-delimited list of XUnitProjects to parse and send to Helix; requires XUnitRuntimeTargetFramework, XUnitPublishTargetFramework, XUnitRunnerVersion, and IncludeDotNetCli=true + XUnitWorkItemTimeout: '' # optional -- the workitem timeout in seconds for all workitems created from the xUnit projects specified by XUnitProjects + XUnitPublishTargetFramework: '' # optional -- framework to use to publish your xUnit projects + XUnitRuntimeTargetFramework: '' # optional -- framework to use for the xUnit console runner + XUnitRunnerVersion: '' # optional -- version of the xUnit nuget package you wish to use on Helix; required for XUnitProjects + IncludeDotNetCli: false # optional -- true will download a version of the .NET CLI onto the Helix machine as a correlation payload; requires DotNetCliPackageType and DotNetCliVersion + DotNetCliPackageType: '' # optional -- either 'sdk', 'runtime' or 'aspnetcore-runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json + DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json + WaitForWorkItemCompletion: true # optional -- true will make the task wait until work items have been completed and fail the build if work items fail. False is "fire and forget." + IsExternal: false # [DEPRECATED] -- doesn't do anything, jobs are external if HelixAccessToken is empty and Creator is set + HelixBaseUri: 'https://helix.dot.net/' # optional -- sets the Helix API base URI (allows targeting https://helix.int-dot.net ) + Creator: '' # optional -- if the build is external, use this to specify who is sending the job + DisplayNamePrefix: 'Run Tests' # optional -- rename the beginning of the displayName of the steps in AzDO + condition: succeeded() # optional -- condition for step to execute; defaults to succeeded() + continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false + +steps: + - powershell: 'powershell "$env:BUILD_SOURCESDIRECTORY\eng\common\msbuild.ps1 $env:BUILD_SOURCESDIRECTORY\eng\common\helixpublish.proj /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$env:BUILD_SOURCESDIRECTORY\artifacts\log\$env:BuildConfig\SendToHelix.binlog"' + displayName: ${{ parameters.DisplayNamePrefix }} (Windows) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + HelixAccessToken: ${{ parameters.HelixAccessToken }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} + Creator: ${{ parameters.Creator }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} + - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/eng/common/helixpublish.proj /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog + displayName: ${{ parameters.DisplayNamePrefix }} (Unix) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + HelixAccessToken: ${{ parameters.HelixAccessToken }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} + Creator: ${{ parameters.Creator }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/common/templates-official/steps/source-build.yml b/eng/common/templates-official/steps/source-build.yml new file mode 100644 index 00000000000..829f17c34d1 --- /dev/null +++ b/eng/common/templates-official/steps/source-build.yml @@ -0,0 +1,129 @@ +parameters: + # This template adds arcade-powered source-build to CI. + + # This is a 'steps' template, and is intended for advanced scenarios where the existing build + # infra has a careful build methodology that must be followed. For example, a repo + # (dotnet/runtime) might choose to clone the GitHub repo only once and store it as a pipeline + # artifact for all subsequent jobs to use, to reduce dependence on a strong network connection to + # GitHub. Using this steps template leaves room for that infra to be included. + + # Defines the platform on which to run the steps. See 'eng/common/templates-official/job/source-build.yml' + # for details. The entire object is described in the 'job' template for simplicity, even though + # the usage of the properties on this object is split between the 'job' and 'steps' templates. + platform: {} + +steps: +# Build. Keep it self-contained for simple reusability. (No source-build-specific job variables.) +- script: | + set -x + df -h + + # If building on the internal project, the artifact feeds variable may be available (usually only if needed) + # In that case, call the feed setup script to add internal feeds corresponding to public ones. + # In addition, add an msbuild argument to copy the WIP from the repo to the target build location. + # This is because SetupNuGetSources.sh will alter the current NuGet.config file, and we need to preserve those + # changes. + internalRestoreArgs= + if [ '$(dn-bot-dnceng-artifact-feeds-rw)' != '$''(dn-bot-dnceng-artifact-feeds-rw)' ]; then + # Temporarily work around https://github.com/dotnet/arcade/issues/7709 + chmod +x $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh + $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh $(Build.SourcesDirectory)/NuGet.config $(dn-bot-dnceng-artifact-feeds-rw) + internalRestoreArgs='/p:CopyWipIntoInnerSourceBuildRepo=true' + + # The 'Copy WIP' feature of source build uses git stash to apply changes from the original repo. + # This only works if there is a username/email configured, which won't be the case in most CI runs. + git config --get user.email + if [ $? -ne 0 ]; then + git config user.email dn-bot@microsoft.com + git config user.name dn-bot + fi + fi + + # If building on the internal project, the internal storage variable may be available (usually only if needed) + # In that case, add variables to allow the download of internal runtimes if the specified versions are not found + # in the default public locations. + internalRuntimeDownloadArgs= + if [ '$(dotnetbuilds-internal-container-read-token-base64)' != '$''(dotnetbuilds-internal-container-read-token-base64)' ]; then + internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://dotnetbuilds.blob.core.windows.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://dotnetbuilds.blob.core.windows.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)' + fi + + buildConfig=Release + # Check if AzDO substitutes in a build config from a variable, and use it if so. + if [ '$(_BuildConfig)' != '$''(_BuildConfig)' ]; then + buildConfig='$(_BuildConfig)' + fi + + officialBuildArgs= + if [ '${{ and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}' = 'True' ]; then + officialBuildArgs='/p:DotNetPublishUsingPipelines=true /p:OfficialBuildId=$(BUILD.BUILDNUMBER)' + fi + + targetRidArgs= + if [ '${{ parameters.platform.targetRID }}' != '' ]; then + targetRidArgs='/p:TargetRid=${{ parameters.platform.targetRID }}' + fi + + runtimeOsArgs= + if [ '${{ parameters.platform.runtimeOS }}' != '' ]; then + runtimeOsArgs='/p:RuntimeOS=${{ parameters.platform.runtimeOS }}' + fi + + baseOsArgs= + if [ '${{ parameters.platform.baseOS }}' != '' ]; then + baseOsArgs='/p:BaseOS=${{ parameters.platform.baseOS }}' + fi + + publishArgs= + if [ '${{ parameters.platform.skipPublishValidation }}' != 'true' ]; then + publishArgs='--publish' + fi + + assetManifestFileName=SourceBuild_RidSpecific.xml + if [ '${{ parameters.platform.name }}' != '' ]; then + assetManifestFileName=SourceBuild_${{ parameters.platform.name }}.xml + fi + + ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ + --configuration $buildConfig \ + --restore --build --pack $publishArgs -bl \ + $officialBuildArgs \ + $internalRuntimeDownloadArgs \ + $internalRestoreArgs \ + $targetRidArgs \ + $runtimeOsArgs \ + $baseOsArgs \ + /p:SourceBuildNonPortable=${{ parameters.platform.nonPortable }} \ + /p:ArcadeBuildFromSource=true \ + /p:AssetManifestFileName=$assetManifestFileName + displayName: Build + +# Upload build logs for diagnosis. +- task: CopyFiles@2 + displayName: Prepare BuildLogs staging directory + inputs: + SourceFolder: '$(Build.SourcesDirectory)' + Contents: | + **/*.log + **/*.binlog + artifacts/source-build/self/prebuilt-report/** + TargetFolder: '$(Build.StagingDirectory)/BuildLogs' + CleanTargetFolder: true + continueOnError: true + condition: succeededOrFailed() + +- task: 1ES.PublishPipelineArtifact@1 + displayName: Publish BuildLogs + inputs: + targetPath: '$(Build.StagingDirectory)/BuildLogs' + artifactName: BuildLogs_SourceBuild_${{ parameters.platform.name }}_Attempt$(System.JobAttempt) + continueOnError: true + condition: succeededOrFailed() + +# Manually inject component detection so that we can ignore the source build upstream cache, which contains +# a nupkg cache of input packages (a local feed). +# This path must match the upstream cache path in property 'CurrentRepoSourceBuiltNupkgCacheDir' +# in src\Microsoft.DotNet.Arcade.Sdk\tools\SourceBuild\SourceBuildArcade.targets +- task: ComponentGovernanceComponentDetection@0 + displayName: Component Detection (Exclude upstream cache) + inputs: + ignoreDirectories: '$(Build.SourcesDirectory)/artifacts/source-build/self/src/artifacts/obj/source-built-upstream-cache' diff --git a/eng/common/templates-official/variables/pool-providers.yml b/eng/common/templates-official/variables/pool-providers.yml new file mode 100644 index 00000000000..1f308b24efc --- /dev/null +++ b/eng/common/templates-official/variables/pool-providers.yml @@ -0,0 +1,45 @@ +# Select a pool provider based off branch name. Anything with branch name containing 'release' must go into an -Svc pool, +# otherwise it should go into the "normal" pools. This separates out the queueing and billing of released branches. + +# Motivation: +# Once a given branch of a repository's output has been officially "shipped" once, it is then considered to be COGS +# (Cost of goods sold) and should be moved to a servicing pool provider. This allows both separation of queueing +# (allowing release builds and main PR builds to not intefere with each other) and billing (required for COGS. +# Additionally, the pool provider name itself may be subject to change when the .NET Core Engineering Services +# team needs to move resources around and create new and potentially differently-named pools. Using this template +# file from an Arcade-ified repo helps guard against both having to update one's release/* branches and renaming. + +# How to use: +# This yaml assumes your shipped product branches use the naming convention "release/..." (which many do). +# If we find alternate naming conventions in broad usage it can be added to the condition below. +# +# First, import the template in an arcade-ified repo to pick up the variables, e.g.: +# +# variables: +# - template: /eng/common/templates-official/variables/pool-providers.yml +# +# ... then anywhere specifying the pool provider use the runtime variables, +# $(DncEngInternalBuildPool) +# +# pool: +# name: $(DncEngInternalBuildPool) +# image: 1es-windows-2022 + +variables: + # Coalesce the target and source branches so we know when a PR targets a release branch + # If these variables are somehow missing, fall back to main (tends to have more capacity) + + # Any new -Svc alternative pools should have variables added here to allow for splitting work + + - name: DncEngInternalBuildPool + value: $[ + replace( + replace( + eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), + True, + 'NetCore1ESPool-Svc-Internal' + ), + False, + 'NetCore1ESPool-Internal' + ) + ] \ No newline at end of file diff --git a/eng/common/templates-official/variables/sdl-variables.yml b/eng/common/templates-official/variables/sdl-variables.yml new file mode 100644 index 00000000000..dbdd66d4a4b --- /dev/null +++ b/eng/common/templates-official/variables/sdl-variables.yml @@ -0,0 +1,7 @@ +variables: +# The Guardian version specified in 'eng/common/sdl/packages.config'. This value must be kept in +# sync with the packages.config file. +- name: DefaultGuardianVersion + value: 0.109.0 +- name: GuardianPackagesConfigFile + value: $(Build.SourcesDirectory)\eng\common\sdl\packages.config \ No newline at end of file diff --git a/eng/common/templates/job/job.yml b/eng/common/templates/job/job.yml index e24ca2f46f9..8ec5c4f2d9f 100644 --- a/eng/common/templates/job/job.yml +++ b/eng/common/templates/job/job.yml @@ -15,6 +15,7 @@ parameters: timeoutInMinutes: '' variables: [] workspace: '' + templateContext: '' # Job base template specific parameters # See schema documentation - https://github.com/dotnet/arcade/blob/master/Documentation/AzureDevOps/TemplateSchema.md @@ -68,6 +69,9 @@ jobs: ${{ if ne(parameters.timeoutInMinutes, '') }}: timeoutInMinutes: ${{ parameters.timeoutInMinutes }} + ${{ if ne(parameters.templateContext, '') }}: + templateContext: ${{ parameters.templateContext }} + variables: - ${{ if ne(parameters.enableTelemetry, 'false') }}: - name: DOTNET_CLI_TELEMETRY_PROFILE diff --git a/eng/common/templates/job/publish-build-assets.yml b/eng/common/templates/job/publish-build-assets.yml index fa5446c093d..8ec0151def2 100644 --- a/eng/common/templates/job/publish-build-assets.yml +++ b/eng/common/templates/job/publish-build-assets.yml @@ -58,7 +58,7 @@ jobs: demands: Cmd # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: - name: $(DncEngInternalBuildPool) + name: NetCore1ESPool-Publishing-Internal demands: ImageOverride -equals windows.vs2019.amd64 steps: @@ -71,7 +71,7 @@ jobs: checkDownloadedFiles: true condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - + - task: NuGetAuthenticate@1 - task: PowerShell@2 @@ -86,7 +86,7 @@ jobs: /p:OfficialBuildId=$(Build.BuildNumber) condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - + - task: powershell@2 displayName: Create ReleaseConfigs Artifact inputs: @@ -95,7 +95,7 @@ jobs: Add-Content -Path "$(Build.StagingDirectory)/ReleaseConfigs.txt" -Value $(BARBuildId) Add-Content -Path "$(Build.StagingDirectory)/ReleaseConfigs.txt" -Value "$(DefaultChannels)" Add-Content -Path "$(Build.StagingDirectory)/ReleaseConfigs.txt" -Value $(IsStableBuild) - + - task: PublishBuildArtifacts@1 displayName: Publish ReleaseConfigs Artifact inputs: @@ -121,7 +121,7 @@ jobs: - task: PublishBuildArtifacts@1 displayName: Publish SymbolPublishingExclusionsFile Artifact - condition: eq(variables['SymbolExclusionFile'], 'true') + condition: eq(variables['SymbolExclusionFile'], 'true') inputs: PathtoPublish: '$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt' PublishLocation: Container @@ -137,7 +137,7 @@ jobs: displayName: Publish Using Darc inputs: filePath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 - arguments: -BuildId $(BARBuildId) + arguments: -BuildId $(BARBuildId) -PublishingInfraVersion 3 -AzdoToken '$(publishing-dnceng-devdiv-code-r-build-re)' -MaestroToken '$(MaestroApiAccessToken)' @@ -148,4 +148,4 @@ jobs: - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: - template: /eng/common/templates/steps/publish-logs.yml parameters: - JobLabel: 'Publish_Artifacts_Logs' + JobLabel: 'Publish_Artifacts_Logs' diff --git a/eng/common/templates/post-build/post-build.yml b/eng/common/templates/post-build/post-build.yml index 3f74abf7ce0..aba44a25a33 100644 --- a/eng/common/templates/post-build/post-build.yml +++ b/eng/common/templates/post-build/post-build.yml @@ -39,7 +39,7 @@ parameters: displayName: Enable NuGet validation type: boolean default: true - + - name: publishInstallersAndChecksums displayName: Publish installers and checksums type: boolean @@ -131,8 +131,8 @@ stages: displayName: Validate inputs: filePath: $(Build.SourcesDirectory)/eng/common/post-build/nuget-validation.ps1 - arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ - -ToolDestinationPath $(Agent.BuildDirectory)/Extract/ + arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ + -ToolDestinationPath $(Agent.BuildDirectory)/Extract/ - job: displayName: Signing Validation @@ -221,9 +221,9 @@ stages: displayName: Validate inputs: filePath: $(Build.SourcesDirectory)/eng/common/post-build/sourcelink-validation.ps1 - arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ - -ExtractPath $(Agent.BuildDirectory)/Extract/ - -GHRepoName $(Build.Repository.Name) + arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ + -ExtractPath $(Agent.BuildDirectory)/Extract/ + -GHRepoName $(Build.Repository.Name) -GHCommit $(Build.SourceVersion) -SourcelinkCliVersion $(SourceLinkCLIVersion) continueOnError: true @@ -258,7 +258,7 @@ stages: demands: Cmd # If it's not devdiv, it's dnceng ${{ else }}: - name: $(DncEngInternalBuildPool) + name: NetCore1ESPool-Publishing-Internal demands: ImageOverride -equals windows.vs2019.amd64 steps: - template: setup-maestro-vars.yml @@ -272,7 +272,7 @@ stages: displayName: Publish Using Darc inputs: filePath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 - arguments: -BuildId $(BARBuildId) + arguments: -BuildId $(BARBuildId) -PublishingInfraVersion ${{ parameters.publishingInfraVersion }} -AzdoToken '$(publishing-dnceng-devdiv-code-r-build-re)' -MaestroToken '$(MaestroApiAccessToken)' diff --git a/eng/common/templates/steps/component-governance.yml b/eng/common/templates/steps/component-governance.yml index 0ecec47b0c9..cbba0596709 100644 --- a/eng/common/templates/steps/component-governance.yml +++ b/eng/common/templates/steps/component-governance.yml @@ -4,7 +4,7 @@ parameters: steps: - ${{ if eq(parameters.disableComponentGovernance, 'true') }}: - - script: "echo ##vso[task.setvariable variable=skipComponentGovernanceDetection]true" + - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true" displayName: Set skipComponentGovernanceDetection variable - ${{ if ne(parameters.disableComponentGovernance, 'true') }}: - task: ComponentGovernanceComponentDetection@0 diff --git a/eng/common/templates/steps/generate-sbom.yml b/eng/common/templates/steps/generate-sbom.yml index a06373f38fa..2b21eae4273 100644 --- a/eng/common/templates/steps/generate-sbom.yml +++ b/eng/common/templates/steps/generate-sbom.yml @@ -5,7 +5,7 @@ # IgnoreDirectories - Directories to ignore for SBOM generation. This will be passed through to the CG component detector. parameters: - PackageVersion: 7.0.0 + PackageVersion: 8.0.0 BuildDropPath: '$(Build.SourcesDirectory)/artifacts' PackageName: '.NET' ManifestDirPath: $(Build.ArtifactStagingDirectory)/sbom diff --git a/eng/common/templates/variables/pool-providers.yml b/eng/common/templates/variables/pool-providers.yml index 9cc5c550d3b..d236f9fdbb1 100644 --- a/eng/common/templates/variables/pool-providers.yml +++ b/eng/common/templates/variables/pool-providers.yml @@ -1,15 +1,15 @@ -# Select a pool provider based off branch name. Anything with branch name containing 'release' must go into an -Svc pool, +# Select a pool provider based off branch name. Anything with branch name containing 'release' must go into an -Svc pool, # otherwise it should go into the "normal" pools. This separates out the queueing and billing of released branches. -# Motivation: +# Motivation: # Once a given branch of a repository's output has been officially "shipped" once, it is then considered to be COGS # (Cost of goods sold) and should be moved to a servicing pool provider. This allows both separation of queueing # (allowing release builds and main PR builds to not intefere with each other) and billing (required for COGS. -# Additionally, the pool provider name itself may be subject to change when the .NET Core Engineering Services -# team needs to move resources around and create new and potentially differently-named pools. Using this template +# Additionally, the pool provider name itself may be subject to change when the .NET Core Engineering Services +# team needs to move resources around and create new and potentially differently-named pools. Using this template # file from an Arcade-ified repo helps guard against both having to update one's release/* branches and renaming. -# How to use: +# How to use: # This yaml assumes your shipped product branches use the naming convention "release/..." (which many do). # If we find alternate naming conventions in broad usage it can be added to the condition below. # @@ -54,4 +54,4 @@ variables: False, 'NetCore1ESPool-Internal' ) - ] \ No newline at end of file + ] diff --git a/global.json b/global.json index ba4499884a8..83a7611b408 100644 --- a/global.json +++ b/global.json @@ -13,7 +13,7 @@ } }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24059.4", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24059.4" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24204.3", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24204.3" } } diff --git a/src/EFCore.Abstractions/PACKAGE.md b/src/EFCore.Abstractions/PACKAGE.md new file mode 100644 index 00000000000..0d43afc8caf --- /dev/null +++ b/src/EFCore.Abstractions/PACKAGE.md @@ -0,0 +1,13 @@ +`Microsoft.EntityFrameworkCore.Abstractions` is a small package containing abstractions which may be useful for applications in places where a dependency on the full `Microsoft.EntityFrameworkCore` is not desirable. + +## Usage + +This package is included automatically as a dependency of the main [Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore) package. Usually, the abstractions package is only explicitly installed in places where it is undesirable to use the main package. For example, it can be installed to use mapping attributes on POCO entity types which are otherwise independent of EF Core. + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). diff --git a/src/EFCore.Analyzers/AnalyzerReleases.Shipped.md b/src/EFCore.Analyzers/AnalyzerReleases.Shipped.md index 9b4322118c3..ed10f609a61 100644 --- a/src/EFCore.Analyzers/AnalyzerReleases.Shipped.md +++ b/src/EFCore.Analyzers/AnalyzerReleases.Shipped.md @@ -22,4 +22,4 @@ EF1000 | Security | Disabled | RawSqlStringInjectionDiagnosticAnalyzer, [Docume ### New Rules Rule ID | Category | Severity | Notes --------|----------|----------|------- -EF1002 | Security | Warning | InterpolatedStringUsageInRawQueriesDiagnosticAnalyzer, [Documentation](https://learn.microsoft.com/en-us/ef/core/querying/sql-queries#passing-parameters) \ No newline at end of file +EF1002 | Security | Warning | InterpolatedStringUsageInRawQueriesDiagnosticAnalyzer, [Documentation](https://learn.microsoft.com/ef/core/querying/sql-queries#passing-parameters) \ No newline at end of file diff --git a/src/EFCore.Analyzers/EFCore.Analyzers.nuspec b/src/EFCore.Analyzers/EFCore.Analyzers.nuspec index 66cccde5ecc..3ad15319e11 100644 --- a/src/EFCore.Analyzers/EFCore.Analyzers.nuspec +++ b/src/EFCore.Analyzers/EFCore.Analyzers.nuspec @@ -5,10 +5,12 @@ + docs\PACKAGE.md $CommonFileElements$ + diff --git a/src/EFCore.Analyzers/PACKAGE.md b/src/EFCore.Analyzers/PACKAGE.md new file mode 100644 index 00000000000..5d9877db6c4 --- /dev/null +++ b/src/EFCore.Analyzers/PACKAGE.md @@ -0,0 +1,13 @@ +`Microsoft.EntityFrameworkCore.Analyzers` contains C# analyzers for Entity Framework Core. + +## Usage + +This package is included automatically as a dependency of the main [Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore) package. It is unusual to install it explicitly. + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). diff --git a/src/EFCore.Cosmos/Infrastructure/CosmosDbContextOptionsBuilder.cs b/src/EFCore.Cosmos/Infrastructure/CosmosDbContextOptionsBuilder.cs index e26e454ba6d..85a1cd37136 100644 --- a/src/EFCore.Cosmos/Infrastructure/CosmosDbContextOptionsBuilder.cs +++ b/src/EFCore.Cosmos/Infrastructure/CosmosDbContextOptionsBuilder.cs @@ -93,7 +93,7 @@ public virtual CosmosDbContextOptionsBuilder LimitToEndpoint(bool enable = true) /// /// /// Use - /// + /// /// static lambda expressions /// /// to avoid creating multiple instances. diff --git a/src/EFCore.Cosmos/PACKAGE.md b/src/EFCore.Cosmos/PACKAGE.md new file mode 100644 index 00000000000..297535b7fec --- /dev/null +++ b/src/EFCore.Cosmos/PACKAGE.md @@ -0,0 +1,25 @@ +`Microsoft.EntityFrameworkCore.Cosmos` is the EF Core database provider package for Azure Cosmos DB. + +## Usage + +Call the `UseCosmos` method to choose the Azure Cosmos DB database provider for your `DbContext`. For example: + +```csharp +protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseCosmos( + "https://localhost:8081", + "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", + databaseName: "OrdersDB"); +``` + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Additional documentation + +See [Microsoft SQL Server EF Core Database Provider](https://learn.microsoft.com/ef/core/providers/cosmos/) for more information about the features of this database provider. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). diff --git a/src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs b/src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs index 26a4dc2e855..70bb2c43bc1 100644 --- a/src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs +++ b/src/EFCore.Cosmos/ValueGeneration/Internal/IdValueGenerator.cs @@ -117,7 +117,7 @@ private static StringBuilder AppendEscape(StringBuilder builder, string stringVa return builder.Append(stringValue) // We need this to avoid collisions with the value separator .Replace("|", "^|", startingIndex, builder.Length - startingIndex) - // These are invalid characters, see https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.documents.resource.id + // These are invalid characters, see https://docs.microsoft.com/dotnet/api/microsoft.azure.documents.resource.id .Replace("/", "^2F", startingIndex, builder.Length - startingIndex) .Replace("\\", "^5C", startingIndex, builder.Length - startingIndex) .Replace("?", "^3F", startingIndex, builder.Length - startingIndex) diff --git a/src/EFCore.Design/PACKAGE.md b/src/EFCore.Design/PACKAGE.md new file mode 100644 index 00000000000..0f8531c9bb6 --- /dev/null +++ b/src/EFCore.Design/PACKAGE.md @@ -0,0 +1,24 @@ +The Entity Framework Core tools help with design-time development tasks. They're primarily used to manage Migrations and to scaffold a `DbContext` and entity types by reverse engineering the schema of a database. + +The `Microsoft.EntityFrameworkCore.Design` package is required for either command-line or Package Manager Console-based tooling, and is a dependency of [dotnet-ef](https://www.nuget.org/packages/dotnet-ef) and [Microsoft.EntityFrameworkCore.Tools](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Tools). + +## Usage + +Install the package into your project and then use either [dotnet-ef](https://www.nuget.org/packages/dotnet-ef) or [Microsoft.EntityFrameworkCore.Tools](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Tools). + +By default, the package will install with `PrivateAssets="All" `so that the tooling assembly will not be included with your published app. For example: + +```xml + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + +``` + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). diff --git a/src/EFCore.Design/README.md b/src/EFCore.Design/README.md deleted file mode 100644 index ba2b49b132b..00000000000 --- a/src/EFCore.Design/README.md +++ /dev/null @@ -1,45 +0,0 @@ -The Entity Framework Core tools help with design-time development tasks. They're primarily used to manage Migrations and to scaffold a DbContext and entity types by reverse engineering the schema of a database. -Microsoft.EntityFrameworkCore.Design is for cross-platform command line tooling. - -## Getting started - -`Microsoft.EntityFrameworkCore.Design` contains all the design-time logic for Entity Framework Core. It's the code that all of the various tools (PMC cmdlets like `Add-Migration`, `dotnet ef` & `ef.exe`) call into. - -If you don't use Migrations or Reverse Engineering, you don't need it. - -And when you do need it, we encourage `PrivateAssets="All" `so it doesn't get published to the server where you almost certainly won't need it. - -### Prerequisites - -Before using the tools: - -- [Understand the difference between target and startup project](https://learn.microsoft.com/en-us/ef/core/cli/powershell#target-and-startup-project). -- [Learn how to use the tools with .NET Standard class libraries](https://learn.microsoft.com/en-us/ef/core/cli/powershell#other-target-frameworks). -- [For ASP.NET Core projects, set the environment](https://learn.microsoft.com/en-us/ef/core/cli/powershell#aspnet-core-environment). - -## Usage - -PMC Command | Usage --- | -- -Get-Help entityframework |Displays information about entity framework commands. -[Add-Migration](https://learn.microsoft.com/en-us/ef/core/cli/powershell#add-migration) | Creates a migration by adding a migration snapshot. -[Bundle-Migration](https://learn.microsoft.com/en-us/ef/core/cli/powershell#bundle-migration) | Creates an executable to update the database. -[Get-DbContext](https://learn.microsoft.com/en-us/ef/core/cli/powershell#get-dbcontext) | Gets information about a DbContext type. -[Drop-Database](https://learn.microsoft.com/en-us/ef/core/cli/powershell#drop-database) | Drops the database. -[Get-Migration](https://learn.microsoft.com/en-us/ef/core/cli/powershell#get-migration) | Lists available migrations. -[Optimize-DbContext](https://learn.microsoft.com/en-us/ef/core/cli/powershell#optimize-dbcontext) | Generates a compiled version of the model used by the `DbContext`. -[Remove-Migration](https://learn.microsoft.com/en-us/ef/core/cli/powershell#remove-migration) | Removes the last migration snapshot. -[Scaffold-DbContext](https://learn.microsoft.com/en-us/ef/core/cli/powershell#scaffold-dbcontext) | Generates a DbContext and entity type classes for a specified database. This is called reverse engineering. -[Script-DbContext](https://learn.microsoft.com/en-us/ef/core/cli/powershell#script-dbcontext) | Generates a SQL script from the DbContext. Bypasses any migrations. -[Script-Migration](https://learn.microsoft.com/en-us/ef/core/cli/powershell#script-migration) | Generates a SQL script using all the migration snapshots. -[Update-Database](https://learn.microsoft.com/en-us/ef/core/cli/powershell#update-database) | Updates the database schema based on the last migration snapshot. - -## Additional documentation - -- [Migrations](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/) -- [Reverse Engineering](https://learn.microsoft.com/en-us/ef/core/managing-schemas/scaffolding/?tabs=dotnet-core-cli) -- [Compiled models](https://learn.microsoft.com/en-us/ef/core/performance/advanced-performance-topics?tabs=with-di%2Cwith-constant#compiled-models) - -## Feedback - -If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). \ No newline at end of file diff --git a/src/EFCore.InMemory/PACKAGE.md b/src/EFCore.InMemory/PACKAGE.md new file mode 100644 index 00000000000..33e824a3add --- /dev/null +++ b/src/EFCore.InMemory/PACKAGE.md @@ -0,0 +1,26 @@ +`Microsoft.EntityFrameworkCore.InMemory` is the EF Core database provider package for the built-in in-memory database. + +This database provider allows Entity Framework Core to be used with an in-memory database. While it has become common to use the in-memory database for testing, this is discouraged. For more information on how to test EF Core applications, see [Testing EF Core Applications](https://learn.microsoft.com/ef/core/testing/). + +## Usage + +Call the `UseInMemoryDatabase` method to choose the SQL Server/Azure SQL database provider for your `DbContext`. For example: + +```csharp +protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) +{ + optionsBuilder.UseInMemoryDatabase("MyDatabase"); +} +``` + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Additional documentation + +See [Microsoft SQL Server EF Core Database Provider](https://learn.microsoft.com/en-us/ef/core/providers/in-memory/) for more information about the features of this database provider. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). \ No newline at end of file diff --git a/src/EFCore.Proxies/PACKAGE.md b/src/EFCore.Proxies/PACKAGE.md new file mode 100644 index 00000000000..2c76ea8955f --- /dev/null +++ b/src/EFCore.Proxies/PACKAGE.md @@ -0,0 +1,34 @@ +The `Microsoft.EntityFrameworkCore.Proxies` package contains implementations of dynamic proxies for [lazy-loading](https://learn.microsoft.com/ef/core/querying/related-data/lazy#lazy-loading-with-proxies) and/or [change-tracking](https://learn.microsoft.com/ef/core/change-tracking/change-detection#change-tracking-proxies). + +## Usage + +Call `UseLazyLoadingProxies` when configuring your `DbContext` to enable lazy-loading proxies. For example: + +```csharp +protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseLazyLoadingProxies(); +``` + +Call `UseChangeTrackingProxies` when configuring your `DbContext` to enable change-tracking proxies. For example: + +```csharp +protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseChangeTrackingProxies(); +``` + +Call both methods for proxies that implement both lazy-loading and change-tracking. For example: + +```csharp +protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder + .UseLazyLoadingProxies() + .UseChangeTrackingProxies(); +``` + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). \ No newline at end of file diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index 97ba303a91a..929fb4f470e 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -17,6 +17,12 @@ namespace Microsoft.EntityFrameworkCore; /// public static class RelationalPropertyExtensions { + private static readonly bool UseOldBehavior32763 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32763", out var enabled32763) && enabled32763; + + private static readonly bool UseOldBehavior33004 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue33004", out var enabled33004) && enabled33004; + private static readonly MethodInfo GetFieldValueMethod = typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.GetFieldValue), new[] { typeof(int) })!; @@ -183,7 +189,10 @@ public static string GetDefaultColumnName(this IReadOnlyProperty property) var foreignKey = property.GetContainingForeignKeys().First(); var principalEntityType = foreignKey.PrincipalEntityType; if (principalEntityType is { HasSharedClrType: false, ClrType.IsConstructedGenericType: true } - && foreignKey.DependentToPrincipal == null) + && foreignKey.DependentToPrincipal == null + && (UseOldBehavior32763 + || principalEntityType.GetTableName() != foreignKey.DeclaringEntityType.GetTableName() + || principalEntityType.GetSchema() != foreignKey.DeclaringEntityType.GetSchema())) { var principalProperty = property.FindFirstPrincipal()!; var principalName = principalEntityType.ShortName(); @@ -1185,8 +1194,12 @@ public static bool IsColumnNullable(this IReadOnlyProperty property, in StoreObj return sharedTableRootProperty.IsColumnNullable(storeObject); } + var declaringEntityType = UseOldBehavior33004 + ? property.DeclaringType + : property.DeclaringType.ContainingEntityType; + return property.IsNullable - || (property.DeclaringType is IReadOnlyEntityType entityType + || (declaringEntityType is IReadOnlyEntityType entityType && ((entityType.BaseType != null && entityType.GetMappingStrategy() == RelationalAnnotationNames.TphMappingStrategy) || IsOptionalSharingDependent(entityType, storeObject, 0))); diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 816cf4b74ba..8b07aa9ae54 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.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.Text; using System.Text.Json; namespace Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -13,6 +14,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// public class RelationalModel : Annotatable, IRelationalModel { + internal static readonly bool UseOldBehavior32699 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32699", out var enabled32699) && enabled32699; + private bool _isReadOnly; /// @@ -340,31 +344,38 @@ private static void AddDefaultMappings( } else { - foreach (var property in entityType.GetProperties()) + if (UseOldBehavior32699) { - var columnName = property.IsPrimaryKey() || isTpc || isTph || property.DeclaringType == mappedType - ? property.GetColumnName() - : null; - if (columnName == null) + foreach (var property in entityType.GetProperties()) { - continue; - } + var columnName = property.IsPrimaryKey() || isTpc || isTph || property.DeclaringType == mappedType + ? property.GetColumnName() + : null; + if (columnName == null) + { + continue; + } - var column = (ColumnBase?)defaultTable.FindColumn(columnName); - if (column == null) - { - column = new ColumnBase(columnName, property.GetColumnType(), defaultTable) + var column = (ColumnBase?)defaultTable.FindColumn(columnName); + if (column == null) { - IsNullable = property.IsColumnNullable() - }; - defaultTable.Columns.Add(columnName, column); - } - else if (!property.IsColumnNullable()) - { - column.IsNullable = false; - } + column = new ColumnBase(columnName, property.GetColumnType(), defaultTable) + { + IsNullable = property.IsColumnNullable() + }; + defaultTable.Columns.Add(columnName, column); + } + else if (!property.IsColumnNullable()) + { + column.IsNullable = false; + } - CreateColumnMapping(column, property, tableMapping); + CreateColumnMapping(column, property, tableMapping); + } + } + else + { + CreateDefaultColumnMapping(entityType, mappedType, defaultTable, tableMapping, isTph, isTpc); } } @@ -386,6 +397,83 @@ private static void AddDefaultMappings( tableMappings.Reverse(); } + private static void CreateDefaultColumnMapping( + ITypeBase typeBase, + ITypeBase mappedType, + TableBase defaultTable, + TableMappingBase tableMapping, + bool isTph, + bool isTpc) + { + foreach (var property in typeBase.GetProperties()) + { + var columnName = property.IsPrimaryKey() || isTpc || isTph || property.DeclaringType == mappedType + ? GetColumnName(property) + : null; + + if (columnName == null) + { + continue; + } + + var column = (ColumnBase?)defaultTable.FindColumn(columnName); + if (column == null) + { + column = new ColumnBase(columnName, property.GetColumnType(), defaultTable) + { + IsNullable = property.IsColumnNullable() + }; + defaultTable.Columns.Add(columnName, column); + } + else if (!property.IsColumnNullable()) + { + column.IsNullable = false; + } + + CreateColumnMapping(column, property, tableMapping); + } + + foreach (var complexProperty in typeBase.GetDeclaredComplexProperties()) + { + var complexType = complexProperty.ComplexType; + tableMapping = new TableMappingBase(complexType, defaultTable, includesDerivedTypes: false); + + CreateDefaultColumnMapping(complexType, complexType, defaultTable, tableMapping, isTph, isTpc); + + var tableMappings = (List>?)complexType + .FindRuntimeAnnotationValue(RelationalAnnotationNames.DefaultMappings); + if (tableMappings == null) + { + tableMappings = new List>(); + complexType.AddRuntimeAnnotation(RelationalAnnotationNames.DefaultMappings, tableMappings); + } + tableMappings.Add(tableMapping); + + defaultTable.ComplexTypeMappings.Add(tableMapping); + } + + static string GetColumnName(IProperty property) + { + var complexType = property.DeclaringType as IComplexType; + if (complexType != null) + { + var builder = new StringBuilder(); + builder.Append(property.Name); + while (complexType != null) + { + builder.Insert(0, "_"); + builder.Insert(0, complexType.ComplexProperty.Name); + + complexType = complexType.ComplexProperty.DeclaringType as IComplexType; + } + + return builder.ToString(); + } + + return property.GetColumnName(); + } + } + private static void AddTables( RelationalModel databaseModel, IEntityType entityType, diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index a14998c9fd4..04cc44f1621 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -16,6 +16,9 @@ namespace Microsoft.EntityFrameworkCore.Migrations.Internal; /// public class MigrationsModelDiffer : IMigrationsModelDiffer { + private static readonly bool UseOldBehavior32972 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32972", out var enabled32972) && enabled32972; + private static readonly Type[] DropOperationTypes = { typeof(DropIndexOperation), @@ -1190,7 +1193,14 @@ private void Initialize( if (!column.TryGetDefaultValue(out var defaultValue)) { - defaultValue = null; + // for non-nullable collections of primitives that are mapped to JSON we set a default value corresponding to empty JSON collection + defaultValue = !UseOldBehavior32972 + && !inline + && column is { IsNullable: false, StoreTypeMapping: { ElementTypeMapping: not null, Converter: ValueConverter columnValueConverter } } + && columnValueConverter.GetType() is Type { IsGenericType: true } columnValueConverterType + && columnValueConverterType.GetGenericTypeDefinition() == typeof(CollectionToJsonStringConverter<>) + ? "[]" + : null; } columnOperation.ColumnType = column.StoreType; diff --git a/src/EFCore.Relational/PACKAGE.md b/src/EFCore.Relational/PACKAGE.md new file mode 100644 index 00000000000..938c855927e --- /dev/null +++ b/src/EFCore.Relational/PACKAGE.md @@ -0,0 +1,13 @@ +`Microsoft.EntityFrameworkCore.Relational` package contains common code for relational database providers that are based on ADO.NET. + +## Usage + +This package is included automatically as a dependency of each relational provider package. Adding an explicit reference can be useful to ensure the latest version is used even if the provider is depending on an older version. + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). \ No newline at end of file diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index f4f48b500b8..7ac113066b1 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -30,6 +30,8 @@ public sealed partial class SelectExpression : TableExpressionBase AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue31107", out var enabled31107) && enabled31107; private static readonly bool UseOldBehavior32234 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32234", out var enabled32234) && enabled32234; + private static readonly bool UseOldBehavior32911 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32911", out var enabled32911) && enabled32911; private static readonly IdentifierComparer IdentifierComparerInstance = new(); @@ -2706,116 +2708,243 @@ void HandleStructuralTypeProjection( projection1.StructuralType.DisplayName(), projection2.StructuralType.DisplayName())); } - var propertyExpressions = new Dictionary(); - - ProcessStructuralType(projection1, projection2); - - void ProcessStructuralType( - StructuralTypeProjectionExpression nestedProjection1, - StructuralTypeProjectionExpression nestedProjection2) + if (UseOldBehavior32911) { - var type = nestedProjection1.StructuralType; + var propertyExpressions = new Dictionary(); + + ProcessStructuralType(projection1, projection2); - foreach (var property in GetAllPropertiesInHierarchy(type)) + void ProcessStructuralType( + StructuralTypeProjectionExpression nestedProjection1, + StructuralTypeProjectionExpression nestedProjection2) { - var column1 = nestedProjection1.BindProperty(property); - var column2 = nestedProjection2.BindProperty(property); - var alias = GenerateUniqueColumnAlias(column1.Name); - var innerProjection = new ProjectionExpression(column1, alias); - select1._projection.Add(innerProjection); - select2._projection.Add(new ProjectionExpression(column2, alias)); - var outerColumn = new ConcreteColumnExpression(innerProjection, tableReferenceExpression); - if (column1.IsNullable - || column2.IsNullable) + var type = nestedProjection1.StructuralType; + + foreach (var property in GetAllPropertiesInHierarchy(type)) { - outerColumn = outerColumn.MakeNullable(); - } + var column1 = nestedProjection1.BindProperty(property); + var column2 = nestedProjection2.BindProperty(property); + var alias = GenerateUniqueColumnAlias(column1.Name); + var innerProjection = new ProjectionExpression(column1, alias); + select1._projection.Add(innerProjection); + select2._projection.Add(new ProjectionExpression(column2, alias)); + var outerColumn = new ConcreteColumnExpression(innerProjection, tableReferenceExpression); + if (column1.IsNullable + || column2.IsNullable) + { + outerColumn = outerColumn.MakeNullable(); + } - propertyExpressions[property] = outerColumn; + propertyExpressions[property] = outerColumn; - // Lift up any identifier columns to the set operation result (the outer). - // This is typically the entity primary key columns, but can also be all of a complex type's properties if Distinct - // was previously called. - if (outerIdentifiers.Length > 0) - { - var index = select1._identifier.FindIndex(e => e.Column.Equals(column1)); - if (index != -1) + // Lift up any identifier columns to the set operation result (the outer). + // This is typically the entity primary key columns, but can also be all of a complex type's properties if Distinct + // was previously called. + if (outerIdentifiers.Length > 0) { - if (select2._identifier[index].Column.Equals(column2)) + var index = select1._identifier.FindIndex(e => e.Column.Equals(column1)); + if (index != -1) { - outerIdentifiers[index] = outerColumn; + if (select2._identifier[index].Column.Equals(column2)) + { + outerIdentifiers[index] = outerColumn; + } + else + { + // If select1 matched but select2 did not then we erase all identifiers + // TODO: We could make this little more robust by allow the indexes to be different. See issue#24475 + // i.e. Identifier ordering being different. + outerIdentifiers = Array.Empty(); + } } - else + // If the top-level projection - not the current nested one - is a complex type and not an entity type, then add + // all its columns to the "otherExpressions" list (i.e. columns not part of a an entity primary key). This is + // the same as with a non-structural type projection. + else if (projection1.StructuralType is IComplexType) { - // If select1 matched but select2 did not then we erase all identifiers - // TODO: We could make this little more robust by allow the indexes to be different. See issue#24475 - // i.e. Identifier ordering being different. - outerIdentifiers = Array.Empty(); + var outerTypeMapping = column1.TypeMapping ?? column1.TypeMapping; + if (outerTypeMapping == null) + { + throw new InvalidOperationException( + RelationalStrings.SetOperationsRequireAtLeastOneSideWithValidTypeMapping(setOperationType)); + } + + otherExpressions.Add((outerColumn, outerTypeMapping.KeyComparer)); } } - // If the top-level projection - not the current nested one - is a complex type and not an entity type, then add - // all its columns to the "otherExpressions" list (i.e. columns not part of a an entity primary key). This is - // the same as with a non-structural type projection. - else if (projection1.StructuralType is IComplexType) - { - var outerTypeMapping = column1.TypeMapping ?? column1.TypeMapping; - if (outerTypeMapping == null) - { - throw new InvalidOperationException( - RelationalStrings.SetOperationsRequireAtLeastOneSideWithValidTypeMapping(setOperationType)); - } + } - otherExpressions.Add((outerColumn, outerTypeMapping.KeyComparer)); - } + foreach (var complexProperty in GetAllComplexPropertiesInHierarchy(nestedProjection1.StructuralType)) + { + ProcessStructuralType( + (StructuralTypeProjectionExpression)nestedProjection1.BindComplexProperty(complexProperty).ValueBufferExpression, + (StructuralTypeProjectionExpression)nestedProjection2.BindComplexProperty(complexProperty).ValueBufferExpression); } } - foreach (var complexProperty in GetAllComplexPropertiesInHierarchy(nestedProjection1.StructuralType)) + Check.DebugAssert( + projection1.TableMap.Count == projection2.TableMap.Count, + "Set operation over entity projections with different table map counts"); + Check.DebugAssert( + projection1.TableMap.Keys.All(t => projection2.TableMap.ContainsKey(t)), + "Set operation over entity projections with table map discrepancy"); + + var tableMap = projection1.TableMap.ToDictionary(kvp => kvp.Key, kvp => tableReferenceExpression); + + var discriminatorExpression = projection1.DiscriminatorExpression; + if (projection1.DiscriminatorExpression != null + && projection2.DiscriminatorExpression != null) { - ProcessStructuralType( - (StructuralTypeProjectionExpression)nestedProjection1.BindComplexProperty(complexProperty).ValueBufferExpression, - (StructuralTypeProjectionExpression)nestedProjection2.BindComplexProperty(complexProperty).ValueBufferExpression); + var alias = GenerateUniqueColumnAlias(DiscriminatorColumnAlias); + var innerProjection = new ProjectionExpression(projection1.DiscriminatorExpression, alias); + select1._projection.Add(innerProjection); + select2._projection.Add(new ProjectionExpression(projection2.DiscriminatorExpression, alias)); + discriminatorExpression = new ConcreteColumnExpression(innerProjection, tableReferenceExpression); } - } - - Check.DebugAssert( - projection1.TableMap.Count == projection2.TableMap.Count, - "Set operation over entity projections with different table map counts"); - Check.DebugAssert( - projection1.TableMap.Keys.All(t => projection2.TableMap.ContainsKey(t)), - "Set operation over entity projections with table map discrepancy"); - var tableMap = projection1.TableMap.ToDictionary(kvp => kvp.Key, kvp => tableReferenceExpression); + var outerProjection = new StructuralTypeProjectionExpression( + projection1.StructuralType, propertyExpressions, tableMap, nullable: false, discriminatorExpression); - var discriminatorExpression = projection1.DiscriminatorExpression; - if (projection1.DiscriminatorExpression != null - && projection2.DiscriminatorExpression != null) - { - var alias = GenerateUniqueColumnAlias(DiscriminatorColumnAlias); - var innerProjection = new ProjectionExpression(projection1.DiscriminatorExpression, alias); - select1._projection.Add(innerProjection); - select2._projection.Add(new ProjectionExpression(projection2.DiscriminatorExpression, alias)); - discriminatorExpression = new ConcreteColumnExpression(innerProjection, tableReferenceExpression); - } + if (outerIdentifiers.Length > 0 && outerProjection is { StructuralType: IEntityType entityType }) + { + var primaryKey = entityType.FindPrimaryKey(); - var outerProjection = new StructuralTypeProjectionExpression( - projection1.StructuralType, propertyExpressions, tableMap, nullable: false, discriminatorExpression); + // We know that there are existing identifiers (see condition above); we know we must have a key since a keyless + // entity type would have wiped the identifiers when generating the join. + Check.DebugAssert(primaryKey != null, "primary key is null."); + foreach (var property in primaryKey.Properties) + { + entityProjectionIdentifiers.Add(outerProjection.BindProperty(property)); + entityProjectionValueComparers.Add(property.GetKeyValueComparer()); + } + } - if (outerIdentifiers.Length > 0 && outerProjection is { StructuralType: IEntityType entityType }) + _projectionMapping[projectionMember] = outerProjection; + } + else { - var primaryKey = entityType.FindPrimaryKey(); + var resultProjection = ProcessStructuralType(projection1, projection2); + _projectionMapping[projectionMember] = resultProjection; - // We know that there are existing identifiers (see condition above); we know we must have a key since a keyless - // entity type would have wiped the identifiers when generating the join. - Check.DebugAssert(primaryKey != null, "primary key is null."); - foreach (var property in primaryKey.Properties) + StructuralTypeProjectionExpression ProcessStructuralType( + StructuralTypeProjectionExpression structuralProjection1, + StructuralTypeProjectionExpression structuralProjection2) { - entityProjectionIdentifiers.Add(outerProjection.BindProperty(property)); - entityProjectionValueComparers.Add(property.GetKeyValueComparer()); + var propertyExpressions = new Dictionary(); + var complexPropertyCache = new Dictionary(); + var type = structuralProjection1.StructuralType; + + foreach (var property in GetAllPropertiesInHierarchy(type)) + { + var column1 = structuralProjection1.BindProperty(property); + var column2 = structuralProjection2.BindProperty(property); + var alias = GenerateUniqueColumnAlias(column1.Name); + var innerProjection = new ProjectionExpression(column1, alias); + select1._projection.Add(innerProjection); + select2._projection.Add(new ProjectionExpression(column2, alias)); + var outerColumn = new ConcreteColumnExpression(innerProjection, tableReferenceExpression); + if (column1.IsNullable + || column2.IsNullable) + { + outerColumn = outerColumn.MakeNullable(); + } + + propertyExpressions[property] = outerColumn; + + // Lift up any identifier columns to the set operation result (the outer). + // This is typically the entity primary key columns, but can also be all of a complex type's properties if Distinct + // was previously called. + if (outerIdentifiers.Length > 0) + { + var index = select1._identifier.FindIndex(e => e.Column.Equals(column1)); + if (index != -1) + { + if (select2._identifier[index].Column.Equals(column2)) + { + outerIdentifiers[index] = outerColumn; + } + else + { + // If select1 matched but select2 did not then we erase all identifiers + // TODO: We could make this little more robust by allow the indexes to be different. See issue#24475 + // i.e. Identifier ordering being different. + outerIdentifiers = Array.Empty(); + } + } + // If the top-level projection - not the current nested one - is a complex type and not an entity type, then add + // all its columns to the "otherExpressions" list (i.e. columns not part of a an entity primary key). This is + // the same as with a non-structural type projection. + else if (projection1.StructuralType is IComplexType) + { + var outerTypeMapping = column1.TypeMapping ?? column1.TypeMapping; + if (outerTypeMapping == null) + { + throw new InvalidOperationException( + RelationalStrings.SetOperationsRequireAtLeastOneSideWithValidTypeMapping(setOperationType)); + } + + otherExpressions.Add((outerColumn, outerTypeMapping.KeyComparer)); + } + } + } + + foreach (var complexProperty in GetAllComplexPropertiesInHierarchy(type)) + { + var complexPropertyShaper1 = structuralProjection1.BindComplexProperty(complexProperty); + var complexPropertyShaper2 = structuralProjection2.BindComplexProperty(complexProperty); + + var resultComplexProjection = ProcessStructuralType( + (StructuralTypeProjectionExpression)complexPropertyShaper1.ValueBufferExpression, + (StructuralTypeProjectionExpression)complexPropertyShaper2.ValueBufferExpression); + + var resultComplexShaper = new RelationalStructuralTypeShaperExpression( + complexProperty.ComplexType, + resultComplexProjection, + resultComplexProjection.IsNullable); + + complexPropertyCache[complexProperty] = resultComplexShaper; + } + + Check.DebugAssert( + structuralProjection1.TableMap.Count == structuralProjection2.TableMap.Count, + "Set operation over entity projections with different table map counts"); + Check.DebugAssert( + structuralProjection1.TableMap.Keys.All(t => structuralProjection2.TableMap.ContainsKey(t)), + "Set operation over entity projections with table map discrepancy"); + + var tableMap = structuralProjection1.TableMap.ToDictionary(kvp => kvp.Key, _ => tableReferenceExpression); + + var discriminatorExpression = structuralProjection1.DiscriminatorExpression; + if (structuralProjection1.DiscriminatorExpression != null + && structuralProjection2.DiscriminatorExpression != null) + { + var alias = GenerateUniqueColumnAlias(DiscriminatorColumnAlias); + var innerProjection = new ProjectionExpression(structuralProjection1.DiscriminatorExpression, alias); + select1._projection.Add(innerProjection); + select2._projection.Add(new ProjectionExpression(structuralProjection2.DiscriminatorExpression, alias)); + discriminatorExpression = new ConcreteColumnExpression(innerProjection, tableReferenceExpression); + } + + var outerProjection = new StructuralTypeProjectionExpression( + type, propertyExpressions, complexPropertyCache, tableMap, nullable: false, discriminatorExpression); + + if (outerIdentifiers.Length > 0 && outerProjection is { StructuralType: IEntityType entityType }) + { + var primaryKey = entityType.FindPrimaryKey(); + + // We know that there are existing identifiers (see condition above); we know we must have a key since a keyless + // entity type would have wiped the identifiers when generating the join. + Check.DebugAssert(primaryKey != null, "primary key is null."); + foreach (var property in primaryKey.Properties) + { + entityProjectionIdentifiers.Add(outerProjection.BindProperty(property)); + entityProjectionValueComparers.Add(property.GetKeyValueComparer()); + } + } + + return outerProjection; } } - - _projectionMapping[projectionMember] = outerProjection; } string GenerateUniqueColumnAlias(string baseAlias) @@ -3201,9 +3330,18 @@ public static StructuralTypeShaperExpression GenerateComplexPropertyShaperExpres var propertyExpressionMap = new Dictionary(); // We do not support complex type splitting, so we will only ever have a single table/view mapping to it. + // See Issue #32853 and Issue #31248 var complexTypeTable = complexProperty.ComplexType.GetViewOrTableMappings().Single().Table; - var tableReferenceExpression = containerProjection.TableMap[complexTypeTable]; - + TableReferenceExpression? tableReferenceExpression; + if (RelationalModel.UseOldBehavior32699) + { + tableReferenceExpression = containerProjection.TableMap[complexTypeTable]; + } + else if (!containerProjection.TableMap.TryGetValue(complexTypeTable, out tableReferenceExpression)) + { + complexTypeTable = complexProperty.ComplexType.GetDefaultMappings().Single().Table; + tableReferenceExpression = containerProjection.TableMap[complexTypeTable]; + } var isComplexTypeNullable = containerProjection.IsNullable || complexProperty.IsNullable; // If the complex property is declared on a type that's derived relative to the type being projected, the projected column is @@ -4175,32 +4313,67 @@ StructuralTypeProjectionExpression LiftEntityProjectionFromSubquery( TableReferenceExpression subqueryTableReference) { var propertyExpressions = new Dictionary(); + var complexPropertyCache = new Dictionary(); + + if (UseOldBehavior32911) + { + HandleTypeProjection(projection); + + void HandleTypeProjection(StructuralTypeProjectionExpression typeProjection) + { + foreach (var property in GetAllPropertiesInHierarchy(typeProjection.StructuralType)) + { + // json entity projection (i.e. JSON entity that was transformed into query root) may have synthesized keys + // but they don't correspond to any columns - we need to skip those + if (typeProjection is { StructuralType: IEntityType entityType } + && entityType.IsMappedToJson() + && property.IsOrdinalKeyProperty()) + { + continue; + } - HandleTypeProjection(projection); + var innerColumn = typeProjection.BindProperty(property); + var outerColumn = subquery.GenerateOuterColumn(subqueryTableReferenceExpression, innerColumn); + projectionMap[innerColumn] = outerColumn; + propertyExpressions[property] = outerColumn; + } - void HandleTypeProjection(StructuralTypeProjectionExpression typeProjection) + foreach (var complexProperty in GetAllComplexPropertiesInHierarchy(typeProjection.StructuralType)) + { + HandleTypeProjection( + (StructuralTypeProjectionExpression)typeProjection.BindComplexProperty(complexProperty).ValueBufferExpression); + } + } + } + else { - foreach (var property in GetAllPropertiesInHierarchy(typeProjection.StructuralType)) + foreach (var property in GetAllPropertiesInHierarchy(projection.StructuralType)) { // json entity projection (i.e. JSON entity that was transformed into query root) may have synthesized keys // but they don't correspond to any columns - we need to skip those - if (typeProjection is { StructuralType: IEntityType entityType } + if (projection is { StructuralType: IEntityType entityType } && entityType.IsMappedToJson() && property.IsOrdinalKeyProperty()) { continue; } - var innerColumn = typeProjection.BindProperty(property); - var outerColumn = subquery.GenerateOuterColumn(subqueryTableReferenceExpression, innerColumn); + var innerColumn = projection.BindProperty(property); + var outerColumn = subquery.GenerateOuterColumn(subqueryTableReference, innerColumn); + projectionMap[innerColumn] = outerColumn; propertyExpressions[property] = outerColumn; } - foreach (var complexProperty in GetAllComplexPropertiesInHierarchy(typeProjection.StructuralType)) + foreach (var complexProperty in GetAllComplexPropertiesInHierarchy(projection.StructuralType)) { - HandleTypeProjection( - (StructuralTypeProjectionExpression)typeProjection.BindComplexProperty(complexProperty).ValueBufferExpression); + var complexPropertyShaper = projection.BindComplexProperty(complexProperty); + + var complexTypeProjectionExpression = LiftEntityProjectionFromSubquery( + (StructuralTypeProjectionExpression)complexPropertyShaper.ValueBufferExpression, + subqueryTableReference); + + complexPropertyCache[complexProperty] = complexPropertyShaper.Update(complexTypeProjectionExpression); } } @@ -4214,8 +4387,11 @@ void HandleTypeProjection(StructuralTypeProjectionExpression typeProjection) var tableMap = projection.TableMap.ToDictionary(kvp => kvp.Key, _ => subqueryTableReference); - var newEntityProjection = new StructuralTypeProjectionExpression( - projection.StructuralType, propertyExpressions, tableMap, nullable: false, discriminatorExpression); + var newEntityProjection = UseOldBehavior32911 + ? new StructuralTypeProjectionExpression( + projection.StructuralType, propertyExpressions, tableMap, nullable: false, discriminatorExpression) + : new StructuralTypeProjectionExpression( + projection.StructuralType, propertyExpressions, complexPropertyCache, tableMap, nullable: false, discriminatorExpression); if (projection.StructuralType is IEntityType entityType2) { diff --git a/src/EFCore.Relational/Query/StructuralTypeProjectionExpression.cs b/src/EFCore.Relational/Query/StructuralTypeProjectionExpression.cs index 828a7c95b2b..5b9c7dd152d 100644 --- a/src/EFCore.Relational/Query/StructuralTypeProjectionExpression.cs +++ b/src/EFCore.Relational/Query/StructuralTypeProjectionExpression.cs @@ -17,6 +17,9 @@ namespace Microsoft.EntityFrameworkCore.Query; /// public class StructuralTypeProjectionExpression : Expression { + private static readonly bool UseOldBehavior32911 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32911", out var enabled32911) && enabled32911; + private readonly IReadOnlyDictionary _propertyExpressionMap; private readonly Dictionary _ownedNavigationMap; private Dictionary? _complexPropertyCache; @@ -38,6 +41,32 @@ public StructuralTypeProjectionExpression( type, propertyExpressionMap, new Dictionary(), + null, + tableMap, + nullable, + discriminatorExpression) + { + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public StructuralTypeProjectionExpression( + ITypeBase type, + IReadOnlyDictionary propertyExpressionMap, + Dictionary complexPropertyCache, + IReadOnlyDictionary tableMap, + bool nullable = false, + SqlExpression? discriminatorExpression = null) + : this( + type, + propertyExpressionMap, + new Dictionary(), + complexPropertyCache, tableMap, nullable, discriminatorExpression) @@ -48,6 +77,7 @@ private StructuralTypeProjectionExpression( ITypeBase type, IReadOnlyDictionary propertyExpressionMap, Dictionary ownedNavigationMap, + Dictionary? complexPropertyCache, IReadOnlyDictionary tableMap, bool nullable, SqlExpression? discriminatorExpression = null) @@ -55,6 +85,7 @@ private StructuralTypeProjectionExpression( StructuralType = type; _propertyExpressionMap = propertyExpressionMap; _ownedNavigationMap = ownedNavigationMap; + _complexPropertyCache = complexPropertyCache; TableMap = tableMap; IsNullable = nullable; DiscriminatorExpression = discriminatorExpression; @@ -115,6 +146,21 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) propertyExpressionMap[property] = newExpression; } + var complexPropertyCache = default(Dictionary); + if (!UseOldBehavior32911) + { + if (_complexPropertyCache != null) + { + complexPropertyCache = new(); + foreach (var (complexProperty, complexShaper) in _complexPropertyCache) + { + var newComplexShaper = (StructuralTypeShaperExpression)visitor.Visit(complexShaper); + changed |= complexShaper != newComplexShaper; + complexPropertyCache[complexProperty] = newComplexShaper; + } + } + } + // We only need to visit the table map since TableReferenceUpdatingExpressionVisitor may need to modify it; it mutates // TableReferenceExpression (a new TableReferenceExpression is never returned), so we never need a new table map. foreach (var (_, tableExpression) in TableMap) @@ -136,7 +182,7 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) return changed ? new StructuralTypeProjectionExpression( - StructuralType, propertyExpressionMap, ownedNavigationMap, TableMap, IsNullable, discriminatorExpression) + StructuralType, propertyExpressionMap, ownedNavigationMap, complexPropertyCache, TableMap, IsNullable, discriminatorExpression) : this; } @@ -159,6 +205,19 @@ public virtual StructuralTypeProjectionExpression MakeNullable() discriminatorExpression = ce.MakeNullable(); } + var complexPropertyCache = default(Dictionary); + if (!UseOldBehavior32911) + { + if (_complexPropertyCache != null) + { + complexPropertyCache = new(); + foreach (var (complexProperty, complexShaper) in _complexPropertyCache) + { + complexPropertyCache[complexProperty] = complexShaper.MakeNullable(); + } + } + } + var ownedNavigationMap = new Dictionary(); foreach (var (navigation, shaper) in _ownedNavigationMap) { @@ -178,6 +237,7 @@ public virtual StructuralTypeProjectionExpression MakeNullable() StructuralType, propertyExpressionMap, ownedNavigationMap, + complexPropertyCache, TableMap, nullable: true, discriminatorExpression); @@ -212,6 +272,23 @@ public virtual StructuralTypeProjectionExpression UpdateEntityType(IEntityType d } } + var complexPropertyCache = default(Dictionary); + if (!UseOldBehavior32911) + { + if (_complexPropertyCache != null) + { + complexPropertyCache = new(); + foreach (var (complexProperty, complexShaper) in _complexPropertyCache) + { + if (derivedType.IsAssignableFrom(complexProperty.DeclaringType) + || complexProperty.DeclaringType.IsAssignableFrom(derivedType)) + { + complexPropertyCache[complexProperty] = complexShaper; + } + } + } + } + var ownedNavigationMap = new Dictionary(); foreach (var (navigation, entityShaperExpression) in _ownedNavigationMap) { @@ -263,7 +340,7 @@ public virtual StructuralTypeProjectionExpression UpdateEntityType(IEntityType d } return new StructuralTypeProjectionExpression( - derivedType, propertyExpressionMap, ownedNavigationMap, newTableMap ?? TableMap, IsNullable, discriminatorExpression); + derivedType, propertyExpressionMap, ownedNavigationMap, complexPropertyCache, newTableMap ?? TableMap, IsNullable, discriminatorExpression); } /// diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs index 32391ba4ab2..260b65b7e31 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs @@ -151,7 +151,7 @@ public RelationalTypeMappingInfo( int? scale) { // Note: Empty string is allowed for store type name because SQLite - _coreTypeMappingInfo = new TypeMappingInfo(null, null, false, unicode, size, null, precision, scale); + _coreTypeMappingInfo = new TypeMappingInfo(null, null, false, unicode, size, null, precision, scale, false); StoreTypeName = storeTypeName; StoreTypeNameBase = storeTypeNameBase; IsFixedLength = null; @@ -225,6 +225,42 @@ public RelationalTypeMappingInfo( /// Specifies a precision for the mapping, or for default. /// Specifies a scale for the mapping, or for default. /// The suggested , or for default. + [Obsolete("Use overload that takes 'key' parameter.")] + public RelationalTypeMappingInfo( + Type? type, + RelationalTypeMapping? elementTypeMapping, + string? storeTypeName, + string? storeTypeNameBase, + bool keyOrIndex, + bool? unicode, + int? size, + bool? rowVersion, + bool? fixedLength, + int? precision, + int? scale, + DbType? dbType) + : this( + type, elementTypeMapping, storeTypeName, storeTypeNameBase, keyOrIndex, unicode, size, rowVersion, fixedLength, + precision, scale, dbType, keyOrIndex) + { + } + + /// + /// Creates a new instance of . + /// + /// The CLR type in the model for which mapping is needed. + /// The type mapping for elements, if known. + /// The database type name. + /// The provider-specific relational type name, with any facets removed. + /// If , then a special mapping for a key or index may be returned. + /// Specifies Unicode or ANSI mapping, or for default. + /// Specifies a size for the mapping, or for default. + /// Specifies a row-version, or for default. + /// Specifies a fixed length mapping, or for default. + /// Specifies a precision for the mapping, or for default. + /// Specifies a scale for the mapping, or for default. + /// The suggested , or for default. + /// If , then a special mapping for a key may be returned. public RelationalTypeMappingInfo( Type? type = null, RelationalTypeMapping? elementTypeMapping = null, @@ -237,9 +273,10 @@ public RelationalTypeMappingInfo( bool? fixedLength = null, int? precision = null, int? scale = null, - DbType? dbType = null) + DbType? dbType = null, + bool key = false) { - _coreTypeMappingInfo = new TypeMappingInfo(type, elementTypeMapping, keyOrIndex, unicode, size, rowVersion, precision, scale); + _coreTypeMappingInfo = new TypeMappingInfo(type, elementTypeMapping, keyOrIndex, unicode, size, rowVersion, precision, scale, key); IsFixedLength = fixedLength; StoreTypeName = storeTypeName; @@ -301,7 +338,16 @@ public int? Scale public DbType? DbType { get; init; } /// - /// Indicates whether or not the mapping is part of a key or index. + /// Indicates whether or not the mapping is part of a key or foreign key. + /// + public bool IsKey + { + get => _coreTypeMappingInfo.IsKey; + init => _coreTypeMappingInfo = _coreTypeMappingInfo with { IsKey = value }; + } + + /// + /// Indicates whether or not the mapping is part of a key, foreign key, or index. /// public bool IsKeyOrIndex { diff --git a/src/EFCore.SqlServer.Abstractions/PACKAGE.md b/src/EFCore.SqlServer.Abstractions/PACKAGE.md new file mode 100644 index 00000000000..90452191d3a --- /dev/null +++ b/src/EFCore.SqlServer.Abstractions/PACKAGE.md @@ -0,0 +1,13 @@ +`Microsoft.EntityFrameworkCore.SqlServer.Abstractions` is a small package containing abstractions which may be useful for applications in places where a dependency on the full `Microsoft.EntityFrameworkCore.SqlServer` is not desirable. + +## Usage + +This package is included automatically as a dependency of the main [Microsoft.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.SqlServer) package. Usually, the abstractions package is only explicitly installed in places where it is undesirable to use the main package. For example, it can be installed to use `HierarchyId` in POCO entity types which are otherwise independent of EF Core. + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). diff --git a/src/EFCore.SqlServer.HierarchyId/PACKAGE.md b/src/EFCore.SqlServer.HierarchyId/PACKAGE.md new file mode 100644 index 00000000000..7a9fb3bdc9b --- /dev/null +++ b/src/EFCore.SqlServer.HierarchyId/PACKAGE.md @@ -0,0 +1,22 @@ +`Microsoft.EntityFrameworkCore.SqlServer.HierarchyId` enables use of [hierarchical data for SQL Server and Azure SQL]() with [Entity Framework Core](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore/) and [NetTopologySuite](https://www.nuget.org/packages/NetTopologySuite/). + +## Usage + +Call `UseHierarchyId` inside the call to `UseSqServer` when configuring the SQLite database provider for your `DbContext`. For example: + +```csharp +protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => options.UseSqlServer( + "Server=localhost;Database=MyDatabase;Trusted_Connection=True;", + b => b.HierarchyId()); +``` + +See [_Hierarchical Data in the SQL Server EF Core Provider_](https://learn.microsoft.com/ef/core/providers/sql-server/hierarchyid) for more information on `HierarchyId` with EF Core. + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). diff --git a/src/EFCore.SqlServer.NTS/PACKAGE.md b/src/EFCore.SqlServer.NTS/PACKAGE.md new file mode 100644 index 00000000000..7491cd864a2 --- /dev/null +++ b/src/EFCore.SqlServer.NTS/PACKAGE.md @@ -0,0 +1,25 @@ +`Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite` enables use of spatial data for SQL Server and Azure SQL with [Entity Framework Core](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore/) and [NetTopologySuite](https://www.nuget.org/packages/NetTopologySuite/). + +## Usage + +Call `UseNetTopologySuite` inside the call to `UseSqServer` when configuring the SQLite database provider for your `DbContext`. For example: + +```csharp +protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => options.UseSqlServer( + "Server=localhost;Database=MyDatabase;Trusted_Connection=True;", + b => b.UseNetTopologySuite()); +``` + +For more information on using spatial data with EF Core and SQLite, see: + +- [Spatial Data in EF Core](https://learn.microsoft.com/ef/core/modeling/spatial) +- [Spatial Data in the SQL Server EF Core Provider](https://learn.microsoft.com/ef/core/providers/sql-server/spatial) + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). \ No newline at end of file diff --git a/src/EFCore.SqlServer/EFCore.SqlServer.csproj b/src/EFCore.SqlServer/EFCore.SqlServer.csproj index ad1cdb7d882..339e8774d57 100644 --- a/src/EFCore.SqlServer/EFCore.SqlServer.csproj +++ b/src/EFCore.SqlServer/EFCore.SqlServer.csproj @@ -48,7 +48,7 @@ - + diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerOnDeleteConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerOnDeleteConvention.cs index d250176508b..6a8fff3c479 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerOnDeleteConvention.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerOnDeleteConvention.cs @@ -18,6 +18,9 @@ public class SqlServerOnDeleteConvention : CascadeDeleteConvention, ISkipNavigationForeignKeyChangedConvention, IEntityTypeAnnotationChangedConvention { + private static readonly bool UseOldBehavior32732 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32732", out var enabled32732) && enabled32732; + /// /// Creates a new instance of . /// @@ -68,7 +71,8 @@ protected override DeleteBehavior GetTargetDeleteBehavior(IConventionForeignKey s => s.Inverse != null && IsMappedToSameTable(s.DeclaringEntityType, s.TargetEntityType)); - if (skipNavigation != null) + if (skipNavigation != null + && (UseOldBehavior32732 || skipNavigation.ForeignKey != null)) { var isFirstSkipNavigation = IsFirstSkipNavigation(skipNavigation); if (!isFirstSkipNavigation) diff --git a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs index 1119544f93a..f4192293658 100644 --- a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs +++ b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs @@ -32,6 +32,9 @@ public class SqlServerMigrationsSqlGenerator : MigrationsSqlGenerator private static readonly bool UseOldBehavior32457 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32457", out var enabled32457) && enabled32457; + private static readonly bool UseOldBehavior32730 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32730", out var enabled32730) && enabled32730; + private IReadOnlyList _operations = null!; private int _variableCounter; @@ -1415,17 +1418,18 @@ protected override void Generate(SqlOperation operation, IModel? model, Migratio var batchBuilder = new StringBuilder(); foreach (var line in preBatched) { - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - var trimmed = line.TrimStart(); if (trimmed.StartsWith("GO", StringComparison.OrdinalIgnoreCase) && (UseOldBehavior32457 || trimmed.Length == 2 || char.IsWhiteSpace(trimmed[2]))) { + if (UseOldBehavior32730 + && string.IsNullOrWhiteSpace(line)) + { + continue; + } + var batch = batchBuilder.ToString(); batchBuilder.Clear(); diff --git a/src/EFCore.SqlServer/PACKAGE.md b/src/EFCore.SqlServer/PACKAGE.md new file mode 100644 index 00000000000..4cd837f73e9 --- /dev/null +++ b/src/EFCore.SqlServer/PACKAGE.md @@ -0,0 +1,24 @@ +`Microsoft.EntityFrameworkCore.SqlServer` is the EF Core database provider package for Microsoft SQL Server and Azure SQL. + +## Usage + +Call the `UseSqlServer` method to choose the SQL Server/Azure SQL database provider for your `DbContext`. For example: + +```csharp +protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) +{ + optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=MyDatabase;Trusted_Connection=True;"); +} +``` + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Additional documentation + +See [Microsoft SQL Server EF Core Database Provider](https://learn.microsoft.com/ef/core/providers/sql-server/) for more information about the features of this database provider. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). \ No newline at end of file diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerJsonPostprocessor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerJsonPostprocessor.cs index 4baa71272c9..28c74f76cbc 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerJsonPostprocessor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerJsonPostprocessor.cs @@ -32,6 +32,9 @@ private readonly private RelationalTypeMapping? _nvarcharMaxTypeMapping; + private static readonly bool UseOldBehavior32976 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32976", out var enabled32976) && enabled32976; + /// /// 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 @@ -70,7 +73,14 @@ public virtual Expression Process(Expression expression) switch (expression) { case ShapedQueryExpression shapedQueryExpression: - return shapedQueryExpression.UpdateQueryExpression(Visit(shapedQueryExpression.QueryExpression)); + shapedQueryExpression = shapedQueryExpression.UpdateQueryExpression(Visit(shapedQueryExpression.QueryExpression)); + + if (!UseOldBehavior32976) + { + shapedQueryExpression = shapedQueryExpression.UpdateShaperExpression(Visit(shapedQueryExpression.ShaperExpression)); + } + + return shapedQueryExpression; case SelectExpression selectExpression: { diff --git a/src/EFCore.SqlServer/README.md b/src/EFCore.SqlServer/README.md deleted file mode 100644 index e1540d20a2b..00000000000 --- a/src/EFCore.SqlServer/README.md +++ /dev/null @@ -1,35 +0,0 @@ -Microsoft.EntityFrameworkCore.SqlServer is the database provider for Microsoft SQL Server and Azure SQL. This providers allows you to use Entity Framework Core with Microsoft SQL Server and Azure SQL databases. - -## Getting started - -`Microsoft.EntityFrameworkCore.SqlServer` is the EF Core provider for Microsoft SQL Server and Azure SQL. See [Getting Started](https://learn.microsoft.com/ef/core/modeling/#use-fluent-api-to-configure-a-model) for more information. - -### Prerequisites - -- Supported database Engines: Microsoft SQL Server (2012 onwards) -- The provider references Microsoft.Data.SqlClient (not System.Data.SqlClient). If your project takes a direct dependency on SqlClient, make sure it references the Microsoft.Data.SqlClient package. - -## Usage - -Once you've installed the package, you can use it in your Entity Framework Core application by specifying the SQL Server provider in your DbContext's OnConfiguring method: - -```csharp -protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) -{ - optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=MyDatabase;Trusted_Connection=True;"); -} -``` - -In this example, we're using the (localdb)\mssqllocaldb server with the MyDatabase database. You'll need to adjust the connection string to match your own SQL Server instance and database. - -## Features - -The SQL Server provider supports all common features of [Entity Framework Core](https://learn.microsoft.com/ef/core/) as well as some [SQL Server-specific features](https://learn.microsoft.com/ef/core/providers/sql-server/?tabs=dotnet-core-cli) including temporal tables and memory-optimized tables. - -## Additional documentation - -For more information on using the SQL Server provider for Entity Framework Core, you can refer to the official [documentation](https://learn.microsoft.com/en-us/ef/core/providers/sql-server/?tabs=dotnet-core-cli). - -## Feedback - -If you encounter a bug or would like to request a feature, [submit an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). \ No newline at end of file diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerDatabaseCreator.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerDatabaseCreator.cs index c85be77e61c..589440790a1 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerDatabaseCreator.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerDatabaseCreator.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.ComponentModel; using System.Transactions; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore.SqlServer.Internal; @@ -322,7 +323,8 @@ private bool RetryOnExistsFailure(SqlException exception) // Microsoft.Data.SqlClient.SqlException: Unable to open the physical file xxxxxxx. // And (Number 18456) // Microsoft.Data.SqlClient.SqlException: Login failed for user 'xxxxxxx'. - if (exception.Number is 233 or -2 or 4060 or 1832 or 5120 or 18456) + if ((exception.Number is 203 && exception.InnerException is Win32Exception) + || (exception.Number is 233 or -2 or 4060 or 1832 or 5120 or 18456)) { ClearPool(); return true; diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerTransientExceptionDetector.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerTransientExceptionDetector.cs index 766d0c0504d..6b11941e6b3 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerTransientExceptionDetector.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerTransientExceptionDetector.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.ComponentModel; using Microsoft.Data.SqlClient; namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; @@ -670,6 +671,16 @@ public static bool ShouldRetryOn(Exception? ex) // allowed connections) on the server. (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by // the remote host.) case 233: + return true; + // SQL Error Code: 203 + // A connection was successfully established with the server, but then an error occurred during the pre-login handshake. + // (provider: TCP Provider, error: 0 - 20) ---> System.ComponentModel.Win32Exception (203): Unknown error: 203 + case 203: + if (ex.InnerException is Win32Exception) + { + return true; + } + continue; // SQL Error Code: 121 // The semaphore timeout period has expired case 121: diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs index a736f5e9053..d3b01e5bdc4 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs @@ -15,6 +15,9 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; /// public class SqlServerTypeMappingSource : RelationalTypeMappingSource { + private static readonly bool UseOldBehavior32898 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32898", out var enabled32898) && enabled32898; + private static readonly SqlServerFloatTypeMapping RealAlias = new("placeholder", storeTypePostfix: StoreTypePostfix.None); @@ -338,7 +341,7 @@ public SqlServerTypeMappingSource( size: size, fixedLength: isFixedLength, storeTypePostfix: storeTypeName == null ? StoreTypePostfix.Size : StoreTypePostfix.None, - useKeyComparison: mappingInfo.IsKeyOrIndex); + useKeyComparison: UseOldBehavior32898 ? mappingInfo.IsKeyOrIndex : mappingInfo.IsKey); } if (clrType == typeof(byte[])) diff --git a/src/EFCore.Sqlite.Core/PACKAGE.md b/src/EFCore.Sqlite.Core/PACKAGE.md new file mode 100644 index 00000000000..e5b96b6b252 --- /dev/null +++ b/src/EFCore.Sqlite.Core/PACKAGE.md @@ -0,0 +1,21 @@ +The `Microsoft.EntityFrameworkCore.Sqlite.Core` package contains the code for the SQLite EF Core database provider. However, it does not automatically bring in any SQLite native binary, instead requiring that the application install and initialize the binary to use. + +## Usage + +Only use this package if you need to change to a different SQLite native binary that the one supplied by [Microsoft.EntityFrameworkCore.Sqlite](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Sqlite). + +To use this "Core" package, also install a [SQLite binary package](https://www.nuget.org/profiles/SQLitePCLRaw) and initialize it with `SQLitePCL.Batteries_V2.Init();` or similar. See [github.com/ericsink/SQLitePCL.raw](https://github.com/ericsink/SQLitePCL.raw) for more information. + +Following this, call `UseSqlite` just as you when using [Microsoft.EntityFrameworkCore.Sqlite](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Sqlite). + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Additional documentation + +See [SQLite EF Core Database Provider](https://learn.microsoft.com/ef/core/providers/sqlite/) for more information about the features of this database provider. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). \ No newline at end of file diff --git a/src/EFCore.Sqlite.NTS/PACKAGE.md b/src/EFCore.Sqlite.NTS/PACKAGE.md new file mode 100644 index 00000000000..3e6ee1c35a0 --- /dev/null +++ b/src/EFCore.Sqlite.NTS/PACKAGE.md @@ -0,0 +1,23 @@ +`Microsoft.EntityFrameworkCore.Sqlite.NetTopologySuite` enables use of [SpatiaLite spatial data for SQLite](https://www.gaia-gis.it/fossil/libspatialite/index) with [Entity Framework Core](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore/) and [NetTopologySuite](https://www.nuget.org/packages/NetTopologySuite/). + +## Usage + +Call `UseNetTopologySuite` inside the call to `UseSqlite` when configuring the SQLite database provider for your `DbContext`. For example: + +```csharp +protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => options.UseSqlite("Data Source=spatialdata.dat", b => b.UseNetTopologySuite()); +``` + +For more information on using spatial data with EF Core and SQLite, see: + +- [Spatial Data in EF Core](https://learn.microsoft.com/ef/core/modeling/spatial) +- [Spatial Data in the SQLite EF Core Provider](https://learn.microsoft.com/ef/core/providers/sqlite/spatial) + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). \ No newline at end of file diff --git a/src/EFCore.Sqlite/PACKAGE.md b/src/EFCore.Sqlite/PACKAGE.md new file mode 100644 index 00000000000..27712755653 --- /dev/null +++ b/src/EFCore.Sqlite/PACKAGE.md @@ -0,0 +1,24 @@ +`Microsoft.EntityFrameworkCore.Sqlite` is the EF Core database provider package for SQLite. + +## Usage + +Call the `UseSqlite` method to choose the SQLite database provider for your `DbContext`. For example: + +```csharp +protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) +{ + optionsBuilder.UseSqlite("Data Source=databse.dat"); +} +``` + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Additional documentation + +See [SQLite EF Core Database Provider](https://learn.microsoft.com/ef/core/providers/sqlite/) for more information about the features of this database provider. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). \ No newline at end of file diff --git a/src/EFCore.Templates/PACKAGE.md b/src/EFCore.Templates/PACKAGE.md new file mode 100644 index 00000000000..4a6aa837842 --- /dev/null +++ b/src/EFCore.Templates/PACKAGE.md @@ -0,0 +1,25 @@ +The `Microsoft.EntityFrameworkCore.Templates` package contains T4 templates for scaffolding (reverse engineering) a `DbContext` and entity types from an existing database. + +## Usage + +First, install the templates NuGet package: + +``` +dotnet new install Microsoft.EntityFrameworkCore.Templates +``` + +Next, can templates to your project: + +```dotnetcli +dotnet new ef-templates +``` + +See [_Custom Reverse Engineering Templates_](https://learn.microsoft.com/ef/core/managing-schemas/scaffolding/templates) for more information on using T4 templates with EF Core. + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). diff --git a/src/EFCore.Tools/EFCore.Tools.nuspec b/src/EFCore.Tools/EFCore.Tools.nuspec index 23c5e9edff5..fcc3a5c9448 100644 --- a/src/EFCore.Tools/EFCore.Tools.nuspec +++ b/src/EFCore.Tools/EFCore.Tools.nuspec @@ -9,9 +9,11 @@ + docs\PACKAGE.md $CommonFileElements$ + diff --git a/src/EFCore.Tools/PACKAGE.md b/src/EFCore.Tools/PACKAGE.md new file mode 100644 index 00000000000..b8c900eb63d --- /dev/null +++ b/src/EFCore.Tools/PACKAGE.md @@ -0,0 +1,38 @@ +The Entity Framework Core tools help with design-time development tasks. They're primarily used to manage Migrations and to scaffold a `DbContext` and entity types by reverse engineering the schema of a database. + +This package, `Microsoft.EntityFrameworkCore.Tools` is for PowerShell tooling that works in the Visual Studio Package Manager Console (PMC). + +## Usage + +Install the tools package by running the following in the Visual Studio PMC: + +```powershell +Install-Package Microsoft.EntityFrameworkCore.Tools +``` + +The available commands are listed in the following table. + +| PMC Command | Usage | +|--------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------| +| [Add-Migration](https://learn.microsoft.com/ef/core/cli/powershell#add-migration) | Adds a new migration. | +| [Bundle-Migration](https://learn.microsoft.com/ef/core/cli/powershell#bundle-migration) | Creates an executable to update the database. | +| [Drop-Database](https://learn.microsoft.com/ef/core/cli/powershell#drop-database) | Drops the database. | +| [Get-DbContext](https://learn.microsoft.com/ef/core/cli/powershell#get-dbcontext) | Gets information about a `DbContext` type. | +| [Get-Help EntityFramework](https://learn.microsoft.com/en-us/ef/core/cli/powershell#common-parameters) | Displays information about Entity Framework commands. | +| [Get-Migration](https://learn.microsoft.com/ef/core/cli/powershell#get-migration) | Lists available migrations. | +| [Optimize-DbContext](https://learn.microsoft.com/ef/core/cli/powershell#optimize-dbcontext) | Generates a compiled version of the model used by the `DbContext`. | +| [Remove-Migration](https://learn.microsoft.com/ef/core/cli/powershell#remove-migration) | Removes the last migration. | +| [Scaffold-DbContext](https://learn.microsoft.com/ef/core/cli/powershell#scaffold-dbcontext) | Generates a `DbContext` and entity type classes for a specified database. | +| [Script-DbContext](https://learn.microsoft.com/ef/core/cli/powershell#script-dbcontext) | Generates a SQL script from the `DbContext`. Bypasses any migrations. | +| [Script-Migration](https://learn.microsoft.com/ef/core/cli/powershell#script-migration) | Generates a SQL script from the migrations. | +| [Update-Database](https://learn.microsoft.com/ef/core/cli/powershell#update-database) | Updates the database to the last migration or to a specified migration. | + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). + + diff --git a/src/EFCore.Tools/README.md b/src/EFCore.Tools/README.md deleted file mode 100644 index a82cd1c5ef2..00000000000 --- a/src/EFCore.Tools/README.md +++ /dev/null @@ -1,41 +0,0 @@ -The Entity Framework Core tools help with design-time development tasks. They're primarily used to manage Migrations and to scaffold a DbContext and entity types by reverse engineering the schema of a database. -Microsoft.EntityFrameworkCore.Tools is for PowerShell tooling that works in the Visual Studio Package Manager Console. - -## Getting started - -The Package Manager Console (PMC) tools for Entity Framework Core perform design-time development tasks. For example, they create migrations, apply migrations, and generate code for a model based on an existing database. The commands run inside of Visual Studio using the Package Manager Console. These tools work with both .NET Framework and .NET Core projects. - -### Prerequisites - -Before using the tools: - -- [Understand the difference between target and startup project](https://learn.microsoft.com/en-us/ef/core/cli/powershell#target-and-startup-project). -- [Learn how to use the tools with .NET Standard class libraries](https://learn.microsoft.com/en-us/ef/core/cli/powershell#other-target-frameworks). -- [For ASP.NET Core projects, set the environment](https://learn.microsoft.com/en-us/ef/core/cli/powershell#aspnet-core-environment). - -## Usage - -PMC Command | Usage --- | -- -Get-Help entityframework |Displays information about entity framework commands. -[Add-Migration](https://learn.microsoft.com/en-us/ef/core/cli/powershell#add-migration) | Creates a migration by adding a migration snapshot. -[Bundle-Migration](https://learn.microsoft.com/en-us/ef/core/cli/powershell#bundle-migration) | Creates an executable to update the database. -[Get-DbContext](https://learn.microsoft.com/en-us/ef/core/cli/powershell#get-dbcontext) | Gets information about a DbContext type. -[Drop-Database](https://learn.microsoft.com/en-us/ef/core/cli/powershell#drop-database) | Drops the database. -[Get-Migration](https://learn.microsoft.com/en-us/ef/core/cli/powershell#get-migration) | Lists available migrations. -[Optimize-DbContext](https://learn.microsoft.com/en-us/ef/core/cli/powershell#optimize-dbcontext) | Generates a compiled version of the model used by the `DbContext`. -[Remove-Migration](https://learn.microsoft.com/en-us/ef/core/cli/powershell#remove-migration) | Removes the last migration snapshot. -[Scaffold-DbContext](https://learn.microsoft.com/en-us/ef/core/cli/powershell#scaffold-dbcontext) | Generates a DbContext and entity type classes for a specified database. This is called reverse engineering. -[Script-DbContext](https://learn.microsoft.com/en-us/ef/core/cli/powershell#script-dbcontext) | Generates a SQL script from the DbContext. Bypasses any migrations. -[Script-Migration](https://learn.microsoft.com/en-us/ef/core/cli/powershell#script-migration) | Generates a SQL script using all the migration snapshots. -[Update-Database](https://learn.microsoft.com/en-us/ef/core/cli/powershell#update-database) | Updates the database schema based on the last migration snapshot. - -## Additional documentation - -- [Migrations](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/) -- [Reverse Engineering](https://learn.microsoft.com/en-us/ef/core/managing-schemas/scaffolding/?tabs=dotnet-core-cli) -- [Compiled models](https://learn.microsoft.com/en-us/ef/core/performance/advanced-performance-topics?tabs=with-di%2Cwith-constant#compiled-models) - -## Feedback - -If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). \ No newline at end of file diff --git a/src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs b/src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs index 011bb336e01..b0b0b9692b6 100644 --- a/src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs +++ b/src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Query.Internal; namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; @@ -99,7 +100,7 @@ public override void SetValues(PropertyValues propertyValues) for (var i = 0; i < _values.Length; i++) { - SetValue(i, propertyValues[Properties[i].Name]); + SetValue(i, EntityMaterializerSource.UseOldBehavior32701 ? propertyValues[Properties[i].Name] : propertyValues[Properties[i]]); } } @@ -110,7 +111,9 @@ public override void SetValues(PropertyValues propertyValues) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override IReadOnlyList Properties - => _properties ??= EntityType.GetProperties().ToList(); + => _properties ??= EntityMaterializerSource.UseOldBehavior32701 + ? EntityType.GetProperties().ToList() + : EntityType.GetFlattenedProperties().ToList(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/EntryPropertyValues.cs b/src/EFCore/ChangeTracking/Internal/EntryPropertyValues.cs index 927f2e1c678..3d3d68b973e 100644 --- a/src/EFCore/ChangeTracking/Internal/EntryPropertyValues.cs +++ b/src/EFCore/ChangeTracking/Internal/EntryPropertyValues.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Query.Internal; namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; @@ -94,7 +95,8 @@ public override void SetValues(PropertyValues propertyValues) foreach (var property in Properties) { - SetValueInternal(property, propertyValues[property.Name]); + SetValueInternal( + property, EntityMaterializerSource.UseOldBehavior32701 ? propertyValues[property.Name] : propertyValues[property]); } } @@ -107,7 +109,9 @@ public override void SetValues(PropertyValues propertyValues) public override IReadOnlyList Properties { [DebuggerStepThrough] - get => _properties ??= EntityType.GetProperties().ToList(); + get => _properties ??= EntityMaterializerSource.UseOldBehavior32701 + ? EntityType.GetProperties().ToList() + : EntityType.GetFlattenedProperties().ToList(); } /// diff --git a/src/EFCore/Internal/EntityFinder.cs b/src/EFCore/Internal/EntityFinder.cs index 6e95ffababe..cc9d612a886 100644 --- a/src/EFCore/Internal/EntityFinder.cs +++ b/src/EFCore/Internal/EntityFinder.cs @@ -3,6 +3,7 @@ using System.Collections; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Query.Internal; namespace Microsoft.EntityFrameworkCore.Internal; @@ -859,17 +860,53 @@ private static Expression> BuildProjection(IEntityType en var entityParameter = Expression.Parameter(typeof(object), "e"); var projections = new List(); - foreach (var property in entityType.GetProperties()) + + if (EntityMaterializerSource.UseOldBehavior32701) { - projections.Add( - Expression.Convert( + foreach (var property in entityType.GetProperties()) + { + projections.Add( Expression.Convert( - Expression.Call( - EF.PropertyMethod.MakeGenericMethod(property.ClrType), - entityParameter, - Expression.Constant(property.Name, typeof(string))), - property.ClrType), - typeof(object))); + Expression.Convert( + Expression.Call( + EF.PropertyMethod.MakeGenericMethod(property.ClrType), + entityParameter, + Expression.Constant(property.Name, typeof(string))), + property.ClrType), + typeof(object))); + } + } + else + { + foreach (var property in entityType.GetFlattenedProperties()) + { + var path = new List { property }; + while (path[^1].DeclaringType is IComplexType complexType) + { + path.Add(complexType.ComplexProperty); + } + + Expression instanceExpression = entityParameter; + for (var i = path.Count - 1; i >= 0; i--) + { + instanceExpression = Expression.Call( + EF.PropertyMethod.MakeGenericMethod(path[i].ClrType), + instanceExpression, + Expression.Constant(path[i].Name, typeof(string))); + + if (i != 0 && instanceExpression.Type.IsValueType) + { + instanceExpression = Expression.Convert(instanceExpression, typeof(object)); + } + } + + projections.Add( + Expression.Convert( + Expression.Convert( + instanceExpression, + property.ClrType), + typeof(object))); + } } return Expression.Lambda>( diff --git a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs index 30476e2396e..fcdf6afcd26 100644 --- a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs +++ b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs @@ -4,6 +4,8 @@ // ReSharper disable ArgumentsStyleOther // ReSharper disable ArgumentsStyleNamedExpression +using Microsoft.EntityFrameworkCore.Query.Internal; + namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// @@ -309,7 +311,9 @@ public static IProperty CheckPropertyBelongsToType( { Check.NotNull(property, nameof(property)); - if ((property.DeclaringType as IEntityType)?.IsAssignableFrom(entityType) != true) + if ((EntityMaterializerSource.UseOldBehavior32701 + && (property.DeclaringType as IEntityType)?.IsAssignableFrom(entityType) != true) + || !property.DeclaringType.ContainingEntityType.IsAssignableFrom(entityType)) { throw new InvalidOperationException( CoreStrings.PropertyDoesNotBelong(property.Name, property.DeclaringType.DisplayName(), entityType.DisplayName())); diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index a8fcb4eee96..e88f9f4bef5 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.cs @@ -40,6 +40,9 @@ public class Property : PropertyBase, IMutableProperty, IConventionProperty, IPr public static readonly bool UseOldBehavior32422 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32422", out var enabled32422) && enabled32422; + private static readonly bool UseOldBehavior33176 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue33176", out var enabled33176) && enabled33176; + /// /// 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 @@ -911,9 +914,10 @@ public virtual (ValueConverter? ValueConverter, Type? ValueConverterType, Type? bool throwOnValueConverterConflict = true, bool throwOnProviderClrTypeConflict = true) { - Queue<(Property CurrentProperty, Property CycleBreakingPropert, int CyclePosition, int MaxCycleLength)>? queue = null; - (Property CurrentProperty, Property CycleBreakingPropert, int CyclePosition, int MaxCycleLength)? currentNode = + Queue<(Property CurrentProperty, Property CycleBreakingProperty, int CyclePosition, int MaxCycleLength)>? queue = null; + (Property CurrentProperty, Property CycleBreakingProperty, int CyclePosition, int MaxCycleLength)? currentNode = (this, this, 0, 2); + HashSet? visitedProperties = null; ValueConverter? valueConverter = null; Type? valueConverterType = null; @@ -922,12 +926,17 @@ public virtual (ValueConverter? ValueConverter, Type? ValueConverterType, Type? { var (property, cycleBreakingProperty, cyclePosition, maxCycleLength) = currentNode ?? queue!.Dequeue(); currentNode = null; - if (cyclePosition >= ForeignKey.LongestFkChainAllowedLength) + if (cyclePosition >= ForeignKey.LongestFkChainAllowedLength + || (!UseOldBehavior33176 + && queue is not null + && queue.Count >= ForeignKey.LongestFkChainAllowedLength)) { throw new InvalidOperationException( CoreStrings.RelationshipCycle(DeclaringType.DisplayName(), Name, "ValueConverter")); } + visitedProperties?.Add(property); + foreach (var foreignKey in property.GetContainingForeignKeys()) { for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) @@ -956,6 +965,13 @@ public virtual (ValueConverter? ValueConverter, Type? ValueConverterType, Type? { queue = new(); queue.Enqueue(currentNode.Value); + visitedProperties = new() { property }; + } + + if (!UseOldBehavior33176 + && visitedProperties?.Contains(principalProperty) == true) + { + break; } if (cyclePosition == maxCycleLength - 1) diff --git a/src/EFCore/README.md b/src/EFCore/PACKAGE.md similarity index 92% rename from src/EFCore/README.md rename to src/EFCore/PACKAGE.md index 8d763f3028d..cffa0337676 100644 --- a/src/EFCore/README.md +++ b/src/EFCore/PACKAGE.md @@ -64,10 +64,10 @@ public class Customer ## Additional documentation -- [Getting Started with Entity Framework Core](https://learn.microsoft.com/en-us/ef/core/get-started/overview/first-app?tabs=netcore-cli). -- Follow the [ASP.NET Core Tutorial](https://learn.microsoft.com/en-us/aspnet/core/data/ef-rp/intro?view=aspnetcore-7.0&tabs=visual-studio) to use EF Core in a web app. -- [Releases and planning(roadmap)](https://learn.microsoft.com/en-us/ef/core/what-is-new/) -- [How to write an EF Core provider](https://learn.microsoft.com/en-us/ef/core/providers/writing-a-provider) +- [Getting Started with Entity Framework Core](https://learn.microsoft.com/ef/core/get-started/overview/first-app). +- Follow the [ASP.NET Core Tutorial](https://learn.microsoft.com/aspnet/core/data/ef-rp/intro?view=aspnetcore-7.0&tabs=visual-studio) to use EF Core in a web app. +- [Releases and planning(roadmap)](https://learn.microsoft.com/ef/core/what-is-new/) +- [How to write an EF Core provider](https://learn.microsoft.com/ef/core/providers/writing-a-provider) ## Feedback diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index fd7a93208b0..8c9cd8e6df6 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -993,6 +993,12 @@ public static string EmptyComplexType(object? complexType) GetString("EmptyComplexType", nameof(complexType)), complexType); + /// + /// The empty string is not valid JSON. + /// + public static string EmptyJsonString + => GetString("EmptyJsonString"); + /// /// Cannot translate '{comparisonOperator}' on a subquery expression of entity type '{entityType}' because it has a composite primary key. See https://go.microsoft.com/fwlink/?linkid=2141942 for information on how to rewrite your query. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 45b45251ed8..e3f7f5d18d2 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -486,6 +486,9 @@ Complex type '{complexType}' has no properties defines. Configure at least one property or don't include this type in the model. + + The empty string is not valid JSON. + Cannot translate '{comparisonOperator}' on a subquery expression of entity type '{entityType}' because it has a composite primary key. See https://go.microsoft.com/fwlink/?linkid=2141942 for information on how to rewrite your query. diff --git a/src/EFCore/Query/Internal/EntityMaterializerSource.cs b/src/EFCore/Query/Internal/EntityMaterializerSource.cs index b1a72bc24c3..7c1d09a73ec 100644 --- a/src/EFCore/Query/Internal/EntityMaterializerSource.cs +++ b/src/EFCore/Query/Internal/EntityMaterializerSource.cs @@ -24,6 +24,9 @@ public class EntityMaterializerSource : IEntityMaterializerSource public static readonly bool UseOldBehavior31866 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue31866", out var enabled31866) && enabled31866; + internal static readonly bool UseOldBehavior32701 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32701", out var enabled32701) && enabled32701; + private static readonly MethodInfo InjectableServiceInjectedMethod = typeof(IInjectableService).GetMethod(nameof(IInjectableService.Injected))!; @@ -117,15 +120,16 @@ public Expression CreateMaterializeExpression( var constructorExpression = constructorBinding.CreateConstructorExpression(bindingInfo); - if (_materializationInterceptor == null) + if (_materializationInterceptor == null + // TODO: This currently applies the materialization interceptor only on the root structural type - any contained complex types + // don't get intercepted. + || (structuralType is not IEntityType && !UseOldBehavior32701)) { return properties.Count == 0 && blockExpressions.Count == 0 ? constructorExpression : CreateMaterializeExpression(blockExpressions, instanceVariable, constructorExpression, properties, bindingInfo); } - // TODO: This currently applies the materialization interceptor only on the root structural type - any contained complex types - // don't get intercepted. return CreateInterceptionMaterializeExpression( structuralType, properties, diff --git a/src/EFCore/Storage/Json/JsonValueReaderWriter.cs b/src/EFCore/Storage/Json/JsonValueReaderWriter.cs index 92e2c2f3021..2a085ebbfed 100644 --- a/src/EFCore/Storage/Json/JsonValueReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonValueReaderWriter.cs @@ -14,6 +14,9 @@ namespace Microsoft.EntityFrameworkCore.Storage.Json; /// public abstract class JsonValueReaderWriter { + private static readonly bool UseOldBehavior32896 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32896", out var enabled32896) && enabled32896; + /// /// Ensures the external types extend from the generic /// @@ -63,6 +66,12 @@ internal JsonValueReaderWriter() /// The read value. public object FromJsonString(string json, object? existingObject = null) { + if (!UseOldBehavior32896 + && string.IsNullOrWhiteSpace(json)) + { + throw new InvalidOperationException(CoreStrings.EmptyJsonString); + } + var readerManager = new Utf8JsonReaderManager(new JsonReaderData(Encoding.UTF8.GetBytes(json)), null); return FromJson(ref readerManager, existingObject); } diff --git a/src/EFCore/Storage/TypeMappingInfo.cs b/src/EFCore/Storage/TypeMappingInfo.cs index 5cec9557220..b98b109c860 100644 --- a/src/EFCore/Storage/TypeMappingInfo.cs +++ b/src/EFCore/Storage/TypeMappingInfo.cs @@ -182,7 +182,8 @@ public TypeMappingInfo( var property = principals[0]; ElementTypeMapping = property.GetElementType()?.FindTypeMapping(); - IsKeyOrIndex = property.IsKey() || property.IsForeignKey() || property.IsIndex(); + IsKey = property.IsKey() || property.IsForeignKey(); + IsKeyOrIndex = IsKey || property.IsIndex(); Size = fallbackSize ?? mappingHints?.Size; IsUnicode = fallbackUnicode ?? mappingHints?.IsUnicode; IsRowVersion = property is { IsConcurrencyToken: true, ValueGenerated: ValueGenerated.OnAddOrUpdate }; @@ -227,6 +228,32 @@ public TypeMappingInfo( /// Specifies a row-version, or for default. /// Specifies a precision for the mapping, or for default. /// Specifies a scale for the mapping, or for default. + [Obsolete("Use overload that takes 'key' parameter.")] + public TypeMappingInfo( + Type? type, + CoreTypeMapping? elementTypeMapping, + bool keyOrIndex, + bool? unicode, + int? size, + bool? rowVersion, + int? precision, + int? scale) + : this(type, elementTypeMapping, keyOrIndex, unicode, size, rowVersion, precision, scale, false) + { + } + + /// + /// Creates a new instance of . + /// + /// The CLR type in the model for which mapping is needed. + /// The type mapping for elements, if known. + /// If , then a special mapping for a key or index may be returned. + /// Specifies Unicode or ANSI mapping, or for default. + /// Specifies a size for the mapping, or for default. + /// Specifies a row-version, or for default. + /// Specifies a precision for the mapping, or for default. + /// Specifies a scale for the mapping, or for default. + /// If , then a special mapping for a key may be returned. public TypeMappingInfo( Type? type = null, CoreTypeMapping? elementTypeMapping = null, @@ -235,11 +262,12 @@ public TypeMappingInfo( int? size = null, bool? rowVersion = null, int? precision = null, - int? scale = null) + int? scale = null, + bool key = false) { ClrType = type?.UnwrapNullableType(); ElementTypeMapping = elementTypeMapping; - + IsKey = key; IsKeyOrIndex = keyOrIndex; Size = size; IsUnicode = unicode; @@ -266,6 +294,7 @@ public TypeMappingInfo( int? scale = null) { IsRowVersion = source.IsRowVersion; + IsKey = source.IsKey; IsKeyOrIndex = source.IsKeyOrIndex; var mappingHints = converter.MappingHints; @@ -295,7 +324,12 @@ public TypeMappingInfo WithConverter(in ValueConverterInfo converterInfo) => new(this, converterInfo); /// - /// Indicates whether or not the mapping is part of a key or index. + /// Indicates whether or not the mapping is part of a key or foreign key. + /// + public bool IsKey { get; init; } + + /// + /// Indicates whether or not the mapping is part of a key, foreign key, or index. /// public bool IsKeyOrIndex { get; init; } diff --git a/src/Microsoft.Data.Sqlite.Core/Package.md b/src/Microsoft.Data.Sqlite.Core/Package.md new file mode 100644 index 00000000000..c1c17b8e6e1 --- /dev/null +++ b/src/Microsoft.Data.Sqlite.Core/Package.md @@ -0,0 +1,12 @@ +The `Microsoft.Data.Sqlite.Core` package contains the code for SQLite ADO.NET driver. However, it does not automatically bring in any SQLite native binary, instead requiring that the application install and initialize the binary to use. + +## Usage + +Only use this package if you need to change to a different SQLite native binary that the one supplied by [Microsoft.Data.Sqlite](https://www.nuget.org/packages/Microsoft.Data.Sqlite). + +To use this "Core" package, also install a [SQLite binary package](https://www.nuget.org/profiles/SQLitePCLRaw) and initialize it with `SQLitePCL.Batteries_V2.Init();` or similar. See [github.com/ericsink/SQLitePCL.raw](https://github.com/ericsink/SQLitePCL.raw) for more information. + +### Getting support + +If you have a specific question about using these projects, we encourage you to [ask it on Stack Overflow](https://stackoverflow.com/questions/tagged/microsoft.data.sqlite). If you encounter a bug or would like to request a feature, [submit an issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](.github/SUPPORT.md). + diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs b/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs index bd368b2370a..02e75c9aa25 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs @@ -66,9 +66,9 @@ static SqliteConnection() storageFolderType = Type.GetType("Windows.Storage.StorageFolder, Windows, ContentType=WindowsRuntime") ?? Type.GetType("Windows.Storage.StorageFolder, Microsoft.Windows.SDK.NET"); } - catch (FileLoadException) + catch (Exception) { - // Ignore "Could not load assembly." + // Ignore "Could not load assembly." or any type initialization error. } object? currentAppData = null; diff --git a/src/Microsoft.Data.Sqlite/PACKAGE.md b/src/Microsoft.Data.Sqlite/PACKAGE.md new file mode 100644 index 00000000000..4c89b180e4f --- /dev/null +++ b/src/Microsoft.Data.Sqlite/PACKAGE.md @@ -0,0 +1,34 @@ +`Microsoft.Data.Sqlite` is a lightweight [ADO.NET provider]([ADO.NET](https://docs.microsoft.com/dotnet/framework/data/adonet/)) for [SQLite](https://www.sqlite.org/index.html). The EF Core provider for SQLite is built on top of this library. However, it can also be used independently or with other data access libraries. + +### Installation + +The latest stable version is available on [NuGet](https://www.nuget.org/packages/Microsoft.Data.Sqlite). + +```sh +dotnet add package Microsoft.Data.Sqlite +``` + +Use the `--version` option to specify a [preview version](https://www.nuget.org/packages/Microsoft.Data.Sqlite/absoluteLatest) to install. + +### Basic usage + +This library implements the common [ADO.NET](https://docs.microsoft.com/dotnet/framework/data/adonet/) abstractions for connections, commands, data readers, and so on. For more information, see [Microsoft.Data.Sqlite](https://docs.microsoft.com/dotnet/standard/data/sqlite/) on Microsoft Docs. + +```cs +using var connection = new SqliteConnection("Data Source=Blogs.db"); +connection.Open(); + +using var command = connection.CreateCommand(); +command.CommandText = "SELECT Url FROM Blogs"; + +using var reader = command.ExecuteReader(); +while (reader.Read()) +{ + var url = reader.GetString(0); +} +``` + +### Getting support + +If you have a specific question about using these projects, we encourage you to [ask it on Stack Overflow](https://stackoverflow.com/questions/tagged/microsoft.data.sqlite). If you encounter a bug or would like to request a feature, [submit an issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](.github/SUPPORT.md). + diff --git a/src/dotnet-ef/PACKAGE.md b/src/dotnet-ef/PACKAGE.md new file mode 100644 index 00000000000..7582724711c --- /dev/null +++ b/src/dotnet-ef/PACKAGE.md @@ -0,0 +1,38 @@ +The Entity Framework Core tools help with design-time development tasks. They're primarily used to manage Migrations and to scaffold a `DbContext` and entity types by reverse engineering the schema of a database. + +This package, `dotnet-ef` is for cross-platform command line tooling that can be used anywhere. + +## Usage + +Install the tool package using: + +```dotnetcli +dotnet tool install --global dotnet-ef +``` + +The available commands are listed in the following table. + +| Command | Usage | +|-------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| +| [dotnet ef --help](https://learn.microsoft.com/ef/core/cli/dotnet#common-options) | Displays information about Entity Framework commands. | +| [dotnet ef database drop](https://learn.microsoft.com/ef/core/cli/dotnet#dotnet-ef-database-drop) | Drops the database. | +| [dotnet ef database update](https://learn.microsoft.com/ef/core/cli/dotnet#dotnet-ef-database-update) | Updates the database to the last migration or to a specified migration | +| [dotnet ef dbcontext info](https://learn.microsoft.com/ef/core/cli/dotnet#dotnet-ef-dbcontext-info) | Gets information about a `DbContext` type. | +| [dotnet ef dbcontext list](https://learn.microsoft.com/ef/core/cli/dotnet#dotnet-ef-dbcontext-list) | Lists available `DbContext` types. | +| [dotnet ef dbcontext optimize](https://learn.microsoft.com/ef/core/cli/dotnet#dotnet-ef-dbcontext-optimize) | Generates a compiled version of the model used by the `DbContext`. | +| [dotnet ef dbcontext scaffold](https://learn.microsoft.com/ef/core/cli/dotnet#dotnet-ef-dbcontext-scaffold) | Generates a `DbContext` and entity type classes for a specified database. | +| [dotnet ef dbcontext script](https://learn.microsoft.com/ef/core/cli/dotnet#dotnet-ef-dbcontext-script) | Generates a SQL script from the `DbContext`. Bypasses any migrations. | +| [dotnet ef migrations add](https://learn.microsoft.com/ef/core/cli/dotnet#dotnet-ef-migrations-add) | Adds a new migration. | +| [dotnet ef migrations bundle](https://learn.microsoft.com/ef/core/cli/dotnet#dotnet-ef-migrations-bundle) | Creates an executable to update the database. | +| [dotnet ef migrations has-pending-model-changes](https://learn.microsoft.com/ef/core/cli/dotnet#dotnet-ef-migrations-has-pending-model-changes) | Checks if any changes have been made to the model since the last migration. | +| [dotnet ef migrations list](https://learn.microsoft.com/ef/core/cli/dotnet#dotnet-ef-migrations-list) | Lists available migrations. | +| [dotnet ef migrations remove](https://learn.microsoft.com/ef/core/cli/dotnet#dotnet-ef-migrations-remove) | Removes the last migration. | +| [dotnet ef migrations script](https://learn.microsoft.com/ef/core/cli/dotnet#dotnet-ef-migrations-script) | Generates a SQL script from the migrations. | + +## Getting started with EF Core + +See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started. + +## Feedback + +If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md). \ No newline at end of file diff --git a/src/dotnet-ef/dotnet-ef.nuspec b/src/dotnet-ef/dotnet-ef.nuspec index 2ceff381f6f..b7df82ccb63 100644 --- a/src/dotnet-ef/dotnet-ef.nuspec +++ b/src/dotnet-ef/dotnet-ef.nuspec @@ -5,10 +5,12 @@ + docs\PACKAGE.md $CommonFileElements$ + diff --git a/test/EFCore.AspNet.Specification.Tests/PACKAGE.md b/test/EFCore.AspNet.Specification.Tests/PACKAGE.md new file mode 100644 index 00000000000..fa068cd075b --- /dev/null +++ b/test/EFCore.AspNet.Specification.Tests/PACKAGE.md @@ -0,0 +1,3 @@ +This package contains tests that can be implemented and then used in database provider repos to test EF Core against that provider. + +See [Writing a Database Provider](https://learn.microsoft.com/ef/core/providers/writing-a-provider) for more information. diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index f79aef83b49..c942f6ee484 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -370,6 +370,25 @@ public class FooExtension public T Bar { get; set; } } + public class Parrot + { + public int Id { get; set; } + public string Name { get; set; } + public TChild Child { get; set; } + } + + public class Parrot + { + public int Id { get; set; } + public string Name { get; set; } + public Beak Child { get; set; } + } + + public class Beak + { + public string Name { get; set; } + } + #region Model [ConditionalFact] @@ -4648,6 +4667,139 @@ public virtual void Property_column_name_is_stored_in_snapshot_when_DefaultColum var property = entityType.FindProperty("FooExtensionId"); Assert.NotNull(property); Assert.Equal("FooExtensionId", property.GetColumnName()); + + Assert.Collection( + model.GetRelationalModel().Tables, + t => + { + + Assert.Equal("BarBase", t.Name); + Assert.Equal(["Id", "Discriminator", "FooExtensionId"], t.Columns.Select(t => t.Name)); + }, + t => + { + + Assert.Equal("FooExtension", t.Name); + Assert.Equal(["Id"], t.Columns.Select(t => t.Name)); + }); + }); + + [ConditionalFact] + public virtual void Generic_entity_type_with_owned_entities() + => Test( + modelBuilder => modelBuilder.Entity>().OwnsOne(e => e.Child), + AddBoilerPlate( + """ + modelBuilder + .HasDefaultSchema("DefaultSchema") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+Parrot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Parrot", "DefaultSchema"); + }); + + modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+Parrot", b => + { + b.OwnsOne("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+Beak", "Child", b1 => + { + b1.Property("ParrotId") + .HasColumnType("int"); + + b1.Property("Name") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("ParrotId"); + + b1.ToTable("Parrot", "DefaultSchema"); + + b1.WithOwner() + .HasForeignKey("ParrotId"); + }); + + b.Navigation("Child"); + }); +"""), + model => + { + var parentType = model.FindEntityType("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+Parrot"); + Assert.NotNull(parentType); + Assert.NotNull(parentType.FindNavigation("Child")!.TargetEntityType); + + var table = model.GetRelationalModel().Tables.Single(); + Assert.Equal(["Id", "Child_Name", "Name"], table.Columns.Select(t => t.Name)); + }); + + [ConditionalFact] + public virtual void Non_generic_entity_type_with_owned_entities() + => Test( + modelBuilder => modelBuilder.Entity().OwnsOne(e => e.Child), + AddBoilerPlate( + """ + modelBuilder + .HasDefaultSchema("DefaultSchema") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+Parrot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Parrot", "DefaultSchema"); + }); + + modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+Parrot", b => + { + b.OwnsOne("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+Beak", "Child", b1 => + { + b1.Property("ParrotId") + .HasColumnType("int"); + + b1.Property("Name") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("ParrotId"); + + b1.ToTable("Parrot", "DefaultSchema"); + + b1.WithOwner() + .HasForeignKey("ParrotId"); + }); + + b.Navigation("Child"); + }); +"""), + model => + { + var parentType = model.FindEntityType("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+Parrot"); + Assert.NotNull(parentType); + Assert.NotNull(parentType.FindNavigation("Child")!.TargetEntityType); + + var table = model.GetRelationalModel().Tables.Single(); + Assert.Equal(["Id", "Child_Name", "Name"], table.Columns.Select(t => t.Name)); }); [ConditionalFact] diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 8432c8ac498..82362c3c216 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -34659,6 +34659,111 @@ private IRelationalModel CreateRelationalModel() microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("FlagsEnum2", flagsEnum2ColumnBase); var idColumnBase = new ColumnBase("Id", "bigint", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase); microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Id", idColumnBase); + var owned_DetailsColumnBase = new ColumnBase("Owned_Details", "varchar(64)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_Details", owned_DetailsColumnBase); + var owned_NumberColumnBase = new ColumnBase("Owned_Number", "int", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase); + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_Number", owned_NumberColumnBase); + var owned_Principal_AlternateIdColumnBase = new ColumnBase("Owned_Principal_AlternateId", "uniqueidentifier", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase); + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_Principal_AlternateId", owned_Principal_AlternateIdColumnBase); + var owned_Principal_Enum1ColumnBase = new ColumnBase("Owned_Principal_Enum1", "int", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase); + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_Principal_Enum1", owned_Principal_Enum1ColumnBase); + var owned_Principal_Enum2ColumnBase = new ColumnBase("Owned_Principal_Enum2", "int", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_Principal_Enum2", owned_Principal_Enum2ColumnBase); + var owned_Principal_FlagsEnum1ColumnBase = new ColumnBase("Owned_Principal_FlagsEnum1", "int", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase); + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_Principal_FlagsEnum1", owned_Principal_FlagsEnum1ColumnBase); + var owned_Principal_FlagsEnum2ColumnBase = new ColumnBase("Owned_Principal_FlagsEnum2", "int", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase); + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_Principal_FlagsEnum2", owned_Principal_FlagsEnum2ColumnBase); + var owned_Principal_IdColumnBase = new ColumnBase("Owned_Principal_Id", "bigint", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_Principal_Id", owned_Principal_IdColumnBase); + var owned_Principal_RefTypeArrayColumnBase = new ColumnBase("Owned_Principal_RefTypeArray", "nvarchar(max)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_Principal_RefTypeArray", owned_Principal_RefTypeArrayColumnBase); + var owned_Principal_RefTypeEnumerableColumnBase = new ColumnBase("Owned_Principal_RefTypeEnumerable", "nvarchar(max)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_Principal_RefTypeEnumerable", owned_Principal_RefTypeEnumerableColumnBase); + var owned_Principal_RefTypeIListColumnBase = new ColumnBase("Owned_Principal_RefTypeIList", "nvarchar(max)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_Principal_RefTypeIList", owned_Principal_RefTypeIListColumnBase); + var owned_Principal_RefTypeListColumnBase = new ColumnBase("Owned_Principal_RefTypeList", "nvarchar(max)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_Principal_RefTypeList", owned_Principal_RefTypeListColumnBase); + var owned_Principal_ValueTypeArrayColumnBase = new ColumnBase("Owned_Principal_ValueTypeArray", "nvarchar(max)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_Principal_ValueTypeArray", owned_Principal_ValueTypeArrayColumnBase); + var owned_Principal_ValueTypeEnumerableColumnBase = new ColumnBase("Owned_Principal_ValueTypeEnumerable", "nvarchar(max)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_Principal_ValueTypeEnumerable", owned_Principal_ValueTypeEnumerableColumnBase); + var owned_Principal_ValueTypeIListColumnBase = new ColumnBase("Owned_Principal_ValueTypeIList", "nvarchar(max)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_Principal_ValueTypeIList", owned_Principal_ValueTypeIListColumnBase); + var owned_Principal_ValueTypeListColumnBase = new ColumnBase("Owned_Principal_ValueTypeList", "nvarchar(max)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_Principal_ValueTypeList", owned_Principal_ValueTypeListColumnBase); + var owned_RefTypeArrayColumnBase = new ColumnBase("Owned_RefTypeArray", "nvarchar(max)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_RefTypeArray", owned_RefTypeArrayColumnBase); + var owned_RefTypeEnumerableColumnBase = new ColumnBase("Owned_RefTypeEnumerable", "nvarchar(max)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_RefTypeEnumerable", owned_RefTypeEnumerableColumnBase); + var owned_RefTypeIListColumnBase = new ColumnBase("Owned_RefTypeIList", "nvarchar(max)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_RefTypeIList", owned_RefTypeIListColumnBase); + var owned_RefTypeListColumnBase = new ColumnBase("Owned_RefTypeList", "nvarchar(max)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_RefTypeList", owned_RefTypeListColumnBase); + var owned_ValueTypeArrayColumnBase = new ColumnBase("Owned_ValueTypeArray", "nvarchar(max)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_ValueTypeArray", owned_ValueTypeArrayColumnBase); + var owned_ValueTypeEnumerableColumnBase = new ColumnBase("Owned_ValueTypeEnumerable", "nvarchar(max)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_ValueTypeEnumerable", owned_ValueTypeEnumerableColumnBase); + var owned_ValueTypeIListColumnBase = new ColumnBase("Owned_ValueTypeIList", "nvarchar(max)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_ValueTypeIList", owned_ValueTypeIListColumnBase); + var owned_ValueTypeListColumnBase = new ColumnBase("Owned_ValueTypeList", "nvarchar(max)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Owned_ValueTypeList", owned_ValueTypeListColumnBase); var principalBaseIdColumnBase = new ColumnBase("PrincipalBaseId", "bigint", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) { IsNullable = true @@ -35371,6 +35476,22 @@ private IRelationalModel CreateRelationalModel() var ownedType = principalBase.FindComplexProperty("Owned")!.ComplexType; + var defaultTableMappings0 = new List>(); + ownedType.SetRuntimeAnnotation("Relational:DefaultMappings", defaultTableMappings0); + var microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0 = new TableMappingBase(ownedType, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase, false); + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.AddTypeMapping(microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0, false); + defaultTableMappings0.Add(microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)owned_DetailsColumnBase, ownedType.FindProperty("Details")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)owned_NumberColumnBase, ownedType.FindProperty("Number")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)owned_RefTypeArrayColumnBase, ownedType.FindProperty("RefTypeArray")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)owned_RefTypeEnumerableColumnBase, ownedType.FindProperty("RefTypeEnumerable")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)owned_RefTypeIListColumnBase, ownedType.FindProperty("RefTypeIList")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)owned_RefTypeListColumnBase, ownedType.FindProperty("RefTypeList")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)owned_ValueTypeArrayColumnBase, ownedType.FindProperty("ValueTypeArray")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)owned_ValueTypeEnumerableColumnBase, ownedType.FindProperty("ValueTypeEnumerable")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)owned_ValueTypeIListColumnBase, ownedType.FindProperty("ValueTypeIList")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)owned_ValueTypeListColumnBase, ownedType.FindProperty("ValueTypeList")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + var tableMappings0 = new List(); ownedType.SetRuntimeAnnotation("Relational:TableMappings", tableMappings0); var principalBaseTableMapping0 = new TableMapping(ownedType, principalBaseTable, true); @@ -35389,6 +35510,26 @@ private IRelationalModel CreateRelationalModel() var principalBase0 = ownedType.FindComplexProperty("Principal")!.ComplexType; + var defaultTableMappings1 = new List>(); + principalBase0.SetRuntimeAnnotation("Relational:DefaultMappings", defaultTableMappings1); + var microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1 = new TableMappingBase(principalBase0, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase, false); + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.AddTypeMapping(microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1, false); + defaultTableMappings1.Add(microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1); + RelationalModel.CreateColumnMapping((ColumnBase)owned_Principal_AlternateIdColumnBase, principalBase0.FindProperty("AlternateId")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1); + RelationalModel.CreateColumnMapping((ColumnBase)owned_Principal_Enum1ColumnBase, principalBase0.FindProperty("Enum1")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1); + RelationalModel.CreateColumnMapping((ColumnBase)owned_Principal_Enum2ColumnBase, principalBase0.FindProperty("Enum2")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1); + RelationalModel.CreateColumnMapping((ColumnBase)owned_Principal_FlagsEnum1ColumnBase, principalBase0.FindProperty("FlagsEnum1")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1); + RelationalModel.CreateColumnMapping((ColumnBase)owned_Principal_FlagsEnum2ColumnBase, principalBase0.FindProperty("FlagsEnum2")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1); + RelationalModel.CreateColumnMapping((ColumnBase)owned_Principal_IdColumnBase, principalBase0.FindProperty("Id")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1); + RelationalModel.CreateColumnMapping((ColumnBase)owned_Principal_RefTypeArrayColumnBase, principalBase0.FindProperty("RefTypeArray")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1); + RelationalModel.CreateColumnMapping((ColumnBase)owned_Principal_RefTypeEnumerableColumnBase, principalBase0.FindProperty("RefTypeEnumerable")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1); + RelationalModel.CreateColumnMapping((ColumnBase)owned_Principal_RefTypeIListColumnBase, principalBase0.FindProperty("RefTypeIList")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1); + RelationalModel.CreateColumnMapping((ColumnBase)owned_Principal_RefTypeListColumnBase, principalBase0.FindProperty("RefTypeList")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1); + RelationalModel.CreateColumnMapping((ColumnBase)owned_Principal_ValueTypeArrayColumnBase, principalBase0.FindProperty("ValueTypeArray")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1); + RelationalModel.CreateColumnMapping((ColumnBase)owned_Principal_ValueTypeEnumerableColumnBase, principalBase0.FindProperty("ValueTypeEnumerable")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1); + RelationalModel.CreateColumnMapping((ColumnBase)owned_Principal_ValueTypeIListColumnBase, principalBase0.FindProperty("ValueTypeIList")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1); + RelationalModel.CreateColumnMapping((ColumnBase)owned_Principal_ValueTypeListColumnBase, principalBase0.FindProperty("ValueTypeList")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase1); + var tableMappings1 = new List(); principalBase0.SetRuntimeAnnotation("Relational:TableMappings", tableMappings1); var principalBaseTableMapping1 = new TableMapping(principalBase0, principalBaseTable, true); @@ -35411,26 +35552,26 @@ private IRelationalModel CreateRelationalModel() var principalDerived = FindEntityType("Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>")!; - var defaultTableMappings0 = new List>(); - principalDerived.SetRuntimeAnnotation("Relational:DefaultMappings", defaultTableMappings0); - var microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0 = new TableMappingBase(principalDerived, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase, true); - microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.AddTypeMapping(microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0, false); - defaultTableMappings0.Add(microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); - RelationalModel.CreateColumnMapping((ColumnBase)idColumnBase, principalDerived.FindProperty("Id")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); - RelationalModel.CreateColumnMapping((ColumnBase)discriminatorColumnBase, principalDerived.FindProperty("Discriminator")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); - RelationalModel.CreateColumnMapping((ColumnBase)enum1ColumnBase, principalDerived.FindProperty("Enum1")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); - RelationalModel.CreateColumnMapping((ColumnBase)enum2ColumnBase, principalDerived.FindProperty("Enum2")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); - RelationalModel.CreateColumnMapping((ColumnBase)flagsEnum1ColumnBase, principalDerived.FindProperty("FlagsEnum1")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); - RelationalModel.CreateColumnMapping((ColumnBase)flagsEnum2ColumnBase, principalDerived.FindProperty("FlagsEnum2")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); - RelationalModel.CreateColumnMapping((ColumnBase)principalBaseIdColumnBase, principalDerived.FindProperty("PrincipalBaseId")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); - RelationalModel.CreateColumnMapping((ColumnBase)refTypeArrayColumnBase, principalDerived.FindProperty("RefTypeArray")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); - RelationalModel.CreateColumnMapping((ColumnBase)refTypeEnumerableColumnBase, principalDerived.FindProperty("RefTypeEnumerable")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); - RelationalModel.CreateColumnMapping((ColumnBase)refTypeIListColumnBase, principalDerived.FindProperty("RefTypeIList")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); - RelationalModel.CreateColumnMapping((ColumnBase)refTypeListColumnBase, principalDerived.FindProperty("RefTypeList")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); - RelationalModel.CreateColumnMapping((ColumnBase)valueTypeArrayColumnBase, principalDerived.FindProperty("ValueTypeArray")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); - RelationalModel.CreateColumnMapping((ColumnBase)valueTypeEnumerableColumnBase, principalDerived.FindProperty("ValueTypeEnumerable")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); - RelationalModel.CreateColumnMapping((ColumnBase)valueTypeIListColumnBase, principalDerived.FindProperty("ValueTypeIList")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); - RelationalModel.CreateColumnMapping((ColumnBase)valueTypeListColumnBase, principalDerived.FindProperty("ValueTypeList")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + var defaultTableMappings2 = new List>(); + principalDerived.SetRuntimeAnnotation("Relational:DefaultMappings", defaultTableMappings2); + var microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2 = new TableMappingBase(principalDerived, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase, true); + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.AddTypeMapping(microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2, false); + defaultTableMappings2.Add(microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2); + RelationalModel.CreateColumnMapping((ColumnBase)idColumnBase, principalDerived.FindProperty("Id")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2); + RelationalModel.CreateColumnMapping((ColumnBase)discriminatorColumnBase, principalDerived.FindProperty("Discriminator")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2); + RelationalModel.CreateColumnMapping((ColumnBase)enum1ColumnBase, principalDerived.FindProperty("Enum1")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2); + RelationalModel.CreateColumnMapping((ColumnBase)enum2ColumnBase, principalDerived.FindProperty("Enum2")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2); + RelationalModel.CreateColumnMapping((ColumnBase)flagsEnum1ColumnBase, principalDerived.FindProperty("FlagsEnum1")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2); + RelationalModel.CreateColumnMapping((ColumnBase)flagsEnum2ColumnBase, principalDerived.FindProperty("FlagsEnum2")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2); + RelationalModel.CreateColumnMapping((ColumnBase)principalBaseIdColumnBase, principalDerived.FindProperty("PrincipalBaseId")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2); + RelationalModel.CreateColumnMapping((ColumnBase)refTypeArrayColumnBase, principalDerived.FindProperty("RefTypeArray")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2); + RelationalModel.CreateColumnMapping((ColumnBase)refTypeEnumerableColumnBase, principalDerived.FindProperty("RefTypeEnumerable")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2); + RelationalModel.CreateColumnMapping((ColumnBase)refTypeIListColumnBase, principalDerived.FindProperty("RefTypeIList")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2); + RelationalModel.CreateColumnMapping((ColumnBase)refTypeListColumnBase, principalDerived.FindProperty("RefTypeList")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2); + RelationalModel.CreateColumnMapping((ColumnBase)valueTypeArrayColumnBase, principalDerived.FindProperty("ValueTypeArray")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2); + RelationalModel.CreateColumnMapping((ColumnBase)valueTypeEnumerableColumnBase, principalDerived.FindProperty("ValueTypeEnumerable")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2); + RelationalModel.CreateColumnMapping((ColumnBase)valueTypeIListColumnBase, principalDerived.FindProperty("ValueTypeIList")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2); + RelationalModel.CreateColumnMapping((ColumnBase)valueTypeListColumnBase, principalDerived.FindProperty("ValueTypeList")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase2); var tableMappings2 = new List(); principalDerived.SetRuntimeAnnotation("Relational:TableMappings", tableMappings2); diff --git a/test/EFCore.InMemory.FunctionalTests/PropertyValuesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/PropertyValuesInMemoryTest.cs index 9e06e8afced..dd2d0dd74e7 100644 --- a/test/EFCore.InMemory.FunctionalTests/PropertyValuesInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/PropertyValuesInMemoryTest.cs @@ -3,19 +3,47 @@ namespace Microsoft.EntityFrameworkCore; -public class PropertyValuesInMemoryTest : PropertyValuesTestBase +public class PropertyValuesInMemoryTest(PropertyValuesInMemoryTest.PropertyValuesInMemoryFixture fixture) + : PropertyValuesTestBase(fixture) { - public PropertyValuesInMemoryTest(PropertyValuesInMemoryFixture fixture) - : base(fixture) - { - } + public override Task Complex_current_values_can_be_accessed_as_a_property_dictionary_using_IProperty() + => Assert.ThrowsAsync( // In-memory database cannot query complex types + () => base.Complex_current_values_can_be_accessed_as_a_property_dictionary_using_IProperty()); + + public override Task Complex_original_values_can_be_accessed_as_a_property_dictionary_using_IProperty() + => Assert.ThrowsAsync( // In-memory database cannot query complex types + () => base.Complex_original_values_can_be_accessed_as_a_property_dictionary_using_IProperty()); + + public override Task Complex_store_values_can_be_accessed_as_a_property_dictionary_using_IProperty() + => Assert.ThrowsAsync( // In-memory database cannot query complex types + () => base.Complex_store_values_can_be_accessed_as_a_property_dictionary_using_IProperty()); + + public override Task Complex_store_values_can_be_accessed_asynchronously_as_a_property_dictionary_using_IProperty() + => Assert.ThrowsAsync( // In-memory database cannot query complex types + () => base.Complex_store_values_can_be_accessed_asynchronously_as_a_property_dictionary_using_IProperty()); public class PropertyValuesInMemoryFixture : PropertyValuesFixtureBase { public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) - => base.AddOptions(builder).EnableSensitiveDataLogging(false); + => base.AddOptions(builder) + .ConfigureWarnings(w => w.Ignore(CoreEventId.MappedComplexPropertyIgnoredWarning)) + .EnableSensitiveDataLogging(false); protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + base.OnModelCreating(modelBuilder, context); + + // In-memory database doesn't support complex type queries + modelBuilder.Entity( + b => + { + b.Ignore(e => e.Culture); + b.Ignore(e => e.Milk); + }); + + } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/AdHocAdvancedMappingsQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/AdHocAdvancedMappingsQueryInMemoryTest.cs new file mode 100644 index 00000000000..bcd988e7562 --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/Query/AdHocAdvancedMappingsQueryInMemoryTest.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query; + +public class AdHocAdvancedMappingsQueryInMemoryTest : AdHocAdvancedMappingsQueryTestBase +{ + protected override ITestStoreFactory TestStoreFactory + => InMemoryTestStoreFactory.Instance; +} diff --git a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs index 0d1a226dcce..db77782f269 100644 --- a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs @@ -72,7 +72,32 @@ public virtual void Can_apply_all_migrations() x => Assert.Equal("00000000000001_Migration1", x.MigrationId), x => Assert.Equal("00000000000002_Migration2", x.MigrationId), x => Assert.Equal("00000000000003_Migration3", x.MigrationId), - x => Assert.Equal("00000000000004_Migration4", x.MigrationId)); + x => Assert.Equal("00000000000004_Migration4", x.MigrationId), + x => Assert.Equal("00000000000005_Migration5", x.MigrationId), + x => Assert.Equal("00000000000006_Migration6", x.MigrationId), + x => Assert.Equal("00000000000007_Migration7", x.MigrationId)); + } + + [ConditionalFact] + public virtual void Can_apply_range_of_migrations() + { + using var db = Fixture.CreateContext(); + db.Database.EnsureDeleted(); + + GiveMeSomeTime(db); + + var migrator = db.GetService(); + migrator.Migrate("Migration6"); + + var history = db.GetService(); + Assert.Collection( + history.GetAppliedMigrations(), + x => Assert.Equal("00000000000001_Migration1", x.MigrationId), + x => Assert.Equal("00000000000002_Migration2", x.MigrationId), + x => Assert.Equal("00000000000003_Migration3", x.MigrationId), + x => Assert.Equal("00000000000004_Migration4", x.MigrationId), + x => Assert.Equal("00000000000005_Migration5", x.MigrationId), + x => Assert.Equal("00000000000006_Migration6", x.MigrationId)); } [ConditionalFact] @@ -100,9 +125,8 @@ public virtual void Can_revert_all_migrations() GiveMeSomeTime(db); - db.Database.Migrate(); - var migrator = db.GetService(); + migrator.Migrate("Migration5"); migrator.Migrate(Migration.InitialDatabase); var history = db.GetService(); @@ -117,15 +141,17 @@ public virtual void Can_revert_one_migrations() GiveMeSomeTime(db); - db.Database.Migrate(); - var migrator = db.GetService(); - migrator.Migrate("Migration1"); + migrator.Migrate("Migration5"); + migrator.Migrate("Migration4"); var history = db.GetService(); Assert.Collection( history.GetAppliedMigrations(), - x => Assert.Equal("00000000000001_Migration1", x.MigrationId)); + x => Assert.Equal("00000000000001_Migration1", x.MigrationId), + x => Assert.Equal("00000000000002_Migration2", x.MigrationId), + x => Assert.Equal("00000000000003_Migration3", x.MigrationId), + x => Assert.Equal("00000000000004_Migration4", x.MigrationId)); } [ConditionalFact] @@ -144,7 +170,10 @@ await history.GetAppliedMigrationsAsync(), x => Assert.Equal("00000000000001_Migration1", x.MigrationId), x => Assert.Equal("00000000000002_Migration2", x.MigrationId), x => Assert.Equal("00000000000003_Migration3", x.MigrationId), - x => Assert.Equal("00000000000004_Migration4", x.MigrationId)); + x => Assert.Equal("00000000000004_Migration4", x.MigrationId), + x => Assert.Equal("00000000000005_Migration5", x.MigrationId), + x => Assert.Equal("00000000000006_Migration6", x.MigrationId), + x => Assert.Equal("00000000000007_Migration7", x.MigrationId)); } [ConditionalFact] @@ -357,6 +386,7 @@ public MigrationsContext(DbContextOptions options) public class Foo { public int Id { get; set; } + public string Description { get; set; } } [DbContext(typeof(MigrationsContext))] @@ -370,7 +400,7 @@ protected override void Up(MigrationBuilder migrationBuilder) migrationBuilder .CreateTable( name: "Table1", - columns: x => new { Id = x.Column(), Foo = x.Column() }) + columns: x => new { Id = x.Column(), Foo = x.Column(), Description = x.Column() }) .PrimaryKey( name: "PK_Table1", columns: x => x.Id); @@ -452,4 +482,72 @@ protected override void Down(MigrationBuilder migrationBuilder) { } } + + [DbContext(typeof(MigrationsContext))] + [Migration("00000000000005_Migration5")] + private class Migration5 : Migration + { + public const string TestValue = """ + Value With + + Empty Lines + """; + + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"INSERT INTO Table1 (Id, Bar, Description) VALUES (-1, ' ', '{TestValue}')"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } + + [DbContext(typeof(MigrationsContext))] + [Migration("00000000000006_Migration6")] + private class Migration6 : Migration + { + public const string TestValue = """ + GO + Value With + + Empty Lines + """; + + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"INSERT INTO Table1 (Id, Bar, Description) VALUES (-2, ' ', '{TestValue}')"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } + + [DbContext(typeof(MigrationsContext))] + [Migration("00000000000007_Migration7")] + private class Migration7 : Migration + { + public const string TestValue = """ + GO + Value With + + GO + + Empty Lines + GO + """; + + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"INSERT INTO Table1 (Id, Bar, Description) VALUES (-3, ' ', '{TestValue}')"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } } diff --git a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs index 8af3b5cbc97..eef96d80c95 100644 --- a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.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.ComponentModel.DataAnnotations; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations.Internal; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; @@ -1874,6 +1875,339 @@ await Test( """); } + [ConditionalFact] + public virtual Task Add_required_primitve_collection_to_existing_table() + => Test( + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + e.ToTable("Customers"); + }), + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + e.Property>("Numbers").IsRequired(); + e.ToTable("Customers"); + }), + model => + { + var customersTable = Assert.Single(model.Tables.Where(t => t.Name == "Customers")); + + Assert.Collection( + customersTable.Columns, + c => Assert.Equal("Id", c.Name), + c => Assert.Equal("Name", c.Name), + c => Assert.Equal("Numbers", c.Name)); + Assert.Same( + customersTable.Columns.Single(c => c.Name == "Id"), + Assert.Single(customersTable.PrimaryKey!.Columns)); + }); + + [ConditionalFact] + public virtual Task Add_required_primitve_collection_with_custom_default_value_to_existing_table() + => Test( + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + e.ToTable("Customers"); + }), + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + e.Property>("Numbers").IsRequired().HasDefaultValue(new List { 1, 2, 3 }); + e.ToTable("Customers"); + }), + model => + { + var customersTable = Assert.Single(model.Tables.Where(t => t.Name == "Customers")); + + Assert.Collection( + customersTable.Columns, + c => Assert.Equal("Id", c.Name), + c => Assert.Equal("Name", c.Name), + c => Assert.Equal("Numbers", c.Name)); + Assert.Same( + customersTable.Columns.Single(c => c.Name == "Id"), + Assert.Single(customersTable.PrimaryKey!.Columns)); + }); + + [ConditionalFact] + public abstract Task Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table(); + + protected virtual Task Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table_core(string defaultValueSql) + => Test( + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + e.ToTable("Customers"); + }), + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + e.Property>("Numbers").IsRequired().HasDefaultValueSql(defaultValueSql); + e.ToTable("Customers"); + }), + model => + { + var customersTable = Assert.Single(model.Tables.Where(t => t.Name == "Customers")); + + Assert.Collection( + customersTable.Columns, + c => Assert.Equal("Id", c.Name), + c => Assert.Equal("Name", c.Name), + c => Assert.Equal("Numbers", c.Name)); + Assert.Same( + customersTable.Columns.Single(c => c.Name == "Id"), + Assert.Single(customersTable.PrimaryKey!.Columns)); + }); + + [ConditionalFact(Skip = "issue #33038")] + public virtual Task Add_required_primitve_collection_with_custom_converter_to_existing_table() + => Test( + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + e.ToTable("Customers"); + }), + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + e.Property>("Numbers").HasConversion(new ValueConverter, string>( + convertToProviderExpression: x => x != null && x.Count > 0 ? "some numbers" : "nothing", + convertFromProviderExpression: x => x == "nothing" ? new List { } : new List { 7, 8, 9 })) + .IsRequired(); + e.ToTable("Customers"); + }), + model => + { + var customersTable = Assert.Single(model.Tables.Where(t => t.Name == "Customers")); + + Assert.Collection( + customersTable.Columns, + c => Assert.Equal("Id", c.Name), + c => Assert.Equal("Name", c.Name), + c => Assert.Equal("Numbers", c.Name)); + Assert.Same( + customersTable.Columns.Single(c => c.Name == "Id"), + Assert.Single(customersTable.PrimaryKey!.Columns)); + }); + + [ConditionalFact] + public virtual Task Add_required_primitve_collection_with_custom_converter_and_custom_default_value_to_existing_table() + => Test( + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + e.ToTable("Customers"); + }), + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + e.Property>("Numbers").HasConversion(new ValueConverter, string>( + convertToProviderExpression: x => x != null && x.Count > 0 ? "some numbers" : "nothing", + convertFromProviderExpression: x => x == "nothing" ? new List { } : new List { 7, 8, 9 })) + .HasDefaultValue(new List { 42 }) + .IsRequired(); + e.ToTable("Customers"); + }), + model => + { + var customersTable = Assert.Single(model.Tables.Where(t => t.Name == "Customers")); + + Assert.Collection( + customersTable.Columns, + c => Assert.Equal("Id", c.Name), + c => Assert.Equal("Name", c.Name), + c => Assert.Equal("Numbers", c.Name)); + Assert.Same( + customersTable.Columns.Single(c => c.Name == "Id"), + Assert.Single(customersTable.PrimaryKey!.Columns)); + }); + + [ConditionalFact] + public virtual Task Add_optional_primitive_collection_to_existing_table() + => Test( + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + e.ToTable("Customers"); + }), + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + e.Property>("Numbers"); + e.ToTable("Customers"); + }), + model => + { + var customersTable = Assert.Single(model.Tables.Where(t => t.Name == "Customers")); + + Assert.Collection( + customersTable.Columns, + c => Assert.Equal("Id", c.Name), + c => Assert.Equal("Name", c.Name), + c => Assert.Equal("Numbers", c.Name)); + Assert.Same( + customersTable.Columns.Single(c => c.Name == "Id"), + Assert.Single(customersTable.PrimaryKey!.Columns)); + }); + + [ConditionalFact] + public virtual Task Create_table_with_required_primitive_collection() + => Test( + builder => { }, + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + e.Property>("Numbers").IsRequired(); + e.ToTable("Customers"); + }), + model => + { + var customersTable = Assert.Single(model.Tables.Where(t => t.Name == "Customers")); + + Assert.Collection( + customersTable.Columns, + c => Assert.Equal("Id", c.Name), + c => Assert.Equal("Name", c.Name), + c => Assert.Equal("Numbers", c.Name)); + Assert.Same( + customersTable.Columns.Single(c => c.Name == "Id"), + Assert.Single(customersTable.PrimaryKey!.Columns)); + }); + + [ConditionalFact] + public virtual Task Create_table_with_optional_primitive_collection() + => Test( + builder => { }, + builder => builder.Entity( + "Customer", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + e.Property>("Numbers"); + e.ToTable("Customers"); + }), + model => + { + var customersTable = Assert.Single(model.Tables.Where(t => t.Name == "Customers")); + + Assert.Collection( + customersTable.Columns, + c => Assert.Equal("Id", c.Name), + c => Assert.Equal("Name", c.Name), + c => Assert.Equal("Numbers", c.Name)); + Assert.Same( + customersTable.Columns.Single(c => c.Name == "Id"), + Assert.Single(customersTable.PrimaryKey!.Columns)); + }); + + [ConditionalFact] + public virtual Task Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH() + => Test( + builder => { }, + builder => + { + builder.Entity( + "Contact", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + e.ToTable("Contacts"); + }); + builder.Entity( + "Supplier", e => + { + e.HasBaseType("Contact"); + e.Property("Number"); + e.ComplexProperty("MyComplex", ct => + { + ct.ComplexProperty("MyNestedComplex").IsRequired(); + }); + }); + }, + model => + { + var contactsTable = Assert.Single(model.Tables.Where(t => t.Name == "Contacts")); + Assert.Collection( + contactsTable.Columns, + c => Assert.Equal("Id", c.Name), + c => Assert.Equal("Discriminator", c.Name), + c => Assert.Equal("Name", c.Name), + c => Assert.Equal("Number", c.Name), + c => + { + Assert.Equal("MyComplex_Prop", c.Name); + Assert.Equal(true, c.IsNullable); + }, + c => + { + Assert.Equal("MyComplex_MyNestedComplex_Bar", c.Name); + Assert.Equal(true, c.IsNullable); + }, + c => + { + Assert.Equal("MyComplex_MyNestedComplex_Foo", c.Name); + Assert.Equal(true, c.IsNullable); + }); + }); + + protected class MyComplex + { + [Required] + public string Prop { get; set; } + + [Required] + public MyNestedComplex Nested { get; set; } + } + + public class MyNestedComplex + { + public int Foo { get; set; } + public DateTime Bar { get; set; } + } + protected class Person { public int Id { get; set; } diff --git a/test/EFCore.Relational.Specification.Tests/PACKAGE.md b/test/EFCore.Relational.Specification.Tests/PACKAGE.md new file mode 100644 index 00000000000..fa068cd075b --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/PACKAGE.md @@ -0,0 +1,3 @@ +This package contains tests that can be implemented and then used in database provider repos to test EF Core against that provider. + +See [Writing a Database Provider](https://learn.microsoft.com/ef/core/providers/writing-a-provider) for more information. diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs new file mode 100644 index 00000000000..d001b5488f2 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs @@ -0,0 +1,230 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query; + +public abstract class AdHocAdvancedMappingsQueryRelationalTestBase : AdHocAdvancedMappingsQueryTestBase +{ + protected TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + protected void ClearLog() + => TestSqlLoggerFactory.Clear(); + + protected void AssertSql(params string[] expected) + => TestSqlLoggerFactory.AssertBaseline(expected); + + #region 32911 + + [ConditionalFact] + public virtual async Task Two_similar_complex_properties_projected_with_split_query1() + { + var contextFactory = await InitializeAsync(seed: c => c.Seed()); + + using var context = contextFactory.CreateContext(); + var query = context.Offers + .Include(e => e.Variations) + .ThenInclude(v => v.Nested) + .AsSplitQuery() + .ToList(); + + var resultElement = query.Single(); + foreach (var variation in resultElement.Variations) + { + Assert.NotEqual(variation.Payment.Brutto, variation.Nested.Payment.Brutto); + Assert.NotEqual(variation.Payment.Netto, variation.Nested.Payment.Netto); + } + } + + [ConditionalFact] + public virtual async Task Two_similar_complex_properties_projected_with_split_query2() + { + var contextFactory = await InitializeAsync(seed: c => c.Seed()); + + using var context = contextFactory.CreateContext(); + var query = context.Offers + .Include(e => e.Variations) + .ThenInclude(v => v.Nested) + .AsSplitQuery() + .Single(x => x.Id == 1); + + foreach (var variation in query.Variations) + { + Assert.NotEqual(variation.Payment.Brutto, variation.Nested.Payment.Brutto); + Assert.NotEqual(variation.Payment.Netto, variation.Nested.Payment.Netto); + } + } + + [ConditionalFact] + public virtual async Task Projecting_one_of_two_similar_complex_types_picks_the_correct_one() + { + var contextFactory = await InitializeAsync(seed: c => c.Seed()); + + using var context = contextFactory.CreateContext(); + + var query = context.Cs + .Where(x => x.B.AId.Value == 1) + .OrderBy(x => x.Id) + .Take(10) + .Select(x => new + { + x.B.A.Id, + x.B.Info.Created, + }).ToList(); + + Assert.Equal(new DateTime(2000, 1, 1), query[0].Created); + } + + protected class Context32911(DbContextOptions options) : DbContext(options) + { + public DbSet Offers { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().ComplexProperty(x => x.Payment, cpb => + { + cpb.IsRequired(); + cpb.Property(p => p.Netto).HasColumnName("payment_netto"); + cpb.Property(p => p.Brutto).HasColumnName("payment_brutto"); + }); + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().ComplexProperty(x => x.Payment, cpb => + { + cpb.IsRequired(); + cpb.Property(p => p.Netto).HasColumnName("payment_netto"); + cpb.Property(p => p.Brutto).HasColumnName("payment_brutto"); + }); + } + + public void Seed() + { + var v1 = new Variation + { + Id = 1, + Payment = new Payment(1, 10), + Nested = new NestedEntity + { + Id = 1, + Payment = new Payment(10, 100) + } + }; + + var v2 = new Variation + { + Id = 2, + Payment = new Payment(2, 20), + Nested = new NestedEntity + { + Id = 2, + Payment = new Payment(20, 200) + } + }; + + var v3 = new Variation + { + Id = 3, + Payment = new Payment(3, 30), + Nested = new NestedEntity + { + Id = 3, + Payment = new Payment(30, 300) + } + }; + + Offers.Add(new Offer { Id = 1, Variations = new List { v1, v2, v3 } }); + + SaveChanges(); + } + + public abstract class EntityBase + { + public int Id { get; set; } + } + + public class Offer : EntityBase + { + public ICollection Variations { get; set; } + } + + public class Variation : EntityBase + { + public Payment Payment { get; set; } = new Payment(0, 0); + + public NestedEntity Nested { get; set; } + } + + public class NestedEntity : EntityBase + { + public Payment Payment { get; set; } = new Payment(0, 0); + } + + public record Payment(decimal Netto, decimal Brutto); + } + + protected class Context32911_2(DbContextOptions options) : DbContext(options) + { + public DbSet As { get; set; } + public DbSet Bs { get; set; } + public DbSet Cs { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + + modelBuilder.Entity(x => x.ComplexProperty(b => b.Info).IsRequired()); + modelBuilder.Entity(x => x.ComplexProperty(c => c.Info).IsRequired()); + } + + public void Seed() + { + var c = new C + { + Id = 100, + Info = new Metadata { Created = new DateTime(2020, 10, 10) }, + B = new B + { + Id = 10, + Info = new Metadata { Created = new DateTime(2000, 1, 1) }, + A = new A { Id = 1 } + } + }; + + Cs.Add(c); + SaveChanges(); + } + + public class Metadata + { + public DateTime Created { get; set; } + } + + public class A + { + public int Id { get; set; } + } + + public class B + { + public int Id { get; set; } + public Metadata Info { get; set; } + public int? AId { get; set; } + + public A A { get; set; } + } + + public class C + { + public int Id { get; set; } + public Metadata Info { get; set; } + public int BId { get; set; } + + public B B { get; set; } + } + } + + #endregion +} diff --git a/test/EFCore.Specification.Tests/PACKAGE.md b/test/EFCore.Specification.Tests/PACKAGE.md new file mode 100644 index 00000000000..fa068cd075b --- /dev/null +++ b/test/EFCore.Specification.Tests/PACKAGE.md @@ -0,0 +1,3 @@ +This package contains tests that can be implemented and then used in database provider repos to test EF Core against that provider. + +See [Writing a Database Provider](https://learn.microsoft.com/ef/core/providers/writing-a-provider) for more information. diff --git a/test/EFCore.Specification.Tests/PropertyValuesTestBase.cs b/test/EFCore.Specification.Tests/PropertyValuesTestBase.cs index 078d9371725..13a78faf380 100644 --- a/test/EFCore.Specification.Tests/PropertyValuesTestBase.cs +++ b/test/EFCore.Specification.Tests/PropertyValuesTestBase.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + using System.ComponentModel.DataAnnotations.Schema; using System.Globalization; @@ -28,11 +30,11 @@ public virtual Task Scalar_original_values_can_be_accessed_as_a_property_diction [ConditionalFact] public virtual Task Scalar_store_values_can_be_accessed_as_a_property_dictionary() - => TestPropertyValuesScalars(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestPropertyValuesScalars(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Scalar_store_values_can_be_accessed_asynchronously_as_a_property_dictionary() - => TestPropertyValuesScalars(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestPropertyValuesScalars(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestPropertyValuesScalars( Func, Task> getPropertyValues, @@ -78,11 +80,11 @@ public virtual Task Scalar_original_values_can_be_accessed_as_a_property_diction [ConditionalFact] public virtual Task Scalar_store_values_can_be_accessed_as_a_property_dictionary_using_IProperty() - => TestPropertyValuesScalarsIProperty(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestPropertyValuesScalarsIProperty(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Scalar_store_values_can_be_accessed_asynchronously_as_a_property_dictionary_using_IProperty() - => TestPropertyValuesScalarsIProperty(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestPropertyValuesScalarsIProperty(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestPropertyValuesScalarsIProperty( Func, Task> getPropertyValues, @@ -129,11 +131,11 @@ public virtual Task Scalar_original_values_of_a_derived_object_can_be_accessed_a [ConditionalFact] public virtual Task Scalar_store_values_of_a_derived_object_can_be_accessed_as_a_property_dictionary() - => TestPropertyValuesDerivedScalars(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestPropertyValuesDerivedScalars(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Scalar_store_values_of_a_derived_object_can_be_accessed_asynchronously_as_a_property_dictionary() - => TestPropertyValuesDerivedScalars(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestPropertyValuesDerivedScalars(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestPropertyValuesDerivedScalars( Func, Task> getPropertyValues, @@ -182,11 +184,11 @@ public virtual Task Scalar_original_values_can_be_accessed_as_a_non_generic_prop [ConditionalFact] public virtual Task Scalar_store_values_can_be_accessed_as_a_non_generic_property_dictionary() - => TestNonGenericPropertyValuesScalars(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestNonGenericPropertyValuesScalars(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Scalar_store_values_can_be_accessed_asynchronously_as_a_non_generic_property_dictionary() - => TestNonGenericPropertyValuesScalars(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestNonGenericPropertyValuesScalars(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestNonGenericPropertyValuesScalars( Func> getPropertyValues, @@ -238,11 +240,11 @@ public virtual Task Scalar_original_values_can_be_accessed_as_a_non_generic_prop [ConditionalFact] public virtual Task Scalar_store_values_can_be_accessed_as_a_non_generic_property_dictionary_using_IProperty() - => TestNonGenericPropertyValuesScalarsIProperty(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestNonGenericPropertyValuesScalarsIProperty(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Scalar_store_values_can_be_accessed_asynchronously_as_a_non_generic_property_dictionary_using_IProperty() - => TestNonGenericPropertyValuesScalarsIProperty(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestNonGenericPropertyValuesScalarsIProperty(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestNonGenericPropertyValuesScalarsIProperty( Func> getPropertyValues, @@ -291,11 +293,11 @@ public virtual Task Scalar_original_values_of_a_derived_object_can_be_accessed_a [ConditionalFact] public virtual Task Scalar_store_values_of_a_derived_object_can_be_accessed_as_a_non_generic_property_dictionary() - => TestNonGenericPropertyValuesDerivedScalars(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestNonGenericPropertyValuesDerivedScalars(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Scalar_store_values_of_a_derived_object_can_be_accessed_asynchronously_as_a_non_generic_property_dictionary() - => TestNonGenericPropertyValuesDerivedScalars(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestNonGenericPropertyValuesDerivedScalars(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestNonGenericPropertyValuesDerivedScalars( Func> getPropertyValues, @@ -332,11 +334,11 @@ private async Task TestNonGenericPropertyValuesDerivedScalars( [ConditionalFact] public virtual void Scalar_current_values_can_be_set_using_a_property_dictionary() - => TestSetPropertyValuesScalars(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestSetPropertyValuesScalars(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Scalar_original_values_can_be_set_using_a_property_dictionary() - => TestSetPropertyValuesScalars(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestSetPropertyValuesScalars(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestSetPropertyValuesScalars( Func, PropertyValues> getPropertyValues, @@ -365,11 +367,11 @@ private void TestSetPropertyValuesScalars( [ConditionalFact] public virtual void Scalar_current_values_can_be_set_using_a_property_dictionary_with_IProperty() - => TestSetPropertyValuesScalarsIProperty(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestSetPropertyValuesScalarsIProperty(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Scalar_original_values_can_be_set_using_a_property_dictionary_with_IProperty() - => TestSetPropertyValuesScalarsIProperty(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestSetPropertyValuesScalarsIProperty(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestSetPropertyValuesScalarsIProperty( Func, PropertyValues> getPropertyValues, @@ -398,11 +400,11 @@ private void TestSetPropertyValuesScalarsIProperty( [ConditionalFact] public virtual void Scalar_current_values_can_be_set_using_a_non_generic_property_dictionary() - => TestSetNonGenericPropertyValuesScalars(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestSetNonGenericPropertyValuesScalars(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Scalar_original_values_can_be_set_using_a_non_generic_property_dictionary() - => TestSetNonGenericPropertyValuesScalars(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestSetNonGenericPropertyValuesScalars(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestSetNonGenericPropertyValuesScalars( Func getPropertyValues, @@ -429,6 +431,96 @@ private void TestSetNonGenericPropertyValuesScalars( Assert.Equal("Pine Walk", getValue(entry, "Shadow2")); } + [ConditionalFact] + public virtual Task Complex_current_values_can_be_accessed_as_a_property_dictionary_using_IProperty() + => TestPropertyValuesComplexIProperty(e => Task.FromResult(e.CurrentValues), expectOriginalValues: false); + + [ConditionalFact] + public virtual Task Complex_original_values_can_be_accessed_as_a_property_dictionary_using_IProperty() + => TestPropertyValuesComplexIProperty(e => Task.FromResult(e.OriginalValues), expectOriginalValues: true); + + [ConditionalFact] + public virtual Task Complex_store_values_can_be_accessed_as_a_property_dictionary_using_IProperty() + => TestPropertyValuesComplexIProperty(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); + + [ConditionalFact] + public virtual Task Complex_store_values_can_be_accessed_asynchronously_as_a_property_dictionary_using_IProperty() + => TestPropertyValuesComplexIProperty(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); + + private async Task TestPropertyValuesComplexIProperty( + Func, Task> getPropertyValues, + bool expectOriginalValues) + { + using var context = CreateContext(); + var building = context.Set().Single(b => b.Name == "Building One"); + var original = Building.Create(building.BuildingId, building.Name!, building.Value); + var changed = Building.Create(building.BuildingId, building.Name!, building.Value, 1); + + building.Culture = changed.Culture; + building.Milk.Rating = changed.Milk.Rating; + building.Milk.License = changed.Milk.License; + building.Milk.Manufacturer = changed.Milk.Manufacturer; + + var entry = context.Entry(building); + var values = await getPropertyValues(entry); + + var cultureEntry = entry.ComplexProperty(e => e.Culture); + var cultureManufacturerEntry = cultureEntry.ComplexProperty(e => e.Manufacturer); + var cultureLicenseEntry = cultureEntry.ComplexProperty(e => e.License); + var cultureManTogEntry = cultureManufacturerEntry.ComplexProperty(e => e.Tog); + var cultureManTagEntry = cultureManufacturerEntry.ComplexProperty(e => e.Tag); + var cultureLicTogEntry = cultureLicenseEntry.ComplexProperty(e => e.Tog); + var cultureLicTagEntry = cultureLicenseEntry.ComplexProperty(e => e.Tag); + + var milkEntry = entry.ComplexProperty(e => e.Milk); + var milkManufacturerEntry = milkEntry.ComplexProperty(e => e.Manufacturer); + var milkLicenseEntry = milkEntry.ComplexProperty(e => e.License); + var milkManTogEntry = milkManufacturerEntry.ComplexProperty(e => e.Tog); + var milkManTagEntry = milkManufacturerEntry.ComplexProperty(e => e.Tag); + var milkLicTogEntry = milkLicenseEntry.ComplexProperty(e => e.Tog); + var milkLicTagEntry = milkLicenseEntry.ComplexProperty(e => e.Tag); + + var expected = expectOriginalValues ? original : changed; + Assert.Equal(expected.Culture.Rating, values[cultureEntry.Property(e => e.Rating).Metadata]); + Assert.Equal(expected.Culture.Species, values[cultureEntry.Property(e => e.Species).Metadata]); + Assert.Equal(expected.Culture.Subspecies, values[cultureEntry.Property(e => e.Subspecies).Metadata]); + Assert.Equal(expected.Culture.Validation, values[cultureEntry.Property(e => e.Validation).Metadata]); + Assert.Equal(expected.Culture.Manufacturer.Name, values[cultureManufacturerEntry.Property(e => e.Name).Metadata]); + Assert.Equal(expected.Culture.Manufacturer.Rating, values[cultureManufacturerEntry.Property(e => e.Rating).Metadata]); + Assert.Equal(expected.Culture.Manufacturer.Tog.Text, values[cultureManTogEntry.Property(e => e.Text).Metadata]); + Assert.Equal(expected.Culture.Manufacturer.Tag.Text, values[cultureManTagEntry.Property(e => e.Text).Metadata]); + Assert.Equal(expected.Culture.License.Title, values[cultureLicenseEntry.Property(e => e.Title).Metadata]); + Assert.Equal(expected.Culture.License.Charge, values[cultureLicenseEntry.Property(e => e.Charge).Metadata]); + Assert.Equal(expected.Culture.License.Tog.Text, values[cultureLicTogEntry.Property(e => e.Text).Metadata]); + Assert.Equal(expected.Culture.License.Tag.Text, values[cultureLicTagEntry.Property(e => e.Text).Metadata]); + Assert.Equal(expected.Milk.Rating, values[milkEntry.Property(e => e.Rating).Metadata]); + Assert.Equal(expected.Milk.Manufacturer.Name, values[milkManufacturerEntry.Property(e => e.Name).Metadata]); + Assert.Equal(expected.Milk.Manufacturer.Rating, values[milkManufacturerEntry.Property(e => e.Rating).Metadata]); + Assert.Equal(expected.Milk.Manufacturer.Tog.Text, values[milkManTogEntry.Property(e => e.Text).Metadata]); + Assert.Equal(expected.Milk.Manufacturer.Tag.Text, values[milkManTagEntry.Property(e => e.Text).Metadata]); + Assert.Equal(expected.Milk.License.Title, values[milkLicenseEntry.Property(e => e.Title).Metadata]); + Assert.Equal(expected.Milk.License.Charge, values[milkLicenseEntry.Property(e => e.Charge).Metadata]); + Assert.Equal(expected.Milk.License.Tog.Text, values[milkLicTogEntry.Property(e => e.Text).Metadata]); + Assert.Equal(expected.Milk.License.Tag.Text, values[milkLicTagEntry.Property(e => e.Text).Metadata]); + + if (expectOriginalValues) + { + Assert.Equal(original.Milk.Species, values[milkEntry.Property(e => e.Species).Metadata]); + Assert.Equal(original.Milk.Subspecies, values[milkEntry.Property(e => e.Subspecies).Metadata]); + Assert.Equal(original.Milk.Validation, values[milkEntry.Property(e => e.Validation).Metadata]); + } + else + { + Assert.Equal(building.Milk.Species, values[milkEntry.Property(e => e.Species).Metadata]); + Assert.Equal(building.Milk.Subspecies, values[milkEntry.Property(e => e.Subspecies).Metadata]); + Assert.Equal(building.Milk.Validation, values[milkEntry.Property(e => e.Validation).Metadata]); + } + + Assert.True(building.CreatedCalled); + Assert.True(building.InitializingCalled); + Assert.True(building.InitializedCalled); + } + [ConditionalFact] public virtual Task Current_values_can_be_copied_into_an_object() => TestPropertyValuesClone(e => Task.FromResult(e.CurrentValues), expectOriginalValues: false); @@ -439,11 +531,11 @@ public virtual Task Original_values_can_be_copied_into_an_object() [ConditionalFact] public virtual Task Store_values_can_be_copied_into_an_object() - => TestPropertyValuesClone(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestPropertyValuesClone(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_copied_into_an_object_asynchronously() - => TestPropertyValuesClone(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestPropertyValuesClone(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestPropertyValuesClone( Func, Task> getPropertyValues, @@ -485,11 +577,11 @@ public virtual Task Original_values_for_derived_object_can_be_copied_into_an_obj [ConditionalFact] public virtual Task Store_values_for_derived_object_can_be_copied_into_an_object() - => TestPropertyValuesDerivedClone(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestPropertyValuesDerivedClone(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_for_derived_object_can_be_copied_into_an_object_asynchronously() - => TestPropertyValuesDerivedClone(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestPropertyValuesDerivedClone(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestPropertyValuesDerivedClone( Func, Task> getPropertyValues, @@ -534,11 +626,11 @@ public virtual Task Original_values_for_join_entity_can_be_copied_into_an_object [ConditionalFact] public virtual Task Store_values_for_join_entity_can_be_copied_into_an_object() - => TestPropertyValuesJoinEntityClone(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestPropertyValuesJoinEntityClone(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_for_join_entity_can_be_copied_into_an_object_asynchronously() - => TestPropertyValuesJoinEntityClone(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestPropertyValuesJoinEntityClone(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestPropertyValuesJoinEntityClone( Func>, Task> getPropertyValues, @@ -582,11 +674,11 @@ public virtual Task Original_values_can_be_copied_non_generic_property_dictionar [ConditionalFact] public virtual Task Store_values_can_be_copied_non_generic_property_dictionary_into_an_object() - => TestNonGenericPropertyValuesClone(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestNonGenericPropertyValuesClone(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_copied_asynchronously_non_generic_property_dictionary_into_an_object() - => TestNonGenericPropertyValuesClone(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestNonGenericPropertyValuesClone(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestNonGenericPropertyValuesClone( Func> getPropertyValues, @@ -628,11 +720,11 @@ public virtual Task Original_values_can_be_copied_into_a_cloned_dictionary() [ConditionalFact] public virtual Task Store_values_can_be_copied_into_a_cloned_dictionary() - => TestPropertyValuesCloneToValues(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestPropertyValuesCloneToValues(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_copied_into_a_cloned_dictionary_asynchronously() - => TestPropertyValuesCloneToValues(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestPropertyValuesCloneToValues(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestPropertyValuesCloneToValues( Func, Task> getPropertyValues, @@ -770,7 +862,7 @@ public virtual void Using_bad_IProperty_instances_throws() var buildingValues = entry.CurrentValues; var clonedBuildingValues = buildingValues.Clone(); - var property = context.Model.FindEntityType(typeof(Whiteboard)).FindProperty(nameof(Whiteboard.AssetTag)); + var property = context.Model.FindEntityType(typeof(Whiteboard))!.FindProperty(nameof(Whiteboard.AssetTag))!; Assert.Equal( CoreStrings.PropertyDoesNotBelong("AssetTag", nameof(Whiteboard), nameof(Building)), @@ -854,8 +946,8 @@ public virtual void Using_bad_IProperty_instances_throws_derived() var values = entry.CurrentValues; var clonedValues = values.Clone(); - var shadowProperty = context.Model.FindEntityType(typeof(PastEmployee)).FindProperty("Shadow4"); - var termProperty = context.Model.FindEntityType(typeof(PastEmployee)).FindProperty(nameof(PastEmployee.TerminationDate)); + var shadowProperty = context.Model.FindEntityType(typeof(PastEmployee))!.FindProperty("Shadow4")!; + var termProperty = context.Model.FindEntityType(typeof(PastEmployee))!.FindProperty(nameof(PastEmployee.TerminationDate))!; Assert.Equal( CoreStrings.PropertyDoesNotBelong("Shadow4", nameof(PastEmployee), nameof(CurrentEmployee)), @@ -908,11 +1000,11 @@ public virtual Task Original_values_can_be_copied_into_a_non_generic_cloned_dict [ConditionalFact] public virtual Task Store_values_can_be_copied_into_a_non_generic_cloned_dictionary() - => TestNonGenericPropertyValuesCloneToValues(e => Task.FromResult(e.GetDatabaseValues()), expectOriginalValues: true); + => TestNonGenericPropertyValuesCloneToValues(e => Task.FromResult(e.GetDatabaseValues()!), expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_copied_asynchronously_into_a_non_generic_cloned_dictionary() - => TestNonGenericPropertyValuesCloneToValues(e => e.GetDatabaseValuesAsync(), expectOriginalValues: true); + => TestNonGenericPropertyValuesCloneToValues(e => e.GetDatabaseValuesAsync()!, expectOriginalValues: true); private async Task TestNonGenericPropertyValuesCloneToValues( Func> getPropertyValues, @@ -990,11 +1082,11 @@ public virtual Task Original_values_can_be_read_and_set_for_an_object_in_the_Del [ConditionalFact] public virtual Task Store_values_can_be_read_and_set_for_an_object_in_the_Deleted_state() => TestPropertyValuesPositiveForState( - e => Task.FromResult(e.GetDatabaseValues()), EntityState.Deleted, expectOriginalValues: true); + e => Task.FromResult(e.GetDatabaseValues()!), EntityState.Deleted, expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_read_and_set_for_an_object_in_the_Deleted_state_asynchronously() - => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync(), EntityState.Deleted, expectOriginalValues: true); + => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync()!, EntityState.Deleted, expectOriginalValues: true); [ConditionalFact] public virtual Task Current_values_can_be_read_and_set_for_an_object_in_the_Unchanged_state() @@ -1009,11 +1101,11 @@ public virtual Task Original_values_can_be_read_and_set_for_an_object_in_the_Unc [ConditionalFact] public virtual Task Store_values_can_be_read_and_set_for_an_object_in_the_Unchanged_state() => TestPropertyValuesPositiveForState( - e => Task.FromResult(e.GetDatabaseValues()), EntityState.Unchanged, expectOriginalValues: true); + e => Task.FromResult(e.GetDatabaseValues()!), EntityState.Unchanged, expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_read_and_set_for_an_object_in_the_Unchanged_state_asynchronously() - => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync(), EntityState.Unchanged, expectOriginalValues: true); + => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync()!, EntityState.Unchanged, expectOriginalValues: true); [ConditionalFact] public virtual Task Current_values_can_be_read_and_set_for_an_object_in_the_Modified_state() @@ -1028,11 +1120,11 @@ public virtual Task Original_values_can_be_read_and_set_for_an_object_in_the_Mod [ConditionalFact] public virtual Task Store_values_can_be_read_and_set_for_an_object_in_the_Modified_state() => TestPropertyValuesPositiveForState( - e => Task.FromResult(e.GetDatabaseValues()), EntityState.Modified, expectOriginalValues: true); + e => Task.FromResult(e.GetDatabaseValues()!), EntityState.Modified, expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_read_and_set_for_an_object_in_the_Modified_state_asynchronously() - => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync(), EntityState.Modified, expectOriginalValues: true); + => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync()!, EntityState.Modified, expectOriginalValues: true); [ConditionalFact] public virtual Task Current_values_can_be_read_and_set_for_an_object_in_the_Added_state() @@ -1047,11 +1139,11 @@ public virtual Task Original_values_can_be_read_or_set_for_an_object_in_the_Adde [ConditionalFact] public virtual Task Store_values_can_be_read_or_set_for_an_object_in_the_Added_state() => TestPropertyValuesPositiveForState( - e => Task.FromResult(e.GetDatabaseValues()), EntityState.Detached, expectOriginalValues: true); + e => Task.FromResult(e.GetDatabaseValues()!), EntityState.Detached, expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_read_or_set_for_an_object_in_the_Added_state_asynchronously() - => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync(), EntityState.Detached, expectOriginalValues: true); + => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync()!, EntityState.Detached, expectOriginalValues: true); [ConditionalFact] public virtual Task Current_values_can_be_read_or_set_for_a_Detached_object() @@ -1066,11 +1158,11 @@ public virtual Task Original_values_can_be_read_or_set_for_a_Detached_object() [ConditionalFact] public virtual Task Store_values_can_be_read_or_set_for_a_Detached_object() => TestPropertyValuesPositiveForState( - e => Task.FromResult(e.GetDatabaseValues()), EntityState.Detached, expectOriginalValues: true); + e => Task.FromResult(e.GetDatabaseValues()!), EntityState.Detached, expectOriginalValues: true); [ConditionalFact] public virtual Task Store_values_can_be_read_or_set_for_a_Detached_object_asynchronously() - => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync(), EntityState.Detached, expectOriginalValues: true); + => TestPropertyValuesPositiveForState(e => e.GetDatabaseValuesAsync()!, EntityState.Detached, expectOriginalValues: true); private async Task TestPropertyValuesPositiveForState( Func, Task> getPropertyValues, @@ -1194,11 +1286,11 @@ public async Task Reload_when_entity_deleted_in_store_can_happen_for_any_state(E [ConditionalFact] public virtual void Current_values_can_be_set_from_an_object_using_generic_dictionary() - => TestGenericObjectSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestGenericObjectSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_an_object_using_generic_dictionary() - => TestGenericObjectSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestGenericObjectSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestGenericObjectSetValues( Func, PropertyValues> getPropertyValues, @@ -1245,11 +1337,11 @@ private static void ValidateBuildingPropereties( [ConditionalFact] public virtual void Current_values_can_be_set_from_an_object_using_non_generic_dictionary() - => TestNonGenericObjectSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestNonGenericObjectSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_an_object_using_non_generic_dictionary() - => TestNonGenericObjectSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestNonGenericObjectSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestNonGenericObjectSetValues( Func getPropertyValues, @@ -1278,11 +1370,11 @@ private void TestNonGenericObjectSetValues( [ConditionalFact] public virtual void Current_values_can_be_set_from_DTO_object_using_non_generic_dictionary() - => TestNonGenericDtoSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestNonGenericDtoSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_DTO_object_using_non_generic_dictionary() - => TestNonGenericDtoSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestNonGenericDtoSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestNonGenericDtoSetValues( Func getPropertyValues, @@ -1314,11 +1406,11 @@ private void TestNonGenericDtoSetValues( [ConditionalFact] public virtual void Current_values_can_be_set_from_DTO_object_missing_key_using_non_generic_dictionary() - => TestNonGenericDtoNoKeySetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestNonGenericDtoNoKeySetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_DTO_object_missing_key_using_non_generic_dictionary() - => TestNonGenericDtoNoKeySetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestNonGenericDtoNoKeySetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestNonGenericDtoNoKeySetValues( Func getPropertyValues, @@ -1348,11 +1440,11 @@ private void TestNonGenericDtoNoKeySetValues( [ConditionalFact] public virtual void Current_values_can_be_set_from_dictionary() - => TestDictionarySetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestDictionarySetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_dictionary() - => TestDictionarySetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestDictionarySetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestDictionarySetValues( Func getPropertyValues, @@ -1386,11 +1478,11 @@ private void TestDictionarySetValues( [ConditionalFact] public virtual void Current_values_can_be_set_from_dictionary_typed_int() - => TestDictionarySetValuesTypedInt(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestDictionarySetValuesTypedInt(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_dictionary_typed_int() - => TestDictionarySetValuesTypedInt(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestDictionarySetValuesTypedInt(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestDictionarySetValuesTypedInt( Func getPropertyValues, @@ -1423,11 +1515,11 @@ private void TestDictionarySetValuesTypedInt( [ConditionalFact] public virtual void Current_values_can_be_set_from_dictionary_typed_string() - => TestDictionarySetValuesTypedString(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestDictionarySetValuesTypedString(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_dictionary_typed_string() - => TestDictionarySetValuesTypedString(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestDictionarySetValuesTypedString(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestDictionarySetValuesTypedString( Func getPropertyValues, @@ -1463,11 +1555,11 @@ private void TestDictionarySetValuesTypedString( [ConditionalFact] public virtual void Current_values_can_be_set_from_dictionary_some_missing() - => TestPartialDictionarySetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestPartialDictionarySetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_dictionary_some_missing() - => TestPartialDictionarySetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestPartialDictionarySetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestPartialDictionarySetValues( Func getPropertyValues, @@ -1499,11 +1591,11 @@ private void TestPartialDictionarySetValues( [ConditionalFact] public virtual void Current_values_can_be_set_from_one_generic_dictionary_to_another_generic_dictionary() - => TestGenericValuesSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestGenericValuesSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_one_generic_dictionary_to_another_generic_dictionary() - => TestGenericValuesSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestGenericValuesSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestGenericValuesSetValues( Func, PropertyValues> getPropertyValues, @@ -1535,11 +1627,11 @@ private void TestGenericValuesSetValues( [ConditionalFact] public virtual void Current_values_can_be_set_from_one_non_generic_dictionary_to_another_generic_dictionary() - => TestNonGenericValuesSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue); + => TestNonGenericValuesSetValues(e => e.CurrentValues, (e, n) => e.Property(n).CurrentValue!); [ConditionalFact] public virtual void Original_values_can_be_set_from_one_non_generic_dictionary_to_another_generic_dictionary() - => TestNonGenericValuesSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue); + => TestNonGenericValuesSetValues(e => e.OriginalValues, (e, n) => e.Property(n).OriginalValue!); private void TestNonGenericValuesSetValues( Func getPropertyValues, @@ -1606,7 +1698,7 @@ public virtual void Non_nullable_property_in_current_values_results_in_conceptua if (deleteOrphansTiming == CascadeTiming.Immediate) { - if (context.GetService().FindExtension().IsSensitiveDataLoggingEnabled) + if (context.GetService().FindExtension()!.IsSensitiveDataLoggingEnabled) { Assert.Equal( CoreStrings.PropertyConceptualNullSensitive( @@ -1650,7 +1742,7 @@ public virtual void Non_nullable_shadow_property_in_current_values_results_in_co if (deleteOrphansTiming == CascadeTiming.Immediate) { - if (context.GetService().FindExtension().IsSensitiveDataLoggingEnabled) + if (context.GetService().FindExtension()!.IsSensitiveDataLoggingEnabled) { Assert.Equal( CoreStrings.PropertyConceptualNullSensitive("Shadow1", nameof(Building), "{Shadow1: 11}"), @@ -1821,11 +1913,11 @@ public virtual Task Properties_for_original_values_returns_properties() [ConditionalFact] public virtual Task Properties_for_store_values_returns_properties() - => TestProperties(e => Task.FromResult(e.GetDatabaseValues())); + => TestProperties(e => Task.FromResult(e.GetDatabaseValues()!)); [ConditionalFact] public virtual Task Properties_for_store_values_returns_properties_asynchronously() - => TestProperties(e => e.GetDatabaseValuesAsync()); + => TestProperties(e => e.GetDatabaseValuesAsync()!); [ConditionalFact] public virtual Task Properties_for_cloned_dictionary_returns_properties() @@ -1836,27 +1928,67 @@ private async Task TestProperties(Func, Task().Single(b => b.Name == "Building One"); var buildingValues = await getPropertyValues(context.Entry(building)); + var properties = buildingValues.Properties.Select(p => (p.DeclaringType.DisplayName(), p.Name)).ToList(); - Assert.Equal( - new List - { - "BuildingId", - "Name", - "PrincipalMailRoomId", - "Shadow1", - "Shadow2", - "Value" - }, - buildingValues.Properties.Select(p => p.Name).ToList()); + if (context.Model.FindEntityType(typeof(Building))!.GetComplexProperties().Any()) + { + Assert.Equal( + [ + ("Building", "BuildingId"), + ("Building", "Name"), + ("Building", "PrincipalMailRoomId"), + ("Building", "Shadow1"), + ("Building", "Shadow2"), + ("Building", "Value"), + ("Building.Culture#Culture", "Rating"), + ("Building.Culture#Culture", "Species"), + ("Building.Culture#Culture", "Subspecies"), + ("Building.Culture#Culture", "Validation"), + ("Building.Culture#Culture.License#License", "Charge"), + ("Building.Culture#Culture.License#License", "Title"), + ("Building.Culture#Culture.License#License.Tag#Tag", "Text"), + ("Building.Culture#Culture.License#License.Tog#Tog", "Text"), + ("Building.Culture#Culture.Manufacturer#Manufacturer", "Name"), + ("Building.Culture#Culture.Manufacturer#Manufacturer", "Rating"), + ("Building.Culture#Culture.Manufacturer#Manufacturer.Tag#Tag", "Text"), + ("Building.Culture#Culture.Manufacturer#Manufacturer.Tog#Tog", "Text"), + ("Building.Milk#Milk", "Rating"), + ("Building.Milk#Milk", "Species"), + ("Building.Milk#Milk", "Subspecies"), + ("Building.Milk#Milk", "Validation"), + ("Building.Milk#Milk.License#License", "Charge"), + ("Building.Milk#Milk.License#License", "Title"), + ("Building.Milk#Milk.License#License.Tag#Tag", "Text"), + ("Building.Milk#Milk.License#License.Tog#Tog", "Text"), + ("Building.Milk#Milk.Manufacturer#Manufacturer", "Name"), + ("Building.Milk#Milk.Manufacturer#Manufacturer", "Rating"), + ("Building.Milk#Milk.Manufacturer#Manufacturer.Tag#Tag", "Text"), + ("Building.Milk#Milk.Manufacturer#Manufacturer.Tog#Tog", "Text"), + ], + properties); + } + else + { + Assert.Equal( + [ + ("Building", "BuildingId"), + ("Building", "Name"), + ("Building", "PrincipalMailRoomId"), + ("Building", "Shadow1"), + ("Building", "Shadow2"), + ("Building", "Value"), + ], + properties); + } } [ConditionalFact] public virtual Task GetDatabaseValues_for_entity_not_in_the_store_returns_null() - => GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation(e => Task.FromResult(e.GetDatabaseValues())); + => GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation(e => Task.FromResult(e.GetDatabaseValues()!)); [ConditionalFact] public virtual Task GetDatabaseValuesAsync_for_entity_not_in_the_store_returns_null() - => GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation(e => e.GetDatabaseValuesAsync()); + => GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation(e => e.GetDatabaseValuesAsync()!); private async Task GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation( Func> getPropertyValues) @@ -1875,11 +2007,11 @@ private async Task GetDatabaseValues_for_entity_not_in_the_store_returns_null_im [ConditionalFact] public virtual Task NonGeneric_GetDatabaseValues_for_entity_not_in_the_store_returns_null() => NonGeneric_GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation( - e => Task.FromResult(e.GetDatabaseValues())); + e => Task.FromResult(e.GetDatabaseValues()!)); [ConditionalFact] public virtual Task NonGeneric_GetDatabaseValuesAsync_for_entity_not_in_the_store_returns_null() - => NonGeneric_GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation(e => e.GetDatabaseValuesAsync()); + => NonGeneric_GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation(e => e.GetDatabaseValuesAsync()!); private async Task NonGeneric_GetDatabaseValues_for_entity_not_in_the_store_returns_null_implementation( Func> getPropertyValues) @@ -1902,11 +2034,11 @@ private async Task NonGeneric_GetDatabaseValues_for_entity_not_in_the_store_retu [ConditionalFact] public virtual Task GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null() => GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null_implementation( - e => Task.FromResult(e.GetDatabaseValues())); + e => Task.FromResult(e.GetDatabaseValues()!)); [ConditionalFact] public virtual Task GetDatabaseValuesAsync_for_derived_entity_not_in_the_store_returns_null() - => GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null_implementation(e => e.GetDatabaseValuesAsync()); + => GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null_implementation(e => e.GetDatabaseValuesAsync()!); private async Task GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null_implementation( Func> getPropertyValues) @@ -1928,12 +2060,12 @@ private async Task GetDatabaseValues_for_derived_entity_not_in_the_store_returns [ConditionalFact] public virtual Task NonGeneric_GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null() => NonGeneric_GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null_implementation( - e => Task.FromResult(e.GetDatabaseValues())); + e => Task.FromResult(e.GetDatabaseValues()!)); [ConditionalFact] public virtual Task NonGeneric_GetDatabaseValuesAsync_for_derived_entity_not_in_the_store_returns_null() => NonGeneric_GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null_implementation( - e => e.GetDatabaseValuesAsync()); + e => e.GetDatabaseValuesAsync()!); private async Task NonGeneric_GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null_implementation( Func> getPropertyValues) @@ -1955,11 +2087,11 @@ private async Task NonGeneric_GetDatabaseValues_for_derived_entity_not_in_the_st [ConditionalFact] public virtual Task GetDatabaseValues_for_the_wrong_type_in_the_store_returns_null() => GetDatabaseValues_for_the_wrong_type_in_the_store_returns_null_implementation( - e => Task.FromResult(e.GetDatabaseValues())); + e => Task.FromResult(e.GetDatabaseValues()!)); [ConditionalFact] public virtual Task GetDatabaseValuesAsync_for_the_wrong_type_in_the_store_returns_null() - => GetDatabaseValues_for_the_wrong_type_in_the_store_returns_null_implementation(e => e.GetDatabaseValuesAsync()); + => GetDatabaseValues_for_the_wrong_type_in_the_store_returns_null_implementation(e => e.GetDatabaseValuesAsync()!); private async Task GetDatabaseValues_for_the_wrong_type_in_the_store_returns_null_implementation( Func> getPropertyValues) @@ -1969,7 +2101,7 @@ private async Task GetDatabaseValues_for_the_wrong_type_in_the_store_returns_nul .OfType() .AsNoTracking() .OrderBy(e => e.EmployeeId) - .FirstOrDefault() + .FirstOrDefault()! .EmployeeId; var employee = (CurrentEmployee)context.Entry( @@ -1988,11 +2120,11 @@ private async Task GetDatabaseValues_for_the_wrong_type_in_the_store_returns_nul [ConditionalFact] public virtual Task NonGeneric_GetDatabaseValues_for_the_wrong_type_in_the_store_throws() => NonGeneric_GetDatabaseValues_for_the_wrong_type_in_the_store_throws_implementation( - e => Task.FromResult(e.GetDatabaseValues())); + e => Task.FromResult(e.GetDatabaseValues()!)); [ConditionalFact] public virtual Task NonGeneric_GetDatabaseValuesAsync_for_the_wrong_type_in_the_store_throws() - => NonGeneric_GetDatabaseValues_for_the_wrong_type_in_the_store_throws_implementation(e => e.GetDatabaseValuesAsync()); + => NonGeneric_GetDatabaseValues_for_the_wrong_type_in_the_store_throws_implementation(e => e.GetDatabaseValuesAsync()!); private async Task NonGeneric_GetDatabaseValues_for_the_wrong_type_in_the_store_throws_implementation( Func> getPropertyValues) @@ -2002,7 +2134,7 @@ private async Task NonGeneric_GetDatabaseValues_for_the_wrong_type_in_the_store_ .OfType() .AsNoTracking() .OrderBy(e => e.EmployeeId) - .FirstOrDefault() + .FirstOrDefault()! .EmployeeId; var employee = (CurrentEmployee)context.Entry( @@ -2021,11 +2153,11 @@ private async Task NonGeneric_GetDatabaseValues_for_the_wrong_type_in_the_store_ [ConditionalFact] public Task Store_values_really_are_store_values_not_current_or_original_values() => Store_values_really_are_store_values_not_current_or_original_values_implementation( - e => Task.FromResult(e.GetDatabaseValues())); + e => Task.FromResult(e.GetDatabaseValues()!)); [ConditionalFact] public Task Store_values_really_are_store_values_not_current_or_original_values_async() - => Store_values_really_are_store_values_not_current_or_original_values_implementation(e => e.GetDatabaseValuesAsync()); + => Store_values_really_are_store_values_not_current_or_original_values_implementation(e => e.GetDatabaseValuesAsync()!); private async Task Store_values_really_are_store_values_not_current_or_original_values_implementation( Func> getPropertyValues) @@ -2047,7 +2179,7 @@ public virtual void Setting_store_values_does_not_change_current_or_original_val using var context = CreateContext(); var building = context.Set().Single(b => b.Name == "Building One"); - var storeValues = context.Entry(building).GetDatabaseValues(); + var storeValues = context.Entry(building).GetDatabaseValues()!; storeValues["Name"] = "Bag End"; var currentValues = (Building)context.Entry(building).CurrentValues.ToObject(); @@ -2085,8 +2217,8 @@ protected abstract class Employee : UnMappedPersonBase protected class VirtualTeam : PropertyValuesBase { public int Id { get; set; } - public string TeamName { get; set; } - public ICollection Employees { get; set; } + public string? TeamName { get; set; } + public ICollection? Employees { get; set; } } protected class Building : PropertyValuesBase @@ -2095,24 +2227,64 @@ private Building() { } - public static Building Create(Guid buildingId, string name, decimal value) + public static Building Create(Guid buildingId, string name, decimal value, int? tag = null) => new() { BuildingId = buildingId, - Name = name, - Value = value + Name = name + tag, + Value = value + (tag ?? 0), + Culture = new Culture + { + License = new License + { + Charge = 1.0m + (tag ?? 0), + Tag = new Tag { Text = "Ta1" + tag }, + Title = "Ti1" + tag, + Tog = new Tog { Text = "To1" + tag } + }, + Manufacturer = new Manufacturer + { + Name = "M1" + tag, + Rating = 7 + (tag ?? 0), + Tag = new Tag { Text = "Ta2" + tag }, + Tog = new Tog { Text = "To2" + tag } + }, + Rating = 8 + (tag ?? 0), + Species = "S1" + tag, + Validation = false + }, + Milk = new Milk + { + License = new License + { + Charge = 1.0m + (tag ?? 0), + Tag = new Tag { Text = "Ta1" + tag }, + Title = "Ti1" + tag, + Tog = new Tog { Text = "To1" + tag } + }, + Manufacturer = new Manufacturer + { + Name = "M1" + tag, + Rating = 7 + (tag ?? 0), + Tag = new Tag { Text = "Ta2" + tag }, + Tog = new Tog { Text = "To2" + tag } + }, + Rating = 8 + (tag ?? 0), + Species = "S1" + tag, + Validation = false + } }; public Guid BuildingId { get; set; } - public string Name { get; set; } + public string? Name { get; set; } public decimal Value { get; set; } public virtual ICollection Offices { get; } = new List(); public virtual IList MailRooms { get; } = new List(); public int? PrincipalMailRoomId { get; set; } - public MailRoom PrincipalMailRoom { get; set; } + public MailRoom? PrincipalMailRoom { get; set; } - public string NotInModel { get; set; } + public string? NotInModel { get; set; } private string _noGetter = "NoGetter"; @@ -2126,17 +2298,66 @@ public string GetNoGetterValue() public string NoSetter => "NoSetter"; + + public Culture Culture { get; set; } + public required Milk Milk { get; set; } + } + + protected struct Culture + { + public string Species { get; set; } + public string? Subspecies { get; set; } + public int Rating { get; set; } + public bool? Validation { get; set; } + public Manufacturer Manufacturer { get; set; } + public License License { get; set; } + } + + protected class Milk + { + public string Species { get; set; } = null!; + public string? Subspecies { get; set; } + public int Rating { get; set; } + public bool? Validation { get; set; } + public Manufacturer Manufacturer { get; set; } = null!; + public License License { get; set; } + } + + protected class Manufacturer + { + public string? Name { get; set; } + public int Rating { get; set; } + public Tag Tag { get; set; } = null!; + public Tog Tog { get; set; } + } + + protected struct License + { + public string Title { get; set; } + public decimal Charge { get; set; } + public Tag Tag { get; set; } + public Tog Tog { get; set; } + } + + protected class Tag + { + public string? Text { get; set; } + } + + protected struct Tog + { + public string? Text { get; set; } } protected class BuildingDto { public Guid BuildingId { get; set; } - public string Name { get; set; } + public string? Name { get; set; } public decimal Value { get; set; } public int? PrincipalMailRoomId { get; set; } - public string NotInModel { get; set; } + public string? NotInModel { get; set; } private string _noGetter = "NoGetter"; @@ -2156,9 +2377,9 @@ public string NoSetter protected class BuildingDtoNoKey { - public string Name { get; set; } + public string? Name { get; set; } public decimal Value { get; set; } - public string Shadow2 { get; set; } + public string? Shadow2 { get; set; } } protected class MailRoom : PropertyValuesBase @@ -2166,51 +2387,51 @@ protected class MailRoom : PropertyValuesBase #pragma warning disable IDE1006 // Naming Styles public int id { get; set; } #pragma warning restore IDE1006 // Naming Styles - public Building Building { get; set; } + public Building? Building { get; set; } public Guid BuildingId { get; set; } } protected class Office : UnMappedOfficeBase { public Guid BuildingId { get; set; } - public Building Building { get; set; } + public Building? Building { get; set; } public IList WhiteBoards { get; } = new List(); } protected abstract class UnMappedOfficeBase : PropertyValuesBase { - public string Number { get; set; } - public string Description { get; set; } + public string? Number { get; set; } + public string? Description { get; set; } } protected class BuildingDetail : PropertyValuesBase { public Guid BuildingId { get; set; } - public Building Building { get; set; } - public string Details { get; set; } + public Building? Building { get; set; } + public string? Details { get; set; } } protected class WorkOrder : PropertyValuesBase { public int WorkOrderId { get; set; } public int EmployeeId { get; set; } - public Employee Employee { get; set; } - public string Details { get; set; } + public Employee? Employee { get; set; } + public string? Details { get; set; } } protected class Whiteboard : PropertyValuesBase { #pragma warning disable IDE1006 // Naming Styles - public byte[] iD { get; set; } + public byte[]? iD { get; set; } #pragma warning restore IDE1006 // Naming Styles - public string AssetTag { get; set; } - public Office Office { get; set; } + public string? AssetTag { get; set; } + public Office? Office { get; set; } } protected class UnMappedPersonBase : PropertyValuesBase { - public string FirstName { get; set; } - public string LastName { get; set; } + public string? FirstName { get; set; } + public string? LastName { get; set; } } protected class UnMappedOffice : Office @@ -2219,10 +2440,10 @@ protected class UnMappedOffice : Office protected class CurrentEmployee : Employee { - public CurrentEmployee Manager { get; set; } + public CurrentEmployee? Manager { get; set; } public decimal LeaveBalance { get; set; } - public Office Office { get; set; } - public ICollection VirtualTeams { get; set; } + public Office? Office { get; set; } + public ICollection? VirtualTeams { get; set; } } protected class PastEmployee : Employee @@ -2303,6 +2524,40 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b.Ignore(e => e.NotInModel); b.Property("Shadow1"); b.Property("Shadow2"); + + b.ComplexProperty( + e => e.Culture, b => + { + b.ComplexProperty( + e => e.License, b => + { + b.ComplexProperty(e => e.Tag); + b.ComplexProperty(e => e.Tog); + }); + b.ComplexProperty( + e => e.Manufacturer, b => + { + b.ComplexProperty(e => e.Tag); + b.ComplexProperty(e => e.Tog); + }); + }); + + b.ComplexProperty( + e => e.Milk, b => + { + b.ComplexProperty( + e => e.License, b => + { + b.ComplexProperty(e => e.Tag); + b.ComplexProperty(e => e.Tog); + }); + b.ComplexProperty( + e => e.Manufacturer, b => + { + b.ComplexProperty(e => e.Tag); + b.ComplexProperty(e => e.Tog); + }); + }); }); } diff --git a/test/EFCore.Specification.Tests/Query/AdHocAdvancedMappingsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocAdvancedMappingsQueryTestBase.cs new file mode 100644 index 00000000000..7782e297b48 --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/AdHocAdvancedMappingsQueryTestBase.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query; + +public abstract class AdHocAdvancedMappingsQueryTestBase : NonSharedModelTestBase +{ + protected override string StoreName + => "AdHocAdvancedMappingsQueryTests"; +} diff --git a/test/EFCore.Specification.Tests/Query/ComplexTypeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexTypeQueryTestBase.cs index ceb4a5d65af..1f45144803e 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexTypeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexTypeQueryTestBase.cs @@ -506,6 +506,292 @@ public virtual Task Union_two_different_struct_complex_type(bool async) async, ss => ss.Set().Select(c => c.ShippingAddress).Union(ss.Set().Select(c => c.BillingAddress))); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Project_same_nested_complex_type_twice_with_pushdown(bool async) + => AssertQuery( + async, + ss => (from c1 in ss.Set() + from c2 in ss.Set() + select new { BA1 = c1.BillingAddress, BA2 = c2.BillingAddress }) + .Distinct() + .Select(x => new { x.BA1, x.BA2 }), + elementSorter: e => (e.BA1.ZipCode, e.BA2.ZipCode), + elementAsserter: (e, a) => + { + AssertEqual(e.BA1, a.BA1); + AssertEqual(e.BA2, a.BA2); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Project_same_entity_with_nested_complex_type_twice_with_pushdown(bool async) + => AssertQuery( + async, + ss => (from c1 in ss.Set() + from c2 in ss.Set() + select new { c1, c2 }) + .Distinct() + .Select(x => new { x.c1, x.c2 }), + elementSorter: e => (e.c1.Id, e.c2.Id), + elementAsserter: (e, a) => + { + AssertEqual(e.c1, a.c1); + AssertEqual(e.c2, a.c2); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Project_same_nested_complex_type_twice_with_double_pushdown(bool async) + => AssertQuery( + async, + ss => (from c1 in ss.Set() + from c2 in ss.Set() + orderby c1.Id, c2.Id + select new { BA1 = c1.BillingAddress, BA2 = c2.BillingAddress }) + .Take(50) + .Distinct() + .Select(x => new { x.BA1, x.BA2 }), + elementSorter: e => (e.BA1.ZipCode, e.BA2.ZipCode), + elementAsserter: (e, a) => + { + AssertEqual(e.BA1, a.BA1); + AssertEqual(e.BA2, a.BA2); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Project_same_entity_with_nested_complex_type_twice_with_double_pushdown(bool async) + => AssertQuery( + async, + ss => (from c1 in ss.Set() + from c2 in ss.Set() + orderby c1.Id, c2.Id + select new { c1, c2 }) + .Take(50) + .Distinct() + .Select(x => new { x.c1, x.c2 }), + elementSorter: e => (e.c1.Id, e.c2.Id), + elementAsserter: (e, a) => + { + AssertEqual(e.c1, a.c1); + AssertEqual(e.c2, a.c2); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Project_same_struct_nested_complex_type_twice_with_pushdown(bool async) + => AssertQuery( + async, + ss => (from c1 in ss.Set() + from c2 in ss.Set() + select new { BA1 = c1.BillingAddress, BA2 = c2.BillingAddress }) + .Distinct() + .Select(x => new { x.BA1, x.BA2 }), + elementSorter: e => (e.BA1.ZipCode, e.BA2.ZipCode), + elementAsserter: (e, a) => + { + AssertEqual(e.BA1, a.BA1); + AssertEqual(e.BA2, a.BA2); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Project_same_entity_with_struct_nested_complex_type_twice_with_pushdown(bool async) + => AssertQuery( + async, + ss => (from c1 in ss.Set() + from c2 in ss.Set() + select new { c1, c2 }) + .Distinct() + .Select(x => new { x.c1, x.c2 }), + elementSorter: e => (e.c1.Id, e.c2.Id), + elementAsserter: (e, a) => + { + AssertEqual(e.c1, a.c1); + AssertEqual(e.c2, a.c2); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Project_same_struct_nested_complex_type_twice_with_double_pushdown(bool async) + => AssertQuery( + async, + ss => (from c1 in ss.Set() + from c2 in ss.Set() + orderby c1.Id, c2.Id + select new { BA1 = c1.BillingAddress, BA2 = c2.BillingAddress }) + .Take(50) + .Distinct() + .Select(x => new { x.BA1, x.BA2 }), + elementSorter: e => (e.BA1.ZipCode, e.BA2.ZipCode), + elementAsserter: (e, a) => + { + AssertEqual(e.BA1, a.BA1); + AssertEqual(e.BA2, a.BA2); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Project_same_entity_with_struct_nested_complex_type_twice_with_double_pushdown(bool async) + => AssertQuery( + async, + ss => (from c1 in ss.Set() + from c2 in ss.Set() + orderby c1.Id, c2.Id + select new { c1, c2 }) + .Take(50) + .Distinct() + .Select(x => new { x.c1, x.c2 }), + elementSorter: e => (e.c1.Id, e.c2.Id), + elementAsserter: (e, a) => + { + AssertEqual(e.c1, a.c1); + AssertEqual(e.c2, a.c2); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_of_same_entity_with_nested_complex_type_projected_twice_with_pushdown(bool async) + => AssertQuery( + async, + ss => (from c1 in ss.Set() + from c2 in ss.Set() + orderby c1.Id, c2.Id + select new { c1, c2 }) + .Union(from c1 in ss.Set() + from c2 in ss.Set() + orderby c1.Id, c2.Id + select new { c1, c2 }) + .OrderBy(x => x.c1.Id).ThenBy(x => x.c2.Id) + .Take(50) + .Select(x => new { x.c1, x.c2 }), + assertOrder: true, + elementAsserter: (e, a) => + { + AssertEqual(e.c1, a.c1); + AssertEqual(e.c2, a.c2); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_of_same_entity_with_nested_complex_type_projected_twice_with_double_pushdown(bool async) + => AssertQuery( + async, + ss => (from c1 in ss.Set() + from c2 in ss.Set() + orderby c1.Id, c2.Id + select new { c1, c2 }) + .Union(from c1 in ss.Set() + from c2 in ss.Set() + orderby c1.Id, c2.Id + select new { c1, c2 }) + .OrderBy(x => x.c1.Id).ThenBy(x => x.c2.Id) + .Take(50) + .Select(x => new { x.c1, x.c2 }) + .Distinct() + .OrderBy(x => x.c1.Id).ThenBy(x => x.c2.Id) + .Take(50) + .Select(x => new { x.c1, x.c2 }), + assertOrder: true, + elementAsserter: (e, a) => + { + AssertEqual(e.c1, a.c1); + AssertEqual(e.c2, a.c2); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_of_same_nested_complex_type_projected_twice_with_pushdown(bool async) + => AssertQuery( + async, + ss => (from c1 in ss.Set() + from c2 in ss.Set() + orderby c1.Id, c2.Id + select new { BA1 = c1.BillingAddress, BA2 = c2.BillingAddress }) + .Union(from c1 in ss.Set() + from c2 in ss.Set() + orderby c1.Id, c2.Id + select new { BA1 = c1.BillingAddress, BA2 = c2.BillingAddress }) + .OrderBy(x => x.BA1.ZipCode).ThenBy(x => x.BA2.ZipCode) + .Take(50) + .Select(x => new { x.BA1, x.BA2 }), + assertOrder: true, + elementAsserter: (e, a) => + { + AssertEqual(e.BA1, a.BA1); + AssertEqual(e.BA2, a.BA2); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_of_same_nested_complex_type_projected_twice_with_double_pushdown(bool async) + => AssertQuery( + async, + ss => (from c1 in ss.Set() + from c2 in ss.Set() + orderby c1.Id, c2.Id + select new { BA1 = c1.BillingAddress, BA2 = c2.BillingAddress }) + .Union(from c1 in ss.Set() + from c2 in ss.Set() + orderby c1.Id, c2.Id + select new { BA1 = c1.BillingAddress, BA2 = c2.BillingAddress }) + .OrderBy(x => x.BA1.ZipCode).ThenBy(x => x.BA2.ZipCode) + .Take(50) + .Select(x => new { x.BA1, x.BA2 }) + .Distinct() + .OrderBy(x => x.BA1.ZipCode).ThenBy(x => x.BA2.ZipCode) + .Take(50) + .Select(x => new { x.BA1, x.BA2 }), + assertOrder: true, + elementAsserter: (e, a) => + { + AssertEqual(e.BA1, a.BA1); + AssertEqual(e.BA2, a.BA2); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Same_entity_with_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(bool async) + => AssertQuery( + async, + ss => ss.Set().Select(x => new + { + x.Id, + Complex = (from c1 in ss.Set() + from c2 in ss.Set() + orderby c1.Id, c2.Id descending + select new { One = c1, Two = c2 }).FirstOrDefault() + }), + elementSorter: e => e.Id, + elementAsserter: (e, a) => + { + AssertEqual(e.Id, a.Id); + AssertEqual(e.Complex?.One, a.Complex?.One); + AssertEqual(e.Complex?.Two, a.Complex?.Two); + }); + + [ConditionalTheory(Skip = "issue #31376")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Same_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(bool async) + => AssertQuery( + async, + ss => ss.Set().Select(x => new + { + x.Id, + Complex = (from c1 in ss.Set() + from c2 in ss.Set() + orderby c1.Id, c2.Id descending + select new { One = c1.BillingAddress, Two = c2.BillingAddress }).FirstOrDefault() + }), + elementSorter: e => e.Id, + elementAsserter: (e, a) => + { + AssertEqual(e.Id, a.Id); + AssertEqual(e.Complex?.One, a.Complex?.One); + AssertEqual(e.Complex?.Two, a.Complex?.Two); + }); + protected DbContext CreateContext() => Fixture.CreateContext(); } diff --git a/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerOwnedTest.cs b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerOwnedTest.cs index 1b195ce764c..095bf9f446f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerOwnedTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerOwnedTest.cs @@ -572,6 +572,17 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b.Property(e => e.IdUserState).HasDefaultValue(1).HasSentinel(667); b.HasOne(e => e.UserState).WithMany(e => e.Users).HasForeignKey(e => e.IdUserState); }); + + modelBuilder.Entity( + b => + { + b.HasAlternateKey(e => e.AlternateId); + b.OwnsOne( + x => x.Child, b => + { + b.WithOwner(e => e.Parent).HasForeignKey(e => e.ParentId); + }); + }); } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTestBase.cs b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTestBase.cs index cff10b8897f..6719be3b0df 100644 --- a/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTestBase.cs +++ b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTestBase.cs @@ -11,6 +11,134 @@ protected GraphUpdatesSqlServerTestBase(TFixture fixture) { } + [ConditionalFact] // Issue #32638 + public virtual void Key_and_index_properties_use_appropriate_comparer() + { + var parent = new StringKeyAndIndexParent + { + Id = "Parent", + AlternateId = "Parent", + Index = "Index", + UniqueIndex = "UniqueIndex" + }; + + var child = new StringKeyAndIndexChild + { + Id = "Child", + ParentId = "parent" + }; + + using var context = CreateContext(); + context.AttachRange(parent, child); + + Assert.Same(child, parent.Child); + Assert.Same(parent, child.Parent); + + parent.Id = "parent"; + parent.AlternateId = "parent"; + parent.Index = "index"; + parent.UniqueIndex = "uniqueIndex"; + child.Id = "child"; + child.ParentId = "Parent"; + + context.ChangeTracker.DetectChanges(); + + var parentEntry = context.Entry(parent); + Assert.Equal(EntityState.Modified, parentEntry.State); + Assert.False(parentEntry.Property(e => e.Id).IsModified); + Assert.False(parentEntry.Property(e => e.AlternateId).IsModified); + Assert.True(parentEntry.Property(e => e.Index).IsModified); + Assert.True(parentEntry.Property(e => e.UniqueIndex).IsModified); + + var childEntry = context.Entry(child); + + if (childEntry.Metadata.IsOwned()) + { + Assert.Equal(EntityState.Modified, childEntry.State); + Assert.True(childEntry.Property(e => e.Id).IsModified); // Not a key for the owned type + Assert.False(childEntry.Property(e => e.ParentId).IsModified); + } + else + { + Assert.Equal(EntityState.Unchanged, childEntry.State); + Assert.False(childEntry.Property(e => e.Id).IsModified); + Assert.False(childEntry.Property(e => e.ParentId).IsModified); + } + + } + + protected class StringKeyAndIndexParent : NotifyingEntity + { + private string _id; + private string _alternateId; + private string _uniqueIndex; + private string _index; + private StringKeyAndIndexChild _child; + + public string Id + { + get => _id; + set => SetWithNotify(value, ref _id); + } + + public string AlternateId + { + get => _alternateId; + set => SetWithNotify(value, ref _alternateId); + } + + public string Index + { + get => _index; + set => SetWithNotify(value, ref _index); + } + + public string UniqueIndex + { + get => _uniqueIndex; + set => SetWithNotify(value, ref _uniqueIndex); + } + + public StringKeyAndIndexChild Child + { + get => _child; + set => SetWithNotify(value, ref _child); + } + } + + protected class StringKeyAndIndexChild : NotifyingEntity + { + private string _id; + private string _parentId; + private int _foo; + private StringKeyAndIndexParent _parent; + + public string Id + { + get => _id; + set => SetWithNotify(value, ref _id); + } + + public string ParentId + { + get => _parentId; + set => SetWithNotify(value, ref _parentId); + } + + + public int Foo + { + get => _foo; + set => SetWithNotify(value, ref _foo); + } + + public StringKeyAndIndexParent Parent + { + get => _parent; + set => SetWithNotify(value, ref _parent); + } + } + protected override IQueryable ModifyQueryRoot(IQueryable query) => query.AsSplitQuery(); @@ -59,6 +187,15 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.Entity().Property("CategoryId").HasDefaultValue(1); modelBuilder.Entity().Property(e => e.CategoryId).HasDefaultValue(2); + + modelBuilder.Entity( + b => + { + b.HasOne(e => e.Child) + .WithOne(e => e.Parent) + .HasForeignKey(e => e.ParentId) + .HasPrincipalKey(e => e.AlternateId); + }); } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsInfrastructureSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsInfrastructureSqlServerTest.cs index 7ce04a75909..831ebc159b0 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsInfrastructureSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsInfrastructureSqlServerTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Identity30.Data; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; using Microsoft.EntityFrameworkCore.TestModels.AspNetIdentity; @@ -17,6 +18,12 @@ public MigrationsInfrastructureSqlServerTest(MigrationsInfrastructureSqlServerFi { } + public override void Can_apply_all_migrations() // Issue #32826 + => Assert.Throws(() => base.Can_apply_all_migrations()); + + public override Task Can_apply_all_migrations_async() // Issue #32826 + => Assert.ThrowsAsync(() => base.Can_apply_all_migrations_async()); + public override void Can_generate_migration_from_initial_database_to_initial() { base.Can_generate_migration_from_initial_database_to_initial(); @@ -83,6 +90,7 @@ CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) CREATE TABLE [Table1] ( [Id] int NOT NULL, [Foo] int NOT NULL, + [Description] nvarchar(max) NOT NULL, CONSTRAINT [PK_Table1] PRIMARY KEY ([Id]) ); GO @@ -155,6 +163,57 @@ INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) COMMIT; GO +BEGIN TRANSACTION; +GO + +INSERT INTO Table1 (Id, Bar, Description) VALUES (-1, ' ', 'Value With + +Empty Lines') +GO + +INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'00000000000005_Migration5', N'7.0.0-test'); +GO + +COMMIT; +GO + +BEGIN TRANSACTION; +GO + +INSERT INTO Table1 (Id, Bar, Description) VALUES (-2, ' ', 'GO +Value With + +Empty Lines') +GO + +INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'00000000000006_Migration6', N'7.0.0-test'); +GO + +COMMIT; +GO + +BEGIN TRANSACTION; +GO + +INSERT INTO Table1 (Id, Bar, Description) VALUES (-3, ' ', 'GO +Value With + +GO + + +Empty Lines +GO') +GO + +INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'00000000000007_Migration7', N'7.0.0-test'); +GO + +COMMIT; +GO + """, Sql, @@ -180,6 +239,7 @@ CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) CREATE TABLE [Table1] ( [Id] int NOT NULL, [Foo] int NOT NULL, + [Description] nvarchar(max) NOT NULL, CONSTRAINT [PK_Table1] PRIMARY KEY ([Id]) ); GO @@ -231,6 +291,39 @@ INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) VALUES (N'00000000000004_Migration4', N'7.0.0-test'); GO +INSERT INTO Table1 (Id, Bar, Description) VALUES (-1, ' ', 'Value With + +Empty Lines') +GO + +INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'00000000000005_Migration5', N'7.0.0-test'); +GO + +INSERT INTO Table1 (Id, Bar, Description) VALUES (-2, ' ', 'GO +Value With + +Empty Lines') +GO + +INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'00000000000006_Migration6', N'7.0.0-test'); +GO + +INSERT INTO Table1 (Id, Bar, Description) VALUES (-3, ' ', 'GO +Value With + +GO + + +Empty Lines +GO') +GO + +INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'00000000000007_Migration7', N'7.0.0-test'); +GO + """, Sql, @@ -314,6 +407,7 @@ IF NOT EXISTS ( CREATE TABLE [Table1] ( [Id] int NOT NULL, [Foo] int NOT NULL, + [Description] nvarchar(max) NOT NULL, CONSTRAINT [PK_Table1] PRIMARY KEY ([Id]) ); END; @@ -435,6 +529,99 @@ INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) COMMIT; GO +BEGIN TRANSACTION; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'00000000000005_Migration5' +) +BEGIN + INSERT INTO Table1 (Id, Bar, Description) VALUES (-1, ' ', 'Value With + + Empty Lines') +END; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'00000000000005_Migration5' +) +BEGIN + INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) + VALUES (N'00000000000005_Migration5', N'7.0.0-test'); +END; +GO + +COMMIT; +GO + +BEGIN TRANSACTION; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'00000000000006_Migration6' +) +BEGIN + INSERT INTO Table1 (Id, Bar, Description) VALUES (-2, ' ', 'GO + Value With + + Empty Lines') +END; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'00000000000006_Migration6' +) +BEGIN + INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) + VALUES (N'00000000000006_Migration6', N'7.0.0-test'); +END; +GO + +COMMIT; +GO + +BEGIN TRANSACTION; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'00000000000007_Migration7' +) +BEGIN + INSERT INTO Table1 (Id, Bar, Description) VALUES (-3, ' ', 'GO + Value With + +END; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'00000000000007_Migration7' +) +BEGIN + + Empty Lines + GO') +END; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'00000000000007_Migration7' +) +BEGIN + INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) + VALUES (N'00000000000007_Migration7', N'7.0.0-test'); +END; +GO + +COMMIT; +GO + """, Sql, @@ -465,6 +652,7 @@ IF NOT EXISTS ( CREATE TABLE [Table1] ( [Id] int NOT NULL, [Foo] int NOT NULL, + [Description] nvarchar(max) NOT NULL, CONSTRAINT [PK_Table1] PRIMARY KEY ([Id]) ); END; @@ -565,6 +753,81 @@ INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) END; GO +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'00000000000005_Migration5' +) +BEGIN + INSERT INTO Table1 (Id, Bar, Description) VALUES (-1, ' ', 'Value With + + Empty Lines') +END; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'00000000000005_Migration5' +) +BEGIN + INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) + VALUES (N'00000000000005_Migration5', N'7.0.0-test'); +END; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'00000000000006_Migration6' +) +BEGIN + INSERT INTO Table1 (Id, Bar, Description) VALUES (-2, ' ', 'GO + Value With + + Empty Lines') +END; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'00000000000006_Migration6' +) +BEGIN + INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) + VALUES (N'00000000000006_Migration6', N'7.0.0-test'); +END; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'00000000000007_Migration7' +) +BEGIN + INSERT INTO Table1 (Id, Bar, Description) VALUES (-3, ' ', 'GO + Value With + +END; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'00000000000007_Migration7' +) +BEGIN + + Empty Lines + GO') +END; +GO + +IF NOT EXISTS ( + SELECT * FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'00000000000007_Migration7' +) +BEGIN + INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) + VALUES (N'00000000000007_Migration7', N'7.0.0-test'); +END; +GO + """, Sql, diff --git a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs index a2c64defd70..f8c618f88ca 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs @@ -9230,6 +9230,123 @@ await Test( AssertSql(); } + [ConditionalFact] + public override async Task Add_required_primitve_collection_to_existing_table() + { + await base.Add_required_primitve_collection_to_existing_table(); + + AssertSql( +""" +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'[]'; +"""); + } + + [ConditionalFact] + public override async Task Add_required_primitve_collection_with_custom_default_value_to_existing_table() + { + await base.Add_required_primitve_collection_with_custom_default_value_to_existing_table(); + + AssertSql( +""" +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'[1,2,3]'; +"""); + } + + [ConditionalFact] + public override async Task Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table() + { + await base.Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table_core("N'[3, 2, 1]'"); + + AssertSql( +""" +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT (N'[3, 2, 1]'); +"""); + } + + [ConditionalFact(Skip = "issue #33038")] + public override async Task Add_required_primitve_collection_with_custom_converter_to_existing_table() + { + await base.Add_required_primitve_collection_with_custom_converter_to_existing_table(); + + AssertSql( +""" +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'nothing'; +"""); + } + + [ConditionalFact] + public override async Task Add_required_primitve_collection_with_custom_converter_and_custom_default_value_to_existing_table() + { + await base.Add_required_primitve_collection_with_custom_converter_and_custom_default_value_to_existing_table(); + + AssertSql( +""" +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'some numbers'; +"""); + } + + [ConditionalFact] + public override async Task Add_optional_primitive_collection_to_existing_table() + { + await base.Add_optional_primitive_collection_to_existing_table(); + + AssertSql( +""" +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NULL; +"""); + } + + [ConditionalFact] + public override async Task Create_table_with_required_primitive_collection() + { + await base.Create_table_with_required_primitive_collection(); + + AssertSql( +""" +CREATE TABLE [Customers] ( + [Id] int NOT NULL IDENTITY, + [Name] nvarchar(max) NULL, + [Numbers] nvarchar(max) NOT NULL, + CONSTRAINT [PK_Customers] PRIMARY KEY ([Id]) +); +"""); + } + + [ConditionalFact] + public override async Task Create_table_with_optional_primitive_collection() + { + await base.Create_table_with_optional_primitive_collection(); + + AssertSql( +""" +CREATE TABLE [Customers] ( + [Id] int NOT NULL IDENTITY, + [Name] nvarchar(max) NULL, + [Numbers] nvarchar(max) NULL, + CONSTRAINT [PK_Customers] PRIMARY KEY ([Id]) +); +"""); + } + + public override async Task Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH() + { + await base.Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH(); + + AssertSql( +""" +CREATE TABLE [Contacts] ( + [Id] int NOT NULL IDENTITY, + [Discriminator] nvarchar(8) NOT NULL, + [Name] nvarchar(max) NULL, + [Number] int NULL, + [MyComplex_Prop] nvarchar(max) NULL, + [MyComplex_MyNestedComplex_Bar] datetime2 NULL, + [MyComplex_MyNestedComplex_Foo] int NULL, + CONSTRAINT [PK_Contacts] PRIMARY KEY ([Id]) +); +"""); + } + protected override string NonDefaultCollation => _nonDefaultCollation ??= GetDatabaseCollation() == "German_PhoneBook_CI_AS" ? "French_CI_AS" diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocAdvancedMappingsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocAdvancedMappingsQuerySqlServerTest.cs new file mode 100644 index 00000000000..122d76763bf --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocAdvancedMappingsQuerySqlServerTest.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query; + +public class AdHocAdvancedMappingsQuerySqlServerTest : AdHocAdvancedMappingsQueryRelationalTestBase +{ + protected override ITestStoreFactory TestStoreFactory + => SqlServerTestStoreFactory.Instance; + + public override async Task Two_similar_complex_properties_projected_with_split_query1() + { + await base.Two_similar_complex_properties_projected_with_split_query1(); + + AssertSql( +""" +SELECT [o].[Id] +FROM [Offers] AS [o] +ORDER BY [o].[Id] +""", + // + """ +SELECT [t].[Id], [t].[NestedId], [t].[OfferId], [t].[payment_brutto], [t].[payment_netto], [t].[Id0], [t].[payment_brutto0], [t].[payment_netto0], [o].[Id] +FROM [Offers] AS [o] +INNER JOIN ( + SELECT [v].[Id], [v].[NestedId], [v].[OfferId], [v].[payment_brutto], [v].[payment_netto], [n].[Id] AS [Id0], [n].[payment_brutto] AS [payment_brutto0], [n].[payment_netto] AS [payment_netto0] + FROM [Variation] AS [v] + LEFT JOIN [NestedEntity] AS [n] ON [v].[NestedId] = [n].[Id] +) AS [t] ON [o].[Id] = [t].[OfferId] +ORDER BY [o].[Id] +"""); + } + + public override async Task Two_similar_complex_properties_projected_with_split_query2() + { + await base.Two_similar_complex_properties_projected_with_split_query2(); + + AssertSql( +""" +SELECT TOP(2) [o].[Id] +FROM [Offers] AS [o] +WHERE [o].[Id] = 1 +ORDER BY [o].[Id] +""", + // + """ +SELECT [t0].[Id], [t0].[NestedId], [t0].[OfferId], [t0].[payment_brutto], [t0].[payment_netto], [t0].[Id0], [t0].[payment_brutto0], [t0].[payment_netto0], [t].[Id] +FROM ( + SELECT TOP(1) [o].[Id] + FROM [Offers] AS [o] + WHERE [o].[Id] = 1 +) AS [t] +INNER JOIN ( + SELECT [v].[Id], [v].[NestedId], [v].[OfferId], [v].[payment_brutto], [v].[payment_netto], [n].[Id] AS [Id0], [n].[payment_brutto] AS [payment_brutto0], [n].[payment_netto] AS [payment_netto0] + FROM [Variation] AS [v] + LEFT JOIN [NestedEntity] AS [n] ON [v].[NestedId] = [n].[Id] +) AS [t0] ON [t].[Id] = [t0].[OfferId] +ORDER BY [t].[Id] +"""); + } + + public override async Task Projecting_one_of_two_similar_complex_types_picks_the_correct_one() + { + await base.Projecting_one_of_two_similar_complex_types_picks_the_correct_one(); + + AssertSql( +""" +@__p_0='10' + +SELECT [a].[Id], [t].[Info_Created0] AS [Created] +FROM ( + SELECT TOP(@__p_0) [c].[Id], [b].[AId], [b].[Info_Created] AS [Info_Created0] + FROM [Cs] AS [c] + INNER JOIN [Bs] AS [b] ON [c].[BId] = [b].[Id] + WHERE [b].[AId] = 1 + ORDER BY [c].[Id] +) AS [t] +LEFT JOIN [As] AS [a] ON [t].[AId] = [a].[Id] +ORDER BY [t].[Id] +"""); + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexTypeQuerySqlServerTest.cs index 0f112c82460..cbaf021a01c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexTypeQuerySqlServerTest.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.TestModels.ComplexTypeModel; + namespace Microsoft.EntityFrameworkCore.Query; public class ComplexTypeQuerySqlServerTest : ComplexTypeQueryRelationalTestBase< @@ -740,6 +742,392 @@ public override async Task Union_two_different_struct_complex_type(bool async) AssertSql(); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filter_on_property_inside_complex_type_with_FromSql(bool async) + => AssertQuery( + async, + ss => ((DbSet)ss.Set()).FromSqlRaw( + """ +SELECT [c].[Id], [c].[Name], [c].[BillingAddress_AddressLine1], [c].[BillingAddress_AddressLine2], [c].[BillingAddress_ZipCode], [c].[BillingAddress_Country_Code], [c].[BillingAddress_Country_FullName], [c].[ShippingAddress_AddressLine1], [c].[ShippingAddress_AddressLine2], [c].[ShippingAddress_ZipCode], [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName] +FROM [Customer] AS [c] +WHERE [c].[ShippingAddress_ZipCode] = 7728 +"""), + ss => ss.Set().Where(c => c.ShippingAddress.ZipCode == 07728)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filter_on_property_inside_complex_type_after_subquery_with_FromSql(bool async) + => AssertQuery( + async, + ss => ((DbSet)ss.Set()).FromSql( + $""" + SELECT DISTINCT [t].[Id], [t].[Name], [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[ShippingAddress_AddressLine1], [t].[ShippingAddress_AddressLine2], [t].[ShippingAddress_ZipCode], [t].[ShippingAddress_Country_Code], [t].[ShippingAddress_Country_FullName] + FROM ( + SELECT [c].[Id], [c].[Name], [c].[BillingAddress_AddressLine1], [c].[BillingAddress_AddressLine2], [c].[BillingAddress_ZipCode], [c].[BillingAddress_Country_Code], [c].[BillingAddress_Country_FullName], [c].[ShippingAddress_AddressLine1], [c].[ShippingAddress_AddressLine2], [c].[ShippingAddress_ZipCode], [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName] + FROM [Customer] AS [c] + ORDER BY [c].[Id] + OFFSET {1} ROWS + ) AS [t] + WHERE [t].[ShippingAddress_ZipCode] = 7728 + """), + ss => ss.Set() + .OrderBy(c => c.Id) + .Skip(1) + .Distinct() + .Where(c => c.ShippingAddress.ZipCode == 07728)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Load_complex_type_after_subquery_on_entity_type_with_FromSql(bool async) + => AssertQuery( + async, + ss => ((DbSet)ss.Set()).FromSql( + $""" + SELECT DISTINCT [t].[Id], [t].[Name], [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[ShippingAddress_AddressLine1], [t].[ShippingAddress_AddressLine2], [t].[ShippingAddress_ZipCode], [t].[ShippingAddress_Country_Code], [t].[ShippingAddress_Country_FullName] + FROM ( + SELECT [c].[Id], [c].[Name], [c].[BillingAddress_AddressLine1], [c].[BillingAddress_AddressLine2], [c].[BillingAddress_ZipCode], [c].[BillingAddress_Country_Code], [c].[BillingAddress_Country_FullName], [c].[ShippingAddress_AddressLine1], [c].[ShippingAddress_AddressLine2], [c].[ShippingAddress_ZipCode], [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName] + FROM [Customer] AS [c] + ORDER BY [c].[Id] + OFFSET {1} ROWS + ) AS [t] + """), + ss => ss.Set() + .OrderBy(c => c.Id) + .Skip(1) + .Distinct()); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_complex_type_with_FromSql(bool async) + => AssertQuery( + async, + ss => ((DbSet)ss.Set()).FromSqlRaw( + """ + SELECT [c].[ShippingAddress_AddressLine1], [c].[ShippingAddress_AddressLine2], [c].[ShippingAddress_ZipCode], [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName] + FROM [Customer] AS [c] + """).Select(c => c.ShippingAddress), + ss => ss.Set().Select(c => c.ShippingAddress)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_nested_complex_type_with_FromSql(bool async) + => AssertQuery( + async, + ss => ((DbSet)ss.Set()).FromSqlRaw( + """ + SELECT [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName] + FROM [Customer] AS [c] + """).Select(c => c.ShippingAddress.Country), + ss => ss.Set().Select(c => c.ShippingAddress.Country)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_single_property_on_nested_complex_type_with_FromSql(bool async) + => AssertQuery( + async, + ss => ((DbSet)ss.Set()).FromSqlRaw( + """ + SELECT [c].[ShippingAddress_Country_FullName] + FROM [Customer] AS [c] + """).Select(c => c.ShippingAddress.Country.FullName), + ss => ss.Set().Select(c => c.ShippingAddress.Country.FullName)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_complex_type_Where_with_FromSql(bool async) + => AssertQuery( + async, + ss => ((DbSet)ss.Set()).FromSqlRaw( + """ + SELECT [c].[ShippingAddress_AddressLine1], [c].[ShippingAddress_AddressLine2], [c].[ShippingAddress_ZipCode], [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName] + FROM [Customer] AS [c] + WHERE [c].[ShippingAddress_ZipCode] = 7728 + """).Select(c => c.ShippingAddress).Where(a => a.ZipCode == 07728), + ss => ss.Set().Select(c => c.ShippingAddress).Where(a => a.ZipCode == 07728)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_complex_type_Distinct_with_FromSql(bool async) + => AssertQuery( + async, + ss => ((DbSet)ss.Set()).FromSqlRaw( + """ + SELECT DISTINCT [c].[ShippingAddress_AddressLine1], [c].[ShippingAddress_AddressLine2], [c].[ShippingAddress_ZipCode], [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName] + FROM [Customer] AS [c] + """).Select(c => c.ShippingAddress).Distinct(), + ss => ss.Set().Select(c => c.ShippingAddress).Distinct()); + + public override async Task Project_same_entity_with_nested_complex_type_twice_with_pushdown(bool async) + { + await base.Project_same_entity_with_nested_complex_type_twice_with_pushdown(async); + + AssertSql( +""" +SELECT [t].[Id], [t].[Name], [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[ShippingAddress_AddressLine1], [t].[ShippingAddress_AddressLine2], [t].[ShippingAddress_ZipCode], [t].[ShippingAddress_Country_Code], [t].[ShippingAddress_Country_FullName], [t].[Id0], [t].[Name0], [t].[BillingAddress_AddressLine10], [t].[BillingAddress_AddressLine20], [t].[BillingAddress_ZipCode0], [t].[BillingAddress_Country_Code0], [t].[BillingAddress_Country_FullName0], [t].[ShippingAddress_AddressLine10], [t].[ShippingAddress_AddressLine20], [t].[ShippingAddress_ZipCode0], [t].[ShippingAddress_Country_Code0], [t].[ShippingAddress_Country_FullName0] +FROM ( + SELECT DISTINCT [c].[Id], [c].[Name], [c].[BillingAddress_AddressLine1], [c].[BillingAddress_AddressLine2], [c].[BillingAddress_ZipCode], [c].[BillingAddress_Country_Code], [c].[BillingAddress_Country_FullName], [c].[ShippingAddress_AddressLine1], [c].[ShippingAddress_AddressLine2], [c].[ShippingAddress_ZipCode], [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName], [c0].[Id] AS [Id0], [c0].[Name] AS [Name0], [c0].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [c0].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [c0].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [c0].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [c0].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0], [c0].[ShippingAddress_AddressLine1] AS [ShippingAddress_AddressLine10], [c0].[ShippingAddress_AddressLine2] AS [ShippingAddress_AddressLine20], [c0].[ShippingAddress_ZipCode] AS [ShippingAddress_ZipCode0], [c0].[ShippingAddress_Country_Code] AS [ShippingAddress_Country_Code0], [c0].[ShippingAddress_Country_FullName] AS [ShippingAddress_Country_FullName0] + FROM [Customer] AS [c] + CROSS JOIN [Customer] AS [c0] +) AS [t] +"""); + } + + public override async Task Project_same_nested_complex_type_twice_with_pushdown(bool async) + { + await base.Project_same_nested_complex_type_twice_with_pushdown(async); + + AssertSql( +""" +SELECT [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[BillingAddress_AddressLine10], [t].[BillingAddress_AddressLine20], [t].[BillingAddress_ZipCode0], [t].[BillingAddress_Country_Code0], [t].[BillingAddress_Country_FullName0] +FROM ( + SELECT DISTINCT [c].[BillingAddress_AddressLine1], [c].[BillingAddress_AddressLine2], [c].[BillingAddress_ZipCode], [c].[BillingAddress_Country_Code], [c].[BillingAddress_Country_FullName], [c0].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [c0].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [c0].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [c0].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [c0].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0] + FROM [Customer] AS [c] + CROSS JOIN [Customer] AS [c0] +) AS [t] +"""); + } + + public override async Task Project_same_entity_with_nested_complex_type_twice_with_double_pushdown(bool async) + { + await base.Project_same_entity_with_nested_complex_type_twice_with_double_pushdown(async); + + AssertSql( +""" +@__p_0='50' + +SELECT [t0].[Id], [t0].[Name], [t0].[BillingAddress_AddressLine1], [t0].[BillingAddress_AddressLine2], [t0].[BillingAddress_ZipCode], [t0].[BillingAddress_Country_Code], [t0].[BillingAddress_Country_FullName], [t0].[ShippingAddress_AddressLine1], [t0].[ShippingAddress_AddressLine2], [t0].[ShippingAddress_ZipCode], [t0].[ShippingAddress_Country_Code], [t0].[ShippingAddress_Country_FullName], [t0].[Id0], [t0].[Name0], [t0].[BillingAddress_AddressLine10], [t0].[BillingAddress_AddressLine20], [t0].[BillingAddress_ZipCode0], [t0].[BillingAddress_Country_Code0], [t0].[BillingAddress_Country_FullName0], [t0].[ShippingAddress_AddressLine10], [t0].[ShippingAddress_AddressLine20], [t0].[ShippingAddress_ZipCode0], [t0].[ShippingAddress_Country_Code0], [t0].[ShippingAddress_Country_FullName0] +FROM ( + SELECT DISTINCT [t].[Id], [t].[Name], [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[ShippingAddress_AddressLine1], [t].[ShippingAddress_AddressLine2], [t].[ShippingAddress_ZipCode], [t].[ShippingAddress_Country_Code], [t].[ShippingAddress_Country_FullName], [t].[Id0], [t].[Name0], [t].[BillingAddress_AddressLine10], [t].[BillingAddress_AddressLine20], [t].[BillingAddress_ZipCode0], [t].[BillingAddress_Country_Code0], [t].[BillingAddress_Country_FullName0], [t].[ShippingAddress_AddressLine10], [t].[ShippingAddress_AddressLine20], [t].[ShippingAddress_ZipCode0], [t].[ShippingAddress_Country_Code0], [t].[ShippingAddress_Country_FullName0] + FROM ( + SELECT TOP(@__p_0) [c].[Id], [c].[Name], [c].[BillingAddress_AddressLine1], [c].[BillingAddress_AddressLine2], [c].[BillingAddress_ZipCode], [c].[BillingAddress_Country_Code], [c].[BillingAddress_Country_FullName], [c].[ShippingAddress_AddressLine1], [c].[ShippingAddress_AddressLine2], [c].[ShippingAddress_ZipCode], [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName], [c0].[Id] AS [Id0], [c0].[Name] AS [Name0], [c0].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [c0].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [c0].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [c0].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [c0].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0], [c0].[ShippingAddress_AddressLine1] AS [ShippingAddress_AddressLine10], [c0].[ShippingAddress_AddressLine2] AS [ShippingAddress_AddressLine20], [c0].[ShippingAddress_ZipCode] AS [ShippingAddress_ZipCode0], [c0].[ShippingAddress_Country_Code] AS [ShippingAddress_Country_Code0], [c0].[ShippingAddress_Country_FullName] AS [ShippingAddress_Country_FullName0] + FROM [Customer] AS [c] + CROSS JOIN [Customer] AS [c0] + ORDER BY [c].[Id], [c0].[Id] + ) AS [t] +) AS [t0] +"""); + } + + public override async Task Project_same_nested_complex_type_twice_with_double_pushdown(bool async) + { + await base.Project_same_nested_complex_type_twice_with_double_pushdown(async); + + AssertSql( +""" +@__p_0='50' + +SELECT [t0].[BillingAddress_AddressLine1], [t0].[BillingAddress_AddressLine2], [t0].[BillingAddress_ZipCode], [t0].[BillingAddress_Country_Code], [t0].[BillingAddress_Country_FullName], [t0].[BillingAddress_AddressLine10], [t0].[BillingAddress_AddressLine20], [t0].[BillingAddress_ZipCode0], [t0].[BillingAddress_Country_Code0], [t0].[BillingAddress_Country_FullName0] +FROM ( + SELECT DISTINCT [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[BillingAddress_AddressLine10], [t].[BillingAddress_AddressLine20], [t].[BillingAddress_ZipCode0], [t].[BillingAddress_Country_Code0], [t].[BillingAddress_Country_FullName0] + FROM ( + SELECT TOP(@__p_0) [c].[BillingAddress_AddressLine1], [c].[BillingAddress_AddressLine2], [c].[BillingAddress_ZipCode], [c].[BillingAddress_Country_Code], [c].[BillingAddress_Country_FullName], [c0].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [c0].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [c0].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [c0].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [c0].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0] + FROM [Customer] AS [c] + CROSS JOIN [Customer] AS [c0] + ORDER BY [c].[Id], [c0].[Id] + ) AS [t] +) AS [t0] +"""); + } + + public override async Task Project_same_entity_with_struct_nested_complex_type_twice_with_pushdown(bool async) + { + await base.Project_same_entity_with_struct_nested_complex_type_twice_with_pushdown(async); + + AssertSql( +""" +SELECT [t].[Id], [t].[Name], [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[ShippingAddress_AddressLine1], [t].[ShippingAddress_AddressLine2], [t].[ShippingAddress_ZipCode], [t].[ShippingAddress_Country_Code], [t].[ShippingAddress_Country_FullName], [t].[Id0], [t].[Name0], [t].[BillingAddress_AddressLine10], [t].[BillingAddress_AddressLine20], [t].[BillingAddress_ZipCode0], [t].[BillingAddress_Country_Code0], [t].[BillingAddress_Country_FullName0], [t].[ShippingAddress_AddressLine10], [t].[ShippingAddress_AddressLine20], [t].[ShippingAddress_ZipCode0], [t].[ShippingAddress_Country_Code0], [t].[ShippingAddress_Country_FullName0] +FROM ( + SELECT DISTINCT [v].[Id], [v].[Name], [v].[BillingAddress_AddressLine1], [v].[BillingAddress_AddressLine2], [v].[BillingAddress_ZipCode], [v].[BillingAddress_Country_Code], [v].[BillingAddress_Country_FullName], [v].[ShippingAddress_AddressLine1], [v].[ShippingAddress_AddressLine2], [v].[ShippingAddress_ZipCode], [v].[ShippingAddress_Country_Code], [v].[ShippingAddress_Country_FullName], [v0].[Id] AS [Id0], [v0].[Name] AS [Name0], [v0].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [v0].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [v0].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [v0].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [v0].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0], [v0].[ShippingAddress_AddressLine1] AS [ShippingAddress_AddressLine10], [v0].[ShippingAddress_AddressLine2] AS [ShippingAddress_AddressLine20], [v0].[ShippingAddress_ZipCode] AS [ShippingAddress_ZipCode0], [v0].[ShippingAddress_Country_Code] AS [ShippingAddress_Country_Code0], [v0].[ShippingAddress_Country_FullName] AS [ShippingAddress_Country_FullName0] + FROM [ValuedCustomer] AS [v] + CROSS JOIN [ValuedCustomer] AS [v0] +) AS [t] +"""); + } + + public override async Task Project_same_struct_nested_complex_type_twice_with_pushdown(bool async) + { + await base.Project_same_struct_nested_complex_type_twice_with_pushdown(async); + + AssertSql( +""" +SELECT [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[BillingAddress_AddressLine10], [t].[BillingAddress_AddressLine20], [t].[BillingAddress_ZipCode0], [t].[BillingAddress_Country_Code0], [t].[BillingAddress_Country_FullName0] +FROM ( + SELECT DISTINCT [v].[BillingAddress_AddressLine1], [v].[BillingAddress_AddressLine2], [v].[BillingAddress_ZipCode], [v].[BillingAddress_Country_Code], [v].[BillingAddress_Country_FullName], [v0].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [v0].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [v0].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [v0].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [v0].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0] + FROM [ValuedCustomer] AS [v] + CROSS JOIN [ValuedCustomer] AS [v0] +) AS [t] +"""); + } + + public override async Task Project_same_entity_with_struct_nested_complex_type_twice_with_double_pushdown(bool async) + { + await base.Project_same_entity_with_struct_nested_complex_type_twice_with_double_pushdown(async); + + AssertSql( +""" +@__p_0='50' + +SELECT [t0].[Id], [t0].[Name], [t0].[BillingAddress_AddressLine1], [t0].[BillingAddress_AddressLine2], [t0].[BillingAddress_ZipCode], [t0].[BillingAddress_Country_Code], [t0].[BillingAddress_Country_FullName], [t0].[ShippingAddress_AddressLine1], [t0].[ShippingAddress_AddressLine2], [t0].[ShippingAddress_ZipCode], [t0].[ShippingAddress_Country_Code], [t0].[ShippingAddress_Country_FullName], [t0].[Id0], [t0].[Name0], [t0].[BillingAddress_AddressLine10], [t0].[BillingAddress_AddressLine20], [t0].[BillingAddress_ZipCode0], [t0].[BillingAddress_Country_Code0], [t0].[BillingAddress_Country_FullName0], [t0].[ShippingAddress_AddressLine10], [t0].[ShippingAddress_AddressLine20], [t0].[ShippingAddress_ZipCode0], [t0].[ShippingAddress_Country_Code0], [t0].[ShippingAddress_Country_FullName0] +FROM ( + SELECT DISTINCT [t].[Id], [t].[Name], [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[ShippingAddress_AddressLine1], [t].[ShippingAddress_AddressLine2], [t].[ShippingAddress_ZipCode], [t].[ShippingAddress_Country_Code], [t].[ShippingAddress_Country_FullName], [t].[Id0], [t].[Name0], [t].[BillingAddress_AddressLine10], [t].[BillingAddress_AddressLine20], [t].[BillingAddress_ZipCode0], [t].[BillingAddress_Country_Code0], [t].[BillingAddress_Country_FullName0], [t].[ShippingAddress_AddressLine10], [t].[ShippingAddress_AddressLine20], [t].[ShippingAddress_ZipCode0], [t].[ShippingAddress_Country_Code0], [t].[ShippingAddress_Country_FullName0] + FROM ( + SELECT TOP(@__p_0) [v].[Id], [v].[Name], [v].[BillingAddress_AddressLine1], [v].[BillingAddress_AddressLine2], [v].[BillingAddress_ZipCode], [v].[BillingAddress_Country_Code], [v].[BillingAddress_Country_FullName], [v].[ShippingAddress_AddressLine1], [v].[ShippingAddress_AddressLine2], [v].[ShippingAddress_ZipCode], [v].[ShippingAddress_Country_Code], [v].[ShippingAddress_Country_FullName], [v0].[Id] AS [Id0], [v0].[Name] AS [Name0], [v0].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [v0].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [v0].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [v0].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [v0].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0], [v0].[ShippingAddress_AddressLine1] AS [ShippingAddress_AddressLine10], [v0].[ShippingAddress_AddressLine2] AS [ShippingAddress_AddressLine20], [v0].[ShippingAddress_ZipCode] AS [ShippingAddress_ZipCode0], [v0].[ShippingAddress_Country_Code] AS [ShippingAddress_Country_Code0], [v0].[ShippingAddress_Country_FullName] AS [ShippingAddress_Country_FullName0] + FROM [ValuedCustomer] AS [v] + CROSS JOIN [ValuedCustomer] AS [v0] + ORDER BY [v].[Id], [v0].[Id] + ) AS [t] +) AS [t0] +"""); + } + + public override async Task Project_same_struct_nested_complex_type_twice_with_double_pushdown(bool async) + { + await base.Project_same_struct_nested_complex_type_twice_with_double_pushdown(async); + + AssertSql( +""" +@__p_0='50' + +SELECT [t0].[BillingAddress_AddressLine1], [t0].[BillingAddress_AddressLine2], [t0].[BillingAddress_ZipCode], [t0].[BillingAddress_Country_Code], [t0].[BillingAddress_Country_FullName], [t0].[BillingAddress_AddressLine10], [t0].[BillingAddress_AddressLine20], [t0].[BillingAddress_ZipCode0], [t0].[BillingAddress_Country_Code0], [t0].[BillingAddress_Country_FullName0] +FROM ( + SELECT DISTINCT [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[BillingAddress_AddressLine10], [t].[BillingAddress_AddressLine20], [t].[BillingAddress_ZipCode0], [t].[BillingAddress_Country_Code0], [t].[BillingAddress_Country_FullName0] + FROM ( + SELECT TOP(@__p_0) [v].[BillingAddress_AddressLine1], [v].[BillingAddress_AddressLine2], [v].[BillingAddress_ZipCode], [v].[BillingAddress_Country_Code], [v].[BillingAddress_Country_FullName], [v0].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [v0].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [v0].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [v0].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [v0].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0] + FROM [ValuedCustomer] AS [v] + CROSS JOIN [ValuedCustomer] AS [v0] + ORDER BY [v].[Id], [v0].[Id] + ) AS [t] +) AS [t0] +"""); + } + + public override async Task Union_of_same_entity_with_nested_complex_type_projected_twice_with_pushdown(bool async) + { + await base.Union_of_same_entity_with_nested_complex_type_projected_twice_with_pushdown(async); + + AssertSql( +""" +@__p_0='50' + +SELECT TOP(@__p_0) [t].[Id], [t].[Name], [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[ShippingAddress_AddressLine1], [t].[ShippingAddress_AddressLine2], [t].[ShippingAddress_ZipCode], [t].[ShippingAddress_Country_Code], [t].[ShippingAddress_Country_FullName], [t].[Id0], [t].[Name0], [t].[BillingAddress_AddressLine10], [t].[BillingAddress_AddressLine20], [t].[BillingAddress_ZipCode0], [t].[BillingAddress_Country_Code0], [t].[BillingAddress_Country_FullName0], [t].[ShippingAddress_AddressLine10], [t].[ShippingAddress_AddressLine20], [t].[ShippingAddress_ZipCode0], [t].[ShippingAddress_Country_Code0], [t].[ShippingAddress_Country_FullName0] +FROM ( + SELECT [c].[Id], [c].[Name], [c].[BillingAddress_AddressLine1], [c].[BillingAddress_AddressLine2], [c].[BillingAddress_ZipCode], [c].[BillingAddress_Country_Code], [c].[BillingAddress_Country_FullName], [c].[ShippingAddress_AddressLine1], [c].[ShippingAddress_AddressLine2], [c].[ShippingAddress_ZipCode], [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName], [c0].[Id] AS [Id0], [c0].[Name] AS [Name0], [c0].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [c0].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [c0].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [c0].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [c0].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0], [c0].[ShippingAddress_AddressLine1] AS [ShippingAddress_AddressLine10], [c0].[ShippingAddress_AddressLine2] AS [ShippingAddress_AddressLine20], [c0].[ShippingAddress_ZipCode] AS [ShippingAddress_ZipCode0], [c0].[ShippingAddress_Country_Code] AS [ShippingAddress_Country_Code0], [c0].[ShippingAddress_Country_FullName] AS [ShippingAddress_Country_FullName0] + FROM [Customer] AS [c] + CROSS JOIN [Customer] AS [c0] + UNION + SELECT [c1].[Id], [c1].[Name], [c1].[BillingAddress_AddressLine1], [c1].[BillingAddress_AddressLine2], [c1].[BillingAddress_ZipCode], [c1].[BillingAddress_Country_Code], [c1].[BillingAddress_Country_FullName], [c1].[ShippingAddress_AddressLine1], [c1].[ShippingAddress_AddressLine2], [c1].[ShippingAddress_ZipCode], [c1].[ShippingAddress_Country_Code], [c1].[ShippingAddress_Country_FullName], [c2].[Id] AS [Id0], [c2].[Name] AS [Name0], [c2].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [c2].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [c2].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [c2].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [c2].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0], [c2].[ShippingAddress_AddressLine1] AS [ShippingAddress_AddressLine10], [c2].[ShippingAddress_AddressLine2] AS [ShippingAddress_AddressLine20], [c2].[ShippingAddress_ZipCode] AS [ShippingAddress_ZipCode0], [c2].[ShippingAddress_Country_Code] AS [ShippingAddress_Country_Code0], [c2].[ShippingAddress_Country_FullName] AS [ShippingAddress_Country_FullName0] + FROM [Customer] AS [c1] + CROSS JOIN [Customer] AS [c2] +) AS [t] +ORDER BY [t].[Id], [t].[Id0] +"""); + } + + public override async Task Union_of_same_entity_with_nested_complex_type_projected_twice_with_double_pushdown(bool async) + { + await base.Union_of_same_entity_with_nested_complex_type_projected_twice_with_double_pushdown(async); + + AssertSql( +""" +@__p_0='50' + +SELECT TOP(@__p_0) [t1].[Id], [t1].[Name], [t1].[BillingAddress_AddressLine1], [t1].[BillingAddress_AddressLine2], [t1].[BillingAddress_ZipCode], [t1].[BillingAddress_Country_Code], [t1].[BillingAddress_Country_FullName], [t1].[ShippingAddress_AddressLine1], [t1].[ShippingAddress_AddressLine2], [t1].[ShippingAddress_ZipCode], [t1].[ShippingAddress_Country_Code], [t1].[ShippingAddress_Country_FullName], [t1].[Id0], [t1].[Name0], [t1].[BillingAddress_AddressLine10], [t1].[BillingAddress_AddressLine20], [t1].[BillingAddress_ZipCode0], [t1].[BillingAddress_Country_Code0], [t1].[BillingAddress_Country_FullName0], [t1].[ShippingAddress_AddressLine10], [t1].[ShippingAddress_AddressLine20], [t1].[ShippingAddress_ZipCode0], [t1].[ShippingAddress_Country_Code0], [t1].[ShippingAddress_Country_FullName0] +FROM ( + SELECT DISTINCT [t0].[Id], [t0].[Name], [t0].[BillingAddress_AddressLine1], [t0].[BillingAddress_AddressLine2], [t0].[BillingAddress_ZipCode], [t0].[BillingAddress_Country_Code], [t0].[BillingAddress_Country_FullName], [t0].[ShippingAddress_AddressLine1], [t0].[ShippingAddress_AddressLine2], [t0].[ShippingAddress_ZipCode], [t0].[ShippingAddress_Country_Code], [t0].[ShippingAddress_Country_FullName], [t0].[Id0], [t0].[Name0], [t0].[BillingAddress_AddressLine10], [t0].[BillingAddress_AddressLine20], [t0].[BillingAddress_ZipCode0], [t0].[BillingAddress_Country_Code0], [t0].[BillingAddress_Country_FullName0], [t0].[ShippingAddress_AddressLine10], [t0].[ShippingAddress_AddressLine20], [t0].[ShippingAddress_ZipCode0], [t0].[ShippingAddress_Country_Code0], [t0].[ShippingAddress_Country_FullName0] + FROM ( + SELECT TOP(@__p_0) [t].[Id], [t].[Name], [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[ShippingAddress_AddressLine1], [t].[ShippingAddress_AddressLine2], [t].[ShippingAddress_ZipCode], [t].[ShippingAddress_Country_Code], [t].[ShippingAddress_Country_FullName], [t].[Id0], [t].[Name0], [t].[BillingAddress_AddressLine10], [t].[BillingAddress_AddressLine20], [t].[BillingAddress_ZipCode0], [t].[BillingAddress_Country_Code0], [t].[BillingAddress_Country_FullName0], [t].[ShippingAddress_AddressLine10], [t].[ShippingAddress_AddressLine20], [t].[ShippingAddress_ZipCode0], [t].[ShippingAddress_Country_Code0], [t].[ShippingAddress_Country_FullName0] + FROM ( + SELECT [c].[Id], [c].[Name], [c].[BillingAddress_AddressLine1], [c].[BillingAddress_AddressLine2], [c].[BillingAddress_ZipCode], [c].[BillingAddress_Country_Code], [c].[BillingAddress_Country_FullName], [c].[ShippingAddress_AddressLine1], [c].[ShippingAddress_AddressLine2], [c].[ShippingAddress_ZipCode], [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName], [c0].[Id] AS [Id0], [c0].[Name] AS [Name0], [c0].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [c0].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [c0].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [c0].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [c0].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0], [c0].[ShippingAddress_AddressLine1] AS [ShippingAddress_AddressLine10], [c0].[ShippingAddress_AddressLine2] AS [ShippingAddress_AddressLine20], [c0].[ShippingAddress_ZipCode] AS [ShippingAddress_ZipCode0], [c0].[ShippingAddress_Country_Code] AS [ShippingAddress_Country_Code0], [c0].[ShippingAddress_Country_FullName] AS [ShippingAddress_Country_FullName0] + FROM [Customer] AS [c] + CROSS JOIN [Customer] AS [c0] + UNION + SELECT [c1].[Id], [c1].[Name], [c1].[BillingAddress_AddressLine1], [c1].[BillingAddress_AddressLine2], [c1].[BillingAddress_ZipCode], [c1].[BillingAddress_Country_Code], [c1].[BillingAddress_Country_FullName], [c1].[ShippingAddress_AddressLine1], [c1].[ShippingAddress_AddressLine2], [c1].[ShippingAddress_ZipCode], [c1].[ShippingAddress_Country_Code], [c1].[ShippingAddress_Country_FullName], [c2].[Id] AS [Id0], [c2].[Name] AS [Name0], [c2].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [c2].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [c2].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [c2].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [c2].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0], [c2].[ShippingAddress_AddressLine1] AS [ShippingAddress_AddressLine10], [c2].[ShippingAddress_AddressLine2] AS [ShippingAddress_AddressLine20], [c2].[ShippingAddress_ZipCode] AS [ShippingAddress_ZipCode0], [c2].[ShippingAddress_Country_Code] AS [ShippingAddress_Country_Code0], [c2].[ShippingAddress_Country_FullName] AS [ShippingAddress_Country_FullName0] + FROM [Customer] AS [c1] + CROSS JOIN [Customer] AS [c2] + ) AS [t] + ORDER BY [t].[Id], [t].[Id0] + ) AS [t0] +) AS [t1] +ORDER BY [t1].[Id], [t1].[Id0] +"""); + } + + public override async Task Union_of_same_nested_complex_type_projected_twice_with_pushdown(bool async) + { + await base.Union_of_same_nested_complex_type_projected_twice_with_pushdown(async); + + AssertSql( +""" +@__p_0='50' + +SELECT TOP(@__p_0) [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[BillingAddress_AddressLine10], [t].[BillingAddress_AddressLine20], [t].[BillingAddress_ZipCode0], [t].[BillingAddress_Country_Code0], [t].[BillingAddress_Country_FullName0] +FROM ( + SELECT [c].[BillingAddress_AddressLine1], [c].[BillingAddress_AddressLine2], [c].[BillingAddress_ZipCode], [c].[BillingAddress_Country_Code], [c].[BillingAddress_Country_FullName], [c0].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [c0].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [c0].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [c0].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [c0].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0] + FROM [Customer] AS [c] + CROSS JOIN [Customer] AS [c0] + UNION + SELECT [c1].[BillingAddress_AddressLine1], [c1].[BillingAddress_AddressLine2], [c1].[BillingAddress_ZipCode], [c1].[BillingAddress_Country_Code], [c1].[BillingAddress_Country_FullName], [c2].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [c2].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [c2].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [c2].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [c2].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0] + FROM [Customer] AS [c1] + CROSS JOIN [Customer] AS [c2] +) AS [t] +ORDER BY [t].[BillingAddress_ZipCode], [t].[BillingAddress_ZipCode0] +"""); + } + + public override async Task Union_of_same_nested_complex_type_projected_twice_with_double_pushdown(bool async) + { + await base.Union_of_same_nested_complex_type_projected_twice_with_double_pushdown(async); + + AssertSql( +""" +@__p_0='50' + +SELECT TOP(@__p_0) [t1].[BillingAddress_AddressLine1], [t1].[BillingAddress_AddressLine2], [t1].[BillingAddress_ZipCode], [t1].[BillingAddress_Country_Code], [t1].[BillingAddress_Country_FullName], [t1].[BillingAddress_AddressLine10], [t1].[BillingAddress_AddressLine20], [t1].[BillingAddress_ZipCode0], [t1].[BillingAddress_Country_Code0], [t1].[BillingAddress_Country_FullName0] +FROM ( + SELECT DISTINCT [t0].[BillingAddress_AddressLine1], [t0].[BillingAddress_AddressLine2], [t0].[BillingAddress_ZipCode], [t0].[BillingAddress_Country_Code], [t0].[BillingAddress_Country_FullName], [t0].[BillingAddress_AddressLine10], [t0].[BillingAddress_AddressLine20], [t0].[BillingAddress_ZipCode0], [t0].[BillingAddress_Country_Code0], [t0].[BillingAddress_Country_FullName0] + FROM ( + SELECT TOP(@__p_0) [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[BillingAddress_AddressLine10], [t].[BillingAddress_AddressLine20], [t].[BillingAddress_ZipCode0], [t].[BillingAddress_Country_Code0], [t].[BillingAddress_Country_FullName0] + FROM ( + SELECT [c].[BillingAddress_AddressLine1], [c].[BillingAddress_AddressLine2], [c].[BillingAddress_ZipCode], [c].[BillingAddress_Country_Code], [c].[BillingAddress_Country_FullName], [c0].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [c0].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [c0].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [c0].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [c0].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0] + FROM [Customer] AS [c] + CROSS JOIN [Customer] AS [c0] + UNION + SELECT [c1].[BillingAddress_AddressLine1], [c1].[BillingAddress_AddressLine2], [c1].[BillingAddress_ZipCode], [c1].[BillingAddress_Country_Code], [c1].[BillingAddress_Country_FullName], [c2].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [c2].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [c2].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [c2].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [c2].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0] + FROM [Customer] AS [c1] + CROSS JOIN [Customer] AS [c2] + ) AS [t] + ORDER BY [t].[BillingAddress_ZipCode], [t].[BillingAddress_ZipCode0] + ) AS [t0] +) AS [t1] +ORDER BY [t1].[BillingAddress_ZipCode], [t1].[BillingAddress_ZipCode0] +"""); + } + + public override async Task Same_entity_with_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(bool async) + { + await base.Same_entity_with_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(async); + + AssertSql( +""" +SELECT [c].[Id], [t].[Id], [t].[Name], [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[ShippingAddress_AddressLine1], [t].[ShippingAddress_AddressLine2], [t].[ShippingAddress_ZipCode], [t].[ShippingAddress_Country_Code], [t].[ShippingAddress_Country_FullName], [t].[Id0], [t].[Name0], [t].[BillingAddress_AddressLine10], [t].[BillingAddress_AddressLine20], [t].[BillingAddress_ZipCode0], [t].[BillingAddress_Country_Code0], [t].[BillingAddress_Country_FullName0], [t].[ShippingAddress_AddressLine10], [t].[ShippingAddress_AddressLine20], [t].[ShippingAddress_ZipCode0], [t].[ShippingAddress_Country_Code0], [t].[ShippingAddress_Country_FullName0], [t].[c] +FROM [Customer] AS [c] +OUTER APPLY ( + SELECT TOP(1) [c0].[Id], [c0].[Name], [c0].[BillingAddress_AddressLine1], [c0].[BillingAddress_AddressLine2], [c0].[BillingAddress_ZipCode], [c0].[BillingAddress_Country_Code], [c0].[BillingAddress_Country_FullName], [c0].[ShippingAddress_AddressLine1], [c0].[ShippingAddress_AddressLine2], [c0].[ShippingAddress_ZipCode], [c0].[ShippingAddress_Country_Code], [c0].[ShippingAddress_Country_FullName], [c1].[Id] AS [Id0], [c1].[Name] AS [Name0], [c1].[BillingAddress_AddressLine1] AS [BillingAddress_AddressLine10], [c1].[BillingAddress_AddressLine2] AS [BillingAddress_AddressLine20], [c1].[BillingAddress_ZipCode] AS [BillingAddress_ZipCode0], [c1].[BillingAddress_Country_Code] AS [BillingAddress_Country_Code0], [c1].[BillingAddress_Country_FullName] AS [BillingAddress_Country_FullName0], [c1].[ShippingAddress_AddressLine1] AS [ShippingAddress_AddressLine10], [c1].[ShippingAddress_AddressLine2] AS [ShippingAddress_AddressLine20], [c1].[ShippingAddress_ZipCode] AS [ShippingAddress_ZipCode0], [c1].[ShippingAddress_Country_Code] AS [ShippingAddress_Country_Code0], [c1].[ShippingAddress_Country_FullName] AS [ShippingAddress_Country_FullName0], 1 AS [c] + FROM [Customer] AS [c0] + CROSS JOIN [Customer] AS [c1] + ORDER BY [c0].[Id], [c1].[Id] DESC +) AS [t] +"""); + } + + public override async Task Same_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(bool async) + { + await base.Same_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(async); + + AssertSql(""); + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs index 28703f1f91d..a28150f3ad6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs @@ -871,6 +871,42 @@ public virtual async Task Same_collection_with_conflicting_type_mappings_not_sup #endregion Type mapping inference + [ConditionalFact] + public virtual async Task Ordered_collection_with_split_query() + { + var contextFactory = await InitializeAsync( + onModelCreating: mb => mb.Entity(), + seed: context => + { + context.Add(new Context32976.Principal { Ints = [2, 3, 4]}); + context.SaveChanges(); + }); + + await using var context = contextFactory.CreateContext(); + + _ = await context.Set() + .Where(p => p.Ints.Skip(1).Contains(3)) + .Include(p => p.Dependents) + .AsSplitQuery() + .SingleAsync(); + } + + public class Context32976(DbContextOptions options) : DbContext(options) + { + public class Principal + { + public int Id { get; set; } + public List Ints { get; set; } + public List Dependents { get; set; } + } + + public class Dependent + { + public int Id { get; set; } + public Principal Principal { get; set; } + } + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs index 7019f8ada98..a311cb5d41b 100644 --- a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs +++ b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs @@ -11,6 +11,20 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure; public class SqlServerModelValidatorTest : RelationalModelValidatorTest { + [ConditionalFact] + public virtual void Passes_on_TPT_with_nested_owned_types() + { + var modelBuilder = base.CreateConventionModelBuilder(); + + modelBuilder.Entity().UseTptMappingStrategy(); + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + + Validate(modelBuilder); + } + public override void Detects_duplicate_columns_in_derived_types_with_different_types() { var modelBuilder = CreateConventionModelBuilder(); diff --git a/test/EFCore.SqlServer.Tests/Metadata/Conventions/SqlServerOnDeleteConventionTest.cs b/test/EFCore.SqlServer.Tests/Metadata/Conventions/SqlServerOnDeleteConventionTest.cs new file mode 100644 index 00000000000..ee369699f25 --- /dev/null +++ b/test/EFCore.SqlServer.Tests/Metadata/Conventions/SqlServerOnDeleteConventionTest.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +public class SqlServerOnDeleteConventionTest +{ + [ConditionalFact] // Issue #32732 + public void Convention_does_not_assume_skip_navigations_have_non_null_FK() + { + using var context = new SkippyDbContext(); + var model = context.Model; + Assert.Equal(["ArenaPropensity", "Arena", "Propensity"], model.GetEntityTypes().Select(e => e.ShortName())); + Assert.Equal( + [DeleteBehavior.Cascade, DeleteBehavior.ClientCascade], + model.GetEntityTypes().Single(e => e.ShortName() == "ArenaPropensity").GetForeignKeys().Select(k => k.DeleteBehavior)); + } + + public class SkippyDbContext : DbContext + { + public DbSet Areas { get; private set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlServer(); + } + + public class Arena : Propensity + { + public virtual ICollection AreaProperties { get; set; } + } + + public abstract class Propensity + { + public int Id { get; set; } + + public int? PrimaryYId { get; set; } + public virtual Propensity PrimaryYProp { get; set; } + + public virtual ICollection PropertyAreas { get; set; } + } +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsInfrastructureSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsInfrastructureSqliteTest.cs index c8c33bfc715..73ec9fb795d 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsInfrastructureSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsInfrastructureSqliteTest.cs @@ -64,7 +64,8 @@ public override void Can_generate_up_scripts() CREATE TABLE "Table1" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_Table1" PRIMARY KEY, - "Foo" INTEGER NOT NULL + "Foo" INTEGER NOT NULL, + "Description" TEXT NOT NULL ); INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") @@ -95,6 +96,44 @@ public override void Can_generate_up_scripts() COMMIT; +BEGIN TRANSACTION; + +INSERT INTO Table1 (Id, Bar, Description) VALUES (-1, ' ', 'Value With + +Empty Lines') + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('00000000000005_Migration5', '7.0.0-test'); + +COMMIT; + +BEGIN TRANSACTION; + +INSERT INTO Table1 (Id, Bar, Description) VALUES (-2, ' ', 'GO +Value With + +Empty Lines') + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('00000000000006_Migration6', '7.0.0-test'); + +COMMIT; + +BEGIN TRANSACTION; + +INSERT INTO Table1 (Id, Bar, Description) VALUES (-3, ' ', 'GO +Value With + +GO + +Empty Lines +GO') + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('00000000000007_Migration7', '7.0.0-test'); + +COMMIT; + """, Sql, @@ -114,7 +153,8 @@ public override void Can_generate_up_scripts_noTransactions() CREATE TABLE "Table1" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_Table1" PRIMARY KEY, - "Foo" INTEGER NOT NULL + "Foo" INTEGER NOT NULL, + "Description" TEXT NOT NULL ); INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") @@ -131,6 +171,32 @@ public override void Can_generate_up_scripts_noTransactions() INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") VALUES ('00000000000004_Migration4', '7.0.0-test'); +INSERT INTO Table1 (Id, Bar, Description) VALUES (-1, ' ', 'Value With + +Empty Lines') + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('00000000000005_Migration5', '7.0.0-test'); + +INSERT INTO Table1 (Id, Bar, Description) VALUES (-2, ' ', 'GO +Value With + +Empty Lines') + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('00000000000006_Migration6', '7.0.0-test'); + +INSERT INTO Table1 (Id, Bar, Description) VALUES (-3, ' ', 'GO +Value With + +GO + +Empty Lines +GO') + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('00000000000007_Migration7', '7.0.0-test'); + """, Sql, diff --git a/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs index c5de28da7a2..1dfe9c2d5a1 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs @@ -1702,6 +1702,24 @@ await Test( """); } + public override async Task Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH() + { + await base.Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH(); + + AssertSql( +""" +CREATE TABLE "Contacts" ( + "Id" INTEGER NOT NULL CONSTRAINT "PK_Contacts" PRIMARY KEY AUTOINCREMENT, + "Discriminator" TEXT NOT NULL, + "Name" TEXT NULL, + "Number" INTEGER NULL, + "MyComplex_Prop" TEXT NULL, + "MyComplex_MyNestedComplex_Bar" TEXT NULL, + "MyComplex_MyNestedComplex_Foo" INTEGER NULL +); +"""); + } + public override Task Create_sequence() => AssertNotSupportedAsync(base.Create_sequence, SqliteStrings.SequencesNotSupported); @@ -1732,6 +1750,103 @@ public override Task Rename_sequence() public override Task Move_sequence() => AssertNotSupportedAsync(base.Move_sequence, SqliteStrings.SequencesNotSupported); + + [ConditionalFact] + public override async Task Add_required_primitve_collection_to_existing_table() + { + await base.Add_required_primitve_collection_to_existing_table(); + + AssertSql( +""" +ALTER TABLE "Customers" ADD "Numbers" TEXT NOT NULL DEFAULT '[]'; +"""); + } + + [ConditionalFact] + public override async Task Add_required_primitve_collection_with_custom_default_value_to_existing_table() + { + await base.Add_required_primitve_collection_with_custom_default_value_to_existing_table(); + + AssertSql( +""" +ALTER TABLE "Customers" ADD "Numbers" TEXT NOT NULL DEFAULT '[1,2,3]'; +"""); + } + + [ConditionalFact] + public override async Task Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table() + { + await base.Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table_core("'[3, 2, 1]'"); + + AssertSql( +""" +ALTER TABLE "Customers" ADD "Numbers" TEXT NOT NULL DEFAULT ('[3, 2, 1]'); +"""); + } + + [ConditionalFact(Skip = "issue #33038")] + public override async Task Add_required_primitve_collection_with_custom_converter_to_existing_table() + { + await base.Add_required_primitve_collection_with_custom_converter_to_existing_table(); + + AssertSql( +""" +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'nothing'; +"""); + } + + [ConditionalFact] + public override async Task Add_required_primitve_collection_with_custom_converter_and_custom_default_value_to_existing_table() + { + await base.Add_required_primitve_collection_with_custom_converter_and_custom_default_value_to_existing_table(); + + AssertSql( +""" +ALTER TABLE "Customers" ADD "Numbers" TEXT NOT NULL DEFAULT 'some numbers'; +"""); + } + + [ConditionalFact] + public override async Task Add_optional_primitive_collection_to_existing_table() + { + await base.Add_optional_primitive_collection_to_existing_table(); + + AssertSql( +""" +ALTER TABLE "Customers" ADD "Numbers" TEXT NULL; +"""); + } + + [ConditionalFact] + public override async Task Create_table_with_required_primitive_collection() + { + await base.Create_table_with_required_primitive_collection(); + + AssertSql( +""" +CREATE TABLE "Customers" ( + "Id" INTEGER NOT NULL CONSTRAINT "PK_Customers" PRIMARY KEY AUTOINCREMENT, + "Name" TEXT NULL, + "Numbers" TEXT NOT NULL +); +"""); + } + + [ConditionalFact] + public override async Task Create_table_with_optional_primitive_collection() + { + await base.Create_table_with_optional_primitive_collection(); + + AssertSql( +""" +CREATE TABLE "Customers" ( + "Id" INTEGER NOT NULL CONSTRAINT "PK_Customers" PRIMARY KEY AUTOINCREMENT, + "Name" TEXT NULL, + "Numbers" TEXT NULL +); +"""); + } + // SQLite does not support schemas protected override bool AssertSchemaNames => false; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocAdvancedMappingsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocAdvancedMappingsQuerySqliteTest.cs new file mode 100644 index 00000000000..1807f23b4a2 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocAdvancedMappingsQuerySqliteTest.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query; + +public class AdHocAdvancedMappingsQuerySqliteTest : AdHocAdvancedMappingsQueryRelationalTestBase +{ + protected override ITestStoreFactory TestStoreFactory + => SqliteTestStoreFactory.Instance; +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexTypeQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexTypeQuerySqliteTest.cs index 51c426cd289..ace097abb2a 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexTypeQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexTypeQuerySqliteTest.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Sqlite.Internal; + namespace Microsoft.EntityFrameworkCore.Query; public class ComplexTypeQuerySqliteTest : ComplexTypeQueryRelationalTestBase< @@ -740,6 +742,274 @@ public override async Task Union_two_different_struct_complex_type(bool async) AssertSql(); } + public override async Task Project_same_entity_with_nested_complex_type_twice_with_pushdown(bool async) + { + await base.Project_same_entity_with_nested_complex_type_twice_with_pushdown(async); + + AssertSql( +""" +SELECT "t"."Id", "t"."Name", "t"."BillingAddress_AddressLine1", "t"."BillingAddress_AddressLine2", "t"."BillingAddress_ZipCode", "t"."BillingAddress_Country_Code", "t"."BillingAddress_Country_FullName", "t"."ShippingAddress_AddressLine1", "t"."ShippingAddress_AddressLine2", "t"."ShippingAddress_ZipCode", "t"."ShippingAddress_Country_Code", "t"."ShippingAddress_Country_FullName", "t"."Id0", "t"."Name0", "t"."BillingAddress_AddressLine10", "t"."BillingAddress_AddressLine20", "t"."BillingAddress_ZipCode0", "t"."BillingAddress_Country_Code0", "t"."BillingAddress_Country_FullName0", "t"."ShippingAddress_AddressLine10", "t"."ShippingAddress_AddressLine20", "t"."ShippingAddress_ZipCode0", "t"."ShippingAddress_Country_Code0", "t"."ShippingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT "c"."Id", "c"."Name", "c"."BillingAddress_AddressLine1", "c"."BillingAddress_AddressLine2", "c"."BillingAddress_ZipCode", "c"."BillingAddress_Country_Code", "c"."BillingAddress_Country_FullName", "c"."ShippingAddress_AddressLine1", "c"."ShippingAddress_AddressLine2", "c"."ShippingAddress_ZipCode", "c"."ShippingAddress_Country_Code", "c"."ShippingAddress_Country_FullName", "c0"."Id" AS "Id0", "c0"."Name" AS "Name0", "c0"."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", "c0"."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", "c0"."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", "c0"."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", "c0"."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", "c0"."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", "c0"."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", "c0"."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", "c0"."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", "c0"."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS "c" + CROSS JOIN "Customer" AS "c0" +) AS "t" +"""); + } + + public override async Task Project_same_nested_complex_type_twice_with_pushdown(bool async) + { + await base.Project_same_nested_complex_type_twice_with_pushdown(async); + + AssertSql( +""" +SELECT "t"."BillingAddress_AddressLine1", "t"."BillingAddress_AddressLine2", "t"."BillingAddress_ZipCode", "t"."BillingAddress_Country_Code", "t"."BillingAddress_Country_FullName", "t"."BillingAddress_AddressLine10", "t"."BillingAddress_AddressLine20", "t"."BillingAddress_ZipCode0", "t"."BillingAddress_Country_Code0", "t"."BillingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT "c"."BillingAddress_AddressLine1", "c"."BillingAddress_AddressLine2", "c"."BillingAddress_ZipCode", "c"."BillingAddress_Country_Code", "c"."BillingAddress_Country_FullName", "c0"."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", "c0"."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", "c0"."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", "c0"."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", "c0"."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS "c" + CROSS JOIN "Customer" AS "c0" +) AS "t" +"""); + } + + public override async Task Project_same_entity_with_nested_complex_type_twice_with_double_pushdown(bool async) + { + await base.Project_same_entity_with_nested_complex_type_twice_with_double_pushdown(async); + + AssertSql( +""" +@__p_0='50' + +SELECT "t0"."Id", "t0"."Name", "t0"."BillingAddress_AddressLine1", "t0"."BillingAddress_AddressLine2", "t0"."BillingAddress_ZipCode", "t0"."BillingAddress_Country_Code", "t0"."BillingAddress_Country_FullName", "t0"."ShippingAddress_AddressLine1", "t0"."ShippingAddress_AddressLine2", "t0"."ShippingAddress_ZipCode", "t0"."ShippingAddress_Country_Code", "t0"."ShippingAddress_Country_FullName", "t0"."Id0", "t0"."Name0", "t0"."BillingAddress_AddressLine10", "t0"."BillingAddress_AddressLine20", "t0"."BillingAddress_ZipCode0", "t0"."BillingAddress_Country_Code0", "t0"."BillingAddress_Country_FullName0", "t0"."ShippingAddress_AddressLine10", "t0"."ShippingAddress_AddressLine20", "t0"."ShippingAddress_ZipCode0", "t0"."ShippingAddress_Country_Code0", "t0"."ShippingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT "t"."Id", "t"."Name", "t"."BillingAddress_AddressLine1", "t"."BillingAddress_AddressLine2", "t"."BillingAddress_ZipCode", "t"."BillingAddress_Country_Code", "t"."BillingAddress_Country_FullName", "t"."ShippingAddress_AddressLine1", "t"."ShippingAddress_AddressLine2", "t"."ShippingAddress_ZipCode", "t"."ShippingAddress_Country_Code", "t"."ShippingAddress_Country_FullName", "t"."Id0", "t"."Name0", "t"."BillingAddress_AddressLine10", "t"."BillingAddress_AddressLine20", "t"."BillingAddress_ZipCode0", "t"."BillingAddress_Country_Code0", "t"."BillingAddress_Country_FullName0", "t"."ShippingAddress_AddressLine10", "t"."ShippingAddress_AddressLine20", "t"."ShippingAddress_ZipCode0", "t"."ShippingAddress_Country_Code0", "t"."ShippingAddress_Country_FullName0" + FROM ( + SELECT "c"."Id", "c"."Name", "c"."BillingAddress_AddressLine1", "c"."BillingAddress_AddressLine2", "c"."BillingAddress_ZipCode", "c"."BillingAddress_Country_Code", "c"."BillingAddress_Country_FullName", "c"."ShippingAddress_AddressLine1", "c"."ShippingAddress_AddressLine2", "c"."ShippingAddress_ZipCode", "c"."ShippingAddress_Country_Code", "c"."ShippingAddress_Country_FullName", "c0"."Id" AS "Id0", "c0"."Name" AS "Name0", "c0"."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", "c0"."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", "c0"."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", "c0"."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", "c0"."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", "c0"."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", "c0"."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", "c0"."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", "c0"."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", "c0"."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS "c" + CROSS JOIN "Customer" AS "c0" + ORDER BY "c"."Id", "c0"."Id" + LIMIT @__p_0 + ) AS "t" +) AS "t0" +"""); + } + + public override async Task Project_same_nested_complex_type_twice_with_double_pushdown(bool async) + { + await base.Project_same_nested_complex_type_twice_with_double_pushdown(async); + + AssertSql( +""" +@__p_0='50' + +SELECT "t0"."BillingAddress_AddressLine1", "t0"."BillingAddress_AddressLine2", "t0"."BillingAddress_ZipCode", "t0"."BillingAddress_Country_Code", "t0"."BillingAddress_Country_FullName", "t0"."BillingAddress_AddressLine10", "t0"."BillingAddress_AddressLine20", "t0"."BillingAddress_ZipCode0", "t0"."BillingAddress_Country_Code0", "t0"."BillingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT "t"."BillingAddress_AddressLine1", "t"."BillingAddress_AddressLine2", "t"."BillingAddress_ZipCode", "t"."BillingAddress_Country_Code", "t"."BillingAddress_Country_FullName", "t"."BillingAddress_AddressLine10", "t"."BillingAddress_AddressLine20", "t"."BillingAddress_ZipCode0", "t"."BillingAddress_Country_Code0", "t"."BillingAddress_Country_FullName0" + FROM ( + SELECT "c"."BillingAddress_AddressLine1", "c"."BillingAddress_AddressLine2", "c"."BillingAddress_ZipCode", "c"."BillingAddress_Country_Code", "c"."BillingAddress_Country_FullName", "c0"."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", "c0"."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", "c0"."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", "c0"."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", "c0"."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS "c" + CROSS JOIN "Customer" AS "c0" + ORDER BY "c"."Id", "c0"."Id" + LIMIT @__p_0 + ) AS "t" +) AS "t0" +"""); + } + + public override async Task Project_same_entity_with_struct_nested_complex_type_twice_with_pushdown(bool async) + { + await base.Project_same_entity_with_struct_nested_complex_type_twice_with_pushdown(async); + + AssertSql( +""" +SELECT "t"."Id", "t"."Name", "t"."BillingAddress_AddressLine1", "t"."BillingAddress_AddressLine2", "t"."BillingAddress_ZipCode", "t"."BillingAddress_Country_Code", "t"."BillingAddress_Country_FullName", "t"."ShippingAddress_AddressLine1", "t"."ShippingAddress_AddressLine2", "t"."ShippingAddress_ZipCode", "t"."ShippingAddress_Country_Code", "t"."ShippingAddress_Country_FullName", "t"."Id0", "t"."Name0", "t"."BillingAddress_AddressLine10", "t"."BillingAddress_AddressLine20", "t"."BillingAddress_ZipCode0", "t"."BillingAddress_Country_Code0", "t"."BillingAddress_Country_FullName0", "t"."ShippingAddress_AddressLine10", "t"."ShippingAddress_AddressLine20", "t"."ShippingAddress_ZipCode0", "t"."ShippingAddress_Country_Code0", "t"."ShippingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT "v"."Id", "v"."Name", "v"."BillingAddress_AddressLine1", "v"."BillingAddress_AddressLine2", "v"."BillingAddress_ZipCode", "v"."BillingAddress_Country_Code", "v"."BillingAddress_Country_FullName", "v"."ShippingAddress_AddressLine1", "v"."ShippingAddress_AddressLine2", "v"."ShippingAddress_ZipCode", "v"."ShippingAddress_Country_Code", "v"."ShippingAddress_Country_FullName", "v0"."Id" AS "Id0", "v0"."Name" AS "Name0", "v0"."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", "v0"."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", "v0"."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", "v0"."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", "v0"."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", "v0"."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", "v0"."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", "v0"."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", "v0"."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", "v0"."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "ValuedCustomer" AS "v" + CROSS JOIN "ValuedCustomer" AS "v0" +) AS "t" +"""); + } + + public override async Task Project_same_struct_nested_complex_type_twice_with_pushdown(bool async) + { + await base.Project_same_struct_nested_complex_type_twice_with_pushdown(async); + + AssertSql( +""" +SELECT "t"."BillingAddress_AddressLine1", "t"."BillingAddress_AddressLine2", "t"."BillingAddress_ZipCode", "t"."BillingAddress_Country_Code", "t"."BillingAddress_Country_FullName", "t"."BillingAddress_AddressLine10", "t"."BillingAddress_AddressLine20", "t"."BillingAddress_ZipCode0", "t"."BillingAddress_Country_Code0", "t"."BillingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT "v"."BillingAddress_AddressLine1", "v"."BillingAddress_AddressLine2", "v"."BillingAddress_ZipCode", "v"."BillingAddress_Country_Code", "v"."BillingAddress_Country_FullName", "v0"."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", "v0"."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", "v0"."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", "v0"."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", "v0"."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "ValuedCustomer" AS "v" + CROSS JOIN "ValuedCustomer" AS "v0" +) AS "t" +"""); + } + + public override async Task Project_same_entity_with_struct_nested_complex_type_twice_with_double_pushdown(bool async) + { + await base.Project_same_entity_with_struct_nested_complex_type_twice_with_double_pushdown(async); + + AssertSql( +""" +@__p_0='50' + +SELECT "t0"."Id", "t0"."Name", "t0"."BillingAddress_AddressLine1", "t0"."BillingAddress_AddressLine2", "t0"."BillingAddress_ZipCode", "t0"."BillingAddress_Country_Code", "t0"."BillingAddress_Country_FullName", "t0"."ShippingAddress_AddressLine1", "t0"."ShippingAddress_AddressLine2", "t0"."ShippingAddress_ZipCode", "t0"."ShippingAddress_Country_Code", "t0"."ShippingAddress_Country_FullName", "t0"."Id0", "t0"."Name0", "t0"."BillingAddress_AddressLine10", "t0"."BillingAddress_AddressLine20", "t0"."BillingAddress_ZipCode0", "t0"."BillingAddress_Country_Code0", "t0"."BillingAddress_Country_FullName0", "t0"."ShippingAddress_AddressLine10", "t0"."ShippingAddress_AddressLine20", "t0"."ShippingAddress_ZipCode0", "t0"."ShippingAddress_Country_Code0", "t0"."ShippingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT "t"."Id", "t"."Name", "t"."BillingAddress_AddressLine1", "t"."BillingAddress_AddressLine2", "t"."BillingAddress_ZipCode", "t"."BillingAddress_Country_Code", "t"."BillingAddress_Country_FullName", "t"."ShippingAddress_AddressLine1", "t"."ShippingAddress_AddressLine2", "t"."ShippingAddress_ZipCode", "t"."ShippingAddress_Country_Code", "t"."ShippingAddress_Country_FullName", "t"."Id0", "t"."Name0", "t"."BillingAddress_AddressLine10", "t"."BillingAddress_AddressLine20", "t"."BillingAddress_ZipCode0", "t"."BillingAddress_Country_Code0", "t"."BillingAddress_Country_FullName0", "t"."ShippingAddress_AddressLine10", "t"."ShippingAddress_AddressLine20", "t"."ShippingAddress_ZipCode0", "t"."ShippingAddress_Country_Code0", "t"."ShippingAddress_Country_FullName0" + FROM ( + SELECT "v"."Id", "v"."Name", "v"."BillingAddress_AddressLine1", "v"."BillingAddress_AddressLine2", "v"."BillingAddress_ZipCode", "v"."BillingAddress_Country_Code", "v"."BillingAddress_Country_FullName", "v"."ShippingAddress_AddressLine1", "v"."ShippingAddress_AddressLine2", "v"."ShippingAddress_ZipCode", "v"."ShippingAddress_Country_Code", "v"."ShippingAddress_Country_FullName", "v0"."Id" AS "Id0", "v0"."Name" AS "Name0", "v0"."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", "v0"."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", "v0"."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", "v0"."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", "v0"."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", "v0"."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", "v0"."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", "v0"."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", "v0"."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", "v0"."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "ValuedCustomer" AS "v" + CROSS JOIN "ValuedCustomer" AS "v0" + ORDER BY "v"."Id", "v0"."Id" + LIMIT @__p_0 + ) AS "t" +) AS "t0" +"""); + } + + public override async Task Project_same_struct_nested_complex_type_twice_with_double_pushdown(bool async) + { + await base.Project_same_struct_nested_complex_type_twice_with_double_pushdown(async); + + AssertSql( +""" +@__p_0='50' + +SELECT "t0"."BillingAddress_AddressLine1", "t0"."BillingAddress_AddressLine2", "t0"."BillingAddress_ZipCode", "t0"."BillingAddress_Country_Code", "t0"."BillingAddress_Country_FullName", "t0"."BillingAddress_AddressLine10", "t0"."BillingAddress_AddressLine20", "t0"."BillingAddress_ZipCode0", "t0"."BillingAddress_Country_Code0", "t0"."BillingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT "t"."BillingAddress_AddressLine1", "t"."BillingAddress_AddressLine2", "t"."BillingAddress_ZipCode", "t"."BillingAddress_Country_Code", "t"."BillingAddress_Country_FullName", "t"."BillingAddress_AddressLine10", "t"."BillingAddress_AddressLine20", "t"."BillingAddress_ZipCode0", "t"."BillingAddress_Country_Code0", "t"."BillingAddress_Country_FullName0" + FROM ( + SELECT "v"."BillingAddress_AddressLine1", "v"."BillingAddress_AddressLine2", "v"."BillingAddress_ZipCode", "v"."BillingAddress_Country_Code", "v"."BillingAddress_Country_FullName", "v0"."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", "v0"."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", "v0"."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", "v0"."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", "v0"."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "ValuedCustomer" AS "v" + CROSS JOIN "ValuedCustomer" AS "v0" + ORDER BY "v"."Id", "v0"."Id" + LIMIT @__p_0 + ) AS "t" +) AS "t0" +"""); + } + + public override async Task Union_of_same_entity_with_nested_complex_type_projected_twice_with_pushdown(bool async) + { + await base.Union_of_same_entity_with_nested_complex_type_projected_twice_with_pushdown(async); + + AssertSql( +""" +@__p_0='50' + +SELECT "t"."Id", "t"."Name", "t"."BillingAddress_AddressLine1", "t"."BillingAddress_AddressLine2", "t"."BillingAddress_ZipCode", "t"."BillingAddress_Country_Code", "t"."BillingAddress_Country_FullName", "t"."ShippingAddress_AddressLine1", "t"."ShippingAddress_AddressLine2", "t"."ShippingAddress_ZipCode", "t"."ShippingAddress_Country_Code", "t"."ShippingAddress_Country_FullName", "t"."Id0", "t"."Name0", "t"."BillingAddress_AddressLine10", "t"."BillingAddress_AddressLine20", "t"."BillingAddress_ZipCode0", "t"."BillingAddress_Country_Code0", "t"."BillingAddress_Country_FullName0", "t"."ShippingAddress_AddressLine10", "t"."ShippingAddress_AddressLine20", "t"."ShippingAddress_ZipCode0", "t"."ShippingAddress_Country_Code0", "t"."ShippingAddress_Country_FullName0" +FROM ( + SELECT "c"."Id", "c"."Name", "c"."BillingAddress_AddressLine1", "c"."BillingAddress_AddressLine2", "c"."BillingAddress_ZipCode", "c"."BillingAddress_Country_Code", "c"."BillingAddress_Country_FullName", "c"."ShippingAddress_AddressLine1", "c"."ShippingAddress_AddressLine2", "c"."ShippingAddress_ZipCode", "c"."ShippingAddress_Country_Code", "c"."ShippingAddress_Country_FullName", "c0"."Id" AS "Id0", "c0"."Name" AS "Name0", "c0"."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", "c0"."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", "c0"."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", "c0"."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", "c0"."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", "c0"."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", "c0"."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", "c0"."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", "c0"."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", "c0"."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS "c" + CROSS JOIN "Customer" AS "c0" + UNION + SELECT "c1"."Id", "c1"."Name", "c1"."BillingAddress_AddressLine1", "c1"."BillingAddress_AddressLine2", "c1"."BillingAddress_ZipCode", "c1"."BillingAddress_Country_Code", "c1"."BillingAddress_Country_FullName", "c1"."ShippingAddress_AddressLine1", "c1"."ShippingAddress_AddressLine2", "c1"."ShippingAddress_ZipCode", "c1"."ShippingAddress_Country_Code", "c1"."ShippingAddress_Country_FullName", "c2"."Id" AS "Id0", "c2"."Name" AS "Name0", "c2"."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", "c2"."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", "c2"."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", "c2"."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", "c2"."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", "c2"."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", "c2"."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", "c2"."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", "c2"."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", "c2"."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS "c1" + CROSS JOIN "Customer" AS "c2" +) AS "t" +ORDER BY "t"."Id", "t"."Id0" +LIMIT @__p_0 +"""); + } + + public override async Task Union_of_same_entity_with_nested_complex_type_projected_twice_with_double_pushdown(bool async) + { + await base.Union_of_same_entity_with_nested_complex_type_projected_twice_with_double_pushdown(async); + + AssertSql( +""" +@__p_0='50' + +SELECT "t1"."Id", "t1"."Name", "t1"."BillingAddress_AddressLine1", "t1"."BillingAddress_AddressLine2", "t1"."BillingAddress_ZipCode", "t1"."BillingAddress_Country_Code", "t1"."BillingAddress_Country_FullName", "t1"."ShippingAddress_AddressLine1", "t1"."ShippingAddress_AddressLine2", "t1"."ShippingAddress_ZipCode", "t1"."ShippingAddress_Country_Code", "t1"."ShippingAddress_Country_FullName", "t1"."Id0", "t1"."Name0", "t1"."BillingAddress_AddressLine10", "t1"."BillingAddress_AddressLine20", "t1"."BillingAddress_ZipCode0", "t1"."BillingAddress_Country_Code0", "t1"."BillingAddress_Country_FullName0", "t1"."ShippingAddress_AddressLine10", "t1"."ShippingAddress_AddressLine20", "t1"."ShippingAddress_ZipCode0", "t1"."ShippingAddress_Country_Code0", "t1"."ShippingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT "t0"."Id", "t0"."Name", "t0"."BillingAddress_AddressLine1", "t0"."BillingAddress_AddressLine2", "t0"."BillingAddress_ZipCode", "t0"."BillingAddress_Country_Code", "t0"."BillingAddress_Country_FullName", "t0"."ShippingAddress_AddressLine1", "t0"."ShippingAddress_AddressLine2", "t0"."ShippingAddress_ZipCode", "t0"."ShippingAddress_Country_Code", "t0"."ShippingAddress_Country_FullName", "t0"."Id0", "t0"."Name0", "t0"."BillingAddress_AddressLine10", "t0"."BillingAddress_AddressLine20", "t0"."BillingAddress_ZipCode0", "t0"."BillingAddress_Country_Code0", "t0"."BillingAddress_Country_FullName0", "t0"."ShippingAddress_AddressLine10", "t0"."ShippingAddress_AddressLine20", "t0"."ShippingAddress_ZipCode0", "t0"."ShippingAddress_Country_Code0", "t0"."ShippingAddress_Country_FullName0" + FROM ( + SELECT "t"."Id", "t"."Name", "t"."BillingAddress_AddressLine1", "t"."BillingAddress_AddressLine2", "t"."BillingAddress_ZipCode", "t"."BillingAddress_Country_Code", "t"."BillingAddress_Country_FullName", "t"."ShippingAddress_AddressLine1", "t"."ShippingAddress_AddressLine2", "t"."ShippingAddress_ZipCode", "t"."ShippingAddress_Country_Code", "t"."ShippingAddress_Country_FullName", "t"."Id0", "t"."Name0", "t"."BillingAddress_AddressLine10", "t"."BillingAddress_AddressLine20", "t"."BillingAddress_ZipCode0", "t"."BillingAddress_Country_Code0", "t"."BillingAddress_Country_FullName0", "t"."ShippingAddress_AddressLine10", "t"."ShippingAddress_AddressLine20", "t"."ShippingAddress_ZipCode0", "t"."ShippingAddress_Country_Code0", "t"."ShippingAddress_Country_FullName0" + FROM ( + SELECT "c"."Id", "c"."Name", "c"."BillingAddress_AddressLine1", "c"."BillingAddress_AddressLine2", "c"."BillingAddress_ZipCode", "c"."BillingAddress_Country_Code", "c"."BillingAddress_Country_FullName", "c"."ShippingAddress_AddressLine1", "c"."ShippingAddress_AddressLine2", "c"."ShippingAddress_ZipCode", "c"."ShippingAddress_Country_Code", "c"."ShippingAddress_Country_FullName", "c0"."Id" AS "Id0", "c0"."Name" AS "Name0", "c0"."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", "c0"."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", "c0"."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", "c0"."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", "c0"."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", "c0"."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", "c0"."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", "c0"."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", "c0"."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", "c0"."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS "c" + CROSS JOIN "Customer" AS "c0" + UNION + SELECT "c1"."Id", "c1"."Name", "c1"."BillingAddress_AddressLine1", "c1"."BillingAddress_AddressLine2", "c1"."BillingAddress_ZipCode", "c1"."BillingAddress_Country_Code", "c1"."BillingAddress_Country_FullName", "c1"."ShippingAddress_AddressLine1", "c1"."ShippingAddress_AddressLine2", "c1"."ShippingAddress_ZipCode", "c1"."ShippingAddress_Country_Code", "c1"."ShippingAddress_Country_FullName", "c2"."Id" AS "Id0", "c2"."Name" AS "Name0", "c2"."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", "c2"."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", "c2"."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", "c2"."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", "c2"."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", "c2"."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", "c2"."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", "c2"."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", "c2"."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", "c2"."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS "c1" + CROSS JOIN "Customer" AS "c2" + ) AS "t" + ORDER BY "t"."Id", "t"."Id0" + LIMIT @__p_0 + ) AS "t0" +) AS "t1" +ORDER BY "t1"."Id", "t1"."Id0" +LIMIT @__p_0 +"""); + } + + public override async Task Union_of_same_nested_complex_type_projected_twice_with_pushdown(bool async) + { + await base.Union_of_same_nested_complex_type_projected_twice_with_pushdown(async); + + AssertSql( +""" +@__p_0='50' + +SELECT "t"."BillingAddress_AddressLine1", "t"."BillingAddress_AddressLine2", "t"."BillingAddress_ZipCode", "t"."BillingAddress_Country_Code", "t"."BillingAddress_Country_FullName", "t"."BillingAddress_AddressLine10", "t"."BillingAddress_AddressLine20", "t"."BillingAddress_ZipCode0", "t"."BillingAddress_Country_Code0", "t"."BillingAddress_Country_FullName0" +FROM ( + SELECT "c"."BillingAddress_AddressLine1", "c"."BillingAddress_AddressLine2", "c"."BillingAddress_ZipCode", "c"."BillingAddress_Country_Code", "c"."BillingAddress_Country_FullName", "c0"."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", "c0"."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", "c0"."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", "c0"."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", "c0"."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS "c" + CROSS JOIN "Customer" AS "c0" + UNION + SELECT "c1"."BillingAddress_AddressLine1", "c1"."BillingAddress_AddressLine2", "c1"."BillingAddress_ZipCode", "c1"."BillingAddress_Country_Code", "c1"."BillingAddress_Country_FullName", "c2"."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", "c2"."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", "c2"."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", "c2"."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", "c2"."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS "c1" + CROSS JOIN "Customer" AS "c2" +) AS "t" +ORDER BY "t"."BillingAddress_ZipCode", "t"."BillingAddress_ZipCode0" +LIMIT @__p_0 +"""); + } + + public override async Task Union_of_same_nested_complex_type_projected_twice_with_double_pushdown(bool async) + { + await base.Union_of_same_nested_complex_type_projected_twice_with_double_pushdown(async); + + AssertSql( +""" +@__p_0='50' + +SELECT "t1"."BillingAddress_AddressLine1", "t1"."BillingAddress_AddressLine2", "t1"."BillingAddress_ZipCode", "t1"."BillingAddress_Country_Code", "t1"."BillingAddress_Country_FullName", "t1"."BillingAddress_AddressLine10", "t1"."BillingAddress_AddressLine20", "t1"."BillingAddress_ZipCode0", "t1"."BillingAddress_Country_Code0", "t1"."BillingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT "t0"."BillingAddress_AddressLine1", "t0"."BillingAddress_AddressLine2", "t0"."BillingAddress_ZipCode", "t0"."BillingAddress_Country_Code", "t0"."BillingAddress_Country_FullName", "t0"."BillingAddress_AddressLine10", "t0"."BillingAddress_AddressLine20", "t0"."BillingAddress_ZipCode0", "t0"."BillingAddress_Country_Code0", "t0"."BillingAddress_Country_FullName0" + FROM ( + SELECT "t"."BillingAddress_AddressLine1", "t"."BillingAddress_AddressLine2", "t"."BillingAddress_ZipCode", "t"."BillingAddress_Country_Code", "t"."BillingAddress_Country_FullName", "t"."BillingAddress_AddressLine10", "t"."BillingAddress_AddressLine20", "t"."BillingAddress_ZipCode0", "t"."BillingAddress_Country_Code0", "t"."BillingAddress_Country_FullName0" + FROM ( + SELECT "c"."BillingAddress_AddressLine1", "c"."BillingAddress_AddressLine2", "c"."BillingAddress_ZipCode", "c"."BillingAddress_Country_Code", "c"."BillingAddress_Country_FullName", "c0"."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", "c0"."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", "c0"."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", "c0"."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", "c0"."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS "c" + CROSS JOIN "Customer" AS "c0" + UNION + SELECT "c1"."BillingAddress_AddressLine1", "c1"."BillingAddress_AddressLine2", "c1"."BillingAddress_ZipCode", "c1"."BillingAddress_Country_Code", "c1"."BillingAddress_Country_FullName", "c2"."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", "c2"."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", "c2"."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", "c2"."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", "c2"."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS "c1" + CROSS JOIN "Customer" AS "c2" + ) AS "t" + ORDER BY "t"."BillingAddress_ZipCode", "t"."BillingAddress_ZipCode0" + LIMIT @__p_0 + ) AS "t0" +) AS "t1" +ORDER BY "t1"."BillingAddress_ZipCode", "t1"."BillingAddress_ZipCode0" +LIMIT @__p_0 +"""); + } + + public override async Task Same_entity_with_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Same_entity_with_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(async))).Message); + + public override async Task Same_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Same_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(async))).Message); + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs index 98e6272c451..12ba1fc3033 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs @@ -1317,6 +1317,48 @@ FROM json_each(@__strings_0) AS "s" """); } + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] // Issue #32896 + public async Task Empty_string_used_for_primitive_collection_throws(bool async) + { + await using var connection = new SqliteConnection("DataSource=:memory:"); + await connection.OpenAsync(); + + await using var context = new SimpleContext(connection); + await context.Database.EnsureDeletedAsync(); + await context.Database.EnsureCreatedAsync(); + + await context.Database.ExecuteSqlRawAsync("INSERT INTO SimpleEntities (List) VALUES ('');"); + + var set = context.SimpleEntities; + + var message = await Assert.ThrowsAsync( + async () => + { + if (async) + { + await set.FirstAsync(); + } + else + { + set.First(); + } + }); + + Assert.Equal(CoreStrings.EmptyJsonString, message.Message); + } + + public class SimpleContext(SqliteConnection connection) : DbContext + { + public DbSet SimpleEntities => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlite(connection); + } + + public record SimpleEntity(int Id, IEnumerable List); + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs index e6d2953e258..ddee4e89e0b 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs @@ -118,7 +118,43 @@ protected class Generic : Abstract { } - public class SampleEntity +#nullable enable + protected class BaseEntity + { + public int Id { get; set; } + } + + protected class ChildA : BaseEntity + { + public OwnedType OwnedType { get; set; } = null!; + } + + protected class ChildB : BaseEntity + { + } + + protected class ChildC : BaseEntity + { + } + + protected class ChildD : BaseEntity + { + } + + [Owned] + protected class OwnedType + { + public NestedOwnedType NestedOwnedType { get; set; } = null!; + } + + [Owned] + protected class NestedOwnedType + { + } + +#nullable restore + + protected class SampleEntity { public int Id { get; set; } public int Number { get; set; } @@ -131,29 +167,29 @@ public class SampleEntity public ICollection OtherSamples { get; set; } } - public class AnotherSampleEntity + protected class AnotherSampleEntity { public int Id { get; set; } public ReferencedEntity ReferencedEntity { get; set; } } - public class ReferencedEntity + protected class ReferencedEntity { public int Id { get; set; } public int SampleEntityId { get; set; } } - public class SampleEntityMinimal + protected class SampleEntityMinimal { public int Id { get; set; } public ReferencedEntityMinimal ReferencedEntity { get; set; } } - public class ReferencedEntityMinimal + protected class ReferencedEntityMinimal { } - public class AnotherSampleEntityMinimal + protected class AnotherSampleEntityMinimal { public int Id { get; set; } public ReferencedEntityMinimal ReferencedEntity { get; set; } @@ -373,7 +409,7 @@ protected class DependentFour public PrincipalFour PrincipalFour { get; set; } } - public class Blog + protected class Blog { public int BlogId { get; set; } public bool IsDeleted { get; set; } @@ -381,14 +417,14 @@ public class Blog public List BlogOwnedEntities { get; set; } } - public class BlogOwnedEntity + protected class BlogOwnedEntity { public int BlogOwnedEntityId { get; set; } public int BlogId { get; set; } public Blog Blog { get; set; } } - public class Post + protected class Post { public int PostId { get; set; } public int BlogId { get; set; } @@ -397,13 +433,13 @@ public class Post public Blog Blog { get; set; } } - public class PicturePost : Post + protected class PicturePost : Post { public string PictureUrl { get; set; } public List Pictures { get; set; } } - public class Picture + protected class Picture { public int PictureId { get; set; } public bool IsDeleted { get; set; }