From 3d1933a735889f63a9d59500326b75146463c531 Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Wed, 15 Dec 2021 01:44:06 +0000 Subject: [PATCH 001/143] [internal/release/3.1] Update dependencies from dnceng/internal/dotnet-extensions --- NuGet.config | 10 +- eng/Version.Details.xml | 244 ++++++++++++++++++++-------------------- eng/Versions.props | 18 +-- 3 files changed, 138 insertions(+), 134 deletions(-) diff --git a/NuGet.config b/NuGet.config index 198ba405367..52f21cee828 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,10 +4,10 @@ - + - + @@ -22,12 +22,16 @@ - + + + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index c5687acc3c0..4ffb29c56b4 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,21 +1,21 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx @@ -33,85 +33,85 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup 3acd9b0cd16596bad450c82be08780875a73c05c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 @@ -122,153 +122,153 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup 3acd9b0cd16596bad450c82be08780875a73c05c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d8690e6fcc4bc6c2ae77a1b748f87040180894fc + d987abeb248cbc851e09eab268414ec2dff2e684 - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 35fa579a3029aef544cfb0b337eb77b882244d80 + e38b64f0e364276b03d9e318e06c5f9dd5774301 - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 35fa579a3029aef544cfb0b337eb77b882244d80 + e38b64f0e364276b03d9e318e06c5f9dd5774301 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx diff --git a/eng/Versions.props b/eng/Versions.props index 37bd4927ea2..82d10fd1ebc 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -32,13 +32,13 @@ 1.1.3 - 3.1.22 - 3.1.22 - 3.1.22 - 3.1.22 - 3.1.22 - 3.1.22-servicing.21571.3 - 3.1.22 + 3.1.23 + 3.1.23 + 3.1.23 + 3.1.23 + 3.1.23 + 3.1.23-servicing.21614.7 + 3.1.23 1.1.1 @@ -55,8 +55,8 @@ 3.1.6 3.1.6 3.1.0 - 3.1.22 - 3.1.22-servicing.21571.3 + 3.1.23 + 3.1.23-servicing.21608.5 2.1.0 From 26ab5aacbc47e6b0674a94b32e51b844333030de Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Tue, 8 Feb 2022 05:01:10 +0000 Subject: [PATCH 002/143] [internal/release/6.0] Update dependencies from dnceng/internal/dotnet-runtime --- NuGet.config | 8 ++++++++ eng/Version.Details.xml | 6 +++--- eng/Versions.props | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/NuGet.config b/NuGet.config index 34ddb3faaa0..49f87a5ae00 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,6 +4,10 @@ + + + + @@ -20,6 +24,10 @@ + + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index a90af9a5602..81d575e174d 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -17,9 +17,9 @@ https://github.com/dotnet/runtime 4822e3c3aa77eb82b2fb33c9321f923cf11ddde6 - - https://github.com/dotnet/runtime - 4822e3c3aa77eb82b2fb33c9321f923cf11ddde6 + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 9ffc8c2b5db26a04762f92e5357261ae3160381e https://github.com/dotnet/runtime diff --git a/eng/Versions.props b/eng/Versions.props index 240ec7cbbb6..0d9508037ce 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -17,7 +17,7 @@ 6.0.0 6.0.0 6.0.1 - 6.0.0 + 6.0.1 6.0.0 6.0.1 6.0.0 From 47388545299477bfff35e0a3aa36ebdc81276ef5 Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Tue, 8 Feb 2022 10:12:25 +0000 Subject: [PATCH 003/143] [internal/release/6.0] Update dependencies from dnceng/internal/dotnet-runtime --- NuGet.config | 4 ++-- eng/Version.Details.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NuGet.config b/NuGet.config index 49f87a5ae00..7665227e84b 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,7 +4,7 @@ - + @@ -27,7 +27,7 @@ - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 81d575e174d..6340fabeec9 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -19,7 +19,7 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 9ffc8c2b5db26a04762f92e5357261ae3160381e + 9089a0288a1effbf7bbf748deadb561d5a31e988 https://github.com/dotnet/runtime From c35ea7869f653425053de3ecc65356a1d82049d8 Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Wed, 9 Feb 2022 01:57:09 +0000 Subject: [PATCH 004/143] [internal/release/6.0] Update dependencies from dnceng/internal/dotnet-runtime --- NuGet.config | 4 ++-- eng/Version.Details.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NuGet.config b/NuGet.config index 7665227e84b..a68b4604de8 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,11 +4,11 @@ - + @@ -23,11 +23,11 @@ + - diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index adcdf5e5a2b..61270c9e917 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -19,7 +19,7 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 9089a0288a1effbf7bbf748deadb561d5a31e988 + 65b29b54179d43503338d7c0403ea801ebc009d1 https://github.com/dotnet/runtime From 941b6a21af7e4312d6151ecf97ade7853b355529 Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Wed, 9 Feb 2022 05:50:52 +0000 Subject: [PATCH 005/143] [internal/release/6.0] Update dependencies from dnceng/internal/dotnet-runtime --- NuGet.config | 4 ++-- eng/Version.Details.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NuGet.config b/NuGet.config index a68b4604de8..403b0907cd2 100644 --- a/NuGet.config +++ b/NuGet.config @@ -8,7 +8,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 61270c9e917..88856c15c18 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -19,7 +19,7 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 65b29b54179d43503338d7c0403ea801ebc009d1 + 1fae055a57c8456fc60b162d88763244dde8be60 https://github.com/dotnet/runtime From 8e1b7dcc73931d3e525cf0390eabe9ff03500573 Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Wed, 9 Feb 2022 09:53:09 +0000 Subject: [PATCH 006/143] [internal/release/6.0] Update dependencies from dnceng/internal/dotnet-runtime --- NuGet.config | 4 ++-- eng/Version.Details.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NuGet.config b/NuGet.config index 403b0907cd2..3bd8229b0ce 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,11 +4,11 @@ + - @@ -23,11 +23,11 @@ - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 88856c15c18..f8eeba85c70 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -19,7 +19,7 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1fae055a57c8456fc60b162d88763244dde8be60 + ae0c8f15ff9bd01cfd1b6bce2870054ef08bd05c https://github.com/dotnet/runtime From 6cdd55ddb0467aab57b73cecf360583c3859f498 Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Thu, 10 Feb 2022 00:14:33 +0000 Subject: [PATCH 007/143] [internal/release/6.0] Update dependencies from dnceng/internal/dotnet-runtime --- NuGet.config | 4 ++-- eng/Version.Details.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NuGet.config b/NuGet.config index 3bd8229b0ce..f13a921fab8 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,11 +4,11 @@ - + @@ -23,11 +23,11 @@ + - diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index f8eeba85c70..109813f13b7 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -19,7 +19,7 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - ae0c8f15ff9bd01cfd1b6bce2870054ef08bd05c + 343c77b37b473930c1191bb27a8a467f684a20a3 https://github.com/dotnet/runtime From ba7ac921ffa8b90fc827112d587dec18137bcb0f Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Thu, 10 Feb 2022 23:49:06 +0000 Subject: [PATCH 008/143] [internal/release/6.0] Update dependencies from dnceng/internal/dotnet-runtime --- NuGet.config | 12 ++---------- eng/Version.Details.xml | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/NuGet.config b/NuGet.config index f13a921fab8..91c8bc52edb 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,11 +4,7 @@ - - - - - + @@ -23,11 +19,7 @@ - - - - - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 109813f13b7..be7ac7d4790 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -19,7 +19,7 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 343c77b37b473930c1191bb27a8a467f684a20a3 + 7d04aeef1889133c742f9dbdffa9fa8101623c90 https://github.com/dotnet/runtime From e803be5d7b7d0f81fee54c8bd6483fc79548ad65 Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Mon, 14 Feb 2022 20:39:29 +0000 Subject: [PATCH 009/143] [internal/release/6.0] Update dependencies from dnceng/internal/dotnet-runtime --- NuGet.config | 4 ++-- eng/Version.Details.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NuGet.config b/NuGet.config index 91c8bc52edb..9f074df614a 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,7 +4,7 @@ - + @@ -19,7 +19,7 @@ - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index be7ac7d4790..07ad776fc4c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -19,7 +19,7 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 7d04aeef1889133c742f9dbdffa9fa8101623c90 + fa9dc99b5c5c2de38217703d7d29d517fe6dffdc https://github.com/dotnet/runtime From 348a3ca4621a7a75cce74a5586e081570dc28897 Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Wed, 16 Feb 2022 17:53:22 +0000 Subject: [PATCH 010/143] [internal/release/6.0] Update dependencies from dnceng/internal/dotnet-runtime --- NuGet.config | 4 ++-- eng/Version.Details.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NuGet.config b/NuGet.config index 9f074df614a..2485d5572d9 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,7 +4,7 @@ - + @@ -19,7 +19,7 @@ - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 07ad776fc4c..3ad9291699e 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -19,7 +19,7 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - fa9dc99b5c5c2de38217703d7d29d517fe6dffdc + 10db7350e1288caf09e684e1b219f8f5b08eca02 https://github.com/dotnet/runtime From 1b46288a56a3a46f9adab4a287b632ccbbbe6455 Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Thu, 17 Feb 2022 23:43:43 +0000 Subject: [PATCH 011/143] [internal/release/3.1] Update dependencies from dnceng/internal/dotnet-extensions --- NuGet.config | 13 +++- eng/Version.Details.xml | 152 ++++++++++++++++++++-------------------- eng/Versions.props | 6 +- 3 files changed, 89 insertions(+), 82 deletions(-) diff --git a/NuGet.config b/NuGet.config index 52f21cee828..b8a81e0a2b0 100644 --- a/NuGet.config +++ b/NuGet.config @@ -3,11 +3,14 @@ + + + - + - + @@ -20,14 +23,18 @@ + - + + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 5145a33bcec..5a3bd07ced2 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,21 +1,21 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-corefx @@ -33,85 +33,85 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup 3acd9b0cd16596bad450c82be08780875a73c05c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c @@ -124,151 +124,151 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - d987abeb248cbc851e09eab268414ec2dff2e684 + 5784a84e7bbb7a442989e0d96bc167a82edf085c https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - e38b64f0e364276b03d9e318e06c5f9dd5774301 + 64991f80999d5ce31301d5cd2a8e3c0c11a112f4 - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - e38b64f0e364276b03d9e318e06c5f9dd5774301 + 64991f80999d5ce31301d5cd2a8e3c0c11a112f4 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx @@ -282,9 +282,9 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-corefx 059a4a19e602494bfbed473dbbb18f2dbfbd0878 - + https://dev.azure.com/dnceng/internal/_git/dotnet-corefx - 59d2f36ec02c494eec50940c7993257a807f9531 + 072c9ec94e48aed199f24b6aae2e17aec393a716 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx diff --git a/eng/Versions.props b/eng/Versions.props index 82d10fd1ebc..7fd2b4688a9 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -37,7 +37,7 @@ 3.1.23 3.1.23 3.1.23 - 3.1.23-servicing.21614.7 + 3.1.23-servicing.22117.3 3.1.23 @@ -45,7 +45,7 @@ 1.1.1 4.7.0 4.7.2 - 4.7.4 + 4.7.5 4.7.1 1.7.1 4.7.0 @@ -56,7 +56,7 @@ 3.1.6 3.1.0 3.1.23 - 3.1.23-servicing.21608.5 + 3.1.23-servicing.22117.1 2.1.0 From 49729b88f62eadfe9a3a0fc9d1ada9aaa4faccb5 Mon Sep 17 00:00:00 2001 From: DotNet-Bot Date: Wed, 23 Feb 2022 17:49:09 +0000 Subject: [PATCH 012/143] Update dependencies from https://dev.azure.com/dnceng/internal/_git/dotnet-extensions build 20220223.2 Microsoft.JSInterop , Microsoft.Extensions.ValueStopwatch.Sources , Microsoft.Extensions.FileProviders.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks , Microsoft.Extensions.DiagnosticAdapter , Microsoft.Extensions.DependencyInjection.Abstractions , Microsoft.Extensions.DependencyInjection , Microsoft.Extensions.Configuration.Xml , Microsoft.Extensions.Configuration.UserSecrets , Microsoft.Extensions.Configuration.KeyPerFile , Microsoft.Extensions.Configuration.Json , Microsoft.Extensions.Configuration.Ini , Microsoft.Extensions.Configuration.FileExtensions , Microsoft.Extensions.Configuration.EnvironmentVariables , Microsoft.Extensions.Configuration.CommandLine , Microsoft.Extensions.Configuration.Binder , Internal.AspNetCore.Analyzers , Microsoft.AspNetCore.Analyzer.Testing , Microsoft.AspNetCore.BenchmarkRunner.Sources , Microsoft.AspNetCore.Testing , Microsoft.Extensions.ActivatorUtilities.Sources , Microsoft.Extensions.FileProviders.Composite , Microsoft.Extensions.Caching.Abstractions , Microsoft.Extensions.Caching.SqlServer , Microsoft.Extensions.Caching.StackExchangeRedis , Microsoft.Extensions.CommandLineUtils.Sources , Microsoft.Extensions.Configuration , Microsoft.Extensions.Configuration.Abstractions , Microsoft.Extensions.Configuration.AzureKeyVault , Microsoft.Extensions.Caching.Memory , Microsoft.Extensions.WebEncoders , Microsoft.Extensions.FileProviders.Embedded , Microsoft.Extensions.FileSystemGlobbing , Microsoft.Extensions.TypeNameHelper.Sources , Microsoft.Extensions.Primitives , Microsoft.Extensions.ParameterDefaultValue.Sources , Microsoft.Extensions.Options.DataAnnotations , Microsoft.Extensions.Options.ConfigurationExtensions , Microsoft.Extensions.Options , Microsoft.Extensions.ObjectPool , Microsoft.Extensions.Logging.TraceSource , Microsoft.Extensions.Logging.Testing , Microsoft.Extensions.Logging.EventSource , Microsoft.Extensions.Logging.EventLog , Microsoft.Extensions.FileProviders.Physical , Microsoft.Extensions.Logging.Console , Microsoft.Extensions.Logging.Configuration , Microsoft.Extensions.Logging.Debug , Microsoft.Extensions.HashCodeCombiner.Sources , Microsoft.Extensions.HostFactoryResolver.Sources , Microsoft.Extensions.Hosting , Microsoft.Extensions.Hosting.Abstractions , Microsoft.Extensions.Logging.AzureAppServices , Microsoft.Extensions.Http , Microsoft.Extensions.Localization , Microsoft.Extensions.Localization.Abstractions , Microsoft.Extensions.Logging , Microsoft.Extensions.Logging.Abstractions From Version 3.1.23 -> To Version 3.1.23 Dependency coherency updates Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Internal,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,System.IO.Pipelines,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64 From Version 3.1.23 -> To Version 3.1.23 (parent: Microsoft.Extensions.Logging --- NuGet.config | 11 +-- eng/Version.Details.xml | 150 ++++++++++++++++++++-------------------- eng/Versions.props | 4 +- 3 files changed, 83 insertions(+), 82 deletions(-) diff --git a/NuGet.config b/NuGet.config index b8a81e0a2b0..9adc0178300 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,13 +4,13 @@ - + - + - + @@ -23,15 +23,16 @@ - + - + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 5a3bd07ced2..58c11f3bb94 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,21 +1,21 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-corefx @@ -33,85 +33,85 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup 3acd9b0cd16596bad450c82be08780875a73c05c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b @@ -124,151 +124,151 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5784a84e7bbb7a442989e0d96bc167a82edf085c + 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 64991f80999d5ce31301d5cd2a8e3c0c11a112f4 + 7af614fde0e0da1d20b33a6e2da7ae2994c5eec3 - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 64991f80999d5ce31301d5cd2a8e3c0c11a112f4 + 7af614fde0e0da1d20b33a6e2da7ae2994c5eec3 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx @@ -284,7 +284,7 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-corefx - 072c9ec94e48aed199f24b6aae2e17aec393a716 + 641ee87bc17bd8307682c4677c10e0eb33906c0d https://dev.azure.com/dnceng/internal/_git/dotnet-corefx diff --git a/eng/Versions.props b/eng/Versions.props index 7fd2b4688a9..230c15c150c 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -37,7 +37,7 @@ 3.1.23 3.1.23 3.1.23 - 3.1.23-servicing.22117.3 + 3.1.23-servicing.22123.2 3.1.23 @@ -56,7 +56,7 @@ 3.1.6 3.1.0 3.1.23 - 3.1.23-servicing.22117.1 + 3.1.23-servicing.22122.4 2.1.0 From 0257e5c118d78ce1b4d5e514779240c929c4b8aa Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Thu, 24 Feb 2022 05:37:15 +0000 Subject: [PATCH 013/143] [internal/release/6.0] Update dependencies from dnceng/internal/dotnet-runtime --- NuGet.config | 4 ++-- eng/Version.Details.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NuGet.config b/NuGet.config index 2485d5572d9..9ebb469a8f7 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,7 +4,7 @@ - + @@ -19,7 +19,7 @@ - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 3ad9291699e..1eea45eca5d 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -19,7 +19,7 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 10db7350e1288caf09e684e1b219f8f5b08eca02 + c24d9a9c91c5d04b7b4de71f1a9f33ac35e09663 https://github.com/dotnet/runtime From bd15f9dc9678d7f96d3a4260e0e79f456bbca5f7 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Thu, 3 Mar 2022 17:18:25 +0000 Subject: [PATCH 014/143] Update dependencies from https://github.com/dotnet/arcade build 20220222.7 (#27563) [release/6.0] Update dependencies from dotnet/arcade --- NuGet.config | 2 - eng/Version.Details.xml | 8 ++-- eng/common/generate-sbom-prep.ps1 | 19 +++++++++ eng/common/generate-sbom-prep.sh | 22 ++++++++++ eng/common/templates/job/job.yml | 10 +++++ eng/common/templates/jobs/jobs.yml | 4 -- eng/common/templates/steps/generate-sbom.yml | 44 ++++++++++++++++++++ global.json | 4 +- 8 files changed, 101 insertions(+), 12 deletions(-) create mode 100644 eng/common/generate-sbom-prep.ps1 create mode 100644 eng/common/generate-sbom-prep.sh create mode 100644 eng/common/templates/steps/generate-sbom.yml diff --git a/NuGet.config b/NuGet.config index 34ddb3faaa0..31860d3c1d0 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,7 +4,6 @@ - @@ -19,7 +18,6 @@ - diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 0a630153f6a..c5e43009699 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -47,13 +47,13 @@ - + https://github.com/dotnet/arcade - fe5cc1841d12196d94a4ae3b276cb92d8d7ca73d + 7215d8265a7fbcd022eb72ff7a6e2048444c985f - + https://github.com/dotnet/arcade - fe5cc1841d12196d94a4ae3b276cb92d8d7ca73d + 7215d8265a7fbcd022eb72ff7a6e2048444c985f diff --git a/eng/common/generate-sbom-prep.ps1 b/eng/common/generate-sbom-prep.ps1 new file mode 100644 index 00000000000..a733a888582 --- /dev/null +++ b/eng/common/generate-sbom-prep.ps1 @@ -0,0 +1,19 @@ +Param( + [Parameter(Mandatory=$true)][string] $ManifestDirPath # Manifest directory where sbom will be placed +) + +Write-Host "Creating dir $ManifestDirPath" +# create directory for sbom manifest to be placed +if (!(Test-Path -path $ManifestDirPath)) +{ + New-Item -ItemType Directory -path $ManifestDirPath + Write-Host "Successfully created directory $ManifestDirPath" +} +else{ + Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder." +} + +Write-Host "Updating artifact name" +$artifact_name = "${env:SYSTEM_STAGENAME}_${env:AGENT_JOBNAME}_SBOM" -replace '["/:<>\\|?@*"() ]', '_' +Write-Host "Artifact name $artifact_name" +Write-Host "##vso[task.setvariable variable=ARTIFACT_NAME]$artifact_name" diff --git a/eng/common/generate-sbom-prep.sh b/eng/common/generate-sbom-prep.sh new file mode 100644 index 00000000000..f6c77453142 --- /dev/null +++ b/eng/common/generate-sbom-prep.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +manifest_dir=$1 + +if [ ! -d "$manifest_dir" ] ; then + mkdir -p "$manifest_dir" + echo "Sbom directory created." $manifest_dir +else + Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder." +fi + +artifact_name=$SYSTEM_STAGENAME"_"$AGENT_JOBNAME"_SBOM" +echo "Artifact name before : "$artifact_name +# replace all special characters with _, some builds use special characters like : in Agent.Jobname, that is not a permissible name while uploading artifacts. +safe_artifact_name="${artifact_name//["/:<>\\|?@*$" ]/_}" +echo "Artifact name after : "$safe_artifact_name +export ARTIFACT_NAME=$safe_artifact_name +echo "##vso[task.setvariable variable=ARTIFACT_NAME]$safe_artifact_name" + +exit 0 diff --git a/eng/common/templates/job/job.yml b/eng/common/templates/job/job.yml index 30d1de5835e..547d878da07 100644 --- a/eng/common/templates/job/job.yml +++ b/eng/common/templates/job/job.yml @@ -31,6 +31,10 @@ parameters: name: '' preSteps: [] runAsPublic: false +# Sbom related params + enableSbom: true + PackageVersion: 6.0.0 + BuildDropPath: '$(Build.SourcesDirectory)/artifacts' jobs: - job: ${{ parameters.name }} @@ -247,3 +251,9 @@ jobs: ArtifactName: AssetManifests continueOnError: ${{ parameters.continueOnError }} condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.enableSbom, 'true')) }}: + - template: /eng/common/templates/steps/generate-sbom.yml + parameters: + PackageVersion: ${{ parameters.packageVersion}} + BuildDropPath: ${{ parameters.buildDropPath }} diff --git a/eng/common/templates/jobs/jobs.yml b/eng/common/templates/jobs/jobs.yml index 70d44735ace..554e71cfc43 100644 --- a/eng/common/templates/jobs/jobs.yml +++ b/eng/common/templates/jobs/jobs.yml @@ -8,10 +8,6 @@ parameters: # Optional: Enable publishing using release pipelines enablePublishUsingPipelines: false - # Optional: Disable component governance detection. In general, component governance - # should be on for all jobs. Use only in the event of issues. - disableComponentGovernance: false - # Optional: Enable running the source-build jobs to build repo from source enableSourceBuild: false diff --git a/eng/common/templates/steps/generate-sbom.yml b/eng/common/templates/steps/generate-sbom.yml new file mode 100644 index 00000000000..f4d7937f379 --- /dev/null +++ b/eng/common/templates/steps/generate-sbom.yml @@ -0,0 +1,44 @@ +# 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 + +parameters: + PackageVersion: 6.0.0 + BuildDropPath: '$(Build.SourcesDirectory)/artifacts' + PackageName: '.NET' + ManifestDirPath: $(Build.ArtifactStagingDirectory)/sbom + 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 }} + +- task: PublishPipelineArtifact@1 + displayName: Publish SBOM manifest + continueOnError: ${{parameters.sbomContinueOnError}} + inputs: + targetPath: '${{parameters.manifestDirPath}}' + artifactName: $(ARTIFACT_NAME) + diff --git a/global.json b/global.json index b8f9e9954e3..eee133b5cdf 100644 --- a/global.json +++ b/global.json @@ -18,7 +18,7 @@ "rollForward": "latestMajor" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.22107.2", - "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.22107.2" + "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.22122.7", + "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.22122.7" } } From 7e4b7717f349dce1149cccc3f994ed7b0ee35513 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Tue, 8 Mar 2022 10:29:03 -0800 Subject: [PATCH 015/143] Generate temp values for key properties contained in optional FKs Fixes #27455 --- .../Internal/InternalEntityEntry.cs | 6 +- .../Metadata/Internal/PropertyExtensions.cs | 10 ++- .../StoreGeneratedTestBase.cs | 67 +++++++++++++++++-- .../StoreGeneratedSqlServerTest.cs | 2 + 4 files changed, 79 insertions(+), 6 deletions(-) diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs index 31fd7530011..a5afeb7d103 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs @@ -1459,6 +1459,9 @@ public void AcceptChanges() } } + private readonly static bool _useOldBehavior27455 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue27455", out var enabled27455) && enabled27455; + /// /// 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 @@ -1485,7 +1488,8 @@ public InternalEntityEntry PrepareToSave() if (property.IsKey() && property.IsForeignKey() - && _stateData.IsPropertyFlagged(property.GetIndex(), PropertyFlag.Unknown)) + && _stateData.IsPropertyFlagged(property.GetIndex(), PropertyFlag.Unknown) + && (_useOldBehavior27455 || !IsStoreGenerated(property))) { throw new InvalidOperationException(CoreStrings.UnknownKeyValue(entityType.DisplayName(), property.Name)); } diff --git a/src/EFCore/Metadata/Internal/PropertyExtensions.cs b/src/EFCore/Metadata/Internal/PropertyExtensions.cs index 53826c3b025..139bef01d31 100644 --- a/src/EFCore/Metadata/Internal/PropertyExtensions.cs +++ b/src/EFCore/Metadata/Internal/PropertyExtensions.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; +using System.Linq; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Utilities; @@ -74,6 +76,9 @@ public static bool ForUpdate(this ValueGenerated valueGenerated) return null; } + private readonly static bool _useOldBehavior27455 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue27455", out var enabled27455) && enabled27455; + /// /// 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 @@ -83,7 +88,10 @@ public static bool ForUpdate(this ValueGenerated valueGenerated) public static bool RequiresValueGenerator(this IReadOnlyProperty property) => (property.ValueGenerated.ForAdd() && property.IsKey() - && (!property.IsForeignKey() || property.IsForeignKeyToSelf())) + && (!property.IsForeignKey() + || property.IsForeignKeyToSelf() + || (!_useOldBehavior27455 + && property.GetContainingForeignKeys().All(fk => fk.Properties.Any(p => p != property && p.IsNullable))))) || property.GetValueGeneratorFactory() != null; /// diff --git a/test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs b/test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs index d40ff4536d8..767562ced5a 100644 --- a/test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs +++ b/test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs @@ -512,6 +512,45 @@ public virtual void Identity_property_on_Added_entity_with_temporary_value_gets_ context => Assert.Equal("Banana Joe", context.Set().Single(e => e.Id == id).Identity)); } +#nullable enable + protected class CompositePrincipal + { + public int Id { get; set; } + public int? CurrentNumber { get; set; } + public CompositeDependent? Current { get; set; } + public ICollection Periods { get; } = new HashSet(); + } + + protected class CompositeDependent + { + public int PrincipalId { get; set; } + public int Number { get; set; } + public CompositePrincipal? Principal { get; set; } + } +#nullable disable + + [ConditionalFact] + public virtual void Store_generated_values_are_propagated_with_composite_key_cycles() + { + var id = 0; + + ExecuteWithStrategyInTransaction( + context => + { + var period = new CompositeDependent + { + Number = 1, + Principal = new CompositePrincipal() + }; + + context.Add(period); + context.SaveChanges(); + + id = period.PrincipalId; + }, + context => Assert.Equal(1, context.Set().Single(e => e.PrincipalId == id).Number)); + } + protected class NonStoreGenDependent { [DatabaseGenerated(DatabaseGeneratedOption.None)] @@ -1947,10 +1986,30 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.Entity(); modelBuilder.Entity(); - modelBuilder.Entity() - .Property(e => e.HasTemp) - .ValueGeneratedOnAddOrUpdate() - .HasValueGenerator(); + modelBuilder.Entity(eb => + { + eb.Property(e => e.HasTemp) + .ValueGeneratedOnAddOrUpdate() + .HasValueGenerator(); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(x => x.Id); + entity.Property(x => x.Id) + .ValueGeneratedOnAdd(); + entity.HasOne(x => x.Current) + .WithOne() + .HasForeignKey(x => new { x.Id, x.CurrentNumber }); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(x => new { x.PrincipalId, x.Number }); + entity.HasOne(x => x.Principal) + .WithMany(x => x.Periods) + .HasForeignKey(x => x.PrincipalId); + }); } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/StoreGeneratedSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/StoreGeneratedSqlServerTest.cs index 6726e148e09..0ac152f55b9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/StoreGeneratedSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/StoreGeneratedSqlServerTest.cs @@ -228,6 +228,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.Entity().Property(e => e.HasTemp).HasDefaultValue(777); + modelBuilder.Entity().Property(e => e.Id).UseIdentityColumn(); + base.OnModelCreating(modelBuilder, context); } } From 484dc04221dcdfe39227d0c0f47cd8d93c192fe4 Mon Sep 17 00:00:00 2001 From: William Godbe Date: Tue, 8 Mar 2022 13:19:42 -0800 Subject: [PATCH 016/143] Update global.json --- global.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/global.json b/global.json index d6fcf2bae2f..21763ccb176 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "tools": { - "dotnet": "3.1.416", + "dotnet": "3.1.417", "runtimes": { "dotnet": [ "2.1.30", @@ -9,7 +9,7 @@ } }, "sdk": { - "version": "3.1.416" + "version": "3.1.417" }, "msbuild-sdks": { "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.22077.9" From c3a557f14717638453c3d023801ccaa749448126 Mon Sep 17 00:00:00 2001 From: William Godbe Date: Tue, 8 Mar 2022 13:20:08 -0800 Subject: [PATCH 017/143] Update global.json --- global.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/global.json b/global.json index 94693c3f515..8ddddbaeb36 100644 --- a/global.json +++ b/global.json @@ -1,17 +1,17 @@ { "tools": { - "dotnet": "5.0.405", + "dotnet": "5.0.406", "runtimes": { "dotnet": [ - "3.1.22" + "3.1.23" ], "aspnetcore": [ - "3.1.22" + "3.1.23" ] } }, "sdk": { - "version": "5.0.405", + "version": "5.0.406", "allowPrerelease": true, "rollForward": "latestMajor" }, From c728722fce4daafb880c1291e841e4ed168913de Mon Sep 17 00:00:00 2001 From: Doug Bunting <6431421+dougbu@users.noreply.github.com> Date: Wed, 9 Mar 2022 10:27:08 -0800 Subject: [PATCH 018/143] [release/6.0] Update SDK and runtimes --- global.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/global.json b/global.json index eee133b5cdf..bea3cb783d7 100644 --- a/global.json +++ b/global.json @@ -1,19 +1,19 @@ { "tools": { - "dotnet": "6.0.102", + "dotnet": "6.0.103", "runtimes": { "dotnet": [ - "3.1.22", - "5.0.14" + "3.1.23", + "5.0.15" ], "aspnetcore": [ - "3.1.22", - "5.0.14" + "3.1.23", + "5.0.15" ] } }, "sdk": { - "version": "6.0.102", + "version": "6.0.103", "allowPrerelease": true, "rollForward": "latestMajor" }, From c1cbae0bc3f75cdb839d91ff6202b27a8dc86a64 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 9 Mar 2022 21:22:30 +0000 Subject: [PATCH 019/143] Update dependencies from https://github.com/dotnet/arcade build 20220308.4 (#27607) [release/6.0] Update dependencies from dotnet/arcade --- NuGet.config | 2 - eng/Version.Details.xml | 8 +-- eng/common/templates/job/execute-sdl.yml | 69 +++---------------- eng/common/templates/jobs/codeql-build.yml | 2 +- eng/common/templates/steps/execute-sdl.yml | 68 ++++++++++++++++++ .../templates/variables/sdl-variables.yml | 7 ++ global.json | 4 +- 7 files changed, 91 insertions(+), 69 deletions(-) create mode 100644 eng/common/templates/steps/execute-sdl.yml create mode 100644 eng/common/templates/variables/sdl-variables.yml diff --git a/NuGet.config b/NuGet.config index 9ebb469a8f7..31860d3c1d0 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,7 +4,6 @@ - @@ -19,7 +18,6 @@ - diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 4794d49b53c..e367383939c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -47,13 +47,13 @@ - + https://github.com/dotnet/arcade - 7215d8265a7fbcd022eb72ff7a6e2048444c985f + 82e36e8adced60779de76030fbdb25c469ab2353 - + https://github.com/dotnet/arcade - 7215d8265a7fbcd022eb72ff7a6e2048444c985f + 82e36e8adced60779de76030fbdb25c469ab2353 diff --git a/eng/common/templates/job/execute-sdl.yml b/eng/common/templates/job/execute-sdl.yml index d0a1ea8b0f2..24cec0424e5 100644 --- a/eng/common/templates/job/execute-sdl.yml +++ b/eng/common/templates/job/execute-sdl.yml @@ -43,14 +43,9 @@ jobs: value: ${{ parameters.AzDOPipelineId }} - name: AzDOBuildId value: ${{ parameters.AzDOBuildId }} - # 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.110.1 + - template: /eng/common/templates/variables/sdl-variables.yml - name: GuardianVersion value: ${{ coalesce(parameters.overrideGuardianVersion, '$(DefaultGuardianVersion)') }} - - name: GuardianPackagesConfigFile - value: $(Build.SourcesDirectory)\eng\common\sdl\packages.config 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') }}: @@ -126,57 +121,11 @@ jobs: displayName: Extract Archive Artifacts continueOnError: ${{ parameters.sdlContinueOnError }} - - ${{ if ne(parameters.overrideGuardianVersion, '') }}: - - powershell: | - $content = Get-Content $(GuardianPackagesConfigFile) - - Write-Host "packages.config content was:`n$content" - - $content = $content.Replace('$(DefaultGuardianVersion)', '$(GuardianVersion)') - $content | Set-Content $(GuardianPackagesConfigFile) - - Write-Host "packages.config content updated to:`n$content" - displayName: Use overridden Guardian version ${{ parameters.overrideGuardianVersion }} - - - task: NuGetToolInstaller@1 - displayName: 'Install NuGet.exe' - - task: NuGetCommand@2 - displayName: 'Install Guardian' - inputs: - restoreSolution: $(Build.SourcesDirectory)\eng\common\sdl\packages.config - feedsToUse: config - nugetConfigPath: $(Build.SourcesDirectory)\eng\common\sdl\NuGet.config - externalFeedCredentials: GuardianConnect - restoreDirectory: $(Build.SourcesDirectory)\.packages - - - ${{ if ne(parameters.overrideParameters, '') }}: - - powershell: ${{ parameters.executeAllSdlToolsScript }} ${{ parameters.overrideParameters }} - displayName: Execute SDL - continueOnError: ${{ parameters.sdlContinueOnError }} - - ${{ if eq(parameters.overrideParameters, '') }}: - - powershell: ${{ parameters.executeAllSdlToolsScript }} - -GuardianPackageName Microsoft.Guardian.Cli.$(GuardianVersion) - -NugetPackageDirectory $(Build.SourcesDirectory)\.packages - -AzureDevOpsAccessToken $(dn-bot-dotnet-build-rw-code-rw) - ${{ parameters.additionalParameters }} - displayName: Execute SDL - continueOnError: ${{ parameters.sdlContinueOnError }} - - - ${{ 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() + - template: /eng/common/templates/steps/execute-sdl.yml + parameters: + overrideGuardianVersion: ${{ parameters.overrideGuardianVersion }} + executeAllSdlToolsScript: ${{ parameters.executeAllSdlToolsScript }} + overrideParameters: ${{ parameters.overrideParameters }} + additionalParameters: ${{ parameters.additionalParameters }} + publishGuardianDirectoryToPipeline: ${{ parameters.publishGuardianDirectoryToPipeline }} + sdlContinueOnError: ${{ parameters.sdlContinueOnError }} diff --git a/eng/common/templates/jobs/codeql-build.yml b/eng/common/templates/jobs/codeql-build.yml index f7dc5ea4aaa..54c393af440 100644 --- a/eng/common/templates/jobs/codeql-build.yml +++ b/eng/common/templates/jobs/codeql-build.yml @@ -21,7 +21,7 @@ jobs: # 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 + value: 0.110.1 - name: GuardianPackagesConfigFile value: $(Build.SourcesDirectory)\eng\common\sdl\packages.config - name: GuardianVersion diff --git a/eng/common/templates/steps/execute-sdl.yml b/eng/common/templates/steps/execute-sdl.yml new file mode 100644 index 00000000000..7b8ee18a28d --- /dev/null +++ b/eng/common/templates/steps/execute-sdl.yml @@ -0,0 +1,68 @@ +parameters: + overrideGuardianVersion: '' + executeAllSdlToolsScript: '' + overrideParameters: '' + additionalParameters: '' + publishGuardianDirectoryToPipeline: false + sdlContinueOnError: false + condition: '' + +steps: +- ${{ if ne(parameters.overrideGuardianVersion, '') }}: + - powershell: | + $content = Get-Content $(GuardianPackagesConfigFile) + + Write-Host "packages.config content was:`n$content" + + $content = $content.Replace('$(DefaultGuardianVersion)', '$(GuardianVersion)') + $content | Set-Content $(GuardianPackagesConfigFile) + + Write-Host "packages.config content updated to:`n$content" + displayName: Use overridden Guardian version ${{ parameters.overrideGuardianVersion }} + +- task: NuGetToolInstaller@1 + displayName: 'Install NuGet.exe' + +- task: NuGetCommand@2 + displayName: 'Install Guardian' + inputs: + restoreSolution: $(Build.SourcesDirectory)\eng\common\sdl\packages.config + feedsToUse: config + nugetConfigPath: $(Build.SourcesDirectory)\eng\common\sdl\NuGet.config + externalFeedCredentials: GuardianConnect + restoreDirectory: $(Build.SourcesDirectory)\.packages + +- ${{ if ne(parameters.overrideParameters, '') }}: + - powershell: ${{ parameters.executeAllSdlToolsScript }} ${{ parameters.overrideParameters }} + displayName: Execute SDL + continueOnError: ${{ parameters.sdlContinueOnError }} + condition: ${{ parameters.condition }} + +- ${{ if eq(parameters.overrideParameters, '') }}: + - powershell: ${{ parameters.executeAllSdlToolsScript }} + -GuardianPackageName Microsoft.Guardian.Cli.$(GuardianVersion) + -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() \ No newline at end of file diff --git a/eng/common/templates/variables/sdl-variables.yml b/eng/common/templates/variables/sdl-variables.yml new file mode 100644 index 00000000000..1a860bd0406 --- /dev/null +++ b/eng/common/templates/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.110.1 +- name: GuardianPackagesConfigFile + value: $(Build.SourcesDirectory)\eng\common\sdl\packages.config \ No newline at end of file diff --git a/global.json b/global.json index bea3cb783d7..47de93628a3 100644 --- a/global.json +++ b/global.json @@ -18,7 +18,7 @@ "rollForward": "latestMajor" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.22122.7", - "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.22122.7" + "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.22158.4", + "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.22158.4" } } From e800ecc9c2c195b0aad55ff052adb47412de4e54 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 9 Mar 2022 21:31:44 +0000 Subject: [PATCH 020/143] Update dependencies from https://github.com/dotnet/arcade build 20220223.4 (#27606) [release/5.0] Update dependencies from dotnet/arcade --- eng/Version.Details.xml | 8 ++++---- eng/common/templates/jobs/jobs.yml | 4 ---- global.json | 4 ++-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 8bfcf96dbba..c7ac2cc2afa 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -51,13 +51,13 @@ - + https://github.com/dotnet/arcade - 2a9d6f1413a8e3eae0e7a447539669ebddf3e825 + 295d305a5520815cbf4ccb3f209f6ee8ba11b45d - + https://github.com/dotnet/arcade - 2a9d6f1413a8e3eae0e7a447539669ebddf3e825 + 295d305a5520815cbf4ccb3f209f6ee8ba11b45d diff --git a/eng/common/templates/jobs/jobs.yml b/eng/common/templates/jobs/jobs.yml index 3d551585431..c1c6e0d611c 100644 --- a/eng/common/templates/jobs/jobs.yml +++ b/eng/common/templates/jobs/jobs.yml @@ -8,10 +8,6 @@ parameters: # Optional: Enable publishing using release pipelines enablePublishUsingPipelines: false - # Optional: Disable component governance detection. In general, component governance - # should be on for all jobs. Use only in the event of issues. - disableComponentGovernance: false - graphFileGeneration: # Optional: Enable generating the graph files at the end of the build enabled: false diff --git a/global.json b/global.json index 94693c3f515..8fc65ca405e 100644 --- a/global.json +++ b/global.json @@ -16,7 +16,7 @@ "rollForward": "latestMajor" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "5.0.0-beta.22104.7", - "Microsoft.DotNet.Helix.Sdk": "5.0.0-beta.22104.7" + "Microsoft.DotNet.Arcade.Sdk": "5.0.0-beta.22123.4", + "Microsoft.DotNet.Helix.Sdk": "5.0.0-beta.22123.4" } } From 131a8c7b7abe409167e4276f17c97e8cbac982bc Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 9 Mar 2022 22:22:11 +0000 Subject: [PATCH 021/143] Update dependencies from https://github.com/dotnet/arcade build 20220214.9 (#27605) [release/3.1] Update dependencies from dotnet/arcade --- eng/Version.Details.xml | 4 ++-- eng/common/templates/job/job.yml | 1 + global.json | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 5e6b2478507..d76099e2538 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -321,9 +321,9 @@ - + https://github.com/dotnet/arcade - 31e3d884010345c19f6335571e34b2b5c7ce13bc + 184ce2c7f17508264ffa19b307d97e7568da05ab https://github.com/dotnet/roslyn diff --git a/eng/common/templates/job/job.yml b/eng/common/templates/job/job.yml index 7a464c0d722..c43c779e3ca 100644 --- a/eng/common/templates/job/job.yml +++ b/eng/common/templates/job/job.yml @@ -162,6 +162,7 @@ jobs: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), ne(parameters.disableComponentGovernance, 'true')) }}: - task: ComponentGovernanceComponentDetection@0 + continueOnError: true - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: diff --git a/global.json b/global.json index d6fcf2bae2f..12ec2d17675 100644 --- a/global.json +++ b/global.json @@ -12,6 +12,6 @@ "version": "3.1.416" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.22077.9" + "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.22114.9" } } From d1431c9b139f18de75df9df4032efb681bda7b0e Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Thu, 10 Mar 2022 19:35:22 +0000 Subject: [PATCH 022/143] Update dependencies from https://github.com/dotnet/arcade build 20220309.8 (#27617) [release/6.0] Update dependencies from dotnet/arcade --- eng/Version.Details.xml | 8 ++++---- global.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index e367383939c..6ba93019da9 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -47,13 +47,13 @@ - + https://github.com/dotnet/arcade - 82e36e8adced60779de76030fbdb25c469ab2353 + 4f4c8c392d1c51e630f4571e39a095da7fb172c5 - + https://github.com/dotnet/arcade - 82e36e8adced60779de76030fbdb25c469ab2353 + 4f4c8c392d1c51e630f4571e39a095da7fb172c5 diff --git a/global.json b/global.json index 47de93628a3..ff8b5df3b75 100644 --- a/global.json +++ b/global.json @@ -18,7 +18,7 @@ "rollForward": "latestMajor" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.22158.4", - "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.22158.4" + "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.22159.8", + "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.22159.8" } } From ec9c6c235a5a04fcc48520c4585a375b2778600d Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 11 Mar 2022 09:08:28 +0200 Subject: [PATCH 023/143] Fix scaffolding of reference navigations with NRT (#27616) Fixes #27496 --- .../Internal/CSharpEntityTypeGenerator.cs | 2 +- .../Internal/CSharpEntityTypeGeneratorTest.cs | 42 ++++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs index dfd58acb37d..88d0236e569 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs @@ -444,7 +444,7 @@ protected virtual void GenerateNavigationProperties(IEntityType entityType) _sb.AppendLine( !_useNullableReferenceTypes || navigation.IsCollection ? $"public virtual {navigationType} {navigation.Name} {{ get; set; }}" - : navigation.ForeignKey.IsRequired + : navigation.ForeignKey.IsRequired && navigation.IsOnDependent ? $"public virtual {navigationType} {navigation.Name} {{ get; set; }} = null!;" : $"public virtual {navigationType}? {navigation.Name} {{ get; set; }}"); } diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs index b14b813c5e0..b4d6c0afd5f 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs @@ -752,10 +752,27 @@ public void Required_and_not_required_reference_navigations_with_nrt() { x.Property("Id"); - x.HasOne("Dependent1", "RequiredNavigationWithReferenceForeignKey").WithOne("Entity").IsRequired(); - x.HasOne("Dependent2", "OptionalNavigationWithReferenceForeignKey").WithOne("Entity"); - x.HasOne("Dependent3", "RequiredNavigationWithValueForeignKey").WithOne("Entity").IsRequired(); - x.HasOne("Dependent4", "OptionalNavigationWithValueForeignKey").WithOne("Entity"); + x + .HasOne("Dependent1", "RequiredNavigationWithReferenceForeignKey") + .WithOne("Entity") + .HasForeignKey("Dependent1", "RequiredNavigationWithReferenceForeignKey") + .IsRequired(); + + x + .HasOne("Dependent2", "OptionalNavigationWithReferenceForeignKey") + .WithOne("Entity") + .HasForeignKey("Dependent2", "OptionalNavigationWithReferenceForeignKey"); + + x + .HasOne("Dependent3", "RequiredNavigationWithValueForeignKey") + .WithOne("Entity") + .HasForeignKey("Dependent3", "RequiredNavigationWithValueForeignKey") + .IsRequired(); + + x + .HasOne("Dependent4", "OptionalNavigationWithValueForeignKey") + .WithOne("Entity") + .HasForeignKey("Dependent4", "OptionalNavigationWithValueForeignKey"); }) .Entity("Dependent1", x => x.Property("Id")) .Entity("Dependent2", x => x.Property("Id")) @@ -777,23 +794,15 @@ public partial class Entity { [Key] public int Id { get; set; } - public string? OptionalNavigationWithReferenceForeignKeyId { get; set; } - public int? OptionalNavigationWithValueForeignKeyId { get; set; } - public string RequiredNavigationWithReferenceForeignKeyId { get; set; } = null!; - public int RequiredNavigationWithValueForeignKeyId { get; set; } - [ForeignKey(""OptionalNavigationWithReferenceForeignKeyId"")] [InverseProperty(""Entity"")] public virtual Dependent2? OptionalNavigationWithReferenceForeignKey { get; set; } - [ForeignKey(""OptionalNavigationWithValueForeignKeyId"")] [InverseProperty(""Entity"")] public virtual Dependent4? OptionalNavigationWithValueForeignKey { get; set; } - [ForeignKey(""RequiredNavigationWithReferenceForeignKeyId"")] [InverseProperty(""Entity"")] - public virtual Dependent1 RequiredNavigationWithReferenceForeignKey { get; set; } = null!; - [ForeignKey(""RequiredNavigationWithValueForeignKeyId"")] + public virtual Dependent1? RequiredNavigationWithReferenceForeignKey { get; set; } [InverseProperty(""Entity"")] - public virtual Dependent3 RequiredNavigationWithValueForeignKey { get; set; } = null!; + public virtual Dependent3? RequiredNavigationWithValueForeignKey { get; set; } } } ", @@ -803,11 +812,6 @@ public partial class Entity { var entityType = model.FindEntityType("TestNamespace.Entity"); - Assert.False(entityType.GetProperty("RequiredNavigationWithReferenceForeignKeyId").IsNullable); - Assert.True(entityType.GetProperty("OptionalNavigationWithReferenceForeignKeyId").IsNullable); - Assert.False(entityType.GetProperty("RequiredNavigationWithValueForeignKeyId").IsNullable); - Assert.True(entityType.GetProperty("OptionalNavigationWithValueForeignKeyId").IsNullable); - Assert.True(entityType.FindNavigation("RequiredNavigationWithReferenceForeignKey")!.ForeignKey.IsRequired); Assert.False(entityType.FindNavigation("OptionalNavigationWithReferenceForeignKey")!.ForeignKey.IsRequired); Assert.True(entityType.FindNavigation("RequiredNavigationWithValueForeignKey")!.ForeignKey.IsRequired); From a061538d71dbf1988bb2a497875af2b438529c13 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Fri, 11 Mar 2022 12:04:48 +0000 Subject: [PATCH 024/143] Stop deleting orphans for optional relationships configured for cascade deletes (#27557) --- .../Internal/InternalEntityEntry.cs | 3 +- .../ChangeTracking/ChangeTrackerTest.cs | 293 ++++++++++++++++-- 2 files changed, 267 insertions(+), 29 deletions(-) diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs index fc103f00a53..7faa07590dc 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs @@ -1250,7 +1250,8 @@ private void SetProperty( && valueType == CurrentValueType.Normal && (!asProperty.ClrType.IsNullableType() || asProperty.GetContainingForeignKeys().Any( - fk => (fk.DeleteBehavior == DeleteBehavior.Cascade + fk => fk.IsRequired + && (fk.DeleteBehavior == DeleteBehavior.Cascade || fk.DeleteBehavior == DeleteBehavior.ClientCascade) && fk.DeclaringEntityType.IsAssignableFrom(EntityType)))) { diff --git a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs index a00a44336bb..c91101817e5 100644 --- a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs +++ b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs @@ -2016,27 +2016,70 @@ public void Dependent_FKs_are_not_nulled_when_principal_is_detached(bool delayCa } } - [ConditionalTheory] // Issues #16546 #25360; Change reverted in #27174. - [InlineData(false, false, false, true, false)] - [InlineData(true, false, false, true, false)] - [InlineData(false, true, false, true, false)] - [InlineData(true, true, false, true, false)] - [InlineData(false, false, true, true, false)] - [InlineData(true, false, true, true, false)] - [InlineData(false, true, false, false, true)] - [InlineData(true, true, false, false, true)] - [InlineData(false, false, true, false, true)] - [InlineData(true, false, true, false, true)] - [InlineData(false, true, false, true, true)] - [InlineData(true, true, false, true, true)] - [InlineData(false, false, true, true, true)] - [InlineData(true, false, true, true, true)] - public void Optional_relationship_with_cascade_still_cascades( - bool delayCascade, + [ConditionalTheory] // Issues #16546 #25360; Change reverted in #27174. + [InlineData(null, false, false, true, false, false)] + [InlineData(null, true, false, true, false, false)] + [InlineData(null, false, true, true, false, false)] + [InlineData(null, true, false, false, true, false)] + [InlineData(null, false, true, false, true, false)] + [InlineData(null, true, false, true, true, false)] + [InlineData(null, false, true, true, true, false)] + [InlineData(CascadeTiming.Immediate, false, false, true, false, false)] + [InlineData(CascadeTiming.Immediate, true, false, true, false, false)] + [InlineData(CascadeTiming.Immediate, false, true, true, false, false)] + [InlineData(CascadeTiming.Immediate, true, false, false, true, false)] + [InlineData(CascadeTiming.Immediate, false, true, false, true, false)] + [InlineData(CascadeTiming.Immediate, true, false, true, true, false)] + [InlineData(CascadeTiming.Immediate, false, true, true, true, false)] + [InlineData(CascadeTiming.OnSaveChanges, false, false, true, false, false)] + [InlineData(CascadeTiming.OnSaveChanges, true, false, true, false, false)] + [InlineData(CascadeTiming.OnSaveChanges, false, true, true, false, false)] + [InlineData(CascadeTiming.OnSaveChanges, true, false, false, true, false)] + [InlineData(CascadeTiming.OnSaveChanges, false, true, false, true, false)] + [InlineData(CascadeTiming.OnSaveChanges, true, false, true, true, false)] + [InlineData(CascadeTiming.OnSaveChanges, false, true, true, true, false)] + [InlineData(CascadeTiming.Never, false, false, true, false, false)] + [InlineData(CascadeTiming.Never, true, false, true, false, false)] + [InlineData(CascadeTiming.Never, false, true, true, false, false)] + [InlineData(CascadeTiming.Never, true, false, false, true, false)] + [InlineData(CascadeTiming.Never, false, true, false, true, false)] + [InlineData(CascadeTiming.Never, true, false, true, true, false)] + [InlineData(CascadeTiming.Never, false, true, true, true, false)] + [InlineData(null, false, false, true, false, true)] + [InlineData(null, true, false, true, false, true)] + [InlineData(null, false, true, true, false, true)] + [InlineData(null, true, false, false, true, true)] + [InlineData(null, false, true, false, true, true)] + [InlineData(null, true, false, true, true, true)] + [InlineData(null, false, true, true, true, true)] + [InlineData(CascadeTiming.Immediate, false, false, true, false, true)] + [InlineData(CascadeTiming.Immediate, true, false, true, false, true)] + [InlineData(CascadeTiming.Immediate, false, true, true, false, true)] + [InlineData(CascadeTiming.Immediate, true, false, false, true, true)] + [InlineData(CascadeTiming.Immediate, false, true, false, true, true)] + [InlineData(CascadeTiming.Immediate, true, false, true, true, true)] + [InlineData(CascadeTiming.Immediate, false, true, true, true, true)] + [InlineData(CascadeTiming.OnSaveChanges, false, false, true, false, true)] + [InlineData(CascadeTiming.OnSaveChanges, true, false, true, false, true)] + [InlineData(CascadeTiming.OnSaveChanges, false, true, true, false, true)] + [InlineData(CascadeTiming.OnSaveChanges, true, false, false, true, true)] + [InlineData(CascadeTiming.OnSaveChanges, false, true, false, true, true)] + [InlineData(CascadeTiming.OnSaveChanges, true, false, true, true, true)] + [InlineData(CascadeTiming.OnSaveChanges, false, true, true, true, true)] + [InlineData(CascadeTiming.Never, false, false, true, false, true)] + [InlineData(CascadeTiming.Never, true, false, true, false, true)] + [InlineData(CascadeTiming.Never, false, true, true, false, true)] + [InlineData(CascadeTiming.Never, true, false, false, true, true)] + [InlineData(CascadeTiming.Never, false, true, false, true, true)] + [InlineData(CascadeTiming.Never, true, false, true, true, true)] + [InlineData(CascadeTiming.Never, false, true, true, true, true)] + public void Optional_relationship_with_cascade_does_not_delete_orphans( + CascadeTiming? orphanTiming, bool setProperty, bool setCurrentValue, bool useForeignKey, - bool useNavigation) + bool useNavigation, + bool forceCascade) { Kontainer detachedContainer; using (var context = new KontainerContext()) @@ -2076,9 +2119,9 @@ public void Optional_relationship_with_cascade_still_cascades( Assert.Equal(EntityState.Unchanged, context.Entry(attachedRoom).State); Assert.Equal(EntityState.Unchanged, context.Entry(attachedTroduct).State); - if (delayCascade) + if (orphanTiming != null) { - context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges; + context.ChangeTracker.DeleteOrphansTiming = orphanTiming.Value; } if (setProperty) @@ -2115,30 +2158,224 @@ public void Optional_relationship_with_cascade_still_cascades( Assert.Equal(3, context.ChangeTracker.Entries().Count()); Assert.Equal(EntityState.Unchanged, context.Entry(attachedContainer).State); Assert.Equal(EntityState.Unchanged, context.Entry(attachedTroduct).State); + Assert.Equal(EntityState.Modified, context.Entry(attachedRoom).State); - if (delayCascade - || (useForeignKey && setProperty)) + if (forceCascade) { - Assert.Equal(EntityState.Modified, context.Entry(attachedRoom).State); + context.ChangeTracker.CascadeChanges(); + } + + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(EntityState.Unchanged, context.Entry(attachedContainer).State); + Assert.Equal(EntityState.Unchanged, context.Entry(attachedTroduct).State); + Assert.Equal(EntityState.Modified, context.Entry(attachedRoom).State); + + context.SaveChanges(); + + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(EntityState.Unchanged, context.Entry(attachedContainer).State); + Assert.Equal(EntityState.Unchanged, context.Entry(attachedTroduct).State); + Assert.Equal(EntityState.Unchanged, context.Entry(attachedRoom).State); + } + } + + [ConditionalTheory] // Issues #16546 #25360; Change reverted in #27174. + [InlineData(null, false, false, true, false, false)] + [InlineData(null, true, false, true, false, false)] + [InlineData(null, false, true, true, false, false)] + [InlineData(null, true, false, false, true, false)] + [InlineData(null, false, true, false, true, false)] + [InlineData(null, true, false, true, true, false)] + [InlineData(null, false, true, true, true, false)] + [InlineData(CascadeTiming.Immediate, false, false, true, false, false)] + [InlineData(CascadeTiming.Immediate, true, false, true, false, false)] + [InlineData(CascadeTiming.Immediate, false, true, true, false, false)] + [InlineData(CascadeTiming.Immediate, true, false, false, true, false)] + [InlineData(CascadeTiming.Immediate, false, true, false, true, false)] + [InlineData(CascadeTiming.Immediate, true, false, true, true, false)] + [InlineData(CascadeTiming.Immediate, false, true, true, true, false)] + [InlineData(CascadeTiming.OnSaveChanges, false, false, true, false, false)] + [InlineData(CascadeTiming.OnSaveChanges, true, false, true, false, false)] + [InlineData(CascadeTiming.OnSaveChanges, false, true, true, false, false)] + [InlineData(CascadeTiming.OnSaveChanges, true, false, false, true, false)] + [InlineData(CascadeTiming.OnSaveChanges, false, true, false, true, false)] + [InlineData(CascadeTiming.OnSaveChanges, true, false, true, true, false)] + [InlineData(CascadeTiming.OnSaveChanges, false, true, true, true, false)] + [InlineData(CascadeTiming.Never, false, false, true, false, false)] + [InlineData(CascadeTiming.Never, true, false, true, false, false)] + [InlineData(CascadeTiming.Never, false, true, true, false, false)] + [InlineData(CascadeTiming.Never, true, false, false, true, false)] + [InlineData(CascadeTiming.Never, false, true, false, true, false)] + [InlineData(CascadeTiming.Never, true, false, true, true, false)] + [InlineData(CascadeTiming.Never, false, true, true, true, false)] + [InlineData(null, false, false, true, false, true)] + [InlineData(null, true, false, true, false, true)] + [InlineData(null, false, true, true, false, true)] + [InlineData(null, true, false, false, true, true)] + [InlineData(null, false, true, false, true, true)] + [InlineData(null, true, false, true, true, true)] + [InlineData(null, false, true, true, true, true)] + [InlineData(CascadeTiming.Immediate, false, false, true, false, true)] + [InlineData(CascadeTiming.Immediate, true, false, true, false, true)] + [InlineData(CascadeTiming.Immediate, false, true, true, false, true)] + [InlineData(CascadeTiming.Immediate, true, false, false, true, true)] + [InlineData(CascadeTiming.Immediate, false, true, false, true, true)] + [InlineData(CascadeTiming.Immediate, true, false, true, true, true)] + [InlineData(CascadeTiming.Immediate, false, true, true, true, true)] + [InlineData(CascadeTiming.OnSaveChanges, false, false, true, false, true)] + [InlineData(CascadeTiming.OnSaveChanges, true, false, true, false, true)] + [InlineData(CascadeTiming.OnSaveChanges, false, true, true, false, true)] + [InlineData(CascadeTiming.OnSaveChanges, true, false, false, true, true)] + [InlineData(CascadeTiming.OnSaveChanges, false, true, false, true, true)] + [InlineData(CascadeTiming.OnSaveChanges, true, false, true, true, true)] + [InlineData(CascadeTiming.OnSaveChanges, false, true, true, true, true)] + [InlineData(CascadeTiming.Never, false, false, true, false, true)] + [InlineData(CascadeTiming.Never, true, false, true, false, true)] + [InlineData(CascadeTiming.Never, false, true, true, false, true)] + [InlineData(CascadeTiming.Never, true, false, false, true, true)] + [InlineData(CascadeTiming.Never, false, true, false, true, true)] + [InlineData(CascadeTiming.Never, true, false, true, true, true)] + [InlineData(CascadeTiming.Never, false, true, true, true, true)] + public void Optional_relationship_with_cascade_can_be_forced_to_delete_orphans( + CascadeTiming? orphanTiming, + bool setProperty, + bool setCurrentValue, + bool useForeignKey, + bool useNavigation, + bool forceCascade) + { + Kontainer detachedContainer; + using (var context = new KontainerContext()) + { + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + + context.Add( + new Kontainer + { + Name = "C1", + Rooms = { new KontainerRoom { Number = 1, Troduct = new Troduct { Description = "Heavy Engine XT3" } } } + } + ); + + context.SaveChanges(); + + detachedContainer = context.Set() + .Include(container => container.Rooms) + .ThenInclude(room => room.Troduct) + .AsNoTracking() + .Single(); + } + + using (var context = new KontainerContext()) + { + var attachedContainer = context.Set() + .Include(container => container.Rooms) + .ThenInclude(room => room.Troduct) + .Single(); + + var attachedRoom = attachedContainer.Rooms.Single(); + var attachedTroduct = attachedRoom.Troduct; + + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(EntityState.Unchanged, context.Entry(attachedContainer).State); + Assert.Equal(EntityState.Unchanged, context.Entry(attachedRoom).State); + Assert.Equal(EntityState.Unchanged, context.Entry(attachedTroduct).State); + + if (orphanTiming != null) + { + context.ChangeTracker.DeleteOrphansTiming = orphanTiming.Value; + } + + if (setProperty) + { + if (useForeignKey) + { + attachedRoom.TroductId = null; + } + + if (useNavigation) + { + attachedRoom.Troduct = null; + } + } + else if (setCurrentValue) + { + if (useForeignKey) + { + context.Entry(attachedRoom).Property(e => e.TroductId).CurrentValue = null; + } + + if (useNavigation) + { + context.Entry(attachedRoom).Reference(e => e.Troduct).CurrentValue = null; + } } else { - // Deleted because FK with cascade has been set to null + var detachedRoom = detachedContainer.Rooms.Single(); + detachedRoom.TroductId = null; + context.Entry(attachedRoom).CurrentValues.SetValues(detachedRoom); + } + + context.Entry(attachedRoom).GetInfrastructure() + .HandleNullForeignKey(context.Entry(attachedRoom).Property(e => e.TroductId).Metadata); + + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(EntityState.Unchanged, context.Entry(attachedContainer).State); + Assert.Equal(EntityState.Unchanged, context.Entry(attachedTroduct).State); + + if (orphanTiming == null + || orphanTiming == CascadeTiming.Immediate) + { Assert.Equal(EntityState.Deleted, context.Entry(attachedRoom).State); } + else + { + Assert.Equal(EntityState.Modified, context.Entry(attachedRoom).State); + } - context.ChangeTracker.CascadeChanges(); + if (forceCascade) + { + context.ChangeTracker.CascadeChanges(); + } Assert.Equal(3, context.ChangeTracker.Entries().Count()); Assert.Equal(EntityState.Unchanged, context.Entry(attachedContainer).State); Assert.Equal(EntityState.Unchanged, context.Entry(attachedTroduct).State); + if (orphanTiming == null + || orphanTiming == CascadeTiming.Immediate + || forceCascade) + { + Assert.Equal(EntityState.Deleted, context.Entry(attachedRoom).State); + } + else + { + Assert.Equal(EntityState.Modified, context.Entry(attachedRoom).State); + } + if (orphanTiming == CascadeTiming.Never + && !forceCascade) + { Assert.Equal( - useForeignKey && setProperty ? EntityState.Modified : EntityState.Deleted, - context.Entry(attachedRoom).State); + CoreStrings.RelationshipConceptualNull(nameof(Troduct), nameof(KontainerRoom)), + Assert.Throws( + () => context.SaveChanges()).Message); - context.SaveChanges(); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(EntityState.Unchanged, context.Entry(attachedContainer).State); + Assert.Equal(EntityState.Unchanged, context.Entry(attachedTroduct).State); + Assert.Equal(EntityState.Modified, context.Entry(attachedRoom).State); + } + else + { + context.SaveChanges(); + Assert.Equal(2, context.ChangeTracker.Entries().Count()); + Assert.Equal(EntityState.Unchanged, context.Entry(attachedContainer).State); + Assert.Equal(EntityState.Unchanged, context.Entry(attachedTroduct).State); + Assert.Equal(EntityState.Detached, context.Entry(attachedRoom).State); + } } } From 1e962591b6fbde369faf95bad07c49de9ab7ceb5 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Fri, 11 Mar 2022 16:54:47 +0000 Subject: [PATCH 025/143] Remove quirks (#27622) --- src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs | 5 +---- src/EFCore/Metadata/Internal/PropertyExtensions.cs | 6 +----- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs index 7faa07590dc..94d13cf4851 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs @@ -1445,9 +1445,6 @@ public void AcceptChanges() } } - private readonly static bool _useOldBehavior27455 = - AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue27455", out var enabled27455) && enabled27455; - /// /// 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 @@ -1475,7 +1472,7 @@ public InternalEntityEntry PrepareToSave() if (property.IsKey() && property.IsForeignKey() && _stateData.IsPropertyFlagged(property.GetIndex(), PropertyFlag.Unknown) - && (_useOldBehavior27455 || !IsStoreGenerated(property))) + && !IsStoreGenerated(property)) { if (property.GetContainingForeignKeys().Any(fk => fk.IsOwnership)) { diff --git a/src/EFCore/Metadata/Internal/PropertyExtensions.cs b/src/EFCore/Metadata/Internal/PropertyExtensions.cs index c60ea556661..f0714f44d7f 100644 --- a/src/EFCore/Metadata/Internal/PropertyExtensions.cs +++ b/src/EFCore/Metadata/Internal/PropertyExtensions.cs @@ -70,9 +70,6 @@ public static bool ForUpdate(this ValueGenerated valueGenerated) return null; } - private readonly static bool _useOldBehavior27455 = - AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue27455", out var enabled27455) && enabled27455; - /// /// 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 @@ -84,8 +81,7 @@ public static bool RequiresValueGenerator(this IReadOnlyProperty property) && property.IsKey() && (!property.IsForeignKey() || property.IsForeignKeyToSelf() - || (!_useOldBehavior27455 - && property.GetContainingForeignKeys().All(fk => fk.Properties.Any(p => p != property && p.IsNullable))))) + || (property.GetContainingForeignKeys().All(fk => fk.Properties.Any(p => p != property && p.IsNullable))))) || property.GetValueGeneratorFactory() != null; /// From 324eb94cbd7f7536e88fa786d29de3067f372d94 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Sat, 12 Mar 2022 01:34:38 +0000 Subject: [PATCH 026/143] Update dependencies from https://github.com/dotnet/arcade build 20220311.1 (#27627) [release/6.0] Update dependencies from dotnet/arcade --- eng/Version.Details.xml | 8 ++++---- global.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 6ba93019da9..aedff6fe7a0 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -47,13 +47,13 @@ - + https://github.com/dotnet/arcade - 4f4c8c392d1c51e630f4571e39a095da7fb172c5 + 879df783283dfb44c7653493fdf7fd7b07ba6b01 - + https://github.com/dotnet/arcade - 4f4c8c392d1c51e630f4571e39a095da7fb172c5 + 879df783283dfb44c7653493fdf7fd7b07ba6b01 diff --git a/global.json b/global.json index ff8b5df3b75..7b96d22581e 100644 --- a/global.json +++ b/global.json @@ -18,7 +18,7 @@ "rollForward": "latestMajor" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.22159.8", - "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.22159.8" + "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.22161.1", + "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.22161.1" } } From 92709256f13e03983f95a66b06561f485aafd1f1 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 14 Mar 2022 13:14:03 +0200 Subject: [PATCH 027/143] Add default interface implementations to interceptors (#27635) Plus some cleanup Closes #27625 --- .../Diagnostics/DbCommandInterceptor.cs | 358 ++---------------- .../Diagnostics/DbConnectionInterceptor.cs | 143 +------ .../Diagnostics/DbTransactionInterceptor.cs | 279 ++------------ .../Diagnostics/IDbCommandInterceptor.cs | 94 ++--- .../Diagnostics/IDbConnectionInterceptor.cs | 56 ++- .../Diagnostics/IDbTransactionInterceptor.cs | 120 +++--- .../Diagnostics/ISaveChangesInterceptor.cs | 33 +- .../Diagnostics/SaveChangesInterceptor.cs | 104 +---- 8 files changed, 217 insertions(+), 970 deletions(-) diff --git a/src/EFCore.Relational/Diagnostics/DbCommandInterceptor.cs b/src/EFCore.Relational/Diagnostics/DbCommandInterceptor.cs index 4393f7d221d..165cde4038f 100644 --- a/src/EFCore.Relational/Diagnostics/DbCommandInterceptor.cs +++ b/src/EFCore.Relational/Diagnostics/DbCommandInterceptor.cs @@ -12,141 +12,33 @@ namespace Microsoft.EntityFrameworkCore.Diagnostics; /// public abstract class DbCommandInterceptor : IDbCommandInterceptor { - /// - /// Called just before EF intends to call . - /// - /// Contextual information about the command and execution. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation it - /// was about to perform and use instead. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in. - /// - public virtual InterceptionResult CommandCreating( - CommandCorrelatedEventData eventData, - InterceptionResult result) + /// + public virtual InterceptionResult CommandCreating(CommandCorrelatedEventData eventData, InterceptionResult result) => result; - /// - /// Called immediately after EF calls . - /// - /// - /// This method is still called if an interceptor suppressed creation of a command in - /// . - /// In this case, is the result returned by . - /// - /// Contextual information about the command and execution. - /// - /// The result of the call to . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// The result that EF will use. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in. - /// + /// public virtual DbCommand CommandCreated(CommandEndEventData eventData, DbCommand result) => result; - /// - /// Called just before EF intends to call . - /// - /// The command. - /// Contextual information about the command and execution. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation it - /// was about to perform and use instead. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in, often using - /// + /// public virtual InterceptionResult ReaderExecuting( DbCommand command, CommandEventData eventData, InterceptionResult result) => result; - /// - /// Called just before EF intends to call . - /// - /// The command. - /// Contextual information about the command and execution. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation it - /// was about to perform and use instead. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in, often using - /// + /// public virtual InterceptionResult ScalarExecuting( DbCommand command, CommandEventData eventData, InterceptionResult result) => result; - /// - /// Called just before EF intends to call . - /// - /// The command. - /// Contextual information about the command and execution. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation it - /// was about to perform and use instead. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in, often using - /// - public virtual InterceptionResult NonQueryExecuting( - DbCommand command, - CommandEventData eventData, - InterceptionResult result) + /// + public virtual InterceptionResult NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult result) => result; - /// - /// Called just before EF intends to call . - /// - /// The command. - /// Contextual information about the command and execution. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// A to observe while waiting for the task to complete. - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation it - /// was about to perform and use instead. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in, often using - /// - /// If the is canceled. + /// public virtual ValueTask> ReaderExecutingAsync( DbCommand command, CommandEventData eventData, @@ -154,26 +46,7 @@ public virtual ValueTask> ReaderExecutingAsync( CancellationToken cancellationToken = default) => new(result); - /// - /// Called just before EF intends to call . - /// - /// The command. - /// Contextual information about the command and execution. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// A to observe while waiting for the task to complete. - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation it - /// was about to perform and use instead. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in, often using - /// - /// If the is canceled. + /// public virtual ValueTask> ScalarExecutingAsync( DbCommand command, CommandEventData eventData, @@ -181,26 +54,7 @@ public virtual ValueTask> ScalarExecutingAsync( CancellationToken cancellationToken = default) => new(result); - /// - /// Called just before EF intends to call . - /// - /// The command. - /// Contextual information about the command and execution. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// A to observe while waiting for the task to complete. - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation it - /// was about to perform and use instead. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in, often using - /// - /// If the is canceled. + /// public virtual ValueTask> NonQueryExecutingAsync( DbCommand command, CommandEventData eventData, @@ -208,98 +62,19 @@ public virtual ValueTask> NonQueryExecutingAsync( CancellationToken cancellationToken = default) => new(result); - /// - /// Called immediately after EF calls . - /// - /// - /// This method is still called if an interceptor suppressed execution of a command in . - /// In this case, is the result returned by . - /// - /// The command. - /// Contextual information about the command and execution. - /// - /// The result of the call to . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// The result that EF will use. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in. - /// - public virtual DbDataReader ReaderExecuted( - DbCommand command, - CommandExecutedEventData eventData, - DbDataReader result) + /// + public virtual DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventData, DbDataReader result) => result; - /// - /// Called immediately after EF calls . - /// - /// - /// This method is still called if an interceptor suppressed execution of a command in . - /// In this case, is the result returned by . - /// - /// The command. - /// Contextual information about the command and execution. - /// - /// The result of the call to . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// The result that EF will use. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in. - /// - public virtual object? ScalarExecuted( - DbCommand command, - CommandExecutedEventData eventData, - object? result) + /// + public virtual object? ScalarExecuted(DbCommand command, CommandExecutedEventData eventData, object? result) => result; - /// - /// Called immediately after EF calls . - /// - /// - /// This method is still called if an interceptor suppressed execution of a command in . - /// In this case, is the result returned by . - /// - /// The command. - /// Contextual information about the command and execution. - /// - /// The result of the call to . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// The result that EF will use. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in. - /// - public virtual int NonQueryExecuted( - DbCommand command, - CommandExecutedEventData eventData, - int result) + /// + public virtual int NonQueryExecuted(DbCommand command, CommandExecutedEventData eventData, int result) => result; - /// - /// Called immediately after EF calls . - /// - /// - /// This method is still called if an interceptor suppressed execution of a command in . - /// In this case, is the result returned by . - /// - /// The command. - /// Contextual information about the command and execution. - /// - /// The result of the call to . - /// This value is typically used as the return value for the implementation of this method. - /// - /// A to observe while waiting for the task to complete. - /// - /// A providing the result that EF will use. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in, often using - /// - /// If the is canceled. + /// public virtual ValueTask ReaderExecutedAsync( DbCommand command, CommandExecutedEventData eventData, @@ -307,26 +82,7 @@ public virtual ValueTask ReaderExecutedAsync( CancellationToken cancellationToken = default) => new(result); - /// - /// Called immediately after EF calls . - /// - /// - /// This method is still called if an interceptor suppressed execution of a command in . - /// In this case, is the result returned by . - /// - /// The command. - /// Contextual information about the command and execution. - /// - /// The result of the call to . - /// This value is typically used as the return value for the implementation of this method. - /// - /// A to observe while waiting for the task to complete. - /// - /// A providing the result that EF will use. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in, often using - /// - /// If the is canceled. + /// public virtual ValueTask ScalarExecutedAsync( DbCommand command, CommandExecutedEventData eventData, @@ -334,26 +90,7 @@ public virtual ValueTask ReaderExecutedAsync( CancellationToken cancellationToken = default) => new(result); - /// - /// Called immediately after EF calls . - /// - /// - /// This method is still called if an interceptor suppressed execution of a command in . - /// In this case, is the result returned by . - /// - /// The command. - /// Contextual information about the command and execution. - /// - /// The result of the call to . - /// This value is typically used as the return value for the implementation of this method. - /// - /// A to observe while waiting for the task to complete. - /// - /// A providing the result that EF will use. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in, often using - /// - /// If the is canceled. + /// public virtual ValueTask NonQueryExecutedAsync( DbCommand command, CommandExecutedEventData eventData, @@ -361,74 +98,31 @@ public virtual ValueTask NonQueryExecutedAsync( CancellationToken cancellationToken = default) => new(result); - /// - /// Called when a command was canceled. - /// - /// The command. - /// Contextual information about the command and execution. - public virtual void CommandCanceled( - DbCommand command, - CommandEndEventData eventData) + /// + public virtual void CommandCanceled(DbCommand command, CommandEndEventData eventData) { } - /// - /// Called when a command was canceled. - /// - /// The command. - /// Contextual information about the command and execution. - /// A to observe while waiting for the task to complete. - /// A representing the asynchronous operation. - /// If the is canceled. + /// public virtual Task CommandCanceledAsync( DbCommand command, CommandEndEventData eventData, CancellationToken cancellationToken = default) => Task.CompletedTask; - /// - /// Called when execution of a command has failed with an exception. - /// - /// The command. - /// Contextual information about the command and execution. - public virtual void CommandFailed( - DbCommand command, - CommandErrorEventData eventData) + /// + public virtual void CommandFailed(DbCommand command, CommandErrorEventData eventData) { } - /// - /// Called when execution of a command has failed with an exception. - /// - /// The command. - /// Contextual information about the command and execution. - /// A to observe while waiting for the task to complete. - /// A representing the asynchronous operation. - /// If the is canceled. + /// public virtual Task CommandFailedAsync( DbCommand command, CommandErrorEventData eventData, CancellationToken cancellationToken = default) => Task.CompletedTask; - /// - /// Called when execution of a is about to be disposed. - /// - /// The command. - /// Contextual information about the command and reader. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation - /// it was about to perform. - /// A normal implementation of this method for any interceptor that is not attempting to suppress - /// the operation is to return the value passed in. - /// + /// public virtual InterceptionResult DataReaderDisposing( DbCommand command, DataReaderDisposingEventData eventData, diff --git a/src/EFCore.Relational/Diagnostics/DbConnectionInterceptor.cs b/src/EFCore.Relational/Diagnostics/DbConnectionInterceptor.cs index b0786e93323..135e4c01cc4 100644 --- a/src/EFCore.Relational/Diagnostics/DbConnectionInterceptor.cs +++ b/src/EFCore.Relational/Diagnostics/DbConnectionInterceptor.cs @@ -12,50 +12,11 @@ namespace Microsoft.EntityFrameworkCore.Diagnostics; /// public abstract class DbConnectionInterceptor : IDbConnectionInterceptor { - /// - /// Called just before EF intends to call . - /// - /// The connection. - /// Contextual information about the connection. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation - /// it was about to perform. - /// A normal implementation of this method for any interceptor that is not attempting to suppress - /// the operation is to return the value passed in. - /// - public virtual InterceptionResult ConnectionOpening( - DbConnection connection, - ConnectionEventData eventData, - InterceptionResult result) + /// + public virtual InterceptionResult ConnectionOpening(DbConnection connection, ConnectionEventData eventData, InterceptionResult result) => result; - /// - /// Called just before EF intends to call . - /// - /// The connection. - /// Contextual information about the connection. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// A to observe while waiting for the task to complete. - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation - /// it was about to perform. - /// A normal implementation of this method for any interceptor that is not attempting to suppress - /// the operation is to return the value passed in. - /// - /// If the is canceled. + /// public virtual ValueTask ConnectionOpeningAsync( DbConnection connection, ConnectionEventData eventData, @@ -63,118 +24,44 @@ public virtual ValueTask ConnectionOpeningAsync( CancellationToken cancellationToken = default) => new(result); - /// - /// Called just after EF has called . - /// - /// The connection. - /// Contextual information about the connection. + /// public virtual void ConnectionOpened(DbConnection connection, ConnectionEndEventData eventData) { } - /// - /// Called just after EF has called . - /// - /// The connection. - /// Contextual information about the connection. - /// A to observe while waiting for the task to complete. - /// A representing the asynchronous operation. - /// If the is canceled. + /// public virtual Task ConnectionOpenedAsync( DbConnection connection, ConnectionEndEventData eventData, CancellationToken cancellationToken = default) => Task.CompletedTask; - /// - /// Called just before EF intends to call . - /// - /// The connection. - /// Contextual information about the connection. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation - /// it was about to perform. - /// A normal implementation of this method for any interceptor that is not attempting to suppress - /// the operation is to return the value passed in. - /// - public virtual InterceptionResult ConnectionClosing( - DbConnection connection, - ConnectionEventData eventData, - InterceptionResult result) + /// + public virtual InterceptionResult ConnectionClosing(DbConnection connection, ConnectionEventData eventData, InterceptionResult result) => result; - /// - /// Called just before EF intends to call in an async context. - /// - /// The connection. - /// Contextual information about the connection. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation - /// it was about to perform. - /// A normal implementation of this method for any interceptor that is not attempting to suppress - /// the operation is to return the value passed in. - /// + /// public virtual ValueTask ConnectionClosingAsync( DbConnection connection, ConnectionEventData eventData, InterceptionResult result) => new(result); - /// - /// Called just after EF has called in an async context. - /// - /// The connection. - /// Contextual information about the connection. - public virtual void ConnectionClosed( - DbConnection connection, - ConnectionEndEventData eventData) + /// + public virtual void ConnectionClosed(DbConnection connection, ConnectionEndEventData eventData) { } - /// - /// Called just after EF has called . - /// - /// The connection. - /// Contextual information about the connection. - /// A representing the asynchronous operation. - public virtual Task ConnectionClosedAsync( - DbConnection connection, - ConnectionEndEventData eventData) + /// + public virtual Task ConnectionClosedAsync(DbConnection connection, ConnectionEndEventData eventData) => Task.CompletedTask; - /// - /// Called when opening of a connection has failed with an exception. - /// - /// The connection. - /// Contextual information about the connection. - public virtual void ConnectionFailed( - DbConnection connection, - ConnectionErrorEventData eventData) + /// + public virtual void ConnectionFailed(DbConnection connection, ConnectionErrorEventData eventData) { } - /// - /// Called when opening of a connection has failed with an exception. - /// - /// The connection. - /// Contextual information about the connection. - /// A to observe while waiting for the task to complete. - /// A representing the asynchronous operation. - /// If the is canceled. + /// public virtual Task ConnectionFailedAsync( DbConnection connection, ConnectionErrorEventData eventData, diff --git a/src/EFCore.Relational/Diagnostics/DbTransactionInterceptor.cs b/src/EFCore.Relational/Diagnostics/DbTransactionInterceptor.cs index 1fa41428e5c..801330c4a95 100644 --- a/src/EFCore.Relational/Diagnostics/DbTransactionInterceptor.cs +++ b/src/EFCore.Relational/Diagnostics/DbTransactionInterceptor.cs @@ -14,75 +14,18 @@ namespace Microsoft.EntityFrameworkCore.Diagnostics; /// public abstract class DbTransactionInterceptor : IDbTransactionInterceptor { - /// - /// Called just before EF intends to call . - /// - /// The connection. - /// Contextual information about connection and transaction. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation it - /// was about to perform and use instead. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in, often using - /// + /// public virtual InterceptionResult TransactionStarting( DbConnection connection, TransactionStartingEventData eventData, InterceptionResult result) => result; - /// - /// Called immediately after EF calls . - /// - /// - /// This method is still called if an interceptor suppressed creation in . - /// In this case, is the result returned by . - /// - /// The connection. - /// Contextual information about connection and transaction. - /// - /// The result of the call to . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// The result that EF will use. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in. - /// - public virtual DbTransaction TransactionStarted( - DbConnection connection, - TransactionEndEventData eventData, - DbTransaction result) + /// + public virtual DbTransaction TransactionStarted(DbConnection connection, TransactionEndEventData eventData, DbTransaction result) => result; - /// - /// Called just before EF intends to call - /// . - /// - /// The connection. - /// Contextual information about connection and transaction. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// A to observe while waiting for the task to complete. - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation it - /// was about to perform and use instead. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in, often using - /// - /// If the is canceled. + /// public virtual ValueTask> TransactionStartingAsync( DbConnection connection, TransactionStartingEventData eventData, @@ -90,27 +33,7 @@ public virtual ValueTask> TransactionStartingA CancellationToken cancellationToken = default) => new(result); - /// - /// Called immediately after EF calls . - /// - /// - /// This method is still called if an interceptor suppressed creation in . - /// In this case, is the result returned by . - /// - /// The connection. - /// Contextual information about connection and transaction. - /// - /// The result of the call to - /// . - /// This value is typically used as the return value for the implementation of this method. - /// - /// A to observe while waiting for the task to complete. - /// - /// A providing the result that EF will use. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in, often using - /// - /// If the is canceled. + /// public virtual ValueTask TransactionStartedAsync( DbConnection connection, TransactionEndEventData eventData, @@ -118,43 +41,11 @@ public virtual ValueTask TransactionStartedAsync( CancellationToken cancellationToken = default) => new(result); - /// - /// Called immediately after is called. - /// - /// The connection. - /// Contextual information about connection and transaction. - /// - /// The that was passed to . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// The value that will be used as the effective value passed to - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in. - /// - public virtual DbTransaction TransactionUsed( - DbConnection connection, - TransactionEventData eventData, - DbTransaction result) + /// + public virtual DbTransaction TransactionUsed(DbConnection connection, TransactionEventData eventData, DbTransaction result) => result; - /// - /// Called immediately after is called. - /// - /// The connection. - /// Contextual information about connection and transaction. - /// - /// The that was passed to . - /// This value is typically used as the return value for the implementation of this method. - /// - /// A to observe while waiting for the task to complete. - /// - /// A containing the value that will be used as the effective value passed - /// to - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in, often using - /// - /// If the is canceled. + /// public virtual ValueTask TransactionUsedAsync( DbConnection connection, TransactionEventData eventData, @@ -162,62 +53,19 @@ public virtual ValueTask TransactionUsedAsync( CancellationToken cancellationToken = default) => new(result); - /// - /// Called just before EF intends to call . - /// - /// The transaction. - /// Contextual information about connection and transaction. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation - /// it was about to perform. - /// A normal implementation of this method for any interceptor that is not attempting to suppress - /// the operation is to return the value passed in. - /// + /// public virtual InterceptionResult TransactionCommitting( DbTransaction transaction, TransactionEventData eventData, InterceptionResult result) => result; - /// - /// Called immediately after EF calls . - /// - /// The transaction. - /// Contextual information about connection and transaction. - public virtual void TransactionCommitted( - DbTransaction transaction, - TransactionEndEventData eventData) + /// + public virtual void TransactionCommitted(DbTransaction transaction, TransactionEndEventData eventData) { } - /// - /// Called just before EF intends to call - /// . - /// - /// The transaction. - /// Contextual information about connection and transaction. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// A to observe while waiting for the task to complete. - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation - /// it was about to perform. - /// A normal implementation of this method for any interceptor that is not attempting to suppress - /// the operation is to return the value passed in. - /// - /// If the is canceled. + /// public virtual ValueTask TransactionCommittingAsync( DbTransaction transaction, TransactionEventData eventData, @@ -225,76 +73,26 @@ public virtual ValueTask TransactionCommittingAsync( CancellationToken cancellationToken = default) => new(result); - /// - /// Called immediately after EF calls . - /// - /// The transaction. - /// Contextual information about connection and transaction. - /// A to observe while waiting for the task to complete. - /// A representing the asynchronous operation. - /// If the is canceled. + /// public virtual Task TransactionCommittedAsync( DbTransaction transaction, TransactionEndEventData eventData, CancellationToken cancellationToken = default) => Task.CompletedTask; - /// - /// Called just before EF intends to call . - /// - /// The transaction. - /// Contextual information about connection and transaction. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation - /// it was about to perform. - /// A normal implementation of this method for any interceptor that is not attempting to suppress - /// the operation is to return the value passed in. - /// + /// public virtual InterceptionResult TransactionRollingBack( DbTransaction transaction, TransactionEventData eventData, InterceptionResult result) => result; - /// - /// Called immediately after EF calls . - /// - /// The transaction. - /// Contextual information about connection and transaction. - public virtual void TransactionRolledBack( - DbTransaction transaction, - TransactionEndEventData eventData) + /// + public virtual void TransactionRolledBack(DbTransaction transaction, TransactionEndEventData eventData) { } - /// - /// Called just before EF intends to call - /// . - /// - /// The transaction. - /// Contextual information about connection and transaction. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// A to observe while waiting for the task to complete. - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation - /// it was about to perform. - /// A normal implementation of this method for any interceptor that is not attempting to suppress - /// the operation is to return the value passed in. - /// - /// If the is canceled. + /// public virtual ValueTask TransactionRollingBackAsync( DbTransaction transaction, TransactionEventData eventData, @@ -302,14 +100,7 @@ public virtual ValueTask TransactionRollingBackAsync( CancellationToken cancellationToken = default) => new(result); - /// - /// Called immediately after EF calls . - /// - /// The transaction. - /// Contextual information about connection and transaction. - /// A to observe while waiting for the task to complete. - /// A representing the asynchronous operation. - /// If the is canceled. + /// public virtual Task TransactionRolledBackAsync( DbTransaction transaction, TransactionEndEventData eventData, @@ -324,9 +115,7 @@ public virtual InterceptionResult CreatingSavepoint( => result; /// - public virtual void CreatedSavepoint( - DbTransaction transaction, - TransactionEventData eventData) + public virtual void CreatedSavepoint(DbTransaction transaction, TransactionEventData eventData) { } @@ -353,9 +142,7 @@ public virtual InterceptionResult RollingBackToSavepoint( => result; /// - public virtual void RolledBackToSavepoint( - DbTransaction transaction, - TransactionEventData eventData) + public virtual void RolledBackToSavepoint(DbTransaction transaction, TransactionEventData eventData) { } @@ -382,9 +169,7 @@ public virtual InterceptionResult ReleasingSavepoint( => result; /// - public virtual void ReleasedSavepoint( - DbTransaction transaction, - TransactionEventData eventData) + public virtual void ReleasedSavepoint(DbTransaction transaction, TransactionEventData eventData) { } @@ -403,27 +188,13 @@ public virtual Task ReleasedSavepointAsync( CancellationToken cancellationToken = default) => Task.CompletedTask; - /// - /// Called when use of a has failed with an exception. - /// - /// The transaction. - /// Contextual information about connection and transaction. - public virtual void TransactionFailed( - DbTransaction transaction, - TransactionErrorEventData eventData) + /// + public virtual void TransactionFailed(DbTransaction transaction, TransactionErrorEventData eventData) { } - /// - /// Called when use of a has failed with an exception. - /// - /// The transaction. - /// Contextual information about connection and transaction. - /// A to observe while waiting for the task to complete. - /// A representing the asynchronous operation. - /// If the is canceled. - public virtual Task TransactionFailedAsync( - DbTransaction transaction, + /// + public virtual Task TransactionFailedAsync(DbTransaction transaction, TransactionErrorEventData eventData, CancellationToken cancellationToken = default) => Task.CompletedTask; diff --git a/src/EFCore.Relational/Diagnostics/IDbCommandInterceptor.cs b/src/EFCore.Relational/Diagnostics/IDbCommandInterceptor.cs index 6a3d3f50350..dddca4631ad 100644 --- a/src/EFCore.Relational/Diagnostics/IDbCommandInterceptor.cs +++ b/src/EFCore.Relational/Diagnostics/IDbCommandInterceptor.cs @@ -46,9 +46,8 @@ public interface IDbCommandInterceptor : IInterceptor /// A normal implementation of this method for any interceptor that is not attempting to change the result /// is to return the value passed in. /// - InterceptionResult CommandCreating( - CommandCorrelatedEventData eventData, - InterceptionResult result); + InterceptionResult CommandCreating(CommandCorrelatedEventData eventData, InterceptionResult result) + => result; /// /// Called immediately after EF calls . @@ -67,9 +66,8 @@ InterceptionResult CommandCreating( /// A normal implementation of this method for any interceptor that is not attempting to change the result /// is to return the value passed in. /// - DbCommand CommandCreated( - CommandEndEventData eventData, - DbCommand result); + DbCommand CommandCreated(CommandEndEventData eventData, DbCommand result) + => result; /// /// Called just before EF intends to call . @@ -89,10 +87,8 @@ DbCommand CommandCreated( /// A normal implementation of this method for any interceptor that is not attempting to change the result /// is to return the value passed in. /// - InterceptionResult ReaderExecuting( - DbCommand command, - CommandEventData eventData, - InterceptionResult result); + InterceptionResult ReaderExecuting(DbCommand command,CommandEventData eventData, InterceptionResult result) + => result; /// /// Called just before EF intends to call . @@ -112,10 +108,8 @@ InterceptionResult ReaderExecuting( /// A normal implementation of this method for any interceptor that is not attempting to change the result /// is to return the value passed in. /// - InterceptionResult ScalarExecuting( - DbCommand command, - CommandEventData eventData, - InterceptionResult result); + InterceptionResult ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult result) + => result; /// /// Called just before EF intends to call . @@ -135,10 +129,8 @@ InterceptionResult ScalarExecuting( /// A normal implementation of this method for any interceptor that is not attempting to change the result /// is to return the value passed in. /// - InterceptionResult NonQueryExecuting( - DbCommand command, - CommandEventData eventData, - InterceptionResult result); + InterceptionResult NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult result) + => result; /// /// Called just before EF intends to call . @@ -164,7 +156,8 @@ ValueTask> ReaderExecutingAsync( DbCommand command, CommandEventData eventData, InterceptionResult result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called just before EF intends to call . @@ -190,7 +183,8 @@ ValueTask> ScalarExecutingAsync( DbCommand command, CommandEventData eventData, InterceptionResult result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called just before EF intends to call . @@ -216,7 +210,8 @@ ValueTask> NonQueryExecutingAsync( DbCommand command, CommandEventData eventData, InterceptionResult result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called immediately after EF calls . @@ -236,10 +231,8 @@ ValueTask> NonQueryExecutingAsync( /// A normal implementation of this method for any interceptor that is not attempting to change the result /// is to return the value passed in. /// - DbDataReader ReaderExecuted( - DbCommand command, - CommandExecutedEventData eventData, - DbDataReader result); + DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventData, DbDataReader result) + => result; /// /// Called immediately after EF calls . @@ -259,10 +252,8 @@ DbDataReader ReaderExecuted( /// A normal implementation of this method for any interceptor that is not attempting to change the result /// is to return the value passed in. /// - object? ScalarExecuted( - DbCommand command, - CommandExecutedEventData eventData, - object? result); + object? ScalarExecuted(DbCommand command, CommandExecutedEventData eventData, object? result) + => result; /// /// Called immediately after EF calls . @@ -282,10 +273,8 @@ DbDataReader ReaderExecuted( /// A normal implementation of this method for any interceptor that is not attempting to change the result /// is to return the value passed in. /// - int NonQueryExecuted( - DbCommand command, - CommandExecutedEventData eventData, - int result); + int NonQueryExecuted(DbCommand command, CommandExecutedEventData eventData, int result) + => result; /// /// Called immediately after EF calls . @@ -311,7 +300,8 @@ ValueTask ReaderExecutedAsync( DbCommand command, CommandExecutedEventData eventData, DbDataReader result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called immediately after EF calls . @@ -337,7 +327,8 @@ ValueTask ReaderExecutedAsync( DbCommand command, CommandExecutedEventData eventData, object? result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called immediately after EF calls . @@ -363,16 +354,17 @@ ValueTask NonQueryExecutedAsync( DbCommand command, CommandExecutedEventData eventData, int result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called when a command was canceled. /// /// The command. /// Contextual information about the command and execution. - void CommandCanceled( - DbCommand command, - CommandEndEventData eventData); + void CommandCanceled(DbCommand command, CommandEndEventData eventData) + { + } /// /// Called when a command was canceled. @@ -382,19 +374,17 @@ void CommandCanceled( /// A to observe while waiting for the task to complete. /// A representing the asynchronous operation. /// If the is canceled. - Task CommandCanceledAsync( - DbCommand command, - CommandEndEventData eventData, - CancellationToken cancellationToken = default); + Task CommandCanceledAsync(DbCommand command, CommandEndEventData eventData, CancellationToken cancellationToken = default) + => Task.CompletedTask; /// /// Called when execution of a command has failed with an exception. /// /// The command. /// Contextual information about the command and execution. - void CommandFailed( - DbCommand command, - CommandErrorEventData eventData); + void CommandFailed(DbCommand command, CommandErrorEventData eventData) + { + } /// /// Called when execution of a command has failed with an exception. @@ -404,10 +394,8 @@ void CommandFailed( /// A to observe while waiting for the task to complete. /// A representing the asynchronous operation. /// If the is canceled. - Task CommandFailedAsync( - DbCommand command, - CommandErrorEventData eventData, - CancellationToken cancellationToken = default); + Task CommandFailedAsync(DbCommand command, CommandErrorEventData eventData, CancellationToken cancellationToken = default) + => Task.CompletedTask; /// /// Called when execution of a is about to be disposed. @@ -427,8 +415,6 @@ Task CommandFailedAsync( /// A normal implementation of this method for any interceptor that is not attempting to suppress /// the operation is to return the value passed in. /// - InterceptionResult DataReaderDisposing( - DbCommand command, - DataReaderDisposingEventData eventData, - InterceptionResult result); + InterceptionResult DataReaderDisposing(DbCommand command, DataReaderDisposingEventData eventData, InterceptionResult result) + => result; } diff --git a/src/EFCore.Relational/Diagnostics/IDbConnectionInterceptor.cs b/src/EFCore.Relational/Diagnostics/IDbConnectionInterceptor.cs index 684f4e3825e..a036cc633d6 100644 --- a/src/EFCore.Relational/Diagnostics/IDbConnectionInterceptor.cs +++ b/src/EFCore.Relational/Diagnostics/IDbConnectionInterceptor.cs @@ -47,10 +47,8 @@ public interface IDbConnectionInterceptor : IInterceptor /// A normal implementation of this method for any interceptor that is not attempting to suppress /// the operation is to return the value passed in. /// - InterceptionResult ConnectionOpening( - DbConnection connection, - ConnectionEventData eventData, - InterceptionResult result); + InterceptionResult ConnectionOpening(DbConnection connection, ConnectionEventData eventData, InterceptionResult result) + => result; /// /// Called just before EF intends to call . @@ -76,16 +74,17 @@ ValueTask ConnectionOpeningAsync( DbConnection connection, ConnectionEventData eventData, InterceptionResult result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called just after EF has called . /// /// The connection. /// Contextual information about the connection. - void ConnectionOpened( - DbConnection connection, - ConnectionEndEventData eventData); + void ConnectionOpened(DbConnection connection, ConnectionEndEventData eventData) + { + } /// /// Called just after EF has called . @@ -95,10 +94,8 @@ void ConnectionOpened( /// A to observe while waiting for the task to complete. /// A representing the asynchronous operation. /// If the is canceled. - Task ConnectionOpenedAsync( - DbConnection connection, - ConnectionEndEventData eventData, - CancellationToken cancellationToken = default); + Task ConnectionOpenedAsync(DbConnection connection, ConnectionEndEventData eventData, CancellationToken cancellationToken = default) + => Task.CompletedTask; /// /// Called just before EF intends to call . @@ -118,10 +115,8 @@ Task ConnectionOpenedAsync( /// A normal implementation of this method for any interceptor that is not attempting to suppress /// the operation is to return the value passed in. /// - InterceptionResult ConnectionClosing( - DbConnection connection, - ConnectionEventData eventData, - InterceptionResult result); + InterceptionResult ConnectionClosing(DbConnection connection, ConnectionEventData eventData, InterceptionResult result) + => result; /// /// Called just before EF intends to call in an async context. @@ -141,19 +136,17 @@ InterceptionResult ConnectionClosing( /// A normal implementation of this method for any interceptor that is not attempting to suppress /// the operation is to return the value passed in. /// - ValueTask ConnectionClosingAsync( - DbConnection connection, - ConnectionEventData eventData, - InterceptionResult result); + ValueTask ConnectionClosingAsync(DbConnection connection, ConnectionEventData eventData, InterceptionResult result) + => new(result); /// /// Called just after EF has called in an async context. /// /// The connection. /// Contextual information about the connection. - void ConnectionClosed( - DbConnection connection, - ConnectionEndEventData eventData); + void ConnectionClosed(DbConnection connection, ConnectionEndEventData eventData) + { + } /// /// Called just after EF has called . @@ -161,18 +154,17 @@ void ConnectionClosed( /// The connection. /// Contextual information about the connection. /// A representing the asynchronous operation. - Task ConnectionClosedAsync( - DbConnection connection, - ConnectionEndEventData eventData); + Task ConnectionClosedAsync(DbConnection connection, ConnectionEndEventData eventData) + => Task.CompletedTask; /// /// Called when closing of a connection has failed with an exception. /// /// The connection. /// Contextual information about the connection. - void ConnectionFailed( - DbConnection connection, - ConnectionErrorEventData eventData); + void ConnectionFailed(DbConnection connection, ConnectionErrorEventData eventData) + { + } /// /// Called when closing of a connection has failed with an exception. @@ -182,8 +174,6 @@ void ConnectionFailed( /// A to observe while waiting for the task to complete. /// A representing the asynchronous operation. /// If the is canceled. - Task ConnectionFailedAsync( - DbConnection connection, - ConnectionErrorEventData eventData, - CancellationToken cancellationToken = default); + Task ConnectionFailedAsync(DbConnection connection, ConnectionErrorEventData eventData, CancellationToken cancellationToken = default) + => Task.CompletedTask; } diff --git a/src/EFCore.Relational/Diagnostics/IDbTransactionInterceptor.cs b/src/EFCore.Relational/Diagnostics/IDbTransactionInterceptor.cs index 8a6e4db77fa..dc579ba8f6e 100644 --- a/src/EFCore.Relational/Diagnostics/IDbTransactionInterceptor.cs +++ b/src/EFCore.Relational/Diagnostics/IDbTransactionInterceptor.cs @@ -52,7 +52,8 @@ public interface IDbTransactionInterceptor : IInterceptor InterceptionResult TransactionStarting( DbConnection connection, TransactionStartingEventData eventData, - InterceptionResult result); + InterceptionResult result) + => result; /// /// Called immediately after EF calls . @@ -72,10 +73,8 @@ InterceptionResult TransactionStarting( /// A normal implementation of this method for any interceptor that is not attempting to change the result /// is to return the value passed in. /// - DbTransaction TransactionStarted( - DbConnection connection, - TransactionEndEventData eventData, - DbTransaction result); + DbTransaction TransactionStarted(DbConnection connection, TransactionEndEventData eventData, DbTransaction result) + => result; /// /// Called just before EF intends to call @@ -102,7 +101,8 @@ ValueTask> TransactionStartingAsync( DbConnection connection, TransactionStartingEventData eventData, InterceptionResult result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called immediately after EF calls . @@ -129,7 +129,8 @@ ValueTask TransactionStartedAsync( DbConnection connection, TransactionEndEventData eventData, DbTransaction result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called immediately after is called. @@ -145,10 +146,8 @@ ValueTask TransactionStartedAsync( /// A normal implementation of this method for any interceptor that is not attempting to change the result /// is to return the value passed in. /// - DbTransaction TransactionUsed( - DbConnection connection, - TransactionEventData eventData, - DbTransaction result); + DbTransaction TransactionUsed(DbConnection connection, TransactionEventData eventData, DbTransaction result) + => result; /// /// Called immediately after is called. @@ -171,7 +170,8 @@ ValueTask TransactionUsedAsync( DbConnection connection, TransactionEventData eventData, DbTransaction result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called just before EF intends to call . @@ -191,19 +191,17 @@ ValueTask TransactionUsedAsync( /// A normal implementation of this method for any interceptor that is not attempting to suppress /// the operation is to return the value passed in. /// - InterceptionResult TransactionCommitting( - DbTransaction transaction, - TransactionEventData eventData, - InterceptionResult result); + InterceptionResult TransactionCommitting(DbTransaction transaction, TransactionEventData eventData, InterceptionResult result) + => result; /// /// Called immediately after EF calls . /// /// The transaction. /// Contextual information about connection and transaction. - void TransactionCommitted( - DbTransaction transaction, - TransactionEndEventData eventData); + void TransactionCommitted(DbTransaction transaction, TransactionEndEventData eventData) + { + } /// /// Called just before EF intends to call @@ -230,7 +228,8 @@ ValueTask TransactionCommittingAsync( DbTransaction transaction, TransactionEventData eventData, InterceptionResult result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called immediately after EF calls . @@ -243,7 +242,8 @@ ValueTask TransactionCommittingAsync( Task TransactionCommittedAsync( DbTransaction transaction, TransactionEndEventData eventData, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => Task.CompletedTask; /// /// Called just before EF intends to call . @@ -263,19 +263,17 @@ Task TransactionCommittedAsync( /// A normal implementation of this method for any interceptor that is not attempting to suppress /// the operation is to return the value passed in. /// - InterceptionResult TransactionRollingBack( - DbTransaction transaction, - TransactionEventData eventData, - InterceptionResult result); + InterceptionResult TransactionRollingBack(DbTransaction transaction, TransactionEventData eventData, InterceptionResult result) + => result; /// /// Called immediately after EF calls . /// /// The transaction. /// Contextual information about connection and transaction. - void TransactionRolledBack( - DbTransaction transaction, - TransactionEndEventData eventData); + void TransactionRolledBack(DbTransaction transaction, TransactionEndEventData eventData) + { + } /// /// Called just before EF intends to call @@ -302,7 +300,8 @@ ValueTask TransactionRollingBackAsync( DbTransaction transaction, TransactionEventData eventData, InterceptionResult result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called immediately after EF calls . @@ -315,7 +314,8 @@ ValueTask TransactionRollingBackAsync( Task TransactionRolledBackAsync( DbTransaction transaction, TransactionEndEventData eventData, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => Task.CompletedTask; /// /// Called just before EF intends to create a transaction savepoint. @@ -335,19 +335,17 @@ Task TransactionRolledBackAsync( /// A normal implementation of this method for any interceptor that is not attempting to suppress /// the operation is to return the value passed in. /// - InterceptionResult CreatingSavepoint( - DbTransaction transaction, - TransactionEventData eventData, - InterceptionResult result); + InterceptionResult CreatingSavepoint(DbTransaction transaction, TransactionEventData eventData, InterceptionResult result) + => result; /// /// Called immediately after EF creates a transaction savepoint. /// /// The transaction. /// Contextual information about connection and transaction. - void CreatedSavepoint( - DbTransaction transaction, - TransactionEventData eventData); + void CreatedSavepoint(DbTransaction transaction, TransactionEventData eventData) + { + } /// /// Called just before EF intends to create a transaction savepoint. @@ -373,7 +371,8 @@ ValueTask CreatingSavepointAsync( DbTransaction transaction, TransactionEventData eventData, InterceptionResult result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called immediately after EF calls . @@ -386,7 +385,8 @@ ValueTask CreatingSavepointAsync( Task CreatedSavepointAsync( DbTransaction transaction, TransactionEventData eventData, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => Task.CompletedTask; /// /// Called just before EF intends to roll back to a transaction savepoint. @@ -409,16 +409,17 @@ Task CreatedSavepointAsync( InterceptionResult RollingBackToSavepoint( DbTransaction transaction, TransactionEventData eventData, - InterceptionResult result); + InterceptionResult result) + => result; /// /// Called immediately after EF rolls back to a transaction savepoint. /// /// The transaction. /// Contextual information about connection and transaction. - void RolledBackToSavepoint( - DbTransaction transaction, - TransactionEventData eventData); + void RolledBackToSavepoint(DbTransaction transaction, TransactionEventData eventData) + { + } /// /// Called just before EF intends to roll back to a transaction savepoint. @@ -444,7 +445,8 @@ ValueTask RollingBackToSavepointAsync( DbTransaction transaction, TransactionEventData eventData, InterceptionResult result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called immediately after EF rolls back to a transaction savepoint. @@ -457,7 +459,8 @@ ValueTask RollingBackToSavepointAsync( Task RolledBackToSavepointAsync( DbTransaction transaction, TransactionEventData eventData, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => Task.CompletedTask; /// /// Called just before EF intends to release a transaction savepoint. @@ -477,19 +480,17 @@ Task RolledBackToSavepointAsync( /// A normal implementation of this method for any interceptor that is not attempting to suppress /// the operation is to return the value passed in. /// - InterceptionResult ReleasingSavepoint( - DbTransaction transaction, - TransactionEventData eventData, - InterceptionResult result); + InterceptionResult ReleasingSavepoint(DbTransaction transaction, TransactionEventData eventData, InterceptionResult result) + => result; /// /// Called immediately after EF releases a transaction savepoint. /// /// The transaction. /// Contextual information about connection and transaction. - void ReleasedSavepoint( - DbTransaction transaction, - TransactionEventData eventData); + void ReleasedSavepoint(DbTransaction transaction, TransactionEventData eventData) + { + } /// /// Called just before EF intends to release a transaction savepoint. @@ -515,7 +516,8 @@ ValueTask ReleasingSavepointAsync( DbTransaction transaction, TransactionEventData eventData, InterceptionResult result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called immediately after EF releases a transaction savepoint. @@ -528,16 +530,17 @@ ValueTask ReleasingSavepointAsync( Task ReleasedSavepointAsync( DbTransaction transaction, TransactionEventData eventData, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => Task.CompletedTask; /// /// Called when use of a has failed with an exception. /// /// The transaction. /// Contextual information about connection and transaction. - void TransactionFailed( - DbTransaction transaction, - TransactionErrorEventData eventData); + void TransactionFailed(DbTransaction transaction, TransactionErrorEventData eventData) + { + } /// /// Called when use of a has failed with an exception. @@ -550,5 +553,6 @@ void TransactionFailed( Task TransactionFailedAsync( DbTransaction transaction, TransactionErrorEventData eventData, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => Task.CompletedTask; } diff --git a/src/EFCore/Diagnostics/ISaveChangesInterceptor.cs b/src/EFCore/Diagnostics/ISaveChangesInterceptor.cs index a9ac93030d2..cf9215e8e89 100644 --- a/src/EFCore/Diagnostics/ISaveChangesInterceptor.cs +++ b/src/EFCore/Diagnostics/ISaveChangesInterceptor.cs @@ -46,9 +46,8 @@ public interface ISaveChangesInterceptor : IInterceptor /// A normal implementation of this method for any interceptor that is not attempting to change the result /// is to return the value passed in. /// - InterceptionResult SavingChanges( - DbContextEventData eventData, - InterceptionResult result); + InterceptionResult SavingChanges(DbContextEventData eventData, InterceptionResult result) + => result; /// /// Called at the end of . @@ -67,16 +66,16 @@ InterceptionResult SavingChanges( /// A normal implementation of this method for any interceptor that is not attempting to change the result /// is to return the value passed in. /// - int SavedChanges( - SaveChangesCompletedEventData eventData, - int result); + int SavedChanges(SaveChangesCompletedEventData eventData, int result) + => result; /// /// Called when an exception has been thrown in . /// /// Contextual information about the failure. - void SaveChangesFailed( - DbContextErrorEventData eventData); + void SaveChangesFailed(DbContextErrorEventData eventData) + { + } /// /// Called at the start of . @@ -100,7 +99,8 @@ void SaveChangesFailed( ValueTask> SavingChangesAsync( DbContextEventData eventData, InterceptionResult result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called at the end of . @@ -124,7 +124,8 @@ ValueTask> SavingChangesAsync( ValueTask SavedChangesAsync( SaveChangesCompletedEventData eventData, int result, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + => new(result); /// /// Called when an exception has been thrown in . @@ -133,15 +134,16 @@ ValueTask SavedChangesAsync( /// A to observe while waiting for the task to complete. /// A representing the asynchronous operation. /// If the is canceled. - Task SaveChangesFailedAsync( - DbContextErrorEventData eventData, - CancellationToken cancellationToken = default); + Task SaveChangesFailedAsync(DbContextErrorEventData eventData, CancellationToken cancellationToken = default) + => Task.CompletedTask; /// /// Called when was canceled. /// /// Contextual information about the failure. - void SaveChangesCanceled(DbContextEventData eventData); + void SaveChangesCanceled(DbContextEventData eventData) + { + } /// /// Called when was canceled. @@ -150,5 +152,6 @@ Task SaveChangesFailedAsync( /// A to observe while waiting for the task to complete. /// A representing the asynchronous operation. /// If the is canceled. - Task SaveChangesCanceledAsync(DbContextEventData eventData, CancellationToken cancellationToken = default); + Task SaveChangesCanceledAsync(DbContextEventData eventData, CancellationToken cancellationToken = default) + => Task.CompletedTask; } diff --git a/src/EFCore/Diagnostics/SaveChangesInterceptor.cs b/src/EFCore/Diagnostics/SaveChangesInterceptor.cs index e190f9045b8..b190b96539c 100644 --- a/src/EFCore/Diagnostics/SaveChangesInterceptor.cs +++ b/src/EFCore/Diagnostics/SaveChangesInterceptor.cs @@ -12,133 +12,45 @@ namespace Microsoft.EntityFrameworkCore.Diagnostics; /// public abstract class SaveChangesInterceptor : ISaveChangesInterceptor { - /// - /// Called at the start of . - /// - /// Contextual information about the being used. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation it - /// was about to perform and use instead. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in. - /// + /// public virtual InterceptionResult SavingChanges(DbContextEventData eventData, InterceptionResult result) => result; - /// - /// Called at the end of . - /// - /// - /// This method is still called if an interceptor suppressed creation of a command in - /// . - /// In this case, is the result returned by . - /// - /// Contextual information about the being used. - /// - /// The result of the call to . - /// This value is typically used as the return value for the implementation of this method. - /// - /// - /// The result that EF will use. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in. - /// + /// public virtual int SavedChanges(SaveChangesCompletedEventData eventData, int result) => result; - /// - /// Called when an exception has been thrown in . - /// - /// Contextual information about the failure. + /// public virtual void SaveChangesFailed(DbContextErrorEventData eventData) { } - /// - /// Called when was canceled. - /// - /// Contextual information about the failure. + /// public virtual void SaveChangesCanceled(DbContextEventData eventData) { } - /// - /// Called at the start of . - /// - /// Contextual information about the being used. - /// - /// Represents the current result if one exists. - /// This value will have set to if some previous - /// interceptor suppressed execution by calling . - /// This value is typically used as the return value for the implementation of this method. - /// - /// A to observe while waiting for the task to complete. - /// - /// If is false, the EF will continue as normal. - /// If is true, then EF will suppress the operation it - /// was about to perform and use instead. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in. - /// - /// If the is canceled. + /// public virtual ValueTask> SavingChangesAsync( DbContextEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default) => new(result); - /// - /// Called at the end of . - /// - /// - /// This method is still called if an interceptor suppressed creation of a command in - /// . - /// In this case, is the result returned by . - /// - /// Contextual information about the being used. - /// - /// The result of the call to . - /// This value is typically used as the return value for the implementation of this method. - /// - /// A to observe while waiting for the task to complete. - /// - /// The result that EF will use. - /// A normal implementation of this method for any interceptor that is not attempting to change the result - /// is to return the value passed in. - /// - /// If the is canceled. + /// public virtual ValueTask SavedChangesAsync( SaveChangesCompletedEventData eventData, int result, CancellationToken cancellationToken = default) => new(result); - /// - /// Called when an exception has been thrown in . - /// - /// Contextual information about the failure. - /// A to observe while waiting for the task to complete. - /// A representing the asynchronous operation. - /// If the is canceled. + /// public virtual Task SaveChangesFailedAsync( DbContextErrorEventData eventData, CancellationToken cancellationToken = default) => Task.CompletedTask; - /// - /// Called when was canceled. - /// - /// Contextual information about the cancellation. - /// A to observe while waiting for the task to complete. - /// A representing the asynchronous operation. - /// If the is canceled. + /// public virtual Task SaveChangesCanceledAsync( DbContextEventData eventData, CancellationToken cancellationToken = default) From 5ae9bb7956e69c2ecaa9c6a3dfbfb55760aa0ce3 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Mon, 14 Mar 2022 11:54:50 +0000 Subject: [PATCH 028/143] Attribute for configuring composite primary keys (#27571) Co-authored-by: Shay Rojansky --- .../PrimaryKeyAttribute.cs | 45 +++ .../Builders/IConventionEntityTypeBuilder.cs | 12 + .../Conventions/IndexAttributeConvention.cs | 29 +- .../ProviderConventionSetBuilder.cs | 4 +- .../Conventions/KeyAttributeConvention.cs | 226 +++++++++--- .../Internal/InternalEntityTypeBuilder.cs | 14 + src/EFCore/Properties/CoreStrings.Designer.cs | 34 +- src/EFCore/Properties/CoreStrings.resx | 70 ++-- .../CompositeKeyEndToEndTest.cs | 15 +- .../Query/QueryBugsInMemoryTest.cs | 3 +- .../FieldMappingTestBase.cs | 2 +- .../GraphUpdates/GraphUpdatesTestBase.cs | 1 + .../WithConstructorsTestBase.cs | 2 +- .../GraphUpdatesSqlServerOwnedTest.cs | 2 - .../PrimaryKeyAttributeConventionTest.cs | 346 ++++++++++++++++++ .../PropertyAttributeConventionTest.cs | 52 ++- 16 files changed, 746 insertions(+), 111 deletions(-) create mode 100644 src/EFCore.Abstractions/PrimaryKeyAttribute.cs create mode 100644 test/EFCore.Tests/Metadata/Conventions/PrimaryKeyAttributeConventionTest.cs diff --git a/src/EFCore.Abstractions/PrimaryKeyAttribute.cs b/src/EFCore.Abstractions/PrimaryKeyAttribute.cs new file mode 100644 index 00000000000..4704aeb5537 --- /dev/null +++ b/src/EFCore.Abstractions/PrimaryKeyAttribute.cs @@ -0,0 +1,45 @@ +// 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.Utilities; + +namespace Microsoft.EntityFrameworkCore; + +/// +/// Specifies a primary key for the entity type mapped to this CLR type. +/// +/// +/// +/// This attribute can be used for both keys made up of a +/// single property, and for composite keys made up of multiple properties. +/// can be used instead for single-property keys, in which case the behavior is identical. If both attributes are used, then +/// this attribute takes precedence. +/// +/// +/// See Modeling entity types and relationships for more information and +/// examples. +/// +/// +[AttributeUsage(AttributeTargets.Class)] +public sealed class PrimaryKeyAttribute : Attribute +{ + /// + /// Initializes a new instance of the class. + /// + /// The first (or only) property in the primary key. + /// The additional properties which constitute the primary key, if any, in order. + public PrimaryKeyAttribute(string propertyName, params string[] additionalPropertyNames) + { + Check.NotEmpty(propertyName, nameof(propertyName)); + Check.HasNoEmptyElements(additionalPropertyNames, nameof(additionalPropertyNames)); + + PropertyNames = new List { propertyName }; + ((List)PropertyNames).AddRange(additionalPropertyNames); + } + + /// + /// The properties which constitute the primary key, in order. + /// + public IReadOnlyList PropertyNames { get; } +} diff --git a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs index b507c951399..178970003f1 100644 --- a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs @@ -230,6 +230,18 @@ bool CanHaveIndexerProperty( /// IConventionKeyBuilder? PrimaryKey(IReadOnlyList? properties, bool fromDataAnnotation = false); + /// + /// Sets the properties that make up the primary key for this entity type. + /// + /// The names of the properties that make up the primary key. + /// Indicates whether the configuration was specified using a data annotation. + /// An object that can be used to configure the primary key. + /// + /// An object that can be used to configure the primary key if it was set on the entity type, + /// otherwise. + /// + IConventionKeyBuilder? PrimaryKey(IReadOnlyList? propertyNames, bool fromDataAnnotation = false); + /// /// Returns a value indicating whether the given properties can be set as the primary key for this entity type. /// diff --git a/src/EFCore/Metadata/Conventions/IndexAttributeConvention.cs b/src/EFCore/Metadata/Conventions/IndexAttributeConvention.cs index 52831af95ca..c125b974b61 100644 --- a/src/EFCore/Metadata/Conventions/IndexAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/IndexAttributeConvention.cs @@ -29,7 +29,7 @@ public IndexAttributeConvention(ProviderConventionSetBuilderDependencies depende public virtual void ProcessEntityTypeAdded( IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext context) - => CheckIndexAttributesAndEnsureIndex(entityTypeBuilder.Metadata, false); + => CheckIndexAttributesAndEnsureIndex(entityTypeBuilder.Metadata, shouldThrow: false); /// public virtual void ProcessEntityTypeBaseTypeChanged( @@ -43,7 +43,7 @@ public virtual void ProcessEntityTypeBaseTypeChanged( return; } - CheckIndexAttributesAndEnsureIndex(entityTypeBuilder.Metadata, false); + CheckIndexAttributesAndEnsureIndex(entityTypeBuilder.Metadata, shouldThrow: false); } /// @@ -53,7 +53,7 @@ public virtual void ProcessModelFinalizing( { foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) { - CheckIndexAttributesAndEnsureIndex(entityType, true); + CheckIndexAttributesAndEnsureIndex(entityType, shouldThrow: true); } } @@ -62,7 +62,7 @@ private static void CheckIndexAttributesAndEnsureIndex( bool shouldThrow) { foreach (var indexAttribute in - entityType.ClrType.GetCustomAttributes(true)) + entityType.ClrType.GetCustomAttributes(inherit: true)) { IConventionIndexBuilder? indexBuilder; if (!shouldThrow) @@ -100,7 +100,7 @@ private static void CheckIndexAttributesAndEnsureIndex( } catch (InvalidOperationException exception) { - CheckMissingProperties(indexAttribute, entityType, exception); + CheckMissingProperties(entityType, indexAttribute, exception); throw; } @@ -108,7 +108,10 @@ private static void CheckIndexAttributesAndEnsureIndex( if (indexBuilder == null) { - CheckIgnoredProperties(indexAttribute, entityType); + if (shouldThrow) + { + CheckIgnoredProperties(entityType, indexAttribute); + } } else { @@ -119,15 +122,13 @@ private static void CheckIndexAttributesAndEnsureIndex( if (indexBuilder is not null && indexAttribute.IsDescending is not null) { - indexBuilder = indexBuilder.IsDescending(indexAttribute.IsDescending, fromDataAnnotation: true); + indexBuilder.IsDescending(indexAttribute.IsDescending, fromDataAnnotation: true); } } } } - private static void CheckIgnoredProperties( - IndexAttribute indexAttribute, - IConventionEntityType entityType) + private static void CheckIgnoredProperties(IConventionEntityType entityType, IndexAttribute indexAttribute) { foreach (var propertyName in indexAttribute.PropertyNames) { @@ -153,9 +154,9 @@ private static void CheckIgnoredProperties( } private static void CheckMissingProperties( - IndexAttribute indexAttribute, IConventionEntityType entityType, - InvalidOperationException innerException) + IndexAttribute indexAttribute, + InvalidOperationException exception) { foreach (var propertyName in indexAttribute.PropertyNames) { @@ -169,7 +170,7 @@ private static void CheckMissingProperties( entityType.DisplayName(), indexAttribute.PropertyNames.Format(), propertyName), - innerException); + exception); } throw new InvalidOperationException( @@ -178,7 +179,7 @@ private static void CheckMissingProperties( entityType.DisplayName(), indexAttribute.PropertyNames.Format(), propertyName), - innerException); + exception); } } } diff --git a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs index 83b6dbfb060..19b1e2a76d3 100644 --- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs +++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs @@ -60,6 +60,7 @@ public virtual ConventionSet CreateConventionSet() var foreignKeyAttributeConvention = new ForeignKeyAttributeConvention(Dependencies); var relationshipDiscoveryConvention = new RelationshipDiscoveryConvention(Dependencies); var servicePropertyDiscoveryConvention = new ServicePropertyDiscoveryConvention(Dependencies); + var keyAttributeConvention = new KeyAttributeConvention(Dependencies); var indexAttributeConvention = new IndexAttributeConvention(Dependencies); var baseTypeDiscoveryConvention = new BaseTypeDiscoveryConvention(Dependencies); conventionSet.EntityTypeAddedConventions.Add(new NotMappedEntityTypeAttributeConvention(Dependencies)); @@ -70,6 +71,7 @@ public virtual ConventionSet CreateConventionSet() conventionSet.EntityTypeAddedConventions.Add(baseTypeDiscoveryConvention); conventionSet.EntityTypeAddedConventions.Add(propertyDiscoveryConvention); conventionSet.EntityTypeAddedConventions.Add(servicePropertyDiscoveryConvention); + conventionSet.EntityTypeAddedConventions.Add(keyAttributeConvention); conventionSet.EntityTypeAddedConventions.Add(keyDiscoveryConvention); conventionSet.EntityTypeAddedConventions.Add(indexAttributeConvention); conventionSet.EntityTypeAddedConventions.Add(inversePropertyAttributeConvention); @@ -87,6 +89,7 @@ public virtual ConventionSet CreateConventionSet() conventionSet.EntityTypeBaseTypeChangedConventions.Add(propertyDiscoveryConvention); conventionSet.EntityTypeBaseTypeChangedConventions.Add(servicePropertyDiscoveryConvention); + conventionSet.EntityTypeBaseTypeChangedConventions.Add(keyAttributeConvention); conventionSet.EntityTypeBaseTypeChangedConventions.Add(keyDiscoveryConvention); conventionSet.EntityTypeBaseTypeChangedConventions.Add(indexAttributeConvention); conventionSet.EntityTypeBaseTypeChangedConventions.Add(inversePropertyAttributeConvention); @@ -102,7 +105,6 @@ public virtual ConventionSet CreateConventionSet() conventionSet.EntityTypeMemberIgnoredConventions.Add(keyDiscoveryConvention); conventionSet.EntityTypeMemberIgnoredConventions.Add(foreignKeyPropertyDiscoveryConvention); - var keyAttributeConvention = new KeyAttributeConvention(Dependencies); var backingFieldConvention = new BackingFieldConvention(Dependencies); var concurrencyCheckAttributeConvention = new ConcurrencyCheckAttributeConvention(Dependencies); var databaseGeneratedAttributeConvention = new DatabaseGeneratedAttributeConvention(Dependencies); diff --git a/src/EFCore/Metadata/Conventions/KeyAttributeConvention.cs b/src/EFCore/Metadata/Conventions/KeyAttributeConvention.cs index ce98b4bb8c2..f1c3831a2ba 100644 --- a/src/EFCore/Metadata/Conventions/KeyAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/KeyAttributeConvention.cs @@ -7,12 +7,17 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// -/// A convention that configures the entity type key based on the specified on a property. +/// A convention that configures the entity type key based on the specified on a property or +/// specified on a CLR type. /// /// /// See Model building conventions for more information and examples. /// -public class KeyAttributeConvention : PropertyAttributeConventionBase, IModelFinalizingConvention +public class KeyAttributeConvention + : PropertyAttributeConventionBase, + IModelFinalizingConvention, + IEntityTypeAddedConvention, + IEntityTypeBaseTypeChangedConvention { /// /// Creates a new instance of . @@ -23,13 +28,28 @@ public KeyAttributeConvention(ProviderConventionSetBuilderDependencies dependenc { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// + public virtual void ProcessEntityTypeAdded( + IConventionEntityTypeBuilder entityTypeBuilder, + IConventionContext context) + => CheckAttributesAndEnsurePrimaryKey((EntityType)entityTypeBuilder.Metadata, null, shouldThrow: false); + + /// + public virtual void ProcessEntityTypeBaseTypeChanged( + IConventionEntityTypeBuilder entityTypeBuilder, + IConventionEntityType? newBaseType, + IConventionEntityType? oldBaseType, + IConventionContext context) + { + if (oldBaseType == null) + { + return; + } + + CheckAttributesAndEnsurePrimaryKey((EntityType)entityTypeBuilder.Metadata, null, shouldThrow: false); + } + + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, KeyAttribute attribute, @@ -42,8 +62,7 @@ protected override void ProcessPropertyAdded( switch (entityType.GetIsKeylessConfigurationSource()) { case ConfigurationSource.DataAnnotation: - Dependencies.Logger - .ConflictingKeylessAndKeyAttributesWarning(propertyBuilder.Metadata); + Dependencies.Logger.ConflictingKeylessAndKeyAttributesWarning(propertyBuilder.Metadata); return; case ConfigurationSource.Explicit: @@ -52,39 +71,49 @@ protected override void ProcessPropertyAdded( } } - if (entityType.BaseType != null) - { - return; - } + CheckAttributesAndEnsurePrimaryKey( + (EntityType)propertyBuilder.Metadata.DeclaringEntityType, + propertyBuilder, + shouldThrow: false); + } - if (entityType.IsKeyless - && entityType.GetIsKeylessConfigurationSource().Overrides(ConfigurationSource.DataAnnotation)) + private bool CheckAttributesAndEnsurePrimaryKey( + EntityType entityType, + IConventionPropertyBuilder? propertyBuilder, + bool shouldThrow) + { + if (entityType.BaseType != null) { - // TODO: Log a warning that KeyAttribute is being ignored. See issue#20014 - // This code path will also be hit when entity is marked as Keyless explicitly - return; + return false; } - var entityTypeBuilder = entityType.Builder; - var currentKey = entityTypeBuilder.Metadata.FindPrimaryKey(); - var properties = new List { propertyBuilder.Metadata.Name }; + var primaryKeyAttributeExists = CheckPrimaryKeyAttributeAndEnsurePrimaryKey(entityType, shouldThrow); - if (currentKey != null - && entityType.GetPrimaryKeyConfigurationSource() == ConfigurationSource.DataAnnotation) + if (!primaryKeyAttributeExists + && propertyBuilder != null) { - properties.AddRange( - currentKey.Properties - .Where(p => !p.Name.Equals(propertyBuilder.Metadata.Name, StringComparison.OrdinalIgnoreCase)) - .Select(p => p.Name)); - if (properties.Count > 1) + var properties = new List { propertyBuilder.Metadata.Name }; + + var currentKey = entityType.FindPrimaryKey(); + if (currentKey != null + && entityType.GetPrimaryKeyConfigurationSource() == ConfigurationSource.DataAnnotation) { - properties.Sort(StringComparer.OrdinalIgnoreCase); - entityTypeBuilder.HasNoKey(currentKey, fromDataAnnotation: true); + properties.AddRange( + currentKey.Properties + .Where(p => !p.Name.Equals(propertyBuilder.Metadata.Name, StringComparison.OrdinalIgnoreCase)) + .Select(p => p.Name)); + + if (properties.Count > 1) + { + properties.Sort(StringComparer.OrdinalIgnoreCase); + entityType.Builder.HasNoKey(currentKey, ConfigurationSource.DataAnnotation); + } } + + entityType.Builder.PrimaryKey(properties, ConfigurationSource.DataAnnotation); } - entityTypeBuilder.PrimaryKey( - entityTypeBuilder.GetOrCreateProperties(properties, fromDataAnnotation: true), fromDataAnnotation: true); + return primaryKeyAttributeExists; } /// @@ -95,30 +124,137 @@ public virtual void ProcessModelFinalizing( var entityTypes = modelBuilder.Metadata.GetEntityTypes(); foreach (var entityType in entityTypes) { + var primaryKeyAttributeExits = CheckAttributesAndEnsurePrimaryKey((EntityType)entityType, null, shouldThrow: true); + if (entityType.BaseType == null) { - var currentPrimaryKey = entityType.FindPrimaryKey(); - if (currentPrimaryKey?.Properties.Count > 1 - && entityType.GetPrimaryKeyConfigurationSource() == ConfigurationSource.DataAnnotation) + if (!primaryKeyAttributeExits) { - throw new InvalidOperationException(CoreStrings.CompositePKWithDataAnnotation(entityType.DisplayName())); + var currentPrimaryKey = entityType.FindPrimaryKey(); + if (currentPrimaryKey?.Properties.Count > 1 + && entityType.GetPrimaryKeyConfigurationSource() == ConfigurationSource.DataAnnotation) + { + throw new InvalidOperationException(CoreStrings.CompositePKWithDataAnnotation(entityType.DisplayName())); + } } } else { - foreach (var declaredProperty in entityType.GetDeclaredProperties()) + if (Attribute.IsDefined(entityType.ClrType, typeof(PrimaryKeyAttribute), inherit: false)) { - var memberInfo = declaredProperty.GetIdentifyingMemberInfo(); + throw new InvalidOperationException( + CoreStrings.PrimaryKeyAttributeOnDerivedEntity( + entityType.DisplayName(), entityType.GetRootType().DisplayName())); + } - if (memberInfo != null - && Attribute.IsDefined(memberInfo, typeof(KeyAttribute), inherit: true)) + if (!Attribute.IsDefined(entityType.ClrType, typeof(PrimaryKeyAttribute), inherit: true)) + { + foreach (var declaredProperty in entityType.GetDeclaredProperties()) { - throw new InvalidOperationException( - CoreStrings.KeyAttributeOnDerivedEntity( - entityType.DisplayName(), declaredProperty.Name, entityType.GetRootType().DisplayName())); + var memberInfo = declaredProperty.GetIdentifyingMemberInfo(); + + if (memberInfo != null + && Attribute.IsDefined(memberInfo, typeof(KeyAttribute), inherit: true)) + { + throw new InvalidOperationException( + CoreStrings.KeyAttributeOnDerivedEntity( + entityType.DisplayName(), declaredProperty.Name, entityType.GetRootType().DisplayName())); + } } } } } } + + private static bool CheckPrimaryKeyAttributeAndEnsurePrimaryKey( + IConventionEntityType entityType, + bool shouldThrow) + { + var primaryKeyAttribute = entityType.ClrType.GetCustomAttributes(inherit: true).FirstOrDefault(); + if (primaryKeyAttribute == null) + { + return false; + } + + if (Attribute.IsDefined(entityType.ClrType, typeof(KeylessAttribute))) + { + throw new InvalidOperationException( + CoreStrings.ConflictingKeylessAndPrimaryKeyAttributes(entityType.DisplayName())); + } + + IConventionKeyBuilder? keyBuilder; + if (!shouldThrow) + { + var keyProperties = new List(); + foreach (var propertyName in primaryKeyAttribute.PropertyNames) + { + var property = entityType.FindProperty(propertyName); + if (property == null) + { + return true; + } + + keyProperties.Add(property); + } + + keyBuilder = entityType.Builder.PrimaryKey(keyProperties, fromDataAnnotation: true); + } + else + { + try + { + // Using the PrimaryKey(propertyNames) overload gives us a chance to create a missing property + // e.g. if the CLR property existed but was non-public. + keyBuilder = entityType.Builder.PrimaryKey(primaryKeyAttribute.PropertyNames, fromDataAnnotation: true); + } + catch (InvalidOperationException exception) + { + CheckMissingProperties(entityType, primaryKeyAttribute, exception); + + throw; + } + } + + if (keyBuilder == null + && shouldThrow) + { + CheckIgnoredProperties(entityType, primaryKeyAttribute); + } + + return true; + } + + private static void CheckIgnoredProperties(IConventionEntityType entityType, PrimaryKeyAttribute primaryKeyAttribute) + { + foreach (var propertyName in primaryKeyAttribute.PropertyNames) + { + if (entityType.Builder.IsIgnored(propertyName, fromDataAnnotation: true)) + { + throw new InvalidOperationException( + CoreStrings.PrimaryKeyDefinedOnIgnoredProperty( + entityType.DisplayName(), + propertyName)); + } + } + } + + private static void CheckMissingProperties( + IConventionEntityType entityType, + PrimaryKeyAttribute primaryKeyAttribute, + InvalidOperationException exception) + { + foreach (var propertyName in primaryKeyAttribute.PropertyNames) + { + var property = entityType.FindProperty(propertyName); + if (property == null) + { + throw new InvalidOperationException( + CoreStrings.PrimaryKeyDefinedOnNonExistentProperty( + entityType.DisplayName(), + primaryKeyAttribute.PropertyNames.Format(), + propertyName), + exception); + } + } + } } diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index e6bfbd93731..fefe9df1541 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -25,6 +25,20 @@ public InternalEntityTypeBuilder(EntityType metadata, InternalModelBuilder model { } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionKeyBuilder? IConventionEntityTypeBuilder.PrimaryKey( + IReadOnlyList? propertyNames, + bool fromDataAnnotation) + => PrimaryKey( + propertyNames, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 9e1024ec553..3d188fc7b7a 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -433,7 +433,7 @@ public static string CompositeFkOnProperty(object? navigation, object? entityTyp navigation, entityType); /// - /// The entity type '{entityType}' has multiple properties with the [Key] attribute. Composite primary keys can only be set using 'HasKey' in 'OnModelCreating'. + /// The entity type '{entityType}' has multiple properties with the [Key] attribute. Composite primary keys configured by placing the [PrimaryKey] attribute on the entity type class, or by using 'HasKey' in 'OnModelCreating'. /// public static string CompositePKWithDataAnnotation(object? entityType) => string.Format( @@ -462,6 +462,14 @@ public static string ConflictingForeignKeyAttributes(object? propertyList, objec GetString("ConflictingForeignKeyAttributes", nameof(propertyList), nameof(entityType), nameof(principalEntityType)), propertyList, entityType, principalEntityType); + /// + /// The entity type '{entity}' has both [Keyless] and [PrimaryKey] attributes; one must be removed. + /// + public static string ConflictingKeylessAndPrimaryKeyAttributes(object? entity) + => string.Format( + GetString("ConflictingKeylessAndPrimaryKeyAttributes", nameof(entity)), + entity); + /// /// The property or navigation '{member}' cannot be added to the entity type '{entityType}' because a property or navigation with the same name already exists on entity type '{conflictingEntityType}'. /// @@ -1989,6 +1997,30 @@ public static string PrincipalOwnedType(object? referencingEntityTypeOrNavigatio GetString("PrincipalOwnedType", nameof(referencingEntityTypeOrNavigation), nameof(referencedEntityTypeOrNavigation), nameof(ownedType)), referencingEntityTypeOrNavigation, referencedEntityTypeOrNavigation, ownedType); + /// + /// The derived type '{derivedType}' cannot have the [PrimaryKey] attribute since primary keys may only be declared on the root type. Move the attribute to '{rootType}', or remove '{rootType}' from the model by using [NotMapped] attribute or calling 'EntityTypeBuilder.Ignore' on the base type in 'OnModelCreating'. + /// + public static string PrimaryKeyAttributeOnDerivedEntity(object? derivedType, object? rootType) + => string.Format( + GetString("PrimaryKeyAttributeOnDerivedEntity", nameof(derivedType), nameof(rootType)), + derivedType, rootType); + + /// + /// The [PrimaryKey] attribute on the entity type '{entityType}' is invalid because the property '{propertyName}' was marked as unmapped by [NotMapped] attribute or 'Ignore()' in 'OnModelCreating'. A primary key cannot use unmapped properties. + /// + public static string PrimaryKeyDefinedOnIgnoredProperty(object? entityType, object? propertyName) + => string.Format( + GetString("PrimaryKeyDefinedOnIgnoredProperty", nameof(entityType), nameof(propertyName)), + entityType, propertyName); + + /// + /// The [PrimaryKey] attribute on the entity type '{entityType}' references properties {properties}, but no property with name '{propertyName}' exists on that entity type or any of its base types. + /// + public static string PrimaryKeyDefinedOnNonExistentProperty(object? entityType, object? properties, object? propertyName) + => string.Format( + GetString("PrimaryKeyDefinedOnNonExistentProperty", nameof(entityType), nameof(properties), nameof(propertyName)), + entityType, properties, propertyName); + /// /// '{property}' cannot be used as a property on entity type '{entityType}' because it is configured as a navigation. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 4578370aa47..9c546496c44 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1,17 +1,17 @@  - @@ -271,7 +271,7 @@ There are multiple properties with the [ForeignKey] attribute pointing to navigation '{1_entityType}.{0_navigation}'. To define a composite foreign key using data annotations, use the [ForeignKey] attribute on the navigation. - The entity type '{entityType}' has multiple properties with the [Key] attribute. Composite primary keys can only be set using 'HasKey' in 'OnModelCreating'. + The entity type '{entityType}' has multiple properties with the [Key] attribute. Composite primary keys configured by placing the [PrimaryKey] attribute on the entity type class, or by using 'HasKey' in 'OnModelCreating'. A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913. @@ -282,6 +282,9 @@ There are multiple [ForeignKey] attributes which are pointing to same set of properties '{propertyList}' on entity type '{entityType}' and targeting the principal entity type '{principalEntityType}'. + + The entity type '{entity}' has both [Keyless] and [PrimaryKey] attributes; one must be removed. + The property or navigation '{member}' cannot be added to the entity type '{entityType}' because a property or navigation with the same name already exists on entity type '{conflictingEntityType}'. @@ -1163,6 +1166,15 @@ The relationship from '{referencingEntityTypeOrNavigation}' to '{referencedEntityTypeOrNavigation}' is not supported because the owned entity type '{ownedType}' cannot be on the principal side of a non-ownership relationship. Remove the relationship or configure the foreign key to be on '{ownedType}'. + + The derived type '{derivedType}' cannot have the [PrimaryKey] attribute since primary keys may only be declared on the root type. Move the attribute to '{rootType}', or remove '{rootType}' from the model by using [NotMapped] attribute or calling 'EntityTypeBuilder.Ignore' on the base type in 'OnModelCreating'. + + + The [PrimaryKey] attribute on the entity type '{entityType}' is invalid because the property '{propertyName}' was marked as unmapped by [NotMapped] attribute or 'Ignore()' in 'OnModelCreating'. A primary key cannot use unmapped properties. + + + The [PrimaryKey] attribute on the entity type '{entityType}' references properties {properties}, but no property with name '{propertyName}' exists on that entity type or any of its base types. + '{property}' cannot be used as a property on entity type '{entityType}' because it is configured as a navigation. @@ -1475,4 +1487,4 @@ Cannot start tracking the entry for entity type '{entityType}' because it was created by a different StateManager instance. - \ No newline at end of file + diff --git a/test/EFCore.InMemory.FunctionalTests/CompositeKeyEndToEndTest.cs b/test/EFCore.InMemory.FunctionalTests/CompositeKeyEndToEndTest.cs index ba1f74743fe..c7631d347bf 100644 --- a/test/EFCore.InMemory.FunctionalTests/CompositeKeyEndToEndTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/CompositeKeyEndToEndTest.cs @@ -199,23 +199,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) b.Property(e => e.Id2).ValueGeneratedOnAdd(); }); - modelBuilder.Entity().HasKey( - e => new - { - e.Id1, - e.Id2, - e.Id3 - }); modelBuilder.Entity( b => { - b.HasKey( - e => new - { - e.Id1, - e.Id2, - e.Id3 - }); b.Property(e => e.Id1).ValueGeneratedOnAdd(); b.Property(e => e.Id3).ValueGeneratedOnAdd(); }); @@ -240,6 +226,7 @@ private class Pegasus public string Name { get; set; } } + [PrimaryKey(nameof(Id1), nameof(Id2), nameof(Id3))] private class Unicorn { public int Id1 { get; set; } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/QueryBugsInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/QueryBugsInMemoryTest.cs index dee081b0503..8d80f7288ea 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/QueryBugsInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/QueryBugsInMemoryTest.cs @@ -1314,6 +1314,7 @@ private class OwnedClass23687 public string B { get; set; } } + [PrimaryKey(nameof(Id1), nameof(Id2))] private class Root23687 { public int Id1 { get; set; } @@ -1331,7 +1332,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) .UseInMemoryDatabase("23687"); protected override void OnModelCreating(ModelBuilder modelBuilder) - => modelBuilder.Entity().HasKey(e => new { e.Id1, e.Id2 }); + => modelBuilder.Entity(); } #endregion diff --git a/test/EFCore.Specification.Tests/FieldMappingTestBase.cs b/test/EFCore.Specification.Tests/FieldMappingTestBase.cs index a8d4e83e215..e5774af7b88 100644 --- a/test/EFCore.Specification.Tests/FieldMappingTestBase.cs +++ b/test/EFCore.Specification.Tests/FieldMappingTestBase.cs @@ -1561,6 +1561,7 @@ IEnumerable IBlogAccessor.AccessPosts } } + [PrimaryKey(nameof(Id))] protected class PostWriteOnly : IPostAccessor { private int _id; @@ -2073,7 +2074,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.Entity( b => { - b.HasKey("Id"); b.Property("Title"); b.Property("BlogId"); }); diff --git a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBase.cs b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBase.cs index c58f8ac1222..214fd99cb94 100644 --- a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBase.cs +++ b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBase.cs @@ -3309,6 +3309,7 @@ public string Bar } } + [PrimaryKey("PartnerId", "ProviderId")] protected abstract class ProviderContract : NotifyingEntity { private Partner _partner; diff --git a/test/EFCore.Specification.Tests/WithConstructorsTestBase.cs b/test/EFCore.Specification.Tests/WithConstructorsTestBase.cs index 323927069f4..20a7d167769 100644 --- a/test/EFCore.Specification.Tests/WithConstructorsTestBase.cs +++ b/test/EFCore.Specification.Tests/WithConstructorsTestBase.cs @@ -754,6 +754,7 @@ public virtual async Task Add_immutable_record() } } + [PrimaryKey(nameof(_blogId))] protected class Blog { private readonly int _blogId; @@ -1615,7 +1616,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.Entity( b => { - b.HasKey("_blogId"); b.Property(e => e.Title); }); diff --git a/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerOwnedTest.cs b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerOwnedTest.cs index 2fc7649fa3f..29042b41e7b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerOwnedTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerOwnedTest.cs @@ -493,8 +493,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b.HasDiscriminator("ProviderId") .HasValue("prov1") .HasValue("prov2"); - - b.HasKey("PartnerId", "ProviderId"); }); modelBuilder.Entity( diff --git a/test/EFCore.Tests/Metadata/Conventions/PrimaryKeyAttributeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/PrimaryKeyAttributeConventionTest.cs new file mode 100644 index 00000000000..aa600c17d2d --- /dev/null +++ b/test/EFCore.Tests/Metadata/Conventions/PrimaryKeyAttributeConventionTest.cs @@ -0,0 +1,346 @@ +// 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.Schema; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +#nullable enable + +public class PrimaryKeyAttributeConventionTest +{ + [ConditionalFact] + public void PrimaryKeyAttribute_overrides_configuration_from_convention() + { + var modelBuilder = new InternalModelBuilder(new Model()); + + var entityBuilder = modelBuilder.Entity(typeof(EntityWithPrimaryKey), ConfigurationSource.Convention)!; + entityBuilder.Property("Id", ConfigurationSource.Convention); + var propABuilder = entityBuilder.Property("A", ConfigurationSource.Convention)!; + var propBBuilder = entityBuilder.Property("B", ConfigurationSource.Convention)!; + entityBuilder.PrimaryKey(new List { "Id" }, ConfigurationSource.Convention); + + var primaryKeyProperties = new List { propABuilder.Metadata.Name, propBBuilder.Metadata.Name }; + entityBuilder.PrimaryKey(primaryKeyProperties, ConfigurationSource.Convention); + + RunConvention(entityBuilder); + RunConvention(modelBuilder); + + var primaryKey = entityBuilder.Metadata.FindPrimaryKey()!; + Assert.Same(primaryKey, entityBuilder.Metadata.GetKeys().Single()); + Assert.Equal(ConfigurationSource.DataAnnotation, primaryKey.GetConfigurationSource()); + Assert.Collection( + primaryKey.Properties, + prop0 => Assert.Equal("A", prop0.Name), + prop1 => Assert.Equal("B", prop1.Name)); + } + + [ConditionalFact] + public void PrimaryKeyAttribute_can_be_overriden_using_explicit_configuration() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var entityBuilder = modelBuilder.Entity(); + + entityBuilder.HasKey(e => e.A); + + modelBuilder.Model.FinalizeModel(); + + var primaryKey = (Key)entityBuilder.Metadata.FindPrimaryKey()!; + Assert.Equal(ConfigurationSource.Explicit, primaryKey.GetConfigurationSource()); + Assert.Collection( + primaryKey.Properties, + prop0 => Assert.Equal("A", prop0.Name)); + } + + [ConditionalFact] + public void PrimaryKeyAttribute_with_null_properties_array_throws() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + + Assert.Throws(() => modelBuilder.Entity()); + } + + [InlineData(typeof(EntityWithInvalidNullAdditionalProperty))] + [InlineData(typeof(EntityWithInvalidEmptyPrimaryKeyProperty))] + [InlineData(typeof(EntityWithInvalidWhiteSpacePrimaryKeyProperty))] + [ConditionalTheory] + public void PrimaryKeyAttribute_properties_cannot_include_whitespace(Type entityTypeWithInvalidPrimaryKey) + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + + Assert.Equal( + AbstractionsStrings.CollectionArgumentHasEmptyElements("additionalPropertyNames"), + Assert.Throws( + () => modelBuilder.Entity(entityTypeWithInvalidPrimaryKey)).Message); + } + + [ConditionalFact] + public void PrimaryKeyAttribute_can_be_inherited_from_base_entity_type() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var entityBuilder = modelBuilder.Entity(); + modelBuilder.Model.FinalizeModel(); + + // assert that the base type is not part of the model + Assert.Empty( + modelBuilder.Model.GetEntityTypes() + .Where(e => e.ClrType == typeof(BaseUnmappedEntityWithPrimaryKey))); + + // assert that we see the primaryKey anyway + var primaryKey = (Key)entityBuilder.Metadata.FindPrimaryKey()!; + Assert.Equal(ConfigurationSource.DataAnnotation, primaryKey.GetConfigurationSource()); + Assert.Collection( + primaryKey.Properties, + prop0 => Assert.Equal("A", prop0.Name), + prop1 => Assert.Equal("B", prop1.Name)); + } + + [ConditionalFact] + public virtual void PrimaryKeyAttribute_on_ignored_property_causes_error() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + modelBuilder.Entity(); + + Assert.Equal( + CoreStrings.PrimaryKeyDefinedOnIgnoredProperty(nameof(EntityPrimaryKeyWithIgnoredProperty), "B"), + Assert.Throws( + () => modelBuilder.Model.FinalizeModel()).Message); + } + + [ConditionalFact] + public virtual void PrimaryKeyAttribute_with_non_existent_property_causes_error() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + modelBuilder.Entity(); + + Assert.Equal( + CoreStrings.PrimaryKeyDefinedOnNonExistentProperty( + nameof(EntityPrimaryKeyWithNonExistentProperty), + "{'A', 'DoesNotExist'}", + "DoesNotExist"), + Assert.Throws( + () => modelBuilder.Model.FinalizeModel()).Message); + } + + [ConditionalFact] + public virtual void PrimaryKeyAttribute_and_KeylessAttribute_on_same_type() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + + Assert.Equal( + CoreStrings.ConflictingKeylessAndPrimaryKeyAttributes(nameof(EntityPrimaryKeyAndKeyless)), + Assert.Throws( + () => modelBuilder.Entity()).Message); + } + + [ConditionalFact] + public void PrimaryKeyAttribute_primaryKey_replicated_to_derived_type_when_base_type_changes() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var grandparentEntityBuilder = modelBuilder.Entity(typeof(GrandparentEntityWithPrimaryKey)); + var parentEntityBuilder = modelBuilder.Entity(); + var childEntityBuilder = modelBuilder.Entity(); + + Assert.NotNull(parentEntityBuilder.Metadata.BaseType); + Assert.NotNull(childEntityBuilder.Metadata.BaseType); + Assert.Single(grandparentEntityBuilder.Metadata.GetDeclaredKeys()); + Assert.Empty(parentEntityBuilder.Metadata.GetDeclaredKeys()); + Assert.Empty(childEntityBuilder.Metadata.GetDeclaredKeys()); + + parentEntityBuilder.HasBaseType((string)null!); + + Assert.Null(parentEntityBuilder.Metadata.BaseType); + Assert.NotNull(childEntityBuilder.Metadata.BaseType); + + var basePrimaryKey = (Key)grandparentEntityBuilder.Metadata.FindPrimaryKey()!; + Assert.Equal(ConfigurationSource.DataAnnotation, basePrimaryKey.GetConfigurationSource()); + var basePrimaryKeyProperty = Assert.Single(basePrimaryKey.Properties); + Assert.Equal("B", basePrimaryKeyProperty.Name); + + var primaryKey = (Key)parentEntityBuilder.Metadata.FindPrimaryKey()!; + Assert.Equal(ConfigurationSource.DataAnnotation, primaryKey.GetConfigurationSource()); + var primaryKeyProperty = Assert.Single(primaryKey.Properties); + Assert.Equal("A", primaryKeyProperty.Name); + + var childPrimaryKey = (Key)childEntityBuilder.Metadata.FindPrimaryKey()!; + Assert.Equal(ConfigurationSource.DataAnnotation, childPrimaryKey.GetConfigurationSource()); + var childPrimaryKeyProperty = Assert.Single(childPrimaryKey.Properties); + Assert.Equal("A", childPrimaryKeyProperty.Name); + + // Check there are no errors. + modelBuilder.Model.FinalizeModel(); + } + + [ConditionalFact] + public void PrimaryKeyAttribute_primaryKey_is_created_when_missing_property_added() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var entityBuilder = modelBuilder.Entity(typeof(EntityWithPrimaryKeyOnShadowProperty)); + + Assert.Equal("Id", entityBuilder.Metadata.FindPrimaryKey()!.Properties.Single().Name); + + entityBuilder.Property("Y"); + modelBuilder.Model.FinalizeModel(); + + var primaryKey = (Key)entityBuilder.Metadata.FindPrimaryKey()!; + Assert.Equal(ConfigurationSource.DataAnnotation, primaryKey.GetConfigurationSource()); + Assert.Collection( + primaryKey.Properties, + prop0 => Assert.Equal("X", prop0.Name), + prop1 => Assert.Equal("Y", prop1.Name)); + } + + [ConditionalFact] + public void PrimaryKeyAttribute_primaryKey_is_created_when_primaryKey_on_private_property() + { + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var entityBuilder = modelBuilder.Entity(typeof(EntityWithPrimaryKeyOnPrivateProperty)); + modelBuilder.Model.FinalizeModel(); + + var primaryKey = (Key)entityBuilder.Metadata.FindPrimaryKey()!; + Assert.Equal(ConfigurationSource.DataAnnotation, primaryKey.GetConfigurationSource()); + Assert.Collection( + primaryKey.Properties, + prop0 => Assert.Equal("X", prop0.Name), + prop1 => Assert.Equal("Y", prop1.Name)); + } + + private void RunConvention(InternalEntityTypeBuilder entityTypeBuilder) + { + var context = new ConventionContext( + entityTypeBuilder.Metadata.Model.ConventionDispatcher); + + CreatePrimaryKeyAttributeConvention().ProcessEntityTypeAdded(entityTypeBuilder, context); + } + + private void RunConvention(InternalModelBuilder modelBuilder) + { + var context = new ConventionContext(modelBuilder.Metadata.ConventionDispatcher); + + CreatePrimaryKeyAttributeConvention().ProcessModelFinalizing(modelBuilder, context); + } + + private KeyAttributeConvention CreatePrimaryKeyAttributeConvention() + => new(CreateDependencies()); + + private ProviderConventionSetBuilderDependencies CreateDependencies() + => InMemoryTestHelpers.Instance.CreateContextServices().GetRequiredService(); + + [PrimaryKey(nameof(A), nameof(B))] + private class EntityWithPrimaryKey + { + public int Id { get; set; } + public int A { get; set; } + public int B { get; set; } + } + + [PrimaryKey(nameof(A), nameof(B))] + [NotMapped] + private class BaseUnmappedEntityWithPrimaryKey + { + public int Id { get; set; } + public int A { get; set; } + public int B { get; set; } + } + + private class EntityWithPrimaryKeyFromBaseType : BaseUnmappedEntityWithPrimaryKey + { + public int C { get; set; } + public int D { get; set; } + } + + [PrimaryKey(nameof(A), null!)] + private class EntityWithInvalidNullAdditionalProperties + { + public int Id { get; set; } + public int A { get; set; } + public int B { get; set; } + } + + [PrimaryKey(nameof(A), nameof(B), null!)] + private class EntityWithInvalidNullAdditionalProperty + { + public int Id { get; set; } + public int A { get; set; } + public int B { get; set; } + } + + [PrimaryKey(nameof(A), "")] + private class EntityWithInvalidEmptyPrimaryKeyProperty + { + public int Id { get; set; } + public int A { get; set; } + public int B { get; set; } + } + + [PrimaryKey(nameof(A), " \r\n\t")] + private class EntityWithInvalidWhiteSpacePrimaryKeyProperty + { + public int Id { get; set; } + public int A { get; set; } + public int B { get; set; } + } + + [PrimaryKey(nameof(A), nameof(B))] + private class EntityPrimaryKeyWithIgnoredProperty + { + public int Id { get; set; } + public int A { get; set; } + + [NotMapped] + public int B { get; set; } + } + + [PrimaryKey(nameof(A), "DoesNotExist")] + private class EntityPrimaryKeyWithNonExistentProperty + { + public int Id { get; set; } + public int A { get; set; } + public int B { get; set; } + } + + [PrimaryKey(nameof(B))] + private class GrandparentEntityWithPrimaryKey + { + public int Id { get; set; } + public int A { get; set; } + public int B { get; set; } + } + + [PrimaryKey(nameof(A))] + private class ParentEntity : GrandparentEntityWithPrimaryKey + { + public int C { get; set; } + public int D { get; set; } + } + + private class ChildEntity : ParentEntity + { + public int E { get; set; } + public int F { get; set; } + } + + [PrimaryKey(nameof(X), "Y")] + private class EntityWithPrimaryKeyOnShadowProperty + { + public int Id { get; set; } + public int X { get; set; } + } + + [PrimaryKey(nameof(X), nameof(Y))] + private class EntityWithPrimaryKeyOnPrivateProperty + { + public int Id { get; set; } + public int X { get; set; } + private int Y { get; set; } + } + + [PrimaryKey("Id")] + [Keyless] + private class EntityPrimaryKeyAndKeyless + { + public int Id { get; set; } + } +} diff --git a/test/EFCore.Tests/Metadata/Conventions/PropertyAttributeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/PropertyAttributeConventionTest.cs index 2e723a4b30f..3ae574983a9 100644 --- a/test/EFCore.Tests/Metadata/Conventions/PropertyAttributeConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/PropertyAttributeConventionTest.cs @@ -197,6 +197,16 @@ public void KeyAttribute_does_not_throw_when_setting_composite_primary_key_if_fl Assert.Equal("Id", model.FindEntityType(typeof(B)).FindPrimaryKey().Properties[1].Name); } + [ConditionalFact] + public void KeyAttribute_does_not_throw_when_setting_composite_primary_key_with_PrimaryKey_attribute() + { + var model = new MyContext().Model; + + Assert.Equal(2, model.FindEntityType(typeof(B2)).FindPrimaryKey().Properties.Count); + Assert.Equal("MyPrimaryKey", model.FindEntityType(typeof(B2)).FindPrimaryKey().Properties[0].Name); + Assert.Equal("Id", model.FindEntityType(typeof(B2)).FindPrimaryKey().Properties[1].Name); + } + [ConditionalFact] public void KeyAttribute_throws_when_setting_key_in_derived_type() { @@ -213,6 +223,18 @@ public void KeyAttribute_throws_when_setting_key_in_derived_type() .Message); } + [ConditionalFact] + public void KeyAttribute_does_not_throw_when_setting_key_in_derived_type_when_base_has_PrimaryKeyAttribute() + { + var derivedEntityTypeBuilder = CreateInternalEntityTypeBuilder(); + var baseEntityType = derivedEntityTypeBuilder.ModelBuilder.Entity(typeof(BaseEntity2), ConfigurationSource.Explicit).Metadata; + derivedEntityTypeBuilder.HasBaseType(baseEntityType, ConfigurationSource.Explicit); + + derivedEntityTypeBuilder.Property(typeof(int), "Number", ConfigurationSource.Explicit); + + Validate(derivedEntityTypeBuilder); + } + [ConditionalFact] public void KeyAttribute_allows_composite_key_with_inheritance() { @@ -789,6 +811,16 @@ private class B public int MyPrimaryKey { get; set; } } + [PrimaryKey(nameof(MyPrimaryKey), nameof(Id))] + private class B2 + { + [Key] + public int Id { get; set; } + + [Key] + public int MyPrimaryKey { get; set; } + } + public class F { [DatabaseGenerated(DatabaseGeneratedOption.Computed)] @@ -842,6 +874,20 @@ private class CompositeKeyDerivedEntity : BaseEntity { } + [PrimaryKey(nameof(Name))] + private class BaseEntity2 + { + public int Id { get; set; } + + public string Name { get; set; } + } + + private class DerivedEntity2 : BaseEntity2 + { + [Key] + public int Number { get; set; } + } + private static ModelBuilder CreateModelBuilder() => InMemoryTestHelpers.Instance.CreateConventionBuilder(); @@ -853,7 +899,9 @@ protected internal override void OnConfiguring(DbContextOptionsBuilder optionsBu .UseInMemoryDatabase(nameof(MyContext)); protected internal override void OnModelCreating(ModelBuilder modelBuilder) - => modelBuilder.Entity().HasKey( - e => new { e.MyPrimaryKey, e.Id }); + { + modelBuilder.Entity().HasKey(e => new { e.MyPrimaryKey, e.Id }); + modelBuilder.Entity(); + } } } From b661aa51af696921e0339ccd41f9b10d85e9c5a2 Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Tue, 15 Mar 2022 02:52:49 +0000 Subject: [PATCH 029/143] [internal/release/3.1] Update dependencies from dnceng/internal/dotnet-extensions --- NuGet.config | 13 ++- eng/Version.Details.xml | 244 ++++++++++++++++++++-------------------- eng/Versions.props | 18 +-- 3 files changed, 139 insertions(+), 136 deletions(-) diff --git a/NuGet.config b/NuGet.config index 9adc0178300..7bf5731a8dc 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,13 +4,12 @@ - - + - + @@ -23,15 +22,19 @@ - - + + + + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index a3b03d1d4a6..d9bf4e05eb5 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,21 +1,21 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx @@ -33,85 +33,85 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup 3acd9b0cd16596bad450c82be08780875a73c05c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 @@ -122,153 +122,153 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup 3acd9b0cd16596bad450c82be08780875a73c05c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8934ddbf6c4fbc697b8f63eb5a49fb21ccffc56b + 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 7af614fde0e0da1d20b33a6e2da7ae2994c5eec3 + b3f33a3d2630b35fe250cef81b5851fe9553a085 - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 7af614fde0e0da1d20b33a6e2da7ae2994c5eec3 + b3f33a3d2630b35fe250cef81b5851fe9553a085 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx diff --git a/eng/Versions.props b/eng/Versions.props index 959f0fbc7df..05665d223da 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -32,13 +32,13 @@ 1.1.3 - 3.1.23 - 3.1.23 - 3.1.23 - 3.1.23 - 3.1.23 - 3.1.23-servicing.22123.2 - 3.1.23 + 3.1.24 + 3.1.24 + 3.1.24 + 3.1.24 + 3.1.24 + 3.1.24-servicing.22164.4 + 3.1.24 1.1.1 @@ -55,8 +55,8 @@ 3.1.6 3.1.6 3.1.0 - 3.1.23 - 3.1.23-servicing.22122.4 + 3.1.24 + 3.1.24-servicing.22164.7 2.1.0 From 7bcaa95a8176faee9d6b2d121cdd5200db9972f8 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Mon, 14 Mar 2022 23:39:31 -0700 Subject: [PATCH 030/143] Add metadata support for TPC Part of #3170 - Added `UseTpcMappingStrategy` and `UseTptMappingStrategy`, calling either maps all derived to different tables/views - Also just calling `ToTable` on a non-root type maps the hierarchy using TPT - Throw for `ToTable` on abstract entity type if TPC - If an entity type is not mapped to a table in TPT then the properties are mapped to the columns in the tables corresponding to its direct descendants and the identifying FK on the derived types is created pointing to the next mapped base type - Only create check constraints on the first mapped type in TPT - Added support for TPT and TPC for default mappings - Warn that FKs that point to a TPC entity type won't be created in the database - Warn when a PK property is store generated in TPC --- .../Internal/SnapshotModelProcessor.cs | 4 +- .../Diagnostics/RelationalEventId.cs | 30 + .../Diagnostics/RelationalLoggerExtensions.cs | 96 +++ .../RelationalLoggingDefinitions.cs | 18 + .../RelationalEntityTypeBuilderExtensions.cs | 73 +++ .../RelationalEntityTypeExtensions.cs | 92 ++- .../RelationalForeignKeyExtensions.cs | 12 + .../RelationalPropertyExtensions.cs | 38 +- .../RelationalModelValidator.cs | 95 ++- .../EntityTypeHierarchyMappingConvention.cs | 54 +- .../Metadata/Internal/CheckConstraint.cs | 48 +- .../Metadata/Internal/RelationalModel.cs | 312 ++++----- .../Metadata/RelationalAnnotationNames.cs | 20 + .../Properties/RelationalStrings.Designer.cs | 95 ++- .../Properties/RelationalStrings.resx | 27 +- .../Design/CSharpMigrationsGeneratorTest.cs | 8 + .../Design/SnapshotModelProcessorTest.cs | 6 +- .../CSharpRuntimeModelCodeGeneratorTest.cs | 446 ++++++++++++- .../RelationalModelValidatorTest.cs | 312 ++++++++- .../Metadata/RelationalModelTest.cs | 604 +++++++++++++----- .../RelationalTestModelBuilderExtensions.cs | 51 ++ .../SqlServerModelBuilderGenericTest.cs | 84 ++- 22 files changed, 2138 insertions(+), 387 deletions(-) diff --git a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs index d787e6620ea..c50af70cfec 100644 --- a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs +++ b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs @@ -34,7 +34,9 @@ public SnapshotModelProcessor( typeof(RelationalAnnotationNames) .GetRuntimeFields() .Where(p => p.Name != nameof(RelationalAnnotationNames.Prefix)) - .Select(p => ((string)p.GetValue(null)!)[(RelationalAnnotationNames.Prefix.Length - 1)..])); + .Select(p => (string)p.GetValue(null)!) + .Where(v => v.IndexOf(':') > 0) + .Select(v => v[(RelationalAnnotationNames.Prefix.Length - 1)..])); _modelRuntimeInitializer = modelRuntimeInitializer; } diff --git a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs index e231a0b98aa..68c19dcd735 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs @@ -89,6 +89,8 @@ private enum Id ForeignKeyPropertiesMappedToUnrelatedTables, OptionalDependentWithoutIdentifyingPropertyWarning, DuplicateColumnOrders, + ForeignKeyTPCPrincipalWarning, + TpcStoreGeneratedIdentityWarning, // Update events BatchReadyForExecution = CoreEventId.RelationalBaseId + 700, @@ -739,6 +741,34 @@ private static EventId MakeValidationId(Id id) public static readonly EventId ForeignKeyPropertiesMappedToUnrelatedTables = MakeValidationId(Id.ForeignKeyPropertiesMappedToUnrelatedTables); + /// + /// A foreign key specifies properties which don't map to the related tables. + /// + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + public static readonly EventId ForeignKeyTPCPrincipalWarning = + MakeValidationId(Id.ForeignKeyTPCPrincipalWarning); + + /// + /// The PK is using store-generated values in TPC. + /// + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + public static readonly EventId TpcStoreGeneratedIdentityWarning = + MakeValidationId(Id.TpcStoreGeneratedIdentityWarning); + /// /// The entity does not have any property with a non-default value to identify whether the entity exists. /// diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs index 34e61294691..15dc04155a9 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs @@ -2831,6 +2831,102 @@ private static string ForeignKeyPropertiesMappedToUnrelatedTables(EventDefinitio p.ForeignKey.PrincipalEntityType.GetSchemaQualifiedTableName())); } + /// + /// Logs the event. + /// + /// The diagnostics logger to use. + /// The foreign key. + public static void ForeignKeyTPCPrincipalWarning( + this IDiagnosticsLogger diagnostics, + IForeignKey foreignKey) + { + var definition = RelationalResources.LogForeignKeyTPCPrincipal(diagnostics); + + if (diagnostics.ShouldLog(definition)) + { + definition.Log( + diagnostics, + l => l.Log( + definition.Level, + definition.EventId, + definition.MessageFormat, + foreignKey.Properties.Format(), + foreignKey.DeclaringEntityType.DisplayName(), + foreignKey.PrincipalEntityType.DisplayName(), + foreignKey.PrincipalEntityType.DisplayName(), + foreignKey.PrincipalEntityType.GetSchemaQualifiedTableName()!, + foreignKey.DeclaringEntityType.DisplayName(), + foreignKey.PrincipalEntityType.DisplayName())); + } + + if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = new ForeignKeyEventData( + definition, + ForeignKeyTPCPrincipal, + foreignKey); + + diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); + } + } + + private static string ForeignKeyTPCPrincipal(EventDefinitionBase definition, EventData payload) + { + var d = (FallbackEventDefinition)definition; + var p = (ForeignKeyEventData)payload; + return d.GenerateMessage( + l => l.Log( + d.Level, + d.EventId, + d.MessageFormat, + p.ForeignKey.Properties.Format(), + p.ForeignKey.DeclaringEntityType.DisplayName(), + p.ForeignKey.PrincipalEntityType.DisplayName(), + p.ForeignKey.PrincipalEntityType.GetSchemaQualifiedTableName()!, + p.ForeignKey.PrincipalEntityType.DisplayName(), + p.ForeignKey.DeclaringEntityType.DisplayName(), + p.ForeignKey.PrincipalEntityType.DisplayName())); + } + + /// + /// Logs the event. + /// + /// The diagnostics logger to use. + /// The entity type on which the index is defined. + public static void TpcStoreGeneratedIdentityWarning( + this IDiagnosticsLogger diagnostics, + IProperty property) + { + var definition = RelationalResources.LogTpcStoreGeneratedIdentity(diagnostics); + + if (diagnostics.ShouldLog(definition)) + { + definition.Log( + diagnostics, + property.Name, + property.DeclaringEntityType.DisplayName()); + } + + if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = new PropertyEventData( + definition, + TpcStoreGeneratedIdentity, + property); + + diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); + } + } + + private static string TpcStoreGeneratedIdentity(EventDefinitionBase definition, EventData payload) + { + var d = (EventDefinition)definition; + var p = (PropertyEventData)payload; + return d.GenerateMessage( + p.Property.Name, + p.Property.DeclaringEntityType.DisplayName()); + } + /// /// Logs the event. /// diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs index f39c7e15fc0..b2e97e7afb1 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs @@ -484,6 +484,24 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions [EntityFrameworkInternal] public EventDefinitionBase? LogForeignKeyPropertiesMappedToUnrelatedTables; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public EventDefinitionBase? LogForeignKeyTPCPrincipal; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public EventDefinitionBase? LogTpcStoreGeneratedIdentity; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs index 98000b2490b..dae7f560de1 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs @@ -15,6 +15,79 @@ namespace Microsoft.EntityFrameworkCore; /// public static class RelationalEntityTypeBuilderExtensions { + /// + /// Configures TPC as the mapping strategy for the derived types. Each type will be mapped to a different database object. + /// All properties will be mapped to columns on the corresponding object. + /// + /// The builder for the entity type being configured. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder UseTpcMappingStrategy(this EntityTypeBuilder entityTypeBuilder) + { + entityTypeBuilder.Metadata.SetMappingStrategy(RelationalAnnotationNames.TpcMappingStrategy); + + return entityTypeBuilder; + } + + /// + /// Configures TPH as the mapping strategy for the derived types. All types will be mapped to the same database object. + /// This is the default mapping strategy. + /// + /// The builder for the entity type being configured. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder UseTphMappingStrategy(this EntityTypeBuilder entityTypeBuilder) + { + entityTypeBuilder.Metadata.SetMappingStrategy(RelationalAnnotationNames.TphMappingStrategy); + + return entityTypeBuilder; + } + + /// + /// Configures TPT as the mapping strategy for the derived types. Each type will be mapped to a different database object. + /// Only the declared properties will be mapped to columns on the corresponding object. + /// + /// The builder for the entity type being configured. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder UseTptMappingStrategy(this EntityTypeBuilder entityTypeBuilder) + { + entityTypeBuilder.Metadata.SetMappingStrategy(RelationalAnnotationNames.TptMappingStrategy); + + return entityTypeBuilder; + } + + /// + /// Configures TPC as the mapping strategy for the derived types. Each type will be mapped to a different database object. + /// All properties will be mapped to columns on the corresponding object. + /// + /// The builder for the entity type being configured. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder UseTpcMappingStrategy(this EntityTypeBuilder entityTypeBuilder) + where TEntity : class + => (EntityTypeBuilder)((EntityTypeBuilder)entityTypeBuilder).UseTpcMappingStrategy(); + + /// + /// Configures TPH as the mapping strategy for the derived types. All types will be mapped to the same database object. + /// This is the default mapping strategy. + /// + /// The builder for the entity type being configured. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder UseTphMappingStrategy(this EntityTypeBuilder entityTypeBuilder) + where TEntity : class + => (EntityTypeBuilder)((EntityTypeBuilder)entityTypeBuilder).UseTphMappingStrategy(); + + /// + /// Configures TPT as the mapping strategy for the derived types. Each type will be mapped to a different database object. + /// Only the declared properties will be mapped to columns on the corresponding object. + /// + /// + /// See Database migrations for more information and examples. + /// + /// The entity type being configured. + /// The builder for the entity type being configured. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder UseTptMappingStrategy(this EntityTypeBuilder entityTypeBuilder) + where TEntity : class + => (EntityTypeBuilder)((EntityTypeBuilder)entityTypeBuilder).UseTptMappingStrategy(); + /// /// Configures the table that the entity type maps to when targeting a relational database. /// diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs index fea3d7d740d..74fa5620438 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs @@ -34,17 +34,12 @@ public static class RelationalEntityTypeExtensions return (string?)nameAnnotation.Value; } - if (entityType.BaseType != null) - { - return entityType.GetRootType().GetTableName(); - } - return ((entityType as IConventionEntityType)?.GetViewNameConfigurationSource() == null) - && ((entityType as IConventionEntityType)?.GetFunctionNameConfigurationSource() == null) + && (entityType as IConventionEntityType)?.GetFunctionNameConfigurationSource() == null #pragma warning disable CS0618 // Type or member is obsolete - && ((entityType as IConventionEntityType)?.GetDefiningQueryConfigurationSource() == null) + && (entityType as IConventionEntityType)?.GetDefiningQueryConfigurationSource() == null #pragma warning restore CS0618 // Type or member is obsolete - && ((entityType as IConventionEntityType)?.GetSqlQueryConfigurationSource() == null) + && (entityType as IConventionEntityType)?.GetSqlQueryConfigurationSource() == null ? GetDefaultTableName(entityType) : null; } @@ -57,6 +52,12 @@ public static class RelationalEntityTypeExtensions /// The default name of the table to which the entity type would be mapped. public static string? GetDefaultTableName(this IReadOnlyEntityType entityType, bool truncate = true) { + if (entityType.GetDiscriminatorPropertyName() != null + && entityType.BaseType != null) + { + return entityType.GetRootType().GetTableName(); + } + var ownership = entityType.FindOwnership(); if (ownership != null && ownership.IsUnique) @@ -77,6 +78,12 @@ public static class RelationalEntityTypeExtensions : $"{ownership.PrincipalToDependent.Name}_{name}"; } + if (entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy + && !entityType.ClrType.IsInstantiable()) + { + return null; + } + return truncate ? Uniquifier.Truncate(name, entityType.Model.GetMaxIdentifierLength()) : name; @@ -273,15 +280,10 @@ public static IEnumerable GetTableMappings(this IEntityType entit /// The name of the view to which the entity type is mapped. public static string? GetViewName(this IReadOnlyEntityType entityType) { - var nameAnnotation = (string?)entityType[RelationalAnnotationNames.ViewName]; + var nameAnnotation = entityType.FindAnnotation(RelationalAnnotationNames.ViewName); if (nameAnnotation != null) { - return nameAnnotation; - } - - if (entityType.BaseType != null) - { - return entityType.GetRootType().GetViewName(); + return (string?)nameAnnotation.Value; } return ((entityType as IConventionEntityType)?.GetFunctionNameConfigurationSource() == null) @@ -300,6 +302,12 @@ public static IEnumerable GetTableMappings(this IEntityType entit /// The default name of the table to which the entity type would be mapped. public static string? GetDefaultViewName(this IReadOnlyEntityType entityType) { + if (entityType.GetDiscriminatorPropertyName() != null + && entityType.BaseType != null) + { + return entityType.GetRootType().GetViewName(); + } + var ownership = entityType.FindOwnership(); return ownership != null && ownership.IsUnique @@ -957,4 +965,58 @@ public static void SetIsTableExcludedFromMigrations(this IMutableEntityType enti this IConventionEntityType entityType) => entityType.FindAnnotation(RelationalAnnotationNames.IsTableExcludedFromMigrations) ?.GetConfigurationSource(); + + /// + /// Gets the mapping strategy for the derived types. + /// + /// The entity type. + /// The mapping strategy for the derived types. + public static string? GetMappingStrategy(this IReadOnlyEntityType entityType) + { + var mappingStrategy = (string?)entityType[RelationalAnnotationNames.MappingStrategy]; + if (mappingStrategy != null) + { + return mappingStrategy; + } + + if (entityType.BaseType != null) + { + return entityType.GetRootType().GetMappingStrategy(); + } + + return null; + } + + /// + /// Sets the mapping strategy for the derived types. + /// + /// The entity type. + /// The mapping strategy for the derived types. + public static void SetMappingStrategy(this IMutableEntityType entityType, string? strategy) + => entityType.SetOrRemoveAnnotation(RelationalAnnotationNames.MappingStrategy, strategy); + + /// + /// Sets the mapping strategy for the derived types. + /// + /// The entity type. + /// The mapping strategy for the derived types. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static string? SetMappingStrategy( + this IConventionEntityType entityType, + string? strategy, + bool fromDataAnnotation = false) + => (string?)entityType.SetOrRemoveAnnotation( + RelationalAnnotationNames.MappingStrategy, strategy, fromDataAnnotation) + ?.Value; + + /// + /// Gets the for . + /// + /// The entity type to find configuration source for. + /// The for . + public static ConfigurationSource? GetMappingStrategyConfigurationSource( + this IConventionEntityType entityType) + => entityType.FindAnnotation(RelationalAnnotationNames.MappingStrategy) + ?.GetConfigurationSource(); } diff --git a/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs index efeff92fa22..950deee7165 100644 --- a/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs @@ -72,6 +72,12 @@ public static class RelationalForeignKeyExtensions return null; } + if (foreignKey.PrincipalEntityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy + && foreignKey.PrincipalEntityType.GetDerivedTypes().Any(et => StoreObjectIdentifier.Create(et, StoreObjectType.Table) != null)) + { + return null; + } + var name = new StringBuilder() .Append("FK_") .Append(tableName) @@ -150,6 +156,12 @@ public static class RelationalForeignKeyExtensions return rootForeignKey.GetConstraintName(storeObject, principalStoreObject); } + if (foreignKey.PrincipalEntityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy + && foreignKey.PrincipalEntityType.GetDerivedTypes().Any(et => StoreObjectIdentifier.Create(et, StoreObjectType.Table) != null)) + { + return null; + } + var baseName = new StringBuilder() .Append("FK_") .Append(storeObject.Name) diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index 06eba790d14..0ccea516d6c 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -58,9 +58,42 @@ public static string GetColumnBaseName(this IReadOnlyProperty property) return null; } } - else if (StoreObjectIdentifier.Create(property.DeclaringEntityType, storeObject.StoreObjectType) != storeObject) + else if (property.DeclaringEntityType.GetMappingStrategy() != RelationalAnnotationNames.TpcMappingStrategy) { - return null; + var declaringStoreObject = StoreObjectIdentifier.Create(property.DeclaringEntityType, storeObject.StoreObjectType); + if (declaringStoreObject == null) + { + var tableFound = false; + var queue = new Queue(); + queue.Enqueue(property.DeclaringEntityType); + while (queue.Count > 0 && !tableFound) + { + foreach (var containingType in queue.Dequeue().GetDirectlyDerivedTypes()) + { + declaringStoreObject = StoreObjectIdentifier.Create(containingType, storeObject.StoreObjectType); + if (declaringStoreObject == null) + { + queue.Enqueue(containingType); + continue; + } + + if (declaringStoreObject == storeObject) + { + tableFound = true; + break; + } + } + } + + if (!tableFound) + { + return null; + } + } + else if (declaringStoreObject != storeObject) + { + return null; + } } } @@ -1278,7 +1311,6 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope private static IReadOnlyProperty? FindSharedObjectRootProperty(IReadOnlyProperty property, in StoreObjectIdentifier storeObject) { var column = property.GetColumnName(storeObject); - if (column == null) { throw new InvalidOperationException( diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index 59c452f597d..fc68d71a418 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -1051,6 +1051,11 @@ protected virtual void ValidateSharedForeignKeysCompatibility( var foreignKeyName = foreignKey.GetConstraintName(storeObject, principalTable.Value); if (foreignKeyName == null) { + if (foreignKey.PrincipalEntityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy) + { + logger.ForeignKeyTPCPrincipalWarning(foreignKey); + } + var derivedTables = foreignKey.DeclaringEntityType.GetDerivedTypes() .Select(t => StoreObjectIdentifier.Create(t, StoreObjectType.Table)) .Where(t => t != null); @@ -1240,31 +1245,99 @@ protected override void ValidateInheritanceMapping( IModel model, IDiagnosticsLogger logger) { - foreach (var rootEntityType in model.GetEntityTypes()) + foreach (var entityType in model.GetEntityTypes()) { - if (rootEntityType.BaseType != null) + var mappingStrategy = (string?)entityType[RelationalAnnotationNames.MappingStrategy]; + if (mappingStrategy != null) + { + ValidateMappingStrategy(mappingStrategy, entityType); + var storeObject = entityType.GetSchemaQualifiedTableName() + ?? entityType.GetSchemaQualifiedViewName() + ?? entityType.GetFunctionName(); + if (mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy + && !entityType.ClrType.IsInstantiable() + && storeObject != null) + { + throw new InvalidOperationException( + RelationalStrings.AbstractTPC(entityType.DisplayName(), storeObject)); + } + } + + if (entityType.BaseType != null) + { + if (mappingStrategy != null + && mappingStrategy != entityType.BaseType.GetMappingStrategy()) + { + throw new InvalidOperationException( + RelationalStrings.DerivedStrategy(entityType.DisplayName(), mappingStrategy)); + } + + continue; + } + + if (!entityType.GetDirectlyDerivedTypes().Any()) { continue; } // Hierarchy mapping strategy must be the same across all types of mappings - var isTph = rootEntityType.FindPrimaryKey() == null - || rootEntityType.FindDiscriminatorProperty() != null; - if (isTph) + if (entityType.FindDiscriminatorProperty() != null) { - ValidateTPHMapping(rootEntityType, forTables: false); - ValidateTPHMapping(rootEntityType, forTables: true); - ValidateDiscriminatorValues(rootEntityType); + if (mappingStrategy != null + && mappingStrategy != RelationalAnnotationNames.TphMappingStrategy) + { + throw new InvalidOperationException( + RelationalStrings.NonTphMappingStrategy(mappingStrategy, entityType.DisplayName())); + } + + ValidateTPHMapping(entityType, forTables: false); + ValidateTPHMapping(entityType, forTables: true); + ValidateDiscriminatorValues(entityType); } else { - ValidateTPTMapping(rootEntityType, forTables: false); - ValidateTPTMapping(rootEntityType, forTables: true); + var pk = entityType.FindPrimaryKey(); + if (mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy) + { + var storeGeneratedProperty = pk?.Properties.FirstOrDefault(p => (p.ValueGenerated & ValueGenerated.OnAdd) != 0); + if (storeGeneratedProperty != null + && entityType.GetTableName() != null) + { + logger.TpcStoreGeneratedIdentityWarning(storeGeneratedProperty); + } + } + else if (pk == null) + { + throw new InvalidOperationException( + RelationalStrings.KeylessMappingStrategy(mappingStrategy ?? RelationalAnnotationNames.TptMappingStrategy, entityType.DisplayName())); + } + + ValidateNonTPHMapping(entityType, forTables: false); + ValidateNonTPHMapping(entityType, forTables: true); } } } - private static void ValidateTPTMapping(IEntityType rootEntityType, bool forTables) + /// + /// Validates that the given mapping strategy is supported + /// + /// The mapping strategy. + /// The entity type. + protected virtual void ValidateMappingStrategy(string? mappingStrategy, IEntityType entityType) + { + switch (mappingStrategy) + { + case RelationalAnnotationNames.TphMappingStrategy: + case RelationalAnnotationNames.TpcMappingStrategy: + case RelationalAnnotationNames.TptMappingStrategy: + break; + default: + throw new InvalidOperationException(RelationalStrings.InvalidMappingStrategy( + mappingStrategy, entityType.DisplayName())); + }; + } + + private static void ValidateNonTPHMapping(IEntityType rootEntityType, bool forTables) { var derivedTypes = new Dictionary<(string, string?), IEntityType>(); foreach (var entityType in rootEntityType.GetDerivedTypesInclusive()) diff --git a/src/EFCore.Relational/Metadata/Conventions/EntityTypeHierarchyMappingConvention.cs b/src/EFCore.Relational/Metadata/Conventions/EntityTypeHierarchyMappingConvention.cs index f32bbd979e7..85530ab9ce7 100644 --- a/src/EFCore.Relational/Metadata/Conventions/EntityTypeHierarchyMappingConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/EntityTypeHierarchyMappingConvention.cs @@ -49,29 +49,57 @@ public virtual void ProcessModelFinalizing( continue; } + var mappingStrategy = entityType.GetMappingStrategy(); + if (mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy) + { + nonTphRoots.Add(entityType.GetRootType()); + continue; + } + var tableName = entityType.GetTableName(); - var schema = entityType.GetSchema(); - if (tableName != null - && (tableName != entityType.BaseType.GetTableName() - || schema != entityType.BaseType.GetSchema())) + if (tableName != null) { - var pk = entityType.FindPrimaryKey(); - if (pk != null - && !entityType.FindDeclaredForeignKeys(pk.Properties) - .Any(fk => fk.PrincipalKey.IsPrimaryKey() && fk.PrincipalEntityType.IsAssignableFrom(entityType))) + if (mappingStrategy == null) { - entityType.Builder.HasRelationship(entityType.BaseType, pk.Properties, pk)? - .IsUnique(true); + if (tableName != entityType.BaseType.GetTableName() + || entityType.GetSchema() != entityType.BaseType.GetSchema()) + { + mappingStrategy = RelationalAnnotationNames.TptMappingStrategy; + } } - nonTphRoots.Add(entityType.GetRootType()); + if (mappingStrategy == RelationalAnnotationNames.TptMappingStrategy) + { + var pk = entityType.FindPrimaryKey(); + if (pk != null + && !entityType.FindDeclaredForeignKeys(pk.Properties) + .Any(fk => fk.PrincipalKey.IsPrimaryKey() + && fk.PrincipalEntityType.IsAssignableFrom(entityType) + && fk.PrincipalEntityType != entityType)) + { + var closestMappedType = entityType.BaseType; + while (closestMappedType != null + && closestMappedType.GetTableName() == null) + { + closestMappedType = closestMappedType.BaseType; + } + + if (closestMappedType != null) + { + entityType.Builder.HasRelationship(closestMappedType, pk.Properties, pk)? + .IsUnique(true); + } + } + + nonTphRoots.Add(entityType.GetRootType()); + continue; + } } var viewName = entityType.GetViewName(); - var viewSchema = entityType.GetViewSchema(); if (viewName != null && (viewName != entityType.BaseType.GetViewName() - || viewSchema != entityType.BaseType.GetViewSchema())) + || entityType.GetViewSchema() != entityType.BaseType.GetViewSchema())) { nonTphRoots.Add(entityType.GetRootType()); } diff --git a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs index f8655df492d..3d04662e534 100644 --- a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs +++ b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs @@ -296,15 +296,55 @@ public virtual string? Name return null; } - foreach (var containingType in EntityType.GetDerivedTypesInclusive()) + if (EntityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy) { - if (StoreObjectIdentifier.Create(containingType, storeObject.StoreObjectType) == storeObject) + foreach (var containingType in EntityType.GetDerivedTypesInclusive()) { - return _name ?? ((IReadOnlyCheckConstraint)this).GetDefaultName(storeObject); + if (StoreObjectIdentifier.Create(containingType, storeObject.StoreObjectType) == storeObject) + { + return _name ?? ((IReadOnlyCheckConstraint)this).GetDefaultName(storeObject); + } } + + return null; } - return null; + var declaringStoreObject = StoreObjectIdentifier.Create(EntityType, storeObject.StoreObjectType); + if (declaringStoreObject == null) + { + var tableFound = false; + var queue = new Queue(); + queue.Enqueue(EntityType); + while (queue.Count > 0 && !tableFound) + { + foreach (var containingType in queue.Dequeue().GetDirectlyDerivedTypes()) + { + declaringStoreObject = StoreObjectIdentifier.Create(containingType, storeObject.StoreObjectType); + if (declaringStoreObject == null) + { + queue.Enqueue(containingType); + continue; + } + + if (declaringStoreObject == storeObject) + { + tableFound = true; + break; + } + } + } + + if (!tableFound) + { + return null; + } + } + else if (declaringStoreObject != storeObject) + { + return null; + } + + return _name ?? ((IReadOnlyCheckConstraint)this).GetDefaultName(storeObject); } /// diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 1c6b8dd5d43..a5210d6171e 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -245,65 +245,80 @@ public static IRelationalModel Create( private static void AddDefaultMappings(RelationalModel databaseModel, IEntityType entityType) { - var rootType = entityType.GetRootType(); - var name = rootType.Name; - if (!databaseModel.DefaultTables.TryGetValue(name, out var defaultTable)) - { - defaultTable = new TableBase(name, null, databaseModel); - databaseModel.DefaultTables.Add(name, defaultTable); - } - - var tableMapping = new TableMappingBase(entityType, defaultTable, includesDerivedTypes: true) - { - IsSharedTablePrincipal = true, IsSplitEntityTypePrincipal = true - }; + var mappedType = entityType; + Check.DebugAssert(entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.DefaultMappings) == null, "not null"); + var tableMappings = new List(); + entityType.AddRuntimeAnnotation(RelationalAnnotationNames.DefaultMappings, tableMappings); - foreach (var property in entityType.GetProperties()) + var isTpc = entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy; + var isTph = entityType.FindDiscriminatorProperty() != null; + while (mappedType != null) { - var columnName = property.GetColumnBaseName(); - if (columnName == null) + var mappedTableName = isTph ? entityType.GetRootType().Name : mappedType.Name; + if (!databaseModel.DefaultTables.TryGetValue(mappedTableName, out var defaultTable)) { - continue; + defaultTable = new TableBase(mappedTableName, null, databaseModel); + databaseModel.DefaultTables.Add(mappedTableName, defaultTable); } - var column = (ColumnBase?)defaultTable.FindColumn(columnName); - if (column == null) + var tableMapping = new TableMappingBase(entityType, defaultTable, includesDerivedTypes: !isTpc && mappedType == entityType) { - column = new ColumnBase(columnName, property.GetColumnType(), defaultTable); - column.IsNullable = property.IsColumnNullable(); - defaultTable.Columns.Add(columnName, column); - } - else if (!property.IsColumnNullable()) + // Table splitting is not supported for default mapping + IsSharedTablePrincipal = true, + IsSplitEntityTypePrincipal = true + }; + + foreach (var property in entityType.GetProperties()) { - column.IsNullable = false; - } + var columnName = property.IsPrimaryKey() || isTpc || isTph || property.DeclaringEntityType == mappedType + ? property.GetColumnBaseName() + : null; + if (columnName == null) + { + continue; + } - var columnMapping = new ColumnMappingBase(property, column, tableMapping); - tableMapping.ColumnMappings.Add(columnMapping); - column.PropertyMappings.Add(columnMapping); + 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; + } - if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.DefaultColumnMappings) - is not SortedSet columnMappings) - { - columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance); - property.AddRuntimeAnnotation(RelationalAnnotationNames.DefaultColumnMappings, columnMappings); + var columnMapping = new ColumnMappingBase(property, column, tableMapping); + tableMapping.ColumnMappings.Add(columnMapping); + column.PropertyMappings.Add(columnMapping); + + if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.DefaultColumnMappings) + is not SortedSet columnMappings) + { + columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance); + property.AddRuntimeAnnotation(RelationalAnnotationNames.DefaultColumnMappings, columnMappings); + } + + columnMappings.Add(columnMapping); } - columnMappings.Add(columnMapping); - } + if (tableMapping.ColumnMappings.Count != 0 + || tableMappings.Count == 0) + { + tableMappings.Add(tableMapping); + defaultTable.EntityTypeMappings.Add(tableMapping); + } - if (entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.DefaultMappings) - is not List tableMappings) - { - tableMappings = new List(); - entityType.AddRuntimeAnnotation(RelationalAnnotationNames.DefaultMappings, tableMappings); - } + if (isTpc || isTph) + { + break; + } - if (tableMapping.ColumnMappings.Count != 0 - || tableMappings.Count == 0) - { - tableMappings.Add(tableMapping); - defaultTable.EntityTypeMappings.Add(tableMapping); + mappedType = mappedType.BaseType; } tableMappings.Reverse(); @@ -312,89 +327,98 @@ private static void AddDefaultMappings(RelationalModel databaseModel, IEntityTyp private static void AddTables(RelationalModel databaseModel, IEntityType entityType) { var tableName = entityType.GetTableName(); - if (tableName != null) + if (tableName == null) { - var schema = entityType.GetSchema(); - var mappedType = entityType; - List? tableMappings = null; - while (mappedType != null) - { - var mappedTableName = mappedType.GetTableName(); - var mappedSchema = mappedType.GetSchema(); + return; + } - if (mappedTableName == null - || (mappedTableName == tableName - && mappedSchema == schema - && mappedType != entityType)) + var mappedType = entityType; + + Check.DebugAssert(entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableMappings) == null, "not null"); + var tableMappings = new List(); + entityType.SetRuntimeAnnotation(RelationalAnnotationNames.TableMappings, tableMappings); + + var isTpc = entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy; + var isTph = entityType.FindDiscriminatorProperty() != null; + while (mappedType != null) + { + var mappedTableName = mappedType.GetTableName(); + var mappedSchema = mappedType.GetSchema(); + + if (mappedTableName == null) + { + if (isTpc) { break; } - var mappedTable = StoreObjectIdentifier.Table(mappedTableName, mappedSchema); - if (!databaseModel.Tables.TryGetValue((mappedTableName, mappedSchema), out var table)) + mappedType = mappedType.BaseType; + continue; + } + + if (!databaseModel.Tables.TryGetValue((mappedTableName, mappedSchema), out var table)) + { + table = new Table(mappedTableName, mappedSchema, databaseModel); + databaseModel.Tables.Add((mappedTableName, mappedSchema), table); + } + + var mappedTable = StoreObjectIdentifier.Table(mappedTableName, mappedSchema); + var tableMapping = new TableMapping(entityType, table, includesDerivedTypes: !isTpc && mappedType == entityType) + { + IsSplitEntityTypePrincipal = true + }; + foreach (var property in mappedType.GetProperties()) + { + var columnName = property.GetColumnName(mappedTable); + if (columnName == null) { - table = new Table(mappedTableName, mappedSchema, databaseModel); - databaseModel.Tables.Add((mappedTableName, mappedSchema), table); + continue; } - var tableMapping = new TableMapping(entityType, table, includesDerivedTypes: mappedType == entityType) - { - IsSplitEntityTypePrincipal = true - }; - foreach (var property in mappedType.GetProperties()) + var column = (Column?)table.FindColumn(columnName); + if (column == null) { - var columnName = property.GetColumnName(mappedTable); - if (columnName == null) + column = new(columnName, property.GetColumnType(mappedTable), table) { - continue; - } - - var column = (Column?)table.FindColumn(columnName); - if (column == null) - { - column = new Column(columnName, property.GetColumnType(mappedTable), table); - column.IsNullable = property.IsColumnNullable(mappedTable); - table.Columns.Add(columnName, column); - } - else if (!property.IsColumnNullable(mappedTable)) - { - column.IsNullable = false; - } - - var columnMapping = new ColumnMapping(property, column, tableMapping); - tableMapping.ColumnMappings.Add(columnMapping); - column.PropertyMappings.Add(columnMapping); - - if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableColumnMappings) - is not SortedSet columnMappings) - { - columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance); - property.AddRuntimeAnnotation(RelationalAnnotationNames.TableColumnMappings, columnMappings); - } - - columnMappings.Add(columnMapping); + IsNullable = property.IsColumnNullable(mappedTable) + }; + table.Columns.Add(columnName, column); } - - mappedType = mappedType.BaseType; - - tableMappings = entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableMappings) - as List; - if (tableMappings == null) + else if (!property.IsColumnNullable(mappedTable)) { - tableMappings = new List(); - entityType.AddRuntimeAnnotation(RelationalAnnotationNames.TableMappings, tableMappings); + column.IsNullable = false; } - if (tableMapping.ColumnMappings.Count != 0 - || tableMappings.Count == 0) + var columnMapping = new ColumnMapping(property, column, tableMapping); + tableMapping.ColumnMappings.Add(columnMapping); + column.PropertyMappings.Add(columnMapping); + + if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableColumnMappings) + is not SortedSet columnMappings) { - tableMappings.Add(tableMapping); - table.EntityTypeMappings.Add(tableMapping); + columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance); + property.AddRuntimeAnnotation(RelationalAnnotationNames.TableColumnMappings, columnMappings); } + + columnMappings.Add(columnMapping); + } + + if (tableMapping.ColumnMappings.Count != 0 + || tableMappings.Count == 0) + { + tableMappings.Add(tableMapping); + table.EntityTypeMappings.Add(tableMapping); + } + + if (isTpc || isTph) + { + break; } - tableMappings?.Reverse(); + mappedType = mappedType.BaseType; } + + tableMappings.Reverse(); } private static void AddViews(RelationalModel databaseModel, IEntityType entityType) @@ -405,20 +429,28 @@ private static void AddViews(RelationalModel databaseModel, IEntityType entityTy return; } - var schema = entityType.GetViewSchema(); - List? viewMappings = null; var mappedType = entityType; + + Check.DebugAssert(entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.ViewMappings) == null, "not null"); + var viewMappings = new List(); + entityType.SetRuntimeAnnotation(RelationalAnnotationNames.ViewMappings, viewMappings); + + var isTpc = entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy; + var isTph = entityType.FindDiscriminatorProperty() != null; while (mappedType != null) { var mappedViewName = mappedType.GetViewName(); var mappedSchema = mappedType.GetViewSchema(); - if (mappedViewName == null - || (mappedViewName == viewName - && mappedSchema == schema - && mappedType != entityType)) + if (mappedViewName == null) { - break; + if (isTpc) + { + break; + } + + mappedType = mappedType.BaseType; + continue; } if (!databaseModel.Views.TryGetValue((mappedViewName, mappedSchema), out var view)) @@ -428,7 +460,7 @@ private static void AddViews(RelationalModel databaseModel, IEntityType entityTy } var mappedView = StoreObjectIdentifier.View(mappedViewName, mappedSchema); - var viewMapping = new ViewMapping(entityType, view, includesDerivedTypes: mappedType == entityType) + var viewMapping = new ViewMapping(entityType, view, includesDerivedTypes: !isTpc && mappedType == entityType) { IsSplitEntityTypePrincipal = true }; @@ -443,8 +475,10 @@ private static void AddViews(RelationalModel databaseModel, IEntityType entityTy var column = (ViewColumn?)view.FindColumn(columnName); if (column == null) { - column = new ViewColumn(columnName, property.GetColumnType(mappedView), view); - column.IsNullable = property.IsColumnNullable(mappedView); + column = new ViewColumn(columnName, property.GetColumnType(mappedView), view) + { + IsNullable = property.IsColumnNullable(mappedView) + }; view.Columns.Add(columnName, column); } else if (!property.IsColumnNullable(mappedView)) @@ -466,24 +500,22 @@ private static void AddViews(RelationalModel databaseModel, IEntityType entityTy columnMappings.Add(columnMapping); } - mappedType = mappedType.BaseType; - - viewMappings = entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.ViewMappings) as List; - if (viewMappings == null) - { - viewMappings = new List(); - entityType.AddRuntimeAnnotation(RelationalAnnotationNames.ViewMappings, viewMappings); - } - if (viewMapping.ColumnMappings.Count != 0 || viewMappings.Count == 0) { viewMappings.Add(viewMapping); view.EntityTypeMappings.Add(viewMapping); } + + if (isTpc || isTph) + { + break; + } + + mappedType = mappedType.BaseType; } - viewMappings?.Reverse(); + viewMappings.Reverse(); } private static void AddSqlQueries(RelationalModel databaseModel, IEntityType entityType) @@ -770,9 +802,10 @@ private static void PopulateConstraints(Table table, bool designTime) { if (firstPrincipalMapping && !principalMapping.IncludesDerivedTypes - && foreignKey.PrincipalEntityType.GetDirectlyDerivedTypes().Any()) + && foreignKey.PrincipalEntityType.GetDirectlyDerivedTypes().Any(e => e.GetTableMappings().Any())) { // Derived principal entity types are mapped to different tables, so the constraint is not enforceable + // TODO: Allow this to be overriden #15854 break; } @@ -1059,24 +1092,15 @@ private static void PopulateRowInternalForeignKeys(TableBase table) } } - // Re-add the mapping to update the order - if (mainMapping is TableMapping mainTableMapping) - { - ((Table)mainMapping.Table).EntityTypeMappings.Remove(mainTableMapping); - mainMapping.IsSharedTablePrincipal = true; - ((Table)mainMapping.Table).EntityTypeMappings.Add(mainTableMapping); - } - else if (mainMapping is ViewMapping mainViewMapping) - { - ((View)mainMapping.Table).EntityTypeMappings.Remove(mainViewMapping); - mainMapping.IsSharedTablePrincipal = true; - ((View)mainMapping.Table).EntityTypeMappings.Add(mainViewMapping); - } - Check.DebugAssert( mainMapping is not null, $"{nameof(mainMapping)} is neither a {nameof(TableMapping)} nor a {nameof(ViewMapping)}"); + // Re-add the mapping to update the order + mainMapping.Table.EntityTypeMappings.Remove(mainMapping); + mainMapping.IsSharedTablePrincipal = true; + mainMapping.Table.EntityTypeMappings.Add(mainMapping); + if (referencingInternalForeignKeyMap != null) { table.ReferencingRowInternalForeignKeys = referencingInternalForeignKeyMap; diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs index 64e4776b670..2e40c172622 100644 --- a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs @@ -149,6 +149,26 @@ public static class RelationalAnnotationNames /// public const string IsTableExcludedFromMigrations = Prefix + "IsTableExcludedFromMigrations"; + /// + /// The name for the annotation determining the mapping strategy for inherited properties. + /// + public const string MappingStrategy = Prefix + "MappingStrategy"; + + /// + /// The value for the annotation corresponding to the TPC mapping strategy. + /// + public const string TpcMappingStrategy = "TPC"; + + /// + /// The value for the annotation corresponding to the TPH mapping strategy. + /// + public const string TphMappingStrategy = "TPH"; + + /// + /// The value for the annotation corresponding to the TPT mapping strategy. + /// + public const string TptMappingStrategy = "TPT"; + /// /// The name for database model annotation. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index a96438e2c9b..27aad4c18e4 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -26,6 +26,14 @@ public static class RelationalStrings private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.EntityFrameworkCore.Properties.RelationalStrings", typeof(RelationalStrings).Assembly); + /// + /// The corresponding CLR type for entity type '{entityType}' cannot be instantiated, but the entity type was mapped to '{storeObject}' using the 'TPC' mapping strategy. Only instantiable types should be mapped. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. + /// + public static string AbstractTPC(object? entityType, object? storeObject) + => string.Format( + GetString("AbstractTPC", nameof(entityType), nameof(storeObject)), + entityType, storeObject); + /// /// Unable to deserialize a sequence from model metadata. See inner exception for details. /// @@ -295,6 +303,14 @@ public static string DeleteDataOperationValuesCountMismatch(object? valuesCount, GetString("DeleteDataOperationValuesCountMismatch", nameof(valuesCount), nameof(columnsCount), nameof(table)), valuesCount, columnsCount, table); + /// + /// The derived entity type '{entityType}' was configured with the '{strategy}' mapping strategy. Only the root entity type should be configured with a mapping strategy. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. + /// + public static string DerivedStrategy(object? entityType, object? strategy) + => string.Format( + GetString("DerivedStrategy", nameof(entityType), nameof(strategy)), + entityType, strategy); + /// /// Using 'Distinct' operation on a projection containing a collection is not supported. /// @@ -759,6 +775,14 @@ public static string InvalidMappedSqlQueryDerivedType(object? entityType, object GetString("InvalidMappedSqlQueryDerivedType", nameof(entityType), nameof(baseEntityType)), entityType, baseEntityType); + /// + /// The mapping strategy '{mappingStrategy}' specified on '{entityType}' is not supported. + /// + public static string InvalidMappingStrategy(object? mappingStrategy, object? entityType) + => string.Format( + GetString("InvalidMappingStrategy", nameof(mappingStrategy), nameof(entityType)), + mappingStrategy, entityType); + /// /// The specified 'MaxBatchSize' value '{value}' is not valid. It must be a positive number. /// @@ -775,6 +799,14 @@ public static string InvalidMinBatchSize(object? value) GetString("InvalidMinBatchSize", nameof(value)), value); + /// + /// The mapping strategy '{mappingStrategy}' used for '{entityType}' is not supported for keyless entity types. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. + /// + public static string KeylessMappingStrategy(object? mappingStrategy, object? entityType) + => string.Format( + GetString("KeylessMappingStrategy", nameof(mappingStrategy), nameof(entityType)), + mappingStrategy, entityType); + /// /// Queries performing '{method}' operation must have a deterministic sort order. Rewrite the query to apply an 'OrderBy' operation on the sequence before calling '{method}'. /// @@ -844,7 +876,7 @@ public static string MissingParameterValue(object? parameter) parameter); /// - /// Cannot execute an ModificationCommandBatch which hasn't been completed. + /// Cannot add commands to a completed ModificationCommandBatch. /// public static string ModificationCommandBatchAlreadyComplete => GetString("ModificationCommandBatchAlreadyComplete"); @@ -853,7 +885,7 @@ public static string ModificationCommandBatchAlreadyComplete /// Cannot execute an ModificationCommandBatch which hasn't been completed. /// public static string ModificationCommandBatchNotComplete - => GetString("ModificationCommandBatchNotCompleted"); + => GetString("ModificationCommandBatchNotComplete"); /// /// Cannot save changes for an entity of type '{entityType}' in state '{entityState}'. This may indicate a bug in Entity Framework, please open an issue at https://go.microsoft.com/fwlink/?linkid=2142044. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values of the entity. @@ -932,7 +964,15 @@ public static string NonScalarFunctionParameterCannotPropagatesNullability(objec parameterName, functionName); /// - /// Both '{entityType}' and '{otherEntityType}' are mapped to the table '{table}'. All the entity types in a hierarchy that don't have a discriminator must be mapped to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. + /// The mapping strategy '{mappingStrategy}' specified on '{entityType}' is not supported for entity types with a discriminator. + /// + public static string NonTphMappingStrategy(object? mappingStrategy, object? entityType) + => string.Format( + GetString("NonTphMappingStrategy", nameof(mappingStrategy), nameof(entityType)), + mappingStrategy, entityType); + + /// + /// Both '{entityType}' and '{otherEntityType}' are mapped to the table '{table}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. /// public static string NonTPHTableClash(object? entityType, object? otherEntityType, object? table) => string.Format( @@ -940,7 +980,7 @@ public static string NonTPHTableClash(object? entityType, object? otherEntityTyp entityType, otherEntityType, table); /// - /// Both '{entityType}' and '{otherEntityType}' are mapped to the view '{view}'. All the entity types in a hierarchy that don't have a discriminator must be mapped to different views. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. + /// Both '{entityType}' and '{otherEntityType}' are mapped to the view '{view}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different views. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. /// public static string NonTPHViewClash(object? entityType, object? otherEntityType, object? view) => string.Format( @@ -2056,6 +2096,28 @@ public static FallbackEventDefinition LogForeignKeyPropertiesMappedToUnrelatedTa return (FallbackEventDefinition)definition; } + /// + /// The foreign key {foreignKeyProperties} on the entity type '{entityType}' targeting '{principalEntityType}' cannot be represented in the database. '{principalEntityType}' is mapped using the table per concrete type meaning that the derived entities will not be present in {'principalTable'}. If this foreign key on '{entityType}' will never reference entities derived from '{principalEntityType}' then the foreign key constraint name can be specified explicitly to force it to be created. + /// + public static FallbackEventDefinition LogForeignKeyTPCPrincipal(IDiagnosticsLogger logger) + { + var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogForeignKeyTPCPrincipal; + if (definition == null) + { + definition = NonCapturingLazyInitializer.EnsureInitialized( + ref ((RelationalLoggingDefinitions)logger.Definitions).LogForeignKeyTPCPrincipal, + logger, + static logger => new FallbackEventDefinition( + logger.Options, + RelationalEventId.ForeignKeyTPCPrincipalWarning, + LogLevel.Warning, + "RelationalEventId.ForeignKeyTPCPrincipalWarning", + _resourceManager.GetString("LogForeignKeyTPCPrincipal")!)); + } + + return (FallbackEventDefinition)definition; + } + /// /// Generating down script for migration '{migration}'. /// @@ -2653,6 +2715,31 @@ public static EventDefinition LogRollingBackTransaction(IDiagnosticsLogger logge return (EventDefinition)definition; } + /// + /// The property '{property}' on entity type '{entityType}' is configured with a database-generated default, however the entity type is mapped to the database using table per concrete class strategy. Make sure that the generated values are unique across all the tables, duplicated values could result in errors or data corruption. + /// + public static EventDefinition LogTpcStoreGeneratedIdentity(IDiagnosticsLogger logger) + { + var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogTpcStoreGeneratedIdentity; + if (definition == null) + { + definition = NonCapturingLazyInitializer.EnsureInitialized( + ref ((RelationalLoggingDefinitions)logger.Definitions).LogTpcStoreGeneratedIdentity, + logger, + static logger => new EventDefinition( + logger.Options, + RelationalEventId.TpcStoreGeneratedIdentityWarning, + LogLevel.Warning, + "RelationalEventId.TpcStoreGeneratedIdentityWarning", + level => LoggerMessage.Define( + level, + RelationalEventId.TpcStoreGeneratedIdentityWarning, + _resourceManager.GetString("LogTpcStoreGeneratedIdentity")!))); + } + + return (EventDefinition)definition; + } + /// /// An error occurred using a transaction. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 9ba10958462..6a36f8c0084 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + The corresponding CLR type for entity type '{entityType}' cannot be instantiated, but the entity type was mapped to '{storeObject}' using the 'TPC' mapping strategy. Only instantiable types should be mapped. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. + Unable to deserialize a sequence from model metadata. See inner exception for details. Obsolete @@ -223,6 +226,9 @@ The number of key values ({valuesCount}) doesn't match the number of key columns ({columnsCount}) for the data deletion operation on '{table}'. Provide the same number of key values and key columns. + + The derived entity type '{entityType}' was configured with the '{strategy}' mapping strategy. Only the root entity type should be configured with a mapping strategy. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. + Using 'Distinct' operation on a projection containing a collection is not supported. @@ -400,12 +406,18 @@ The entity type '{entityType}' is mapped to a SQL query, but is derived from '{baseEntityType}'. Derived entity types cannot be mapped to a different SQL query than the base entity type. + + The mapping strategy '{mappingStrategy}' specified on '{entityType}' is not supported. + The specified 'MaxBatchSize' value '{value}' is not valid. It must be a positive number. The specified 'MinBatchSize' value '{value}' is not valid. It must be a positive number. + + The mapping strategy '{mappingStrategy}' used for '{entityType}' is not supported for keyless entity types. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. + Queries performing '{method}' operation must have a deterministic sort order. Rewrite the query to apply an 'OrderBy' operation on the sequence before calling '{method}'. @@ -529,6 +541,10 @@ The foreign key {foreignKeyProperties} on the entity type '{entityType}' targeting '{principalEntityType}' cannot be represented in the database. Either the properties {foreignKeyProperties} aren't mapped to table '{table}', or the principal properties {principalProperties} aren't mapped to table '{principalTable}'. All foreign key properties must map to the table to which the dependent type is mapped, and all principal properties must map to a single table to which the principal type is mapped. Error RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables string string string string string string string + + The foreign key {foreignKeyProperties} on the entity type '{entityType}' targeting '{principalEntityType}' cannot be represented in the database. '{principalEntityType}' is mapped using the table per concrete type meaning that the derived entities will not be present in {'principalTable'}. If this foreign key on '{entityType}' will never reference entities derived from '{principalEntityType}' then the foreign key constraint name can be specified explicitly to force it to be created. + Warning RelationalEventId.ForeignKeyTPCPrincipalWarning string string string string string string string + Generating down script for migration '{migration}'. Debug RelationalEventId.MigrationGeneratingDownScript string @@ -625,6 +641,10 @@ Rolling back transaction. Debug RelationalEventId.TransactionRollingBack + + The property '{property}' on entity type '{entityType}' is configured with a database-generated default, however the entity type is mapped to the database using table per concrete class strategy. Make sure that the generated values are unique across all the tables, duplicated values could result in errors or data corruption. + Warning RelationalEventId.TpcStoreGeneratedIdentityWarning string string + An error occurred using a transaction. Error RelationalEventId.TransactionError @@ -708,11 +728,14 @@ Cannot set 'PropagatesNullability' on parameter '{parameterName}' of DbFunction '{functionName}' since function does not represent a scalar function. + + The mapping strategy '{mappingStrategy}' specified on '{entityType}' is not supported for entity types with a discriminator. + - Both '{entityType}' and '{otherEntityType}' are mapped to the table '{table}'. All the entity types in a hierarchy that don't have a discriminator must be mapped to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. + Both '{entityType}' and '{otherEntityType}' are mapped to the table '{table}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. - Both '{entityType}' and '{otherEntityType}' are mapped to the view '{view}'. All the entity types in a hierarchy that don't have a discriminator must be mapped to different views. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. + Both '{entityType}' and '{otherEntityType}' are mapped to the view '{view}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different views. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. No relational database providers are configured. Configure a database provider using 'OnConfiguring' or by creating an ImmutableDbContextOptions with a configured database provider and passing it to the context. diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index e77b22371af..adc6154513b 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -74,6 +74,10 @@ public void Test_new_annotations_handled_for_entity_types() RelationalAnnotationNames.IsFixedLength, RelationalAnnotationNames.Collation, RelationalAnnotationNames.IsStored, + RelationalAnnotationNames.MappingStrategy, // Will be handled in the next PR + RelationalAnnotationNames.TpcMappingStrategy, + RelationalAnnotationNames.TphMappingStrategy, + RelationalAnnotationNames.TptMappingStrategy, RelationalAnnotationNames.RelationalModel, RelationalAnnotationNames.ModelDependencies }; @@ -198,6 +202,10 @@ public void Test_new_annotations_handled_for_properties() RelationalAnnotationNames.Filter, RelationalAnnotationNames.DbFunctions, RelationalAnnotationNames.MaxIdentifierLength, + RelationalAnnotationNames.MappingStrategy, + RelationalAnnotationNames.TpcMappingStrategy, + RelationalAnnotationNames.TphMappingStrategy, + RelationalAnnotationNames.TptMappingStrategy, RelationalAnnotationNames.RelationalModel, RelationalAnnotationNames.ModelDependencies }; diff --git a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs index 00b234072b3..61ba71b3773 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs @@ -232,8 +232,9 @@ private void AddAnnotations(IMutableAnnotatable element) .Where( a => a != RelationalAnnotationNames.MaxIdentifierLength #pragma warning disable CS0618 // Type or member is obsolete - && a != RelationalAnnotationNames.SequencePrefix) + && a != RelationalAnnotationNames.SequencePrefix #pragma warning restore CS0618 // Type or member is obsolete + && a.IndexOf(':') > 0) .Select(a => "Unicorn" + a.Substring(RelationalAnnotationNames.Prefix.Length - 1))) { element[annotationName] = "Value"; @@ -261,8 +262,9 @@ private void AssertAnnotations(IMutableAnnotatable element) && a != RelationalAnnotationNames.UniqueConstraintMappings && a != RelationalAnnotationNames.RelationalOverrides #pragma warning disable CS0618 // Type or member is obsolete - && a != RelationalAnnotationNames.SequencePrefix)) + && a != RelationalAnnotationNames.SequencePrefix #pragma warning restore CS0618 // Type or member is obsolete + && a.IndexOf(':') > 0)) { Assert.Equal("Value", (string)element[annotationName]); } diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 5e365cfcd84..166946c8d5e 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -1019,6 +1019,7 @@ public static RuntimeForeignKey CreateForeignKey2(RuntimeEntityType declaringEnt public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) { + runtimeEntityType.AddAnnotation(""DiscriminatorMappingComplete"", false); runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); runtimeEntityType.AddAnnotation(""Relational:Schema"", null); runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); @@ -1939,7 +1940,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) var dependentBase = dependentNavigation.TargetEntityType; - Assert.True(dependentBase.GetIsDiscriminatorMappingComplete()); + Assert.False(dependentBase.GetIsDiscriminatorMappingComplete()); var principalDiscriminator = dependentBase.FindDiscriminatorProperty(); Assert.IsType( principalDiscriminator.GetValueGeneratorFactory()(principalDiscriminator, dependentBase)); @@ -2130,7 +2131,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) eb.HasDiscriminator("EnumDiscriminator") .HasValue(Enum1.One) - .HasValue>(Enum1.Two); + .HasValue>(Enum1.Two) + .IsComplete(false); }); modelBuilder.Entity>( @@ -2147,6 +2149,436 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } } + [ConditionalFact] + public void TPC_model() + => Test( + new TpcContext(), + new CompiledModelCodeGenerationOptions { UseNullableReferenceTypes = true }, + code => + Assert.Collection( + code, + c => AssertFileContents( + "TpcContextModel.cs", + @"// +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + [DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.TpcContext))] + public partial class TpcContextModel : RuntimeModel + { + static TpcContextModel() + { + var model = new TpcContextModel(); + model.Initialize(); + model.Customize(); + _instance = model; + } + + private static TpcContextModel _instance; + public static IModel Instance => _instance; + + partial void Initialize(); + + partial void Customize(); + } +} +", c), + c => AssertFileContents( + "TpcContextModelBuilder.cs", + @"// +using System; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + public partial class TpcContextModel + { + partial void Initialize() + { + var dependentBasebyte = DependentBasebyteEntityType.Create(this); + var principalBase = PrincipalBaseEntityType.Create(this); + var principalDerivedDependentBasebyte = PrincipalDerivedDependentBasebyteEntityType.Create(this, principalBase); + + DependentBasebyteEntityType.CreateForeignKey1(dependentBasebyte, principalDerivedDependentBasebyte); + PrincipalBaseEntityType.CreateForeignKey1(principalBase, principalBase); + PrincipalBaseEntityType.CreateForeignKey2(principalBase, principalDerivedDependentBasebyte); + + DependentBasebyteEntityType.CreateAnnotations(dependentBasebyte); + PrincipalBaseEntityType.CreateAnnotations(principalBase); + PrincipalDerivedDependentBasebyteEntityType.CreateAnnotations(principalDerivedDependentBasebyte); + + AddAnnotation(""Relational:DefaultSchema"", ""TPC""); + AddAnnotation(""Relational:MaxIdentifierLength"", 128); + AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + } + } +} +", c), + c => AssertFileContents( + "DependentBasebyteEntityType.cs", @"// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + internal partial class DependentBasebyteEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DependentBase"", + typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase), + baseEntityType); + + var id = runtimeEntityType.AddProperty( + ""Id"", + typeof(byte?), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase).GetProperty(""Id"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + afterSaveBehavior: PropertySaveBehavior.Throw); + id.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var principalId = runtimeEntityType.AddProperty( + ""PrincipalId"", + typeof(long?), + nullable: true); + principalId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var key = runtimeEntityType.AddKey( + new[] { id }); + runtimeEntityType.SetPrimaryKey(key); + + var index = runtimeEntityType.AddIndex( + new[] { principalId }, + unique: true); + + return runtimeEntityType; + } + + public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalId"")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id"")! })!, + principalEntityType, + deleteBehavior: DeleteBehavior.ClientCascade, + unique: true, + requiredDependent: true); + + var principal = declaringEntityType.AddNavigation(""Principal"", + runtimeForeignKey, + onDependent: true, + typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase).GetProperty(""Principal"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + var dependent = principalEntityType.AddNavigation(""Dependent"", + runtimeForeignKey, + onDependent: false, + typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetProperty(""Dependent"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + return runtimeForeignKey; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); + runtimeEntityType.AddAnnotation(""Relational:Schema"", ""TPC""); + runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); + runtimeEntityType.AddAnnotation(""Relational:TableName"", ""DependentBase""); + runtimeEntityType.AddAnnotation(""Relational:ViewName"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewSchema"", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", c), + c => AssertFileContents( + "PrincipalBaseEntityType.cs", @"// +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + internal partial class PrincipalBaseEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase"", + typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase), + baseEntityType); + + var id = runtimeEntityType.AddProperty( + ""Id"", + typeof(long?), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty(""Id"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw); + id.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + + var principalBaseId = runtimeEntityType.AddProperty( + ""PrincipalBaseId"", + typeof(long?), + nullable: true); + principalBaseId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var principalDerivedDependentBasebyteId = runtimeEntityType.AddProperty( + ""PrincipalDerived>Id"", + typeof(long?), + nullable: true); + principalDerivedDependentBasebyteId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var key = runtimeEntityType.AddKey( + new[] { id }); + runtimeEntityType.SetPrimaryKey(key); + + var index = runtimeEntityType.AddIndex( + new[] { principalBaseId }); + + var index0 = runtimeEntityType.AddIndex( + new[] { principalDerivedDependentBasebyteId }); + + return runtimeEntityType; + } + + public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalBaseId"")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id"")! })!, + principalEntityType); + + var deriveds = principalEntityType.AddNavigation(""Deriveds"", + runtimeForeignKey, + onDependent: false, + typeof(ICollection), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty(""Deriveds"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + return runtimeForeignKey; + } + + public static RuntimeForeignKey CreateForeignKey2(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalDerived>Id"")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id"")! })!, + principalEntityType); + + var principals = principalEntityType.AddNavigation(""Principals"", + runtimeForeignKey, + onDependent: false, + typeof(ICollection), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetProperty(""Principals"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + return runtimeForeignKey; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); + runtimeEntityType.AddAnnotation(""Relational:MappingStrategy"", ""TPC""); + runtimeEntityType.AddAnnotation(""Relational:Schema"", ""TPC""); + runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); + runtimeEntityType.AddAnnotation(""Relational:TableName"", ""PrincipalBase""); + runtimeEntityType.AddAnnotation(""Relational:ViewDefinitionSql"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewName"", ""PrincipalBaseView""); + runtimeEntityType.AddAnnotation(""Relational:ViewSchema"", ""TPC""); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", c), + c => AssertFileContents( + "PrincipalDerivedDependentBasebyteEntityType.cs", @"// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + internal partial class PrincipalDerivedDependentBasebyteEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>"", + typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>), + baseEntityType); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); + runtimeEntityType.AddAnnotation(""Relational:Schema"", ""TPC""); + runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); + runtimeEntityType.AddAnnotation(""Relational:TableName"", ""PrincipalDerived""); + runtimeEntityType.AddAnnotation(""Relational:ViewDefinitionSql"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewName"", ""PrincipalDerivedView""); + runtimeEntityType.AddAnnotation(""Relational:ViewSchema"", ""TPC""); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", c)), + model => + { + Assert.Equal("TPC", model.GetDefaultSchema()); + + var principalBase = model.FindEntityType(typeof(PrincipalBase)); + Assert.Equal("PrincipalBase", principalBase.GetTableName()); + Assert.Equal("TPC", principalBase.GetSchema()); + Assert.Equal("PrincipalBaseView", principalBase.GetViewName()); + Assert.Equal("TPC", principalBase.GetViewSchema()); + + Assert.Null(principalBase.GetDiscriminatorValue()); + Assert.Null(principalBase.FindDiscriminatorProperty()); + Assert.Equal("TPC", principalBase.GetMappingStrategy()); + + var selfRefNavigation = principalBase.GetDeclaredNavigations().Last(); + Assert.Equal("Deriveds", selfRefNavigation.Name); + Assert.True(selfRefNavigation.IsCollection); + Assert.False(selfRefNavigation.IsOnDependent); + Assert.Equal(principalBase, selfRefNavigation.TargetEntityType); + Assert.Null(selfRefNavigation.Inverse); + + var principalDerived = model.FindEntityType(typeof(PrincipalDerived>)); + Assert.Equal(principalBase, principalDerived.BaseType); + + Assert.Equal("PrincipalDerived", principalDerived.GetTableName()); + Assert.Equal("TPC", principalDerived.GetSchema()); + Assert.Equal("PrincipalDerivedView", principalDerived.GetViewName()); + Assert.Equal("TPC", principalBase.GetViewSchema()); + + Assert.Null(principalDerived.GetDiscriminatorValue()); + Assert.Null(principalDerived.FindDiscriminatorProperty()); + Assert.Equal("TPC", principalDerived.GetMappingStrategy()); + + Assert.Equal(2, principalDerived.GetDeclaredNavigations().Count()); + var derivedNavigation = principalDerived.GetDeclaredNavigations().Last(); + Assert.Equal("Principals", derivedNavigation.Name); + Assert.True(derivedNavigation.IsCollection); + Assert.False(derivedNavigation.IsOnDependent); + Assert.Equal(principalBase, derivedNavigation.TargetEntityType); + Assert.Null(derivedNavigation.Inverse); + + var dependentNavigation = principalDerived.GetDeclaredNavigations().First(); + Assert.Equal("Dependent", dependentNavigation.Name); + Assert.Equal("Dependent", dependentNavigation.PropertyInfo.Name); + Assert.Equal("k__BackingField", dependentNavigation.FieldInfo.Name); + Assert.False(dependentNavigation.IsCollection); + Assert.False(dependentNavigation.IsEagerLoaded); + Assert.False(dependentNavigation.IsOnDependent); + Assert.Equal(principalDerived, dependentNavigation.DeclaringEntityType); + Assert.Equal("Principal", dependentNavigation.Inverse.Name); + + var dependentForeignKey = dependentNavigation.ForeignKey; + Assert.False(dependentForeignKey.IsOwnership); + Assert.False(dependentForeignKey.IsRequired); + Assert.True(dependentForeignKey.IsRequiredDependent); + Assert.True(dependentForeignKey.IsUnique); + Assert.Same(principalDerived, dependentForeignKey.PrincipalEntityType); + Assert.Same(dependentNavigation.Inverse, dependentForeignKey.DependentToPrincipal); + Assert.Same(dependentNavigation, dependentForeignKey.PrincipalToDependent); + Assert.Equal(DeleteBehavior.ClientCascade, dependentForeignKey.DeleteBehavior); + Assert.Equal(new[] { "PrincipalId" }, dependentForeignKey.Properties.Select(p => p.Name)); + + var dependentBase = dependentNavigation.TargetEntityType; + + Assert.True(dependentBase.GetIsDiscriminatorMappingComplete()); + Assert.Null(dependentBase.FindDiscriminatorProperty()); + + Assert.Same(dependentForeignKey, dependentBase.GetForeignKeys().Single()); + + Assert.Equal( + new[] + { + dependentBase, + principalBase, + principalDerived + }, + model.GetEntityTypes()); + }, + typeof(SqlServerNetTopologySuiteDesignTimeServices)); + + public class TpcContext : SqlServerContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.HasDefaultSchema("TPC"); + + modelBuilder.Entity( + eb => + { + eb.Ignore(e => e.Owned); + + eb.UseTpcMappingStrategy(); + eb.ToTable("PrincipalBase"); + eb.ToView("PrincipalBaseView"); + }); + + modelBuilder.Entity>>( + eb => + { + eb.HasOne(e => e.Dependent).WithOne(e => e.Principal) + .HasForeignKey>() + .OnDelete(DeleteBehavior.ClientCascade); + + eb.Navigation(e => e.Dependent).IsRequired(); + + eb.ToTable("PrincipalDerived"); + eb.ToView("PrincipalDerivedView"); + }); + + modelBuilder.Entity>( + eb => + { + eb.Property("Id"); + }); + } + } + public class CustomValueComparer : ValueComparer { public CustomValueComparer() @@ -3623,11 +4055,6 @@ protected void Test( var assembly = build.BuildInMemory(); - if (assertScaffold != null) - { - assertScaffold(scaffoldedFiles); - } - if (assertModel != null) { var modelType = assembly.GetType(options.ModelNamespace + "." + options.ContextType.Name + "Model"); @@ -3637,6 +4064,11 @@ protected void Test( var modelRuntimeInitializer = context.GetService(); assertModel(modelRuntimeInitializer.Initialize(compiledModel, designTime: false)); } + + if (assertScaffold != null) + { + assertScaffold(scaffoldedFiles); + } } protected static void AssertFileContents( diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index b50fdd57f5c..9ce26453f6b 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -73,9 +73,7 @@ public override void Detects_missing_discriminator_property() var entityC = model.AddEntityType(typeof(C)); entityC.BaseType = entityA; - VerifyError( - RelationalStrings.NonTPHTableClash( - entityC.DisplayName(), entityA.DisplayName(), entityA.DisplayName()), modelBuilder); + Validate(modelBuilder); } [ConditionalFact] @@ -1572,16 +1570,14 @@ public virtual void Passes_for_TPT() } [ConditionalFact] - public virtual void Detects_unconfigured_entity_type_in_TPT() + public virtual void Passes_for_unconfigured_entity_type_in_TPT() { var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity(); modelBuilder.Entity().ToTable("Cat"); modelBuilder.Entity(); - VerifyError( - RelationalStrings.NonTPHTableClash(nameof(Dog), nameof(Animal), nameof(Animal)), - modelBuilder); + Validate(modelBuilder); } [ConditionalFact] @@ -1633,6 +1629,18 @@ public virtual void Detects_view_TPT_with_discriminator() modelBuilder); } + [ConditionalFact] + public virtual void Detects_TPT_with_keyless_entity_type() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().HasNoKey(); + modelBuilder.Entity().ToTable("Cat"); + + VerifyError( + RelationalStrings.KeylessMappingStrategy("TPT", nameof(Animal)), + modelBuilder); + } + [ConditionalFact] public virtual void Passes_on_valid_table_sharing_with_TPT() { @@ -1725,6 +1733,261 @@ public virtual void Detects_unmapped_foreign_keys_in_TPT() LogLevel.Error); } + [ConditionalFact] + public virtual void Passes_for_ToTable_for_abstract_class() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().ToTable("Abstract"); + modelBuilder.Entity(); + modelBuilder.Entity>(); + + Validate(modelBuilder); + } + + [ConditionalFact] + public virtual void Passes_for_abstract_class_TPC() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity(); + modelBuilder.Entity().UseTpcMappingStrategy(); + modelBuilder.Entity>().ToTable("G"); + + Validate(modelBuilder); + } + + [ConditionalFact] + public virtual void Passes_for_view_TPC() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().ToTable((string)null).UseTpcMappingStrategy(); + modelBuilder.Entity().ToView("Cat"); + + Validate(modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_invalid_MappingStrategy() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().HasAnnotation(RelationalAnnotationNames.MappingStrategy, "TTT"); + modelBuilder.Entity(); + + VerifyError( + RelationalStrings.InvalidMappingStrategy("TTT", nameof(Animal)), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_MappingStrategy_on_derived_types() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().HasBaseType((string)null); + modelBuilder.Entity(); + modelBuilder.Entity().ToTable("Cat").ToView("Cat").UseTpcMappingStrategy().HasBaseType(typeof(Animal)); + + VerifyError( + RelationalStrings.DerivedStrategy(nameof(Cat), "TPC"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_ToTable_for_abstract_class_TPC() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().ToTable("Abstract", "dbo").UseTpcMappingStrategy(); + modelBuilder.Entity(); + modelBuilder.Entity>(); + + VerifyError( + RelationalStrings.AbstractTPC(nameof(Abstract), "dbo.Abstract"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_ToView_for_abstract_class_TPC() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().ToView("Abstract").UseTpcMappingStrategy(); + modelBuilder.Entity(); + modelBuilder.Entity>(); + + VerifyError( + RelationalStrings.AbstractTPC(nameof(Abstract), "Abstract"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_ToFunction_for_abstract_class_TPC() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().ToFunction("Abstract").UseTpcMappingStrategy(); + modelBuilder.Entity(); + modelBuilder.Entity>(); + + VerifyError( + RelationalStrings.AbstractTPC(nameof(Abstract), "Abstract"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_clashing_entity_types_in_views_TPC() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().UseTpcMappingStrategy(); + modelBuilder.Entity().ToTable("Cat").ToView("Cat"); + modelBuilder.Entity().ToTable("Dog").ToView("Cat"); + + VerifyError( + RelationalStrings.NonTPHViewClash(nameof(Dog), nameof(Cat), "Cat"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_table_and_view_TPC_mismatch() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().UseTpcMappingStrategy().ToTable("Animal").ToView("Animal"); + modelBuilder.Entity().ToTable("Animal").ToView("Cat"); + + VerifyError( + RelationalStrings.NonTPHTableClash(nameof(Cat), nameof(Animal), "Animal"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Passes_on_TPC_with_keyless_entity_type() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().UseTpcMappingStrategy().HasNoKey(); + modelBuilder.Entity().ToTable("Cat"); + + Validate(modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_view_TPC_with_discriminator() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().ToView("Animal").UseTpcMappingStrategy().HasDiscriminator("Discriminator"); + modelBuilder.Entity().ToView("Cat"); + + VerifyError( + RelationalStrings.NonTphMappingStrategy("TPC", nameof(Animal)), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_store_generated_PK_in_TPC() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().UseTpcMappingStrategy(); + modelBuilder.Entity(); + + var definition = + RelationalResources.LogTpcStoreGeneratedIdentity(new TestLogger()); + VerifyWarning( + definition.GenerateMessage(nameof(Animal.Id), nameof(Animal)), + modelBuilder, + LogLevel.Warning); + } + + [ConditionalFact] + public virtual void Passes_on_valid_view_sharing_with_TPC() + { + var modelBuilder = CreateConventionalModelBuilder(); + + modelBuilder.Entity() + .UseTpcMappingStrategy() + .ToView("Animal") + .Ignore(a => a.FavoritePerson); + + modelBuilder.Entity( + x => + { + x.ToView("Cat"); + x.HasOne(c => c.FavoritePerson).WithOne().HasForeignKey(c => c.Id); + }); + + modelBuilder.Entity().ToView("Cat"); + + Validate(modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_linking_relationship_on_derived_type_in_TPC() + { + var modelBuilder = CreateConventionalModelBuilder(); + + modelBuilder.Entity() + .UseTpcMappingStrategy() + .Ignore(a => a.FavoritePerson); + + modelBuilder.Entity( + x => + { + x.ToTable("Cat"); + x.HasOne(c => c.FavoritePerson).WithOne().HasForeignKey(c => c.Id); + }); + + modelBuilder.Entity().ToTable("Cat"); + + VerifyError( + RelationalStrings.IncompatibleTableDerivedRelationship( + "Cat", "Cat", "Person"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_linking_relationship_on_derived_type_in_TPC_views() + { + var modelBuilder = CreateConventionalModelBuilder(); + + modelBuilder.Entity() + .UseTpcMappingStrategy() + .Ignore(a => a.FavoritePerson) + .ToView("Animal"); + + modelBuilder.Entity( + x => + { + x.ToView("Cat"); + x.HasOne(c => c.FavoritePerson).WithOne().HasForeignKey(c => c.Id); + }); + + modelBuilder.Entity().ToView("Cat"); + + VerifyError( + RelationalStrings.IncompatibleViewDerivedRelationship( + "Cat", "Cat", "Person"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_unmapped_foreign_keys_in_TPC() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().UseTpcMappingStrategy() + .Property(a => a.Id).ValueGeneratedNever(); + modelBuilder.Entity().ToTable("Cat"); + modelBuilder.Entity() + .HasOne().WithOne(a => a.FavoritePerson) + .HasForeignKey(p => p.FavoriteBreed) + .HasPrincipalKey(a => a.Name); + + var definition = + RelationalResources.LogForeignKeyTPCPrincipal(new TestLogger()); + VerifyWarning( + definition.GenerateMessage( + l => l.Log( + definition.Level, + definition.EventId, + definition.MessageFormat, + "{'FavoriteBreed'}", nameof(Person), nameof(Animal), nameof(Animal), nameof(Animal), nameof(Person), + nameof(Animal))), + modelBuilder, + LogLevel.Warning); + } + [ConditionalFact] public virtual void Passes_for_valid_table_overrides() { @@ -2008,13 +2271,27 @@ public void Passes_for_named_index_with_all_properties_not_mapped_to_any_table() } [ConditionalFact] - public void Detects_mix_of_index_property_mapped_and_not_mapped_to_any_table_unmapped_first() + public void Passes_for_mix_of_index_properties_declared_and_inherited_TPT() { var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity().ToTable((string)null); - modelBuilder.Entity().ToTable("Cats"); - modelBuilder.Entity().HasIndex(nameof(Animal.Name), nameof(Cat.Identity)); + modelBuilder.Entity().ToTable("Cats") + .HasIndex( + new[] { nameof(Cat.Identity), nameof(Animal.Name) }, + "IX_MixOfMappedAndUnmappedProperties"); + + Validate(modelBuilder); + } + + [ConditionalFact] + public void Detects_mix_of_index_property_mapped_and_not_mapped_to_any_table_mapped_first() + { + var modelBuilder = CreateConventionalModelBuilder(); + + modelBuilder.Entity(); + modelBuilder.Entity().ToTable((string)null) + .HasIndex(nameof(Animal.Name), nameof(Cat.Identity)); var definition = RelationalResources .LogUnnamedIndexPropertiesBothMappedAndNotMappedToTable( @@ -2023,19 +2300,18 @@ public void Detects_mix_of_index_property_mapped_and_not_mapped_to_any_table_unm definition.GenerateMessage( nameof(Cat), "{'Name', 'Identity'}", - "Name"), + "Identity"), modelBuilder, LogLevel.Error); } [ConditionalFact] - public void Detects_mix_of_index_property_mapped_and_not_mapped_to_any_table_mapped_first() + public void Detects_mix_of_index_property_mapped_and_not_mapped_to_any_table_unmapped_first() { var modelBuilder = CreateConventionalModelBuilder(); - modelBuilder.Entity().ToTable((string)null); - modelBuilder.Entity().ToTable("Cats"); - modelBuilder.Entity() + modelBuilder.Entity(); + modelBuilder.Entity().ToTable((string)null) .HasIndex( new[] { nameof(Cat.Identity), nameof(Animal.Name) }, "IX_MixOfMappedAndUnmappedProperties"); @@ -2048,7 +2324,7 @@ public void Detects_mix_of_index_property_mapped_and_not_mapped_to_any_table_map "IX_MixOfMappedAndUnmappedProperties", nameof(Cat), "{'Identity', 'Name'}", - "Name"), + "Identity"), modelBuilder, LogLevel.Error); } @@ -2059,8 +2335,8 @@ public void Passes_for_index_properties_mapped_to_same_table_in_TPT_hierarchy() var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity().ToTable("Animals"); - modelBuilder.Entity().ToTable("Cats"); - modelBuilder.Entity().HasIndex(nameof(Animal.Id), nameof(Cat.Identity)); + modelBuilder.Entity().ToTable("Cats") + .HasIndex(nameof(Animal.Id), nameof(Cat.Identity)); Validate(modelBuilder); diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index 3bc63e87639..3d95323f8cc 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -23,54 +23,74 @@ public void GetRelationalModel_throws_if_convention_has_not_run() [ConditionalTheory] [InlineData(true, Mapping.TPH)] [InlineData(true, Mapping.TPT)] + [InlineData(true, Mapping.TPC)] [InlineData(false, Mapping.TPH)] [InlineData(false, Mapping.TPT)] + [InlineData(false, Mapping.TPC)] public void Can_use_relational_model_with_tables(bool useExplicitMapping, Mapping mapping) { var model = CreateTestModel(mapToTables: useExplicitMapping, mapping: mapping); - Assert.Equal(9, model.Model.GetEntityTypes().Count()); - Assert.Equal(mapping == Mapping.TPH || !useExplicitMapping ? 3 : 5, model.Tables.Count()); + Assert.Equal(11, model.Model.GetEntityTypes().Count()); + Assert.Equal(mapping == Mapping.TPC + ? 5 + : mapping == Mapping.TPH + ? 3 + : 6, model.Tables.Count()); Assert.Empty(model.Views); Assert.True(model.Model.GetEntityTypes().All(et => !et.GetViewMappings().Any())); - AssertDefaultMappings(model); - AssertTables(model, useExplicitMapping ? mapping : Mapping.TPH); + AssertDefaultMappings(model, mapping); + AssertTables(model, mapping); } [ConditionalTheory] [InlineData(Mapping.TPH)] [InlineData(Mapping.TPT)] + [InlineData(Mapping.TPC)] public void Can_use_relational_model_with_views(Mapping mapping) { var model = CreateTestModel(mapToTables: false, mapToViews: true, mapping); - Assert.Equal(9, model.Model.GetEntityTypes().Count()); - Assert.Equal(mapping == Mapping.TPH ? 3 : 5, model.Views.Count()); + Assert.Equal(11, model.Model.GetEntityTypes().Count()); + Assert.Equal(mapping == Mapping.TPC + ? 5 + : mapping == Mapping.TPH + ? 3 + : 6, model.Views.Count()); Assert.Empty(model.Tables); Assert.True(model.Model.GetEntityTypes().All(et => !et.GetTableMappings().Any())); - AssertDefaultMappings(model); + AssertDefaultMappings(model, mapping); AssertViews(model, mapping); } [ConditionalTheory] [InlineData(Mapping.TPH)] [InlineData(Mapping.TPT)] + [InlineData(Mapping.TPC)] public void Can_use_relational_model_with_views_and_tables(Mapping mapping) { var model = CreateTestModel(mapToTables: true, mapToViews: true, mapping); - Assert.Equal(9, model.Model.GetEntityTypes().Count()); - Assert.Equal(mapping == Mapping.TPH ? 3 : 5, model.Tables.Count()); - Assert.Equal(mapping == Mapping.TPH ? 3 : 5, model.Views.Count()); - - AssertDefaultMappings(model); + Assert.Equal(11, model.Model.GetEntityTypes().Count()); + Assert.Equal(mapping == Mapping.TPC + ? 5 + : mapping == Mapping.TPH + ? 3 + : 6, model.Tables.Count()); + Assert.Equal(mapping == Mapping.TPC + ? 5 + : mapping == Mapping.TPH + ? 3 + : 6, model.Views.Count()); + + AssertDefaultMappings(model, mapping); AssertTables(model, mapping); AssertViews(model, mapping); } - private static void AssertDefaultMappings(IRelationalModel model) + private static void AssertDefaultMappings(IRelationalModel model, Mapping mapping) { var orderType = model.Model.FindEntityType(typeof(Order)); var orderMapping = orderType.GetDefaultMappings().Single(); @@ -94,14 +114,20 @@ private static void AssertDefaultMappings(IRelationalModel model) Assert.Equal("default_datetime_mapping", orderDateMapping.TypeMapping.StoreType); Assert.Same(orderMapping, orderDateMapping.TableMapping); + var abstractBaseType = model.Model.FindEntityType(typeof(AbstractBase)); + var abstractCustomerType = model.Model.FindEntityType(typeof(AbstractCustomer)); + var customerType = model.Model.FindEntityType(typeof(Customer)); + var specialCustomerType = model.Model.FindEntityType(typeof(SpecialCustomer)); + var extraSpecialCustomerType = model.Model.FindEntityType(typeof(ExtraSpecialCustomer)); var orderDetailsOwnership = orderType.FindNavigation(nameof(Order.Details)).ForeignKey; var orderDetailsType = orderDetailsOwnership.DeclaringEntityType; var orderDetailsTable = orderDetailsType.GetDefaultMappings().Single().Table; Assert.NotEqual(ordersTable, orderDetailsTable); Assert.Empty(ordersTable.GetReferencingRowInternalForeignKeys(orderType)); - - var orderDetailsDate = orderDetailsType.FindProperty(nameof(OrderDetails.OrderDate)); - Assert.Equal(new[] { orderDetailsDate }, orderDetailsTable.FindColumn("OrderDate").PropertyMappings.Select(m => m.Property)); + Assert.Equal( + RelationalStrings.TableNotMappedEntityType(nameof(SpecialCustomer), ordersTable.Name), + Assert.Throws( + () => ordersTable.IsOptional(specialCustomerType)).Message); var orderDateColumn = orderDateMapping.Column; Assert.Same(orderDateColumn, ordersTable.FindColumn("OrderDate")); @@ -112,35 +138,92 @@ private static void AssertDefaultMappings(IRelationalModel model) Assert.False(orderDateColumn.IsNullable); Assert.Same(ordersTable, orderDateColumn.Table); - var customerType = model.Model.FindEntityType(typeof(Customer)); - var customerTable = customerType.GetDefaultMappings().Single().Table; - Assert.Equal("Microsoft.EntityFrameworkCore.Metadata.RelationalModelTest+Customer", customerTable.Name); - Assert.Null(customerTable.Schema); + var orderDetailsDate = orderDetailsType.FindProperty(nameof(OrderDetails.OrderDate)); + Assert.Equal(new[] { orderDetailsDate }, orderDetailsTable.FindColumn("OrderDate").PropertyMappings.Select(m => m.Property)); - var specialCustomerType = model.Model.FindEntityType(typeof(SpecialCustomer)); - var customerPk = specialCustomerType.FindPrimaryKey(); + var customerTable = customerType.GetDefaultMappings().Last().Table; + Assert.False(customerTable.IsOptional(customerType)); + if (mapping == Mapping.TPC) + { + Assert.Equal( + RelationalStrings.TableNotMappedEntityType(nameof(SpecialCustomer), customerTable.Name), + Assert.Throws( + () => customerTable.IsOptional(specialCustomerType)).Message); + } + else + { + Assert.False(customerTable.IsOptional(specialCustomerType)); + Assert.False(customerTable.IsOptional(extraSpecialCustomerType)); + } + + if (mapping == Mapping.TPT) + { + Assert.Equal("Microsoft.EntityFrameworkCore.Metadata.RelationalModelTest+Customer", customerTable.Name); + Assert.Null(customerTable.Schema); + Assert.Equal(4, specialCustomerType.GetDefaultMappings().Count()); + Assert.True(specialCustomerType.GetDefaultMappings().First().IsSplitEntityTypePrincipal); + Assert.False(specialCustomerType.GetDefaultMappings().First().IncludesDerivedTypes); + Assert.True(specialCustomerType.GetDefaultMappings().Last().IsSplitEntityTypePrincipal); + Assert.True(specialCustomerType.GetDefaultMappings().Last().IncludesDerivedTypes); + + var specialCustomerTable = specialCustomerType.GetDefaultMappings().Last().Table; + Assert.Null(specialCustomerTable.Schema); + Assert.Equal(4, specialCustomerTable.Columns.Count()); + + Assert.True(specialCustomerTable.EntityTypeMappings.Single(m => m.EntityType == specialCustomerType).IsSharedTablePrincipal); + + var specialityColumn = specialCustomerTable.Columns.Single(c => c.Name == nameof(SpecialCustomer.Speciality)); + Assert.False(specialityColumn.IsNullable); - var specialCustomerDefaultMapping = specialCustomerType.GetDefaultMappings().Single(); - Assert.True(specialCustomerDefaultMapping.IsSplitEntityTypePrincipal); - Assert.True(specialCustomerDefaultMapping.IncludesDerivedTypes); + Assert.Null(customerType.FindDiscriminatorProperty()); + Assert.Null(customerType.GetDiscriminatorValue()); + Assert.Null(specialCustomerType.FindDiscriminatorProperty()); + Assert.Null(specialCustomerType.GetDiscriminatorValue()); + } + else + { + var specialCustomerTableMapping = specialCustomerType.GetDefaultMappings().Single(); + Assert.True(specialCustomerTableMapping.IsSplitEntityTypePrincipal); + var specialCustomerTable = specialCustomerTableMapping.Table; + var specialityColumn = specialCustomerTable.Columns.Single(c => c.Name == nameof(SpecialCustomer.Speciality)); + if (mapping == Mapping.TPH) + { + var baseTable = abstractBaseType.GetDefaultMappings().Single().Table; + Assert.Equal("Microsoft.EntityFrameworkCore.Metadata.RelationalModelTest+AbstractBase", baseTable.Name); + Assert.Equal(baseTable.Name, customerTable.Name); + Assert.Equal(baseTable.Schema, customerTable.Schema); + Assert.True(specialCustomerTableMapping.IncludesDerivedTypes); + Assert.Same(customerTable, specialCustomerTable); - var specialCustomerTable = specialCustomerDefaultMapping.Table; - Assert.Equal(customerTable, specialCustomerTable); + Assert.Equal(5, specialCustomerTable.EntityTypeMappings.Count()); + Assert.True(specialCustomerTable.EntityTypeMappings.All(t => t.IsSharedTablePrincipal)); - Assert.Equal(3, specialCustomerTable.EntityTypeMappings.Count()); - Assert.True(specialCustomerTable.EntityTypeMappings.First().IsSharedTablePrincipal); + Assert.Equal(10, specialCustomerTable.Columns.Count()); + + Assert.True(specialityColumn.IsNullable); + } + else + { + Assert.False(specialCustomerTableMapping.IncludesDerivedTypes); + Assert.NotSame(customerTable, specialCustomerTable); - Assert.Equal(specialCustomerType.FindDiscriminatorProperty() == null ? 8 : 9, specialCustomerTable.Columns.Count()); + Assert.True(customerTable.EntityTypeMappings.Single().IsSharedTablePrincipal); + Assert.Equal(5, customerTable.Columns.Count()); - var specialityColumn = specialCustomerTable.Columns.Single(c => c.Name == nameof(SpecialCustomer.Speciality)); - Assert.Equal(specialCustomerType.FindDiscriminatorProperty() != null, specialityColumn.IsNullable); + Assert.True(specialCustomerTable.EntityTypeMappings.Single().IsSharedTablePrincipal); + + Assert.Equal(9, specialCustomerTable.Columns.Count()); + + Assert.False(specialityColumn.IsNullable); + } + } } private static void AssertViews(IRelationalModel model, Mapping mapping) { var orderType = model.Model.FindEntityType(typeof(Order)); var orderMapping = orderType.GetViewMappings().Single(); - Assert.Same(orderType.GetViewMappings(), orderType.GetViewOrTableMappings()); + Assert.Equal(orderType.GetViewMappings(), orderType.GetViewOrTableMappings()); Assert.True(orderMapping.IncludesDerivedTypes); Assert.Equal( new[] { nameof(Order.Id), nameof(Order.AlternateId), nameof(Order.CustomerId), nameof(Order.OrderDate) }, @@ -180,49 +263,71 @@ private static void AssertViews(IRelationalModel model, Mapping mapping) Assert.Equal("default_datetime_mapping", orderDateMapping.TypeMapping.StoreType); Assert.Same(orderMapping, orderDateMapping.ViewMapping); + var abstractBaseType = model.Model.FindEntityType(typeof(AbstractBase)); + var abstractCustomerType = model.Model.FindEntityType(typeof(AbstractCustomer)); + var customerType = model.Model.FindEntityType(typeof(Customer)); + var specialCustomerType = model.Model.FindEntityType(typeof(SpecialCustomer)); + var extraSpecialCustomerType = model.Model.FindEntityType(typeof(ExtraSpecialCustomer)); var orderDetailsOwnership = orderType.FindNavigation(nameof(Order.Details)).ForeignKey; var orderDetailsType = orderDetailsOwnership.DeclaringEntityType; Assert.Same(ordersView, orderDetailsType.GetViewMappings().Single().View); Assert.Equal( ordersView.GetReferencingRowInternalForeignKeys(orderType), ordersView.GetRowInternalForeignKeys(orderDetailsType)); + Assert.Equal( + RelationalStrings.TableNotMappedEntityType(nameof(SpecialCustomer), ordersView.Name), + Assert.Throws( + () => ordersView.GetReferencingRowInternalForeignKeys(specialCustomerType)).Message); + Assert.Equal( + RelationalStrings.TableNotMappedEntityType(nameof(SpecialCustomer), ordersView.Name), + Assert.Throws( + () => ordersView.GetRowInternalForeignKeys(specialCustomerType)).Message); Assert.False(ordersView.IsOptional(orderType)); Assert.True(ordersView.IsOptional(orderDetailsType)); - - var orderDetailsDate = orderDetailsType.FindProperty(nameof(OrderDetails.OrderDate)); + Assert.Equal( + RelationalStrings.TableNotMappedEntityType(nameof(SpecialCustomer), ordersView.Name), + Assert.Throws( + () => ordersView.IsOptional(specialCustomerType)).Message); var orderDateColumn = orderDateMapping.Column; Assert.Same(orderDateColumn, ordersView.FindColumn("OrderDate")); Assert.Same(orderDateColumn, orderDate.FindColumn(StoreObjectIdentifier.View(ordersView.Name, ordersView.Schema))); Assert.Same(orderDateColumn, ordersView.FindColumn(orderDate)); + + var orderDetailsDate = orderDetailsType.FindProperty(nameof(OrderDetails.OrderDate)); Assert.Equal(new[] { orderDate, orderDetailsDate }, orderDateColumn.PropertyMappings.Select(m => m.Property)); Assert.Equal("OrderDate", orderDateColumn.Name); Assert.Equal("default_datetime_mapping", orderDateColumn.StoreType); Assert.False(orderDateColumn.IsNullable); Assert.Same(ordersView, orderDateColumn.Table); - var customerType = model.Model.FindEntityType(typeof(Customer)); - var customerView = customerType.GetViewMappings().Single().View; - Assert.Equal("CustomerView", customerView.Name); - Assert.Equal("viewSchema", customerView.Schema); - - var specialCustomerType = model.Model.FindEntityType(typeof(SpecialCustomer)); - var extraSpecialCustomerType = model.Model.FindEntityType(typeof(ExtraSpecialCustomer)); - var customerPk = specialCustomerType.FindPrimaryKey(); - + var customerView = customerType.GetViewMappings().Last().View; Assert.False(customerView.IsOptional(customerType)); - Assert.False(customerView.IsOptional(specialCustomerType)); - Assert.False(customerView.IsOptional(extraSpecialCustomerType)); + if (mapping == Mapping.TPC) + { + Assert.Equal( + RelationalStrings.TableNotMappedEntityType(nameof(SpecialCustomer), customerView.Name), + Assert.Throws( + () => customerView.IsOptional(specialCustomerType)).Message); + } + else + { + Assert.False(customerView.IsOptional(specialCustomerType)); + Assert.False(customerView.IsOptional(extraSpecialCustomerType)); + } - var mappedToTable = orderType.GetTableName() != null; + var baseTableName = mapping == Mapping.TPH + ? abstractBaseType.GetTableName() + : customerType.GetTableName(); + var mappedToTable = baseTableName != null; var ordersCustomerForeignKey = orderType.FindNavigation(nameof(Order.Customer)).ForeignKey; - Assert.Equal(mappedToTable - ? "FK_Order_Customer_CustomerId" + Assert.Equal(mappedToTable && mapping != Mapping.TPC + ? "FK_Order_" + baseTableName + "_CustomerId" : null, ordersCustomerForeignKey.GetConstraintName()); Assert.Null(ordersCustomerForeignKey.GetConstraintName( StoreObjectIdentifier.View(ordersView.Name, ordersView.Schema), StoreObjectIdentifier.View(customerView.Name, customerView.Schema))); - Assert.Equal(mappedToTable - ? "FK_Order_Customer_CustomerId" + Assert.Equal(mappedToTable && mapping != Mapping.TPC + ? "FK_Order_" + baseTableName + "_CustomerId" : null, ordersCustomerForeignKey.GetDefaultName()); Assert.Null(ordersCustomerForeignKey.GetDefaultName( StoreObjectIdentifier.View(ordersView.Name, ordersView.Schema), @@ -265,7 +370,9 @@ private static void AssertViews(IRelationalModel model, Mapping mapping) if (mapping == Mapping.TPT) { - Assert.Equal(2, specialCustomerType.GetViewMappings().Count()); + Assert.Equal("CustomerView", customerView.Name); + Assert.Equal("viewSchema", customerView.Schema); + Assert.Equal(3, specialCustomerType.GetViewMappings().Count()); Assert.True(specialCustomerType.GetViewMappings().First().IsSplitEntityTypePrincipal); Assert.False(specialCustomerType.GetViewMappings().First().IncludesDerivedTypes); Assert.True(specialCustomerType.GetViewMappings().Last().IsSplitEntityTypePrincipal); @@ -274,7 +381,7 @@ private static void AssertViews(IRelationalModel model, Mapping mapping) var specialCustomerView = specialCustomerType.GetViewMappings().Select(t => t.Table) .First(t => t.Name == "SpecialCustomerView"); Assert.Null(specialCustomerView.Schema); - Assert.Equal(5, specialCustomerView.Columns.Count()); + Assert.Equal(6, specialCustomerView.Columns.Count()); Assert.True(specialCustomerView.EntityTypeMappings.Single(m => m.EntityType == specialCustomerType).IsSharedTablePrincipal); @@ -290,17 +397,40 @@ private static void AssertViews(IRelationalModel model, Mapping mapping) { var specialCustomerViewMapping = specialCustomerType.GetViewMappings().Single(); Assert.True(specialCustomerViewMapping.IsSplitEntityTypePrincipal); - Assert.True(specialCustomerViewMapping.IncludesDerivedTypes); - var specialCustomerView = specialCustomerViewMapping.View; - Assert.Same(customerView, specialCustomerView); + var specialityColumn = specialCustomerView.Columns.Single(c => c.Name == nameof(SpecialCustomer.Speciality)); + if (mapping == Mapping.TPH) + { + var baseView = abstractBaseType.GetViewMappings().Single().Table; + Assert.Equal("BaseView", baseView.Name); + Assert.Equal(baseView.Name, abstractBaseType.GetViewName()); + Assert.Equal(baseView.Name, customerView.Name); + Assert.Equal(baseView.Schema, customerView.Schema); + Assert.True(specialCustomerViewMapping.IncludesDerivedTypes); + Assert.Same(customerView, specialCustomerView); + + Assert.Equal(6, specialCustomerView.EntityTypeMappings.Count()); + Assert.True(specialCustomerView.EntityTypeMappings.First().IsSharedTablePrincipal); + Assert.False(specialCustomerView.EntityTypeMappings.Last().IsSharedTablePrincipal); + + Assert.True(specialityColumn.IsNullable); + } + else + { + Assert.False(specialCustomerViewMapping.IncludesDerivedTypes); + Assert.NotSame(customerView, specialCustomerView); - Assert.Equal(4, specialCustomerView.EntityTypeMappings.Count()); - Assert.True(specialCustomerView.EntityTypeMappings.First().IsSharedTablePrincipal); - Assert.False(specialCustomerView.EntityTypeMappings.Last().IsSharedTablePrincipal); + Assert.True(customerView.EntityTypeMappings.Single().IsSharedTablePrincipal); + Assert.Equal(5, customerView.Columns.Count()); - var specialityColumn = specialCustomerView.Columns.Single(c => c.Name == nameof(SpecialCustomer.Speciality)); - Assert.True(specialityColumn.IsNullable); + Assert.Equal(2, specialCustomerView.EntityTypeMappings.Count()); + Assert.True(specialCustomerView.EntityTypeMappings.First().IsSharedTablePrincipal); + Assert.False(specialCustomerView.EntityTypeMappings.Last().IsSharedTablePrincipal); + + Assert.Equal(10, specialCustomerView.Columns.Count()); + + Assert.False(specialityColumn.IsNullable); + } } } @@ -405,17 +535,9 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) Assert.Equal("DateDetails", orderDateFkConstraint.PrincipalTable.Name); var orderCustomerFk = orderType.GetForeignKeys().Single(fk => fk.PrincipalEntityType.ClrType == typeof(Customer)); - var orderCustomerFkConstraint = orderCustomerFk.GetMappedConstraints().Single(); - - Assert.Equal("FK_Order_Customer_CustomerId", orderCustomerFkConstraint.Name); - Assert.Equal(nameof(Order.CustomerId), orderCustomerFkConstraint.Columns.Single().Name); - Assert.Equal(nameof(Customer.Id), orderCustomerFkConstraint.PrincipalColumns.Single().Name); - Assert.Same(ordersTable, orderCustomerFkConstraint.Table); - Assert.Equal("Customer", orderCustomerFkConstraint.PrincipalTable.Name); - Assert.Equal(ReferentialAction.Cascade, orderCustomerFkConstraint.OnDeleteAction); - Assert.Equal(orderCustomerFk, orderCustomerFkConstraint.MappedForeignKeys.Single()); - Assert.Equal(new[] { orderDateFkConstraint, orderCustomerFkConstraint }, ordersTable.ForeignKeyConstraints); + var abstractBaseType = model.Model.FindEntityType(typeof(AbstractBase)); + var abstractCustomerType = model.Model.FindEntityType(typeof(AbstractCustomer)); var customerType = model.Model.FindEntityType(typeof(Customer)); var specialCustomerType = model.Model.FindEntityType(typeof(SpecialCustomer)); var extraSpecialCustomerType = model.Model.FindEntityType(typeof(ExtraSpecialCustomer)); @@ -477,20 +599,7 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) Assert.Equal("FK_DateDetails", orderDateFkConstraint.Name); - var customerTable = customerType.GetTableMappings().Single().Table; - Assert.Equal("Customer", customerTable.Name); - - var ordersCustomerForeignKey = orderType.FindNavigation(nameof(Order.Customer)).ForeignKey; - Assert.Equal("FK_Order_Customer_CustomerId", ordersCustomerForeignKey.GetConstraintName()); - Assert.Equal("FK_Order_Customer_CustomerId", ordersCustomerForeignKey.GetConstraintName( - StoreObjectIdentifier.Table(ordersTable.Name, ordersTable.Schema), - StoreObjectIdentifier.Table(customerTable.Name, customerTable.Schema))); - Assert.Equal("FK_Order_Customer_CustomerId", ordersCustomerForeignKey.GetDefaultName()); - Assert.Equal("FK_Order_Customer_CustomerId", ordersCustomerForeignKey.GetDefaultName( - StoreObjectIdentifier.Table(ordersTable.Name, ordersTable.Schema), - StoreObjectIdentifier.Table(customerTable.Name, customerTable.Schema))); - - var ordersCustomerIndex = orderType.FindIndex(ordersCustomerForeignKey.Properties); + var ordersCustomerIndex = orderType.FindIndex(orderCustomerFk.Properties); Assert.Equal("IX_Order_CustomerId", ordersCustomerIndex.GetDatabaseName()); Assert.Equal("IX_Order_CustomerId", ordersCustomerIndex.GetDatabaseName( StoreObjectIdentifier.Table(ordersTable.Name, ordersTable.Schema))); @@ -515,23 +624,38 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) Assert.Equal("Speciality", specialityCK.GetDefaultName( StoreObjectIdentifier.Table(specialCustomerTable.Name, specialCustomerTable.Schema))); + var customerTable = customerType.GetTableMappings().Last().Table; Assert.False(customerTable.IsOptional(customerType)); - Assert.False(customerTable.IsOptional(specialCustomerType)); - Assert.False(customerTable.IsOptional(extraSpecialCustomerType)); + if (mapping == Mapping.TPC) + { + Assert.Equal( + RelationalStrings.TableNotMappedEntityType(nameof(SpecialCustomer), customerTable.Name), + Assert.Throws( + () => customerTable.IsOptional(specialCustomerType)).Message); + } + else + { + Assert.False(customerTable.IsOptional(specialCustomerType)); + Assert.False(customerTable.IsOptional(extraSpecialCustomerType)); + } var customerPk = specialCustomerType.FindPrimaryKey(); if (mapping == Mapping.TPT) { - Assert.Equal(2, specialCustomerType.GetTableMappings().Count()); + var baseTable = abstractBaseType.GetTableMappings().Single().Table; + Assert.Equal("AbstractBase", baseTable.Name); + Assert.Equal(nameof(Customer), customerTable.Name); + Assert.Null(abstractCustomerType.GetTableName()); + Assert.Equal(nameof(SpecialCustomer), specialCustomerType.GetTableName()); + Assert.Equal(3, specialCustomerType.GetTableMappings().Count()); Assert.True(specialCustomerType.GetTableMappings().First().IsSplitEntityTypePrincipal); Assert.False(specialCustomerType.GetTableMappings().First().IncludesDerivedTypes); Assert.True(specialCustomerType.GetTableMappings().Last().IsSplitEntityTypePrincipal); Assert.True(specialCustomerType.GetTableMappings().Last().IncludesDerivedTypes); Assert.Equal("SpecialCustomer", specialCustomerTable.Name); - Assert.Equal("SpecialSchema", specialCustomerTable.Schema); - Assert.Equal(5, specialCustomerTable.Columns.Count()); + Assert.Equal(6, specialCustomerTable.Columns.Count()); Assert.True( specialCustomerTable.EntityTypeMappings.Single(m => m.EntityType == specialCustomerType).IsSharedTablePrincipal); @@ -539,53 +663,80 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) var specialityColumn = specialCustomerTable.Columns.Single(c => c.Name == nameof(SpecialCustomer.Speciality)); Assert.False(specialityColumn.IsNullable); - var addressColumn = specialCustomerTable.Columns.Single( - c => c.Name == nameof(SpecialCustomer.Details) + "_" + nameof(CustomerDetails.Address)); + var addressColumn = specialCustomerTable.Columns.Single(c => + c.Name == nameof(SpecialCustomer.Details) + "_" + nameof(CustomerDetails.Address)); Assert.False(addressColumn.IsNullable); var specialityProperty = specialityColumn.PropertyMappings.First().Property; Assert.Equal( RelationalStrings.PropertyNotMappedToTable( nameof(SpecialCustomer.Speciality), nameof(SpecialCustomer), "Customer"), - Assert.Throws( - () => specialityProperty.IsColumnNullable(StoreObjectIdentifier.Table(customerTable.Name, customerTable.Schema))) + Assert.Throws(() => + specialityProperty.IsColumnNullable(StoreObjectIdentifier.Table(customerTable.Name, customerTable.Schema))) .Message); + var abstractStringColumn = specialCustomerTable.Columns.Single(c => c.Name == nameof(AbstractCustomer.AbstractString)); + Assert.False(specialityColumn.IsNullable); + Assert.Equal(2, specialityColumn.PropertyMappings.Count()); + var extraSpecialCustomerTable = extraSpecialCustomerType.GetTableMappings().Select(t => t.Table).First(t => t.Name == "ExtraSpecialCustomer"); Assert.Empty(customerTable.CheckConstraints); Assert.Same(specialityCK, specialCustomerTable.CheckConstraints.Single()); - Assert.Same(specialityCK, extraSpecialCustomerTable.CheckConstraints.Single()); + Assert.Empty(extraSpecialCustomerTable.CheckConstraints); - Assert.Equal(3, customerPk.GetMappedConstraints().Count()); + Assert.Equal(4, customerPk.GetMappedConstraints().Count()); var specialCustomerPkConstraint = specialCustomerTable.PrimaryKey; Assert.Equal("PK_SpecialCustomer", specialCustomerPkConstraint.Name); Assert.Same(specialCustomerPkConstraint.MappedKeys.First(), customerPk); var idProperty = customerPk.Properties.Single(); - Assert.Equal(6, idProperty.GetTableColumnMappings().Count()); - - Assert.Empty(customerTable.ForeignKeyConstraints); - - var specialCustomerUniqueConstraint = customerTable.UniqueConstraints.Single(c => !c.GetIsPrimaryKey()); - Assert.Equal("AK_Customer_SpecialityAk", specialCustomerUniqueConstraint.Name); + Assert.Equal(10, idProperty.GetTableColumnMappings().Count()); + + var customerFk = customerTable.ForeignKeyConstraints.Single(); + Assert.Equal("FK_Customer_AbstractBase_Id", customerFk.Name); + Assert.NotNull(customerFk.MappedForeignKeys.Single()); + Assert.Same(baseTable, customerFk.PrincipalTable); + + var orderCustomerFkConstraint = orderCustomerFk.GetMappedConstraints().Single(); + + Assert.Equal("FK_Order_Customer_CustomerId", orderCustomerFkConstraint.Name); + Assert.Equal(nameof(Order.CustomerId), orderCustomerFkConstraint.Columns.Single().Name); + Assert.Equal(nameof(Customer.Id), orderCustomerFkConstraint.PrincipalColumns.Single().Name); + Assert.Same(ordersTable, orderCustomerFkConstraint.Table); + Assert.Equal("Customer", orderCustomerFkConstraint.PrincipalTable.Name); + Assert.Equal(ReferentialAction.Cascade, orderCustomerFkConstraint.OnDeleteAction); + Assert.Equal(orderCustomerFk, orderCustomerFkConstraint.MappedForeignKeys.Single()); + Assert.Equal(new[] { orderDateFkConstraint, orderCustomerFkConstraint }, ordersTable.ForeignKeyConstraints); + + Assert.Equal(orderCustomerFkConstraint.Name, orderCustomerFk.GetConstraintName()); + Assert.Equal(orderCustomerFkConstraint.Name, orderCustomerFk.GetConstraintName( + StoreObjectIdentifier.Table(ordersTable.Name, ordersTable.Schema), + StoreObjectIdentifier.Table(customerTable.Name, customerTable.Schema))); + Assert.Equal(orderCustomerFkConstraint.Name, orderCustomerFk.GetDefaultName()); + Assert.Equal(orderCustomerFkConstraint.Name, orderCustomerFk.GetDefaultName( + StoreObjectIdentifier.Table(ordersTable.Name, ordersTable.Schema), + StoreObjectIdentifier.Table(customerTable.Name, customerTable.Schema))); + + var specialCustomerUniqueConstraint = baseTable.UniqueConstraints.Single(c => !c.GetIsPrimaryKey()); + Assert.Equal("AK_AbstractBase_SpecialityAk", specialCustomerUniqueConstraint.Name); Assert.NotNull(specialCustomerUniqueConstraint.MappedKeys.Single()); var foreignKeys = specialCustomerTable.ForeignKeyConstraints.ToArray(); Assert.Equal(3, foreignKeys.Length); - var specialCustomerTptFkConstraint = foreignKeys[0]; + var specialCustomerFkConstraint = foreignKeys[0]; + Assert.Equal("FK_SpecialCustomer_AbstractBase_RelatedCustomerSpeciality", specialCustomerFkConstraint.Name); + Assert.NotNull(specialCustomerFkConstraint.MappedForeignKeys.Single()); + Assert.Same(baseTable, specialCustomerFkConstraint.PrincipalTable); + + var specialCustomerTptFkConstraint = foreignKeys[1]; Assert.Equal("FK_SpecialCustomer_Customer_Id", specialCustomerTptFkConstraint.Name); Assert.NotNull(specialCustomerTptFkConstraint.MappedForeignKeys.Single()); Assert.Same(customerTable, specialCustomerTptFkConstraint.PrincipalTable); - var specialCustomerFkConstraint = foreignKeys[1]; - Assert.Equal("FK_SpecialCustomer_Customer_RelatedCustomerSpeciality", specialCustomerFkConstraint.Name); - Assert.NotNull(specialCustomerFkConstraint.MappedForeignKeys.Single()); - Assert.Same(customerTable, specialCustomerFkConstraint.PrincipalTable); - var anotherSpecialCustomerFkConstraint = foreignKeys[2]; Assert.Equal("FK_SpecialCustomer_SpecialCustomer_AnotherRelatedCustomerId", anotherSpecialCustomerFkConstraint.Name); Assert.NotNull(anotherSpecialCustomerFkConstraint.MappedForeignKeys.Single()); @@ -608,48 +759,125 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) { var specialCustomerTypeMapping = specialCustomerType.GetTableMappings().Single(); Assert.True(specialCustomerTypeMapping.IsSplitEntityTypePrincipal); - Assert.True(specialCustomerTypeMapping.IncludesDerivedTypes); - - Assert.Same(customerTable, specialCustomerTable); - - Assert.Equal(4, specialCustomerTable.EntityTypeMappings.Count()); - Assert.True(specialCustomerTable.EntityTypeMappings.First().IsSharedTablePrincipal); - Assert.False(specialCustomerTable.EntityTypeMappings.Last().IsSharedTablePrincipal); var specialityColumn = specialCustomerTable.Columns.Single(c => c.Name == nameof(SpecialCustomer.Speciality)); - Assert.True(specialityColumn.IsNullable); - var addressColumn = specialCustomerTable.Columns.Single(c => - c.Name == nameof(SpecialCustomer.Details) + "_" + nameof(CustomerDetails.Address)); - Assert.True(addressColumn.IsNullable); - - Assert.Same(specialityCK, specialCustomerTable.CheckConstraints.Single()); + c.Name == nameof(SpecialCustomer.Details) + "_" + nameof(CustomerDetails.Address)); var specialCustomerPkConstraint = specialCustomerTable.PrimaryKey; - Assert.Equal("PK_Customer", specialCustomerPkConstraint.Name); - Assert.Same(specialCustomerPkConstraint.MappedKeys.First(), customerPk); + var specialCustomerUniqueConstraint = specialCustomerTable.UniqueConstraints.Single(c => !c.GetIsPrimaryKey()); + var specialCustomerDbIndex = specialCustomerTable.Indexes.Last(); + var anotherSpecialCustomerDbIndex = specialCustomerTable.Indexes.First(); var idProperty = customerPk.Properties.Single(); - Assert.Equal(3, idProperty.GetTableColumnMappings().Count()); - var specialCustomerUniqueConstraint = specialCustomerTable.UniqueConstraints.Single(c => !c.GetIsPrimaryKey()); - Assert.Equal("AK_Customer_SpecialityAk", specialCustomerUniqueConstraint.Name); - Assert.NotNull(specialCustomerUniqueConstraint.MappedKeys.Single()); + if (mapping == Mapping.TPH) + { + var baseTable = abstractBaseType.GetTableMappings().Single().Table; + Assert.Equal("AbstractBase", baseTable.Name); + Assert.Equal(baseTable.Name, abstractBaseType.GetTableName()); + Assert.Equal(baseTable.Name, customerTable.Name); + Assert.Equal(baseTable.Name, abstractCustomerType.GetTableName()); + Assert.Equal(baseTable.Name, specialCustomerType.GetTableName()); + + Assert.True(specialCustomerTypeMapping.IncludesDerivedTypes); + Assert.Same(customerTable, specialCustomerTable); + + Assert.Equal(6, specialCustomerTable.EntityTypeMappings.Count()); + Assert.True(specialCustomerTable.EntityTypeMappings.First().IsSharedTablePrincipal); + Assert.False(specialCustomerTable.EntityTypeMappings.Last().IsSharedTablePrincipal); + + Assert.Equal(11, specialCustomerTable.Columns.Count()); + + Assert.True(specialityColumn.IsNullable); + Assert.True(addressColumn.IsNullable); + + var orderCustomerFkConstraint = orderCustomerFk.GetMappedConstraints().Single(); + + Assert.Equal("FK_Order_" + baseTable.Name + "_CustomerId", orderCustomerFkConstraint.Name); + Assert.Equal(nameof(Order.CustomerId), orderCustomerFkConstraint.Columns.Single().Name); + Assert.Equal(nameof(Customer.Id), orderCustomerFkConstraint.PrincipalColumns.Single().Name); + Assert.Same(ordersTable, orderCustomerFkConstraint.Table); + Assert.Equal(baseTable.Name, orderCustomerFkConstraint.PrincipalTable.Name); + Assert.Equal(ReferentialAction.Cascade, orderCustomerFkConstraint.OnDeleteAction); + Assert.Equal(orderCustomerFk, orderCustomerFkConstraint.MappedForeignKeys.Single()); + Assert.Equal(new[] { orderDateFkConstraint, orderCustomerFkConstraint }, ordersTable.ForeignKeyConstraints); + + Assert.Equal(orderCustomerFkConstraint.Name, orderCustomerFk.GetConstraintName()); + Assert.Equal(orderCustomerFkConstraint.Name, orderCustomerFk.GetConstraintName( + StoreObjectIdentifier.Table(ordersTable.Name, ordersTable.Schema), + StoreObjectIdentifier.Table(customerTable.Name, customerTable.Schema))); + Assert.Equal(orderCustomerFkConstraint.Name, orderCustomerFk.GetDefaultName()); + Assert.Equal(orderCustomerFkConstraint.Name, orderCustomerFk.GetDefaultName( + StoreObjectIdentifier.Table(ordersTable.Name, ordersTable.Schema), + StoreObjectIdentifier.Table(customerTable.Name, customerTable.Schema))); + + Assert.Equal("PK_" + baseTable.Name, specialCustomerPkConstraint.Name); + Assert.Equal("AK_AbstractBase_SpecialityAk", specialCustomerUniqueConstraint.Name); + + var specialCustomerFkConstraint = specialCustomerTable.ForeignKeyConstraints.Last(); + Assert.Equal("FK_AbstractBase_AbstractBase_RelatedCustomerSpeciality", specialCustomerFkConstraint.Name); + Assert.NotNull(specialCustomerFkConstraint.MappedForeignKeys.Single()); + + var anotherSpecialCustomerFkConstraint = specialCustomerTable.ForeignKeyConstraints.First(); + Assert.Equal("FK_AbstractBase_AbstractBase_AnotherRelatedCustomerId", anotherSpecialCustomerFkConstraint.Name); + Assert.NotNull(anotherSpecialCustomerFkConstraint.MappedForeignKeys.Single()); + + Assert.Equal("IX_AbstractBase_RelatedCustomerSpeciality", specialCustomerDbIndex.Name); + Assert.Equal("IX_AbstractBase_AnotherRelatedCustomerId", anotherSpecialCustomerDbIndex.Name); + + Assert.Equal(5, idProperty.GetTableColumnMappings().Count()); + } + else + { + Assert.Null(abstractBaseType.GetTableName()); + Assert.Equal(nameof(Customer), customerTable.Name); + Assert.Null(abstractCustomerType.GetTableName()); + Assert.Equal(nameof(SpecialCustomer), specialCustomerType.GetTableName()); - var specialCustomerFkConstraint = specialCustomerTable.ForeignKeyConstraints.Last(); - Assert.Equal("FK_Customer_Customer_RelatedCustomerSpeciality", specialCustomerFkConstraint.Name); - Assert.NotNull(specialCustomerFkConstraint.MappedForeignKeys.Single()); + Assert.False(specialCustomerTypeMapping.IncludesDerivedTypes); + Assert.NotSame(customerTable, specialCustomerTable); - var anotherSpecialCustomerFkConstraint = specialCustomerTable.ForeignKeyConstraints.First(); - Assert.Equal("FK_Customer_Customer_AnotherRelatedCustomerId", anotherSpecialCustomerFkConstraint.Name); - Assert.NotNull(anotherSpecialCustomerFkConstraint.MappedForeignKeys.Single()); + Assert.True(customerTable.EntityTypeMappings.Single().IsSharedTablePrincipal); + Assert.Equal(5, customerTable.Columns.Count()); - var specialCustomerDbIndex = specialCustomerTable.Indexes.Last(); - Assert.Equal("IX_Customer_RelatedCustomerSpeciality", specialCustomerDbIndex.Name); - Assert.NotNull(specialCustomerDbIndex.MappedIndexes.Single()); + Assert.Equal(2, specialCustomerTable.EntityTypeMappings.Count()); + Assert.True(specialCustomerTable.EntityTypeMappings.First().IsSharedTablePrincipal); + Assert.False(specialCustomerTable.EntityTypeMappings.Last().IsSharedTablePrincipal); + + Assert.Equal(10, specialCustomerTable.Columns.Count()); + + Assert.False(specialityColumn.IsNullable); + Assert.False(addressColumn.IsNullable); + + // Derived principal entity types are mapped to different tables, so the constraint is not enforceable + Assert.Empty(orderCustomerFk.GetMappedConstraints()); + + Assert.Null(orderCustomerFk.GetConstraintName()); + Assert.Null(orderCustomerFk.GetConstraintName( + StoreObjectIdentifier.Table(ordersTable.Name, ordersTable.Schema), + StoreObjectIdentifier.Table(customerTable.Name, customerTable.Schema))); + Assert.Null(orderCustomerFk.GetDefaultName()); + Assert.Null(orderCustomerFk.GetDefaultName( + StoreObjectIdentifier.Table(ordersTable.Name, ordersTable.Schema), + StoreObjectIdentifier.Table(customerTable.Name, customerTable.Schema))); + + Assert.Equal("PK_SpecialCustomer", specialCustomerPkConstraint.Name); + Assert.Equal("AK_SpecialCustomer_SpecialityAk", specialCustomerUniqueConstraint.Name); + + Assert.Empty(specialCustomerTable.ForeignKeyConstraints); + + Assert.Equal("IX_SpecialCustomer_RelatedCustomerSpeciality", specialCustomerDbIndex.Name); + Assert.Equal("IX_SpecialCustomer_AnotherRelatedCustomerId", anotherSpecialCustomerDbIndex.Name); + + Assert.Equal(3, idProperty.GetTableColumnMappings().Count()); + } + + Assert.Same(specialCustomerPkConstraint.MappedKeys.First(), customerPk); + + Assert.NotNull(specialCustomerUniqueConstraint.MappedKeys.Single()); - var anotherSpecialCustomerDbIndex = specialCustomerTable.Indexes.First(); - Assert.Equal("IX_Customer_AnotherRelatedCustomerId", anotherSpecialCustomerDbIndex.Name); + Assert.NotNull(specialCustomerDbIndex.MappedIndexes.Single()); Assert.NotNull(specialCustomerDbIndex.MappedIndexes.Single()); } } @@ -657,37 +885,79 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) private IRelationalModel CreateTestModel(bool mapToTables = false, bool mapToViews = false, Mapping mapping = Mapping.TPH) { var modelBuilder = CreateConventionModelBuilder(); - modelBuilder.Entity( + + modelBuilder.Entity( cb => { - if (mapToViews) + if (mapping != Mapping.TPC) { - cb.ToView("CustomerView", "viewSchema"); + if (mapToViews) + { + cb.ToView("BaseView", "viewSchema"); + } + + if (mapToTables) + { + cb.ToTable("AbstractBase"); + } } - if (mapToTables) + if (mapping == Mapping.TPC) { - cb.ToTable("Customer"); + cb.UseTpcMappingStrategy(); + } + else if (mapping == Mapping.TPT + && (!mapToTables && !mapToViews)) + { + cb.UseTptMappingStrategy(); } + // TODO: Don't map it on the base #19811 cb.Property("SpecialityAk"); }); - modelBuilder.Entity( + modelBuilder.Entity( cb => { - if (mapToViews - && mapping == Mapping.TPT) + if (mapping != Mapping.TPH) { - cb.ToView("SpecialCustomerView"); + if (mapToViews) + { + cb.ToView("CustomerView", "viewSchema"); + } + + if (mapToTables) + { + cb.ToTable("Customer"); + } } + }); - if (mapToTables - && mapping == Mapping.TPT) + modelBuilder.Entity( + cb => + { + if (mapping == Mapping.TPT) { - cb.ToTable("SpecialCustomer", "SpecialSchema"); + cb.ToView(null); + cb.ToTable((string)null); } + }); + modelBuilder.Entity( + cb => + { + if (mapping != Mapping.TPH) + { + if (mapToViews) + { + cb.ToView("SpecialCustomerView"); + } + + if (mapToTables) + { + cb.ToTable("SpecialCustomer", "SpecialSchema"); + } + } cb.HasCheckConstraint($"Speciality", $"[Speciality] IN ('Specialist', 'Generalist')"); cb.Property(s => s.Speciality).IsRequired(); @@ -706,16 +976,17 @@ private IRelationalModel CreateTestModel(bool mapToTables = false, bool mapToVie modelBuilder.Entity( cb => { - if (mapToViews - && mapping == Mapping.TPT) + if (mapping != Mapping.TPH) { - cb.ToView("ExtraSpecialCustomerView"); - } + if (mapToViews) + { + cb.ToView("ExtraSpecialCustomerView"); + } - if (mapToTables - && mapping == Mapping.TPT) - { - cb.ToTable("ExtraSpecialCustomer", "ExtraSpecialSchema"); + if (mapToTables) + { + cb.ToTable("ExtraSpecialCustomer", "ExtraSpecialSchema"); + } } }); @@ -1028,9 +1299,11 @@ protected virtual TestHelpers.TestModelBuilder CreateConventionModelBuilder() public enum Mapping { +#pragma warning disable SA1602 // Enumeration items should be documented TPH, TPT, TPC +#pragma warning restore SA1602 // Enumeration items should be documented } private enum MyEnum : ulong @@ -1040,9 +1313,13 @@ private enum MyEnum : ulong Tue } - private class Customer + private abstract class AbstractBase { public int Id { get; set; } + } + + private class Customer : AbstractBase + { public string Name { get; set; } public short SomeShort { get; set; } public MyEnum EnumValue { get; set; } @@ -1050,7 +1327,14 @@ private class Customer public IEnumerable Orders { get; set; } } - private class SpecialCustomer : Customer +#nullable enable + private abstract class AbstractCustomer : Customer + { + public string AbstractString { get; set; } = null!; + } +#nullable disable + + private class SpecialCustomer : AbstractCustomer { public string Speciality { get; set; } public string RelatedCustomerSpeciality { get; set; } diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs index 51ce5387750..1e600d513fe 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs @@ -109,6 +109,57 @@ public static ModelBuilderTest.TestPropertyBuilder IsFixedLength UseTpcMappingStrategy( + this ModelBuilderTest.TestEntityTypeBuilder builder) + where TEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.UseTpcMappingStrategy(); + break; + case IInfrastructure nongenericBuilder: + nongenericBuilder.Instance.UseTpcMappingStrategy(); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestEntityTypeBuilder UseTphMappingStrategy( + this ModelBuilderTest.TestEntityTypeBuilder builder) + where TEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.UseTphMappingStrategy(); + break; + case IInfrastructure nongenericBuilder: + nongenericBuilder.Instance.UseTphMappingStrategy(); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestEntityTypeBuilder UseTptMappingStrategy( + this ModelBuilderTest.TestEntityTypeBuilder builder) + where TEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.UseTptMappingStrategy(); + break; + case IInfrastructure nongenericBuilder: + nongenericBuilder.Instance.UseTptMappingStrategy(); + break; + } + + return builder; + } + public static ModelBuilderTest.TestEntityTypeBuilder ToTable( this ModelBuilderTest.TestEntityTypeBuilder builder, string? name) diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs index d0fe859cf83..a16912db9b5 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. - - // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ModelBuilding; @@ -244,7 +242,27 @@ public void Index_convention_sets_filter_for_unique_index_when_base_type_changed } [ConditionalFact] - public virtual void TPT_identifying_FK_are_created_only_on_declaring_type() + public virtual void Can_override_TPC_with_TPH() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity

(); + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity() + .UseTpcMappingStrategy() + .UseTphMappingStrategy(); + + var model = modelBuilder.FinalizeModel(); + + Assert.Equal("Discriminator", model.FindEntityType(typeof(PBase)).GetDiscriminatorPropertyName()); + Assert.Equal(nameof(PBase), model.FindEntityType(typeof(PBase)).GetDiscriminatorValue()); + Assert.Equal(nameof(P), model.FindEntityType(typeof(P)).GetDiscriminatorValue()); + Assert.Equal(nameof(Q), model.FindEntityType(typeof(Q)).GetDiscriminatorValue()); + } + + [ConditionalFact] + public virtual void TPT_identifying_FK_is_created_only_on_declaring_table() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity() @@ -273,11 +291,13 @@ public virtual void TPT_identifying_FK_are_created_only_on_declaring_type() var principalType = model.FindEntityType(typeof(BigMak)); Assert.Empty(principalType.GetForeignKeys()); Assert.Empty(principalType.GetIndexes()); + Assert.Null(principalType.FindDiscriminatorProperty()); var ingredientType = model.FindEntityType(typeof(Ingredient)); var bunType = model.FindEntityType(typeof(Bun)); Assert.Empty(bunType.GetIndexes()); + Assert.Null(bunType.FindDiscriminatorProperty()); var bunFk = bunType.GetDeclaredForeignKeys().Single(fk => !fk.IsBaseLinking()); Assert.Equal("FK_Buns_BigMak_Id", bunFk.GetConstraintName()); Assert.Equal( @@ -306,6 +326,64 @@ public virtual void TPT_identifying_FK_are_created_only_on_declaring_type() Assert.Single(sesameBunFk.GetMappedConstraints()); } + [ConditionalFact] + public virtual void TPC_identifying_FKs_are_created_on_all_tables() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .Ignore(b => b.Bun) + .Ignore(b => b.Pickles); + modelBuilder.Entity( + b => + { + b.ToTable("Ingredients"); + b.Ignore(i => i.BigMak); + b.UseTpcMappingStrategy(); + }); + modelBuilder.Entity( + b => + { + b.ToTable("Buns"); + b.HasOne(i => i.BigMak).WithOne().HasForeignKey(i => i.Id); + b.UseTpcMappingStrategy(); + }); + modelBuilder.Entity( + b => + { + b.ToTable("SesameBuns"); + }); + + var model = modelBuilder.FinalizeModel(); + + var principalType = model.FindEntityType(typeof(BigMak)); + Assert.Empty(principalType.GetForeignKeys()); + Assert.Empty(principalType.GetIndexes()); + Assert.Null(principalType.FindDiscriminatorProperty()); + + var ingredientType = model.FindEntityType(typeof(Ingredient)); + + var bunType = model.FindEntityType(typeof(Bun)); + Assert.Empty(bunType.GetIndexes()); + Assert.Null(bunType.FindDiscriminatorProperty()); + var bunFk = bunType.GetDeclaredForeignKeys().Single(); + Assert.Equal("FK_Buns_BigMak_Id", bunFk.GetConstraintName()); + Assert.Equal( + "FK_Buns_BigMak_Id", bunFk.GetConstraintName( + StoreObjectIdentifier.Create(bunType, StoreObjectType.Table).Value, + StoreObjectIdentifier.Create(principalType, StoreObjectType.Table).Value)); + Assert.Equal(2, bunFk.GetMappedConstraints().Count()); + + Assert.Empty(bunType.GetDeclaredForeignKeys().Where(fk => fk.IsBaseLinking())); + + var sesameBunType = model.FindEntityType(typeof(SesameBun)); + Assert.Empty(sesameBunType.GetIndexes()); + Assert.Empty(sesameBunType.GetDeclaredForeignKeys()); + Assert.Equal( + "FK_SesameBuns_BigMak_Id", bunFk.GetConstraintName( + StoreObjectIdentifier.Create(sesameBunType, StoreObjectType.Table).Value, + StoreObjectIdentifier.Create(principalType, StoreObjectType.Table).Value)); + } + [ConditionalFact] public virtual void TPT_index_can_use_inherited_properties() { From 302e60193baf9eb52ea85750450e7d325975a44e Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Tue, 15 Mar 2022 07:44:49 +0000 Subject: [PATCH 031/143] Merged PR 21768: [internal/release/3.1] Update dependencies from dnceng/internal/dotnet-extensions This pull request updates the following dependencies [marker]: <> (Begin:Coherency Updates) ## Coherency Updates The following updates ensure that dependencies with a *CoherentParentDependency* attribute were produced in a build used as input to the parent dependency's build. See [Dependency Description Format](https://github.com/dotnet/arcade/blob/master/Documentation/DependencyDescriptionFormat.md#dependency-description-overview) [DependencyUpdate]: <> (Begin) - **Coherency Updates**: - **Microsoft.NETCore.App.Runtime.win-x64**: from 3.1.24 to 3.1.24 (parent: Microsoft.Extensions.Logging) - **Microsoft.NETCore.App.Runtime.win-x64**: from 3.1.24 to 3.1.24 (parent: Microsoft.Extensions.Logging) - **Microsoft.NETCore.App.Runtime.win-x64**: from 3.1.24 to 3.1.24 (parent: Microsoft.Extensions.Logging) - **Microsoft.NETCore.App.Internal**: from 3.1.24-servicing.22164.7 to 3.1.24-servicing.22164.10 (parent: Microsoft.Extensions.Logging) - **Microsoft.NETCore.App.Runtime.win-x64**: from 3.1.24 to 3.1.24 (parent: Microsoft.Extensions.Logging) - **Microsoft.NETCore.App.Runtime.win-x64**: from 3.1.24 to 3.1.24 (parent: Microsoft.Extensions.Logging) - **Microsoft.NETCore.App.Runtime.win-x64**: from 3.1.24 to 3.1.24 (parent: Microsoft.Extensions.Logging) - **Microsoft.NETCore.App.Runtime.win-x64**: from 3.1.24 to 3.1.24 (parent: Microsoft.Extensions.Logging) - **Microsoft.NETCore.App.Runtime.win-x64**: from 3.1.24 to 3.1.24 (parent: Microsoft.Extensions.Logging) - **Microsoft.NETCore.App.Runtime.win-x64**: from 3.1.24 to 3.1.24 (parent: Microsoft.Extensions.Logging) - **Microsoft.NETCore.App.Runtime.win-x64**: from 3.1.24 to 3.1.24 (parent: Microsoft.Extensions.Logging) - **Microsoft.NETCore.App.Runtime.win-x64**: from 3.1.24 to 3.1.24 (parent: Microsoft.Extensions.Logging) - **Microsoft.NETCore.App.Runtime.win-x64**: from 3.1.24 to 3.1.24 (parent: Microsoft.Extensions.Logging) - **Microsoft.NETCore.App.Runtime.win-x64**: from 3.1.24 to 3.1.24 (parent: Microsoft.Extensions.Logging) [DependencyUpdate]: <> (End) [marker]: <> (End:Coherency Updates) [marker]: <> (Begin:866e0d8e-b549-4674-92fd-08d795576dcc) ## From https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - **Subscription**: 866e0d8e-b549-4674-92fd-08d795576dcc - **Build**: 20220314.6 - **Date Produced**: March 15, 2022 7:01:25 AM UTC - **Commit**: 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 - **Branch**: refs/heads/internal/release/3.1 [DependencyUpdate]: <> (Begin) - **Updates**: - **Microsoft.JSInterop**: [from 3.1.24 to 3.1.24][1] - **Microsoft.Extensions.ValueStopwatch.Sources**: [from 3.1.24-servicing.22164.4 to 3.1.24-servicing.22164.6][1] - **Microsoft.Extensions.FileProviders.Abstractions**: [from 3.1.24 to 3.1.24][1] - **Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions**: [from 3.1.24 to 3.1.24][1] - **Microsoft.Extensions.Diagnostics.HealthChecks**: [from 3.1.24 to 3.1.24][1] - *... --- NuGet.config | 7 +- eng/Version.Details.xml | 148 ++++++++++++++++++++-------------------- eng/Versions.props | 4 +- 3 files changed, 80 insertions(+), 79 deletions(-) diff --git a/NuGet.config b/NuGet.config index 7bf5731a8dc..8a2804dfd46 100644 --- a/NuGet.config +++ b/NuGet.config @@ -6,10 +6,10 @@ - + - + @@ -24,12 +24,13 @@ - + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index d9bf4e05eb5..a59f86e1ba5 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,21 +1,21 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx @@ -33,85 +33,85 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup 3acd9b0cd16596bad450c82be08780875a73c05c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 @@ -124,151 +124,151 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7df8ea91a1065e1b9627fcd1fbd6623d09ba2c00 + 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - b3f33a3d2630b35fe250cef81b5851fe9553a085 + 593c84e5585926c074a74992ccb1f2fb24709775 - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - b3f33a3d2630b35fe250cef81b5851fe9553a085 + 593c84e5585926c074a74992ccb1f2fb24709775 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx diff --git a/eng/Versions.props b/eng/Versions.props index 05665d223da..17a5d4b3722 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -37,7 +37,7 @@ 3.1.24 3.1.24 3.1.24 - 3.1.24-servicing.22164.4 + 3.1.24-servicing.22164.6 3.1.24 @@ -56,7 +56,7 @@ 3.1.6 3.1.0 3.1.24 - 3.1.24-servicing.22164.7 + 3.1.24-servicing.22164.10 2.1.0 From 65220333858e8b06b33d56f9c75a7386489c8f2b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Mar 2022 12:19:51 -0700 Subject: [PATCH 032/143] Bump Microsoft.Azure.Cosmos from 3.25.0 to 3.26.0 (#27641) Bumps [Microsoft.Azure.Cosmos](https://github.com/Azure/azure-cosmos-dotnet-v3) from 3.25.0 to 3.26.0. - [Release notes](https://github.com/Azure/azure-cosmos-dotnet-v3/releases) - [Changelog](https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/changelog.md) - [Commits](https://github.com/Azure/azure-cosmos-dotnet-v3/compare/3.25.0...3.26.0) --- updated-dependencies: - dependency-name: Microsoft.Azure.Cosmos dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/EFCore.Cosmos/EFCore.Cosmos.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EFCore.Cosmos/EFCore.Cosmos.csproj b/src/EFCore.Cosmos/EFCore.Cosmos.csproj index e5ffde7bb30..368f4b33652 100644 --- a/src/EFCore.Cosmos/EFCore.Cosmos.csproj +++ b/src/EFCore.Cosmos/EFCore.Cosmos.csproj @@ -45,7 +45,7 @@ - + From 8cd8c9a8ef75f3e3267bb36b2bd43ee8c6e1cc1a Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 15 Mar 2022 20:07:06 +0000 Subject: [PATCH 033/143] Update dependencies from https://github.com/dotnet/runtime build 20220313.2 (#27638) [main] Update dependencies from dotnet/runtime --- eng/Version.Details.xml | 44 ++++++++++++++++++++--------------------- eng/Versions.props | 22 ++++++++++----------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 368101f46fe..af35c69e92c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,49 +1,49 @@ - + https://github.com/dotnet/runtime - 7698a9a80c5f6270aa1122d79ce419c7b03f2498 + accf6fb6bf18f822fbbd227b1b4e31c65001a4ff - + https://github.com/dotnet/runtime - 7698a9a80c5f6270aa1122d79ce419c7b03f2498 + accf6fb6bf18f822fbbd227b1b4e31c65001a4ff - + https://github.com/dotnet/runtime - 7698a9a80c5f6270aa1122d79ce419c7b03f2498 + accf6fb6bf18f822fbbd227b1b4e31c65001a4ff - + https://github.com/dotnet/runtime - 7698a9a80c5f6270aa1122d79ce419c7b03f2498 + accf6fb6bf18f822fbbd227b1b4e31c65001a4ff - + https://github.com/dotnet/runtime - 7698a9a80c5f6270aa1122d79ce419c7b03f2498 + accf6fb6bf18f822fbbd227b1b4e31c65001a4ff - + https://github.com/dotnet/runtime - 7698a9a80c5f6270aa1122d79ce419c7b03f2498 + accf6fb6bf18f822fbbd227b1b4e31c65001a4ff - + https://github.com/dotnet/runtime - 7698a9a80c5f6270aa1122d79ce419c7b03f2498 + accf6fb6bf18f822fbbd227b1b4e31c65001a4ff - + https://github.com/dotnet/runtime - 7698a9a80c5f6270aa1122d79ce419c7b03f2498 + accf6fb6bf18f822fbbd227b1b4e31c65001a4ff - + https://github.com/dotnet/runtime - 7698a9a80c5f6270aa1122d79ce419c7b03f2498 + accf6fb6bf18f822fbbd227b1b4e31c65001a4ff - + https://github.com/dotnet/runtime - 7698a9a80c5f6270aa1122d79ce419c7b03f2498 + accf6fb6bf18f822fbbd227b1b4e31c65001a4ff - + https://github.com/dotnet/runtime - 7698a9a80c5f6270aa1122d79ce419c7b03f2498 + accf6fb6bf18f822fbbd227b1b4e31c65001a4ff diff --git a/eng/Versions.props b/eng/Versions.props index 1424e4e05ae..0b6a905eaa8 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -15,17 +15,17 @@ False - 7.0.0-preview.3.22157.1 - 7.0.0-preview.3.22157.1 - 7.0.0-preview.3.22157.1 - 7.0.0-preview.3.22157.1 - 7.0.0-preview.3.22157.1 - 7.0.0-preview.3.22157.1 - 7.0.0-preview.3.22157.1 - 7.0.0-preview.3.22157.1 - 7.0.0-preview.3.22157.1 - 7.0.0-preview.3.22157.1 - 7.0.0-preview.3.22157.1 + 7.0.0-preview.3.22163.2 + 7.0.0-preview.3.22163.2 + 7.0.0-preview.3.22163.2 + 7.0.0-preview.3.22163.2 + 7.0.0-preview.3.22163.2 + 7.0.0-preview.3.22163.2 + 7.0.0-preview.3.22163.2 + 7.0.0-preview.3.22163.2 + 7.0.0-preview.3.22163.2 + 7.0.0-preview.3.22163.2 + 7.0.0-preview.3.22163.2 4.0.1 From 4a81fd757a2683eab0197aea8f8c2efac5f1c97b Mon Sep 17 00:00:00 2001 From: Brice Lambson Date: Fri, 11 Mar 2022 12:02:29 -0800 Subject: [PATCH 034/143] Scaffolding: Use "CodeTemplates" directory Part of #4038 --- .../Scaffolding/TemplatedModelGenerator.cs | 2 +- .../TextTemplatingModelGeneratorTest.cs | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/EFCore.Design/Scaffolding/TemplatedModelGenerator.cs b/src/EFCore.Design/Scaffolding/TemplatedModelGenerator.cs index 688aabd9790..8ecd5a0ad91 100644 --- a/src/EFCore.Design/Scaffolding/TemplatedModelGenerator.cs +++ b/src/EFCore.Design/Scaffolding/TemplatedModelGenerator.cs @@ -21,7 +21,7 @@ protected TemplatedModelGenerator(ModelCodeGeneratorDependencies dependencies) /// Gets the subdirectory under the project to look for templates in. ///

/// The subdirectory. - protected static string TemplatesDirectory { get; } = Path.Combine("Templates", "EFCore"); + protected static string TemplatesDirectory { get; } = Path.Combine("CodeTemplates", "EFCore"); /// public override string? Language diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/TextTemplatingModelGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/TextTemplatingModelGeneratorTest.cs index c359d40c655..91c75986478 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/TextTemplatingModelGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/TextTemplatingModelGeneratorTest.cs @@ -16,7 +16,7 @@ public void HasTemplates_works_when_templates() { using var projectDir = new TempDirectory(); - var template = Path.Combine(projectDir, "Templates", "EFCore", "DbContext.t4"); + var template = Path.Combine(projectDir, "CodeTemplates", "EFCore", "DbContext.t4"); Directory.CreateDirectory(Path.GetDirectoryName(template)); File.Create(template).Close(); @@ -44,14 +44,14 @@ public void GenerateModel_uses_templates() { using var projectDir = new TempDirectory(); - var contextTemplate = Path.Combine(projectDir, "Templates", "EFCore", "DbContext.t4"); + var contextTemplate = Path.Combine(projectDir, "CodeTemplates", "EFCore", "DbContext.t4"); Directory.CreateDirectory(Path.GetDirectoryName(contextTemplate)); File.WriteAllText( contextTemplate, "My DbContext template"); File.WriteAllText( - Path.Combine(projectDir, "Templates", "EFCore", "EntityType.t4"), + Path.Combine(projectDir, "CodeTemplates", "EFCore", "EntityType.t4"), "My entity type template"); var generator = CreateGenerator(); @@ -81,7 +81,7 @@ public void GenerateModel_works_when_no_entity_type_template() { using var projectDir = new TempDirectory(); - var contextTemplate = Path.Combine(projectDir, "Templates", "EFCore", "DbContext.t4"); + var contextTemplate = Path.Combine(projectDir, "CodeTemplates", "EFCore", "DbContext.t4"); Directory.CreateDirectory(Path.GetDirectoryName(contextTemplate)); File.WriteAllText( contextTemplate, @@ -112,7 +112,7 @@ public void GenerateModel_sets_session_variables() { using var projectDir = new TempDirectory(); - var contextTemplate = Path.Combine(projectDir, "Templates", "EFCore", "DbContext.t4"); + var contextTemplate = Path.Combine(projectDir, "CodeTemplates", "EFCore", "DbContext.t4"); Directory.CreateDirectory(Path.GetDirectoryName(contextTemplate)); File.WriteAllText( contextTemplate, @@ -122,7 +122,7 @@ public void GenerateModel_sets_session_variables() ProjectDefaultNamespace: <#= Session[""ProjectDefaultNamespace""] #>"); File.WriteAllText( - Path.Combine(projectDir, "Templates", "EFCore", "EntityType.t4"), + Path.Combine(projectDir, "CodeTemplates", "EFCore", "EntityType.t4"), @"EntityType not null: <#= Session[""EntityType""] != null #> Options not null: <#= Session[""Options""] != null #> NamespaceHint: <#= Session[""NamespaceHint""] #> @@ -166,7 +166,7 @@ public void GenerateModel_defaults_to_model_namespace_when_no_context_namespace( { using var projectDir = new TempDirectory(); - var contextTemplate = Path.Combine(projectDir, "Templates", "EFCore", "DbContext.t4"); + var contextTemplate = Path.Combine(projectDir, "CodeTemplates", "EFCore", "DbContext.t4"); Directory.CreateDirectory(Path.GetDirectoryName(contextTemplate)); File.WriteAllText( contextTemplate, @@ -196,14 +196,14 @@ public void GenerateModel_uses_output_extension() { using var projectDir = new TempDirectory(); - var contextTemplate = Path.Combine(projectDir, "Templates", "EFCore", "DbContext.t4"); + var contextTemplate = Path.Combine(projectDir, "CodeTemplates", "EFCore", "DbContext.t4"); Directory.CreateDirectory(Path.GetDirectoryName(contextTemplate)); File.WriteAllText( contextTemplate, @"<#@ output extension="".vb"" #>"); File.WriteAllText( - Path.Combine(projectDir, "Templates", "EFCore", "EntityType.t4"), + Path.Combine(projectDir, "CodeTemplates", "EFCore", "EntityType.t4"), @"<#@ output extension="".fs"" #> My entity type template"); @@ -232,7 +232,7 @@ public void GenerateModel_warns_when_output_encoding() { using var projectDir = new TempDirectory(); - var contextTemplate = Path.Combine(projectDir, "Templates", "EFCore", "DbContext.t4"); + var contextTemplate = Path.Combine(projectDir, "CodeTemplates", "EFCore", "DbContext.t4"); Directory.CreateDirectory(Path.GetDirectoryName(contextTemplate)); File.WriteAllText( contextTemplate, @@ -266,7 +266,7 @@ public void GenerateModel_reports_errors() { using var projectDir = new TempDirectory(); - var contextTemplate = Path.Combine(projectDir, "Templates", "EFCore", "DbContext.t4"); + var contextTemplate = Path.Combine(projectDir, "CodeTemplates", "EFCore", "DbContext.t4"); Directory.CreateDirectory(Path.GetDirectoryName(contextTemplate)); File.WriteAllText( contextTemplate, From bbc4c7be695c2f0e091a3c37305bb67f876b74aa Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 15 Mar 2022 21:59:06 +0000 Subject: [PATCH 035/143] Update dependencies from https://github.com/dotnet/arcade build 20220307.6 (#27639) [main] Update dependencies from dotnet/arcade --- eng/Version.Details.xml | 8 ++++---- eng/common/templates/jobs/codeql-build.yml | 2 +- global.json | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index af35c69e92c..f728cf1405d 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -47,13 +47,13 @@
- + https://github.com/dotnet/arcade - 8ed47fcae6a5d2d40483ed81858f4ede8eab7ae2 + 81001b45bd54f9223905bf55f6ed0125273580fa - + https://github.com/dotnet/arcade - 8ed47fcae6a5d2d40483ed81858f4ede8eab7ae2 + 81001b45bd54f9223905bf55f6ed0125273580fa
diff --git a/eng/common/templates/jobs/codeql-build.yml b/eng/common/templates/jobs/codeql-build.yml index 54c393af440..f7dc5ea4aaa 100644 --- a/eng/common/templates/jobs/codeql-build.yml +++ b/eng/common/templates/jobs/codeql-build.yml @@ -21,7 +21,7 @@ jobs: # 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.110.1 + value: 0.109.0 - name: GuardianPackagesConfigFile value: $(Build.SourcesDirectory)\eng\common\sdl\packages.config - name: GuardianVersion diff --git a/global.json b/global.json index 6d648b191b0..9916afa3473 100644 --- a/global.json +++ b/global.json @@ -18,7 +18,7 @@ "rollForward": "latestMajor" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22154.3", - "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22154.3" + "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22157.6", + "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22157.6" } } From b906ca3dd58dc89952960261e7d6eea766d4a7b4 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 17 Mar 2022 13:17:53 +0200 Subject: [PATCH 036/143] Improve SQL Server insertion logic and other update pipeline optimizations (#27573) Also make RETURNING the default INSERT strategy for retrieving db-generated values (for other providers). Fixes #27372 Fixes #27503 --- .../Design/CSharpSnapshotGenerator.cs | 97 +++- .../Design/AnnotationCodeGenerator.cs | 1 + .../Design/IAnnotationCodeGenerator.cs | 13 + ...nalCSharpRuntimeAnnotationCodeGenerator.cs | 52 ++ .../RelationalEntityTypeBuilderExtensions.cs | 51 ++ .../RelationalEntityTypeExtensions.cs | 244 +++++++++ .../Extensions/RelationalModelExtensions.cs | 20 + .../RelationalModelValidator.cs | 96 +++- .../IConventionCheckConstraintBuilder.cs | 5 +- .../Builders/IConventionTriggerBuilder.cs | 34 ++ .../Metadata/Builders/TableBuilder.cs | 17 + .../Metadata/Builders/TriggerBuilder.cs | 107 ++++ .../Conventions/CheckConstraintConvention.cs | 4 +- .../RelationalConventionSetBuilder.cs | 3 + .../RelationalRuntimeModelConvention.cs | 35 ++ .../Conventions/SharedTableConvention.cs | 90 +++- .../Metadata/Conventions/TriggerConvention.cs | 162 ++++++ .../Metadata/IConventionTrigger.cs | 44 ++ .../Metadata/IMutableTrigger.cs | 28 + .../Metadata/IReadOnlyTrigger.cs | 70 +++ .../Metadata/IRelationalAnnotationProvider.cs | 8 + src/EFCore.Relational/Metadata/ITable.cs | 5 + src/EFCore.Relational/Metadata/ITrigger.cs | 58 +++ .../Metadata/Internal/CheckConstraint.cs | 7 +- .../Internal/InternalTriggerBuilder.cs | 184 +++++++ .../Metadata/Internal/RelationalModel.cs | 29 +- .../Metadata/Internal/Table.cs | 18 +- .../Metadata/Internal/Trigger.cs | 466 +++++++++++++++++ .../Metadata/RelationalAnnotationNames.cs | 5 + .../Metadata/RelationalAnnotationProvider.cs | 4 + .../Metadata/RuntimeTrigger.cs | 58 +++ .../Properties/RelationalStrings.Designer.cs | 24 + .../Properties/RelationalStrings.resx | 9 + .../AffectedCountModificationCommandBatch.cs | 56 +- .../Update/Internal/CommandBatchPreparer.cs | 2 +- .../Update/ReaderModificationCommandBatch.cs | 8 + .../Update/UpdateSqlGenerator.cs | 41 +- .../Properties/SqlServerStrings.Designer.cs | 12 + .../Properties/SqlServerStrings.resx | 6 + .../Internal/ISqlServerUpdateSqlGenerator.cs | 3 +- .../SqlServerModificationCommandBatch.cs | 95 +++- .../Internal/SqlServerUpdateSqlGenerator.cs | 353 ++++++++++--- .../Design/CSharpMigrationsGeneratorTest.cs | 6 +- .../Migrations/ModelSnapshotSqlServerTest.cs | 81 +++ .../CSharpRuntimeModelCodeGeneratorTest.cs | 181 +++++++ .../TransactionTestBase.cs | 4 +- .../Update/StoreValueGenerationFixtureBase.cs | 13 +- .../Update/StoreValueGenerationTestBase.cs | 6 +- .../Update/UpdateSqlGeneratorTestBase.cs | 253 +-------- .../RelationalModelValidatorTest.cs | 14 + .../RelationalBuilderExtensionsTest.cs | 154 ++++++ .../Metadata/RelationalModelTest.cs | 10 + .../Metadata/TriggerTest.cs | 102 ++++ .../RelationalApiConsistencyTest.cs | 11 +- .../Update/UpdateSqlGeneratorTest.cs | 45 ++ .../DataAnnotationSqlServerTest.cs | 35 +- .../Query/QueryBugsTest.cs | 15 +- .../SqlServerEndToEndTest.cs | 6 +- .../SqlServerQueryTriggersTest.cs | 2 +- .../SqlServerTriggersTest.cs | 6 + .../SqlServerValueGenerationScenariosTest.cs | 120 ++++- .../Update/SqlServerUpdateSqlGeneratorTest.cs | 83 +-- ...oreValueGenerationIdentitySqlServerTest.cs | 215 ++++---- ...eGenerationIdentityTriggerSqlServerTest.cs | 483 +++++++++++++++++ ...oreValueGenerationSequenceSqlServerTest.cs | 231 ++++---- ...eGenerationSequenceTriggerSqlServerTest.cs | 491 ++++++++++++++++++ ...eValueGenerationTriggerSqlServerFixture.cs | 38 ++ ...ValueGenerationTriggerSqlServerTestBase.cs | 110 ++++ .../UpdatesSqlServerTest.cs | 91 ++-- .../DataAnnotationSqliteTest.cs | 6 +- .../Update/SqliteUpdateSqlGeneratorTest.cs | 45 ++ .../Update/StoreValueGenerationSqliteTest.cs | 62 +-- 72 files changed, 4699 insertions(+), 844 deletions(-) create mode 100644 src/EFCore.Relational/Metadata/Builders/IConventionTriggerBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/TriggerBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Conventions/TriggerConvention.cs create mode 100644 src/EFCore.Relational/Metadata/IConventionTrigger.cs create mode 100644 src/EFCore.Relational/Metadata/IMutableTrigger.cs create mode 100644 src/EFCore.Relational/Metadata/IReadOnlyTrigger.cs create mode 100644 src/EFCore.Relational/Metadata/ITrigger.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/InternalTriggerBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/Trigger.cs create mode 100644 src/EFCore.Relational/Metadata/RuntimeTrigger.cs create mode 100644 test/EFCore.Relational.Tests/Metadata/TriggerTest.cs create mode 100644 test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationIdentityTriggerSqlServerTest.cs create mode 100644 test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationSequenceTriggerSqlServerTest.cs create mode 100644 test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationTriggerSqlServerFixture.cs create mode 100644 test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationTriggerSqlServerTestBase.cs diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 42c13f2477e..a7163f24519 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -715,14 +715,22 @@ protected virtual void GenerateEntityTypeAnnotations( } var isExcludedAnnotation = annotations.Find(RelationalAnnotationNames.IsTableExcludedFromMigrations); + var isExcludedFromMigrations = (isExcludedAnnotation?.Value as bool?) == true; + if (isExcludedAnnotation is not null) + { + annotations.Remove(isExcludedAnnotation.Name); + } + + var hasTriggers = entityType.GetTriggers().Any(); + var requiresTableBuilder = isExcludedFromMigrations || hasTriggers; + if (schema != null || (schemaAnnotation != null && tableName != null)) { stringBuilder .Append(", "); - if (schema == null - && ((bool?)isExcludedAnnotation?.Value) != true) + if (schema == null && !requiresTableBuilder) { stringBuilder.Append("(string)"); } @@ -730,15 +738,32 @@ protected virtual void GenerateEntityTypeAnnotations( stringBuilder.Append(Code.UnknownLiteral(schema)); } - if (isExcludedAnnotation != null) + if (requiresTableBuilder) { - if (((bool?)isExcludedAnnotation.Value) == true) + if (isExcludedFromMigrations && !hasTriggers) { - stringBuilder - .Append(", t => t.ExcludeFromMigrations()"); + stringBuilder.Append(", t => t.ExcludeFromMigrations()"); } + else + { + stringBuilder + .AppendLine(", t =>") + .AppendLine("{"); - annotations.Remove(isExcludedAnnotation.Name); + using (stringBuilder.Indent()) + { + if (isExcludedFromMigrations) + { + stringBuilder + .AppendLine("t.ExcludeFromMigrations();") + .AppendLine(); + } + + GenerateTriggers("t", entityType, stringBuilder); + } + + stringBuilder.Append("}"); + } } stringBuilder.AppendLine(");"); @@ -943,6 +968,64 @@ protected virtual void GenerateCheckConstraint( stringBuilder.AppendLine(");"); } + /// + /// Generates code for objects. + /// + /// The name of the table builder variable. + /// The entity type. + /// The builder code is added to. + protected virtual void GenerateTriggers( + string tableBuilderName, + IEntityType entityType, + IndentedStringBuilder stringBuilder) + { + foreach (var trigger in entityType.GetTriggers()) + { + GenerateTrigger(tableBuilderName, trigger, stringBuilder); + } + } + + /// + /// Generates code for an . + /// + /// The name of the table builder variable. + /// The check constraint. + /// The builder code is added to. + protected virtual void GenerateTrigger( + string tableBuilderName, + ITrigger trigger, + IndentedStringBuilder stringBuilder) + { + var triggerBuilderNameStringBuilder = new StringBuilder(); + triggerBuilderNameStringBuilder + .Append(tableBuilderName) + .Append(".HasTrigger(") + .Append(Code.Literal(trigger.ModelName)) + .Append(")"); + var triggerBuilderName = triggerBuilderNameStringBuilder.ToString(); + + stringBuilder.Append(triggerBuilderName); + + // Note that GenerateAnnotations below does the corresponding decrement + stringBuilder.IncrementIndent(); + + if (trigger.Name != null + && trigger.Name != (trigger.GetDefaultName() ?? trigger.ModelName)) + { + stringBuilder + .AppendLine() + .Append(".HasName(") + .Append(Code.Literal(trigger.Name)) + .Append(")"); + } + + var annotations = Dependencies.AnnotationCodeGenerator + .FilterIgnoredAnnotations(trigger.GetAnnotations()) + .ToDictionary(a => a.Name, a => a); + + GenerateAnnotations(triggerBuilderName, trigger, stringBuilder, annotations, inChainedCall: true); + } + /// /// Generates code for objects. /// diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index a091fb79db8..7b23e8c26d5 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -28,6 +28,7 @@ public class AnnotationCodeGenerator : IAnnotationCodeGenerator private static readonly ISet IgnoredRelationalAnnotations = new HashSet { RelationalAnnotationNames.CheckConstraints, + RelationalAnnotationNames.Triggers, RelationalAnnotationNames.Sequences, RelationalAnnotationNames.DbFunctions, RelationalAnnotationNames.RelationalOverrides diff --git a/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs index a301c9e4842..9a0b5bc88d5 100644 --- a/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs @@ -223,6 +223,17 @@ IReadOnlyList GenerateFluentApiCalls( IDictionary annotations) => Array.Empty(); + /// + /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls + /// and removes the annotations. + /// + /// The trigger to which the annotations are applied. + /// The set of annotations from which to generate fluent API calls. + IReadOnlyList GenerateFluentApiCalls( + ITrigger trigger, + IDictionary annotations) + => Array.Empty(); + /// /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls /// and removes the annotations. @@ -240,6 +251,8 @@ IReadOnlyList GenerateFluentApiCalls(IAnnotatable annota INavigation navigation => GenerateFluentApiCalls(navigation, annotations), ISkipNavigation skipNavigation => GenerateFluentApiCalls(skipNavigation, annotations), IIndex index => GenerateFluentApiCalls(index, annotations), + ITrigger trigger => GenerateFluentApiCalls(trigger, annotations), + _ => throw new ArgumentException(RelationalStrings.UnhandledAnnotatableType(annotatable.GetType())) }; diff --git a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs index 26e55fb3a33..06401509b79 100644 --- a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs @@ -325,11 +325,63 @@ public override void Generate(IEntityType entityType, CSharpRuntimeAnnotationCod annotations[RelationalAnnotationNames.ViewSchema] = entityType.GetViewSchema(); annotations[RelationalAnnotationNames.SqlQuery] = entityType.GetSqlQuery(); annotations[RelationalAnnotationNames.FunctionName] = entityType.GetFunctionName(); + + if (annotations.TryGetAndRemove( + RelationalAnnotationNames.Triggers, + out SortedDictionary triggers)) + { + parameters.Namespaces.Add(typeof(SortedDictionary<,>).Namespace!); + var triggersVariable = Dependencies.CSharpHelper.Identifier("triggers", parameters.ScopeVariables, capitalize: false); + parameters.MainBuilder + .Append("var ").Append(triggersVariable).AppendLine(" = new SortedDictionary();").AppendLine(); + + foreach (var (_, trigger) in triggers) + { + Create(trigger, triggersVariable, parameters); + } + + GenerateSimpleAnnotation(RelationalAnnotationNames.Triggers, triggersVariable, parameters); + } } base.Generate(entityType, parameters); } + private void Create(ITrigger trigger, string triggersVariable, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + var code = Dependencies.CSharpHelper; + var triggerVariable = code.Identifier(trigger.ModelName, parameters.ScopeVariables, capitalize: false); + var mainBuilder = parameters.MainBuilder; + mainBuilder + .Append("var ").Append(triggerVariable).AppendLine(" = new RuntimeTrigger(").IncrementIndent() + .Append(parameters.TargetName).AppendLine(",") + .Append(code.Literal(trigger.ModelName)).AppendLine(",") + .Append(code.UnknownLiteral(trigger.Name)).AppendLine(",") + .Append(code.Literal(trigger.TableName)).AppendLine(",") + .Append(code.UnknownLiteral(trigger.TableSchema)) + .AppendLine(");") + .DecrementIndent() + .AppendLine(); + + CreateAnnotations( + trigger, + Generate, + parameters with { TargetName = triggerVariable }); + + mainBuilder + .Append(triggersVariable).Append("[").Append(code.Literal(trigger.ModelName)).Append("] = ") + .Append(triggerVariable).AppendLine(";") + .AppendLine(); + } + + /// + /// Generates code to create the given annotations. + /// + /// The trigger to which the annotations are applied. + /// Additional parameters used during code generation. + public virtual void Generate(ITrigger trigger, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + => GenerateSimpleAnnotations(parameters); + /// /// Generates code to create the given annotations. /// diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs index dae7f560de1..2bcca5e7259 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs @@ -1846,4 +1846,55 @@ public static bool CanSetComment( RelationalAnnotationNames.Comment, comment, fromDataAnnotation); + + /// + /// Configures a database trigger when targeting a relational database. + /// + /// + /// See Database triggers for more information and examples. + /// + /// The entity type builder. + /// The name of the trigger. + /// The name of the table on which this trigger is defined. + /// The schema of the table on which this trigger is defined. + /// Indicates whether the configuration was specified using a data annotation. + /// The same builder instance if the check constraint was configured, otherwise. + public static IConventionTriggerBuilder? HasTrigger( + this IConventionEntityTypeBuilder entityTypeBuilder, + string name, + string? tableName, + string? tableSchema, + bool fromDataAnnotation = false) + => InternalTriggerBuilder.HasTrigger( + entityTypeBuilder.Metadata, + name, + tableName, + tableSchema, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + ?.Builder; + + /// + /// Returns a value indicating whether the trigger can be configured. + /// + /// + /// See Database triggers for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the trigger. + /// The name of the table on which this trigger is defined. + /// The schema of the table on which this trigger is defined. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + public static bool CanHaveTrigger( + this IConventionEntityTypeBuilder entityTypeBuilder, + string name, + string? tableName, + string? tableSchema, + bool fromDataAnnotation = false) + => InternalTriggerBuilder.CanHaveTrigger( + entityTypeBuilder.Metadata, + name, + tableName, + tableSchema, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs index 74fa5620438..5e368799897 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs @@ -20,6 +20,8 @@ public static class RelationalEntityTypeExtensions /// public static readonly string DefaultQueryNameBase = "MappedSqlQuery"; + #region Table mapping + /// /// Returns the name of the table to which the entity type is mapped /// or if not mapped to a table. @@ -273,6 +275,10 @@ public static IEnumerable GetTableMappings(this IEntityType entit RelationalAnnotationNames.TableMappings) ?? Enumerable.Empty(); + #endregion Table mapping + + #region View mapping + /// /// Returns the name of the view to which the entity type is mapped or if not mapped to a view. /// @@ -438,6 +444,10 @@ public static IEnumerable GetViewMappings(this IEntityType entityT RelationalAnnotationNames.ViewMappings) ?? Enumerable.Empty(); + #endregion View mapping + + #region SQL query mapping + /// /// Gets the default SQL query name that would be used for this entity type when mapped using /// . @@ -503,6 +513,10 @@ public static IEnumerable GetSqlQueryMappings(this IEntityType RelationalAnnotationNames.SqlQueryMappings) ?? Enumerable.Empty(); + #endregion SQL query mapping + + #region Function mapping + /// /// Returns the name of the function to which the entity type is mapped or if not mapped to a function. /// @@ -559,6 +573,10 @@ public static IEnumerable GetFunctionMappings(this IEntityType RelationalAnnotationNames.FunctionMappings) ?? Enumerable.Empty(); + #endregion Function mapping + + #region Check constraint + /// /// Finds an with the given name. /// @@ -760,6 +778,10 @@ public static IEnumerable GetDeclaredCheckConstraint public static IEnumerable GetDeclaredCheckConstraints(this IEntityType entityType) => CheckConstraint.GetDeclaredCheckConstraints(entityType).Cast(); + #endregion Check constraint + + #region Comment + /// /// Returns the comment for the table this entity is mapped to. /// @@ -804,6 +826,8 @@ public static void SetComment(this IMutableEntityType entityType, string? commen => entityType.FindAnnotation(RelationalAnnotationNames.Comment) ?.GetConfigurationSource(); + #endregion Comment + /// /// Gets the foreign keys for the given entity type that point to other entity types /// sharing the same table-like store object. @@ -900,6 +924,8 @@ public static IEnumerable FindRowInternalForeignKeys( // ReSharper disable once RedundantCast => ((IReadOnlyEntityType)entityType).FindRowInternalForeignKeys(storeObject).Cast(); + #region IsTableExcludedFromMigrations + /// /// Gets a value indicating whether the associated table is ignored by Migrations. /// @@ -987,6 +1013,10 @@ public static void SetIsTableExcludedFromMigrations(this IMutableEntityType enti return null; } + #endregion IsTableExcludedFromMigrations + + #region Mapping strategy + /// /// Sets the mapping strategy for the derived types. /// @@ -1019,4 +1049,218 @@ public static void SetMappingStrategy(this IMutableEntityType entityType, string this IConventionEntityType entityType) => entityType.FindAnnotation(RelationalAnnotationNames.MappingStrategy) ?.GetConfigurationSource(); + + #endregion Mapping strategy + + #region Trigger + + /// + /// Finds a trigger with the given name. + /// + /// The entity type to find the sequence on. + /// The trigger name. + /// The trigger or if no trigger with the given name was found. + public static IReadOnlyTrigger? FindTrigger(this IReadOnlyEntityType entityType, string name) + => Trigger.FindTrigger(entityType, Check.NotEmpty(name, nameof(name))); + + /// + /// Finds a trigger with the given name. + /// + /// The entity type to find the sequence on. + /// The trigger name. + /// The trigger or if no trigger with the given name was found. + public static IMutableTrigger? FindTrigger(this IMutableEntityType entityType, string name) + => (IMutableTrigger?)((IReadOnlyEntityType)entityType).FindTrigger(name); + + /// + /// Finds a trigger with the given name. + /// + /// The entity type to find the sequence on. + /// The trigger name. + /// The trigger or if no trigger with the given name was found. + public static IConventionTrigger? FindTrigger(this IConventionEntityType entityType, string name) + => (IConventionTrigger?)((IReadOnlyEntityType)entityType).FindTrigger(name); + + /// + /// Finds a trigger with the given name. + /// + /// The entity type to find the sequence on. + /// The trigger name. + /// The trigger or if no trigger with the given name was found. + public static ITrigger? FindTrigger(this IEntityType entityType, string name) + => (ITrigger?)((IReadOnlyEntityType)entityType).FindTrigger(name); + + /// + /// Creates a new trigger with the given name on entity type. Throws an exception if a trigger with the same name exists on the same + /// entity type. + /// + /// The entity type to add the trigger to. + /// The trigger name. + /// The trigger. + public static IMutableTrigger AddTrigger(this IMutableEntityType entityType, string name) + { + Check.NotEmpty(name, nameof(name)); + + return new Trigger(entityType, name, tableName: null, tableSchema: null, ConfigurationSource.Explicit); + } + + /// + /// Creates a new trigger with the given name on entity type. Throws an exception if a trigger with the same name exists on the same + /// entity type. + /// + /// The entity type to add the trigger to. + /// The trigger name. + /// The name of the table on which this trigger is defined. + /// The schema of the table on which this trigger is defined. + /// The trigger. + public static IMutableTrigger AddTrigger(this IMutableEntityType entityType, string name, string tableName, string? tableSchema = null) + { + Check.NotEmpty(name, nameof(name)); + + return new Trigger(entityType, name, tableName, tableSchema, ConfigurationSource.Explicit); + } + + /// + /// Creates a new trigger with the given name on entity type. Throws an exception if a trigger with the same name exists on the same + /// entity type. + /// + /// The entityType to add the trigger to. + /// The trigger name. + /// Indicates whether the configuration was specified using a data annotation. + /// The trigger. + public static IConventionTrigger AddTrigger( + this IConventionEntityType entityType, + string name, + bool fromDataAnnotation = false) + { + Check.NotEmpty(name, nameof(name)); + + return new Trigger( + (IMutableEntityType)entityType, name, tableName: null, tableSchema: null, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + } + + /// + /// Creates a new trigger with the given name on entity type. Throws an exception if a trigger with the same name exists on the same + /// entity type. + /// + /// The entityType to add the trigger to. + /// The trigger name. + /// The name of the table on which this trigger is defined. + /// The schema of the table on which this trigger is defined. + /// Indicates whether the configuration was specified using a data annotation. + /// The trigger. + public static IConventionTrigger AddTrigger( + this IConventionEntityType entityType, + string name, + string tableName, + string? tableSchema, + bool fromDataAnnotation = false) + { + Check.NotEmpty(name, nameof(name)); + + return new Trigger( + (IMutableEntityType)entityType, name, tableName, tableSchema, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + } + + /// + /// Removes the with the given name. + /// + /// The entityType to find the trigger in. + /// The trigger name. + /// + /// The removed or if no trigger with the given name was found. + /// + public static IMutableTrigger? RemoveTrigger(this IMutableEntityType entityType, string name) + => Trigger.RemoveTrigger(entityType, name); + + /// + /// Removes the with the given name. + /// + /// The entityType to find the trigger in. + /// The trigger name. + /// + /// The removed or if no trigger with the given name was found. + /// + public static IConventionTrigger? RemoveTrigger(this IConventionEntityType entityType, string name) + => Trigger.RemoveTrigger((IMutableEntityType)entityType, name); + + /// + /// Returns all triggers on the entity type. + /// + /// The entity type to get the triggers on. + public static IEnumerable GetTriggers(this IReadOnlyEntityType entityType) + => Trigger.GetTriggers(entityType); + + /// + /// Returns all triggers on the entity type. + /// + /// The entity type to get the triggers on. + public static IEnumerable GetTriggers(this IMutableEntityType entityType) + => Trigger.GetTriggers(entityType).Cast(); + + /// + /// Returns all triggers on the entity type. + /// + /// The entity type to get the triggers on. + public static IEnumerable GetTriggers(this IConventionEntityType entityType) + => Trigger.GetTriggers(entityType).Cast(); + + /// + /// Returns all triggers on the entity type. + /// + /// The entity type to get the triggers on. + public static IEnumerable GetTriggers(this IEntityType entityType) + => Trigger.GetTriggers(entityType).Cast(); + + /// + /// Returns all triggers on the entity type. + /// + /// The entity type to get the triggers on. + /// + /// This method does not return triggers declared on base types. + /// It is useful when iterating over all entity types to avoid processing the same trigger more than once. + /// Use to also return triggers declared on base types. + /// + public static IEnumerable GetDeclaredTriggers(this IReadOnlyEntityType entityType) + => Trigger.GetDeclaredTriggers(entityType); + + /// + /// Returns all triggers on the entity type. + /// + /// The entity type to get the triggers on. + /// + /// This method does not return triggers declared on base types. + /// It is useful when iterating over all entity types to avoid processing the same trigger more than once. + /// Use to also return triggers declared on base types. + /// + public static IEnumerable GetDeclaredTriggers(this IMutableEntityType entityType) + => Trigger.GetDeclaredTriggers(entityType).Cast(); + + /// + /// Returns all triggers on the entity type. + /// + /// The entity type to get the triggers on. + /// + /// This method does not return triggers declared on base types. + /// It is useful when iterating over all entity types to avoid processing the same trigger more than once. + /// Use to also return triggers declared on base types. + /// + public static IEnumerable GetDeclaredTriggers(this IConventionEntityType entityType) + => Trigger.GetDeclaredTriggers(entityType).Cast(); + + /// + /// Returns all triggers on the entity type. + /// + /// The entity type to get the triggers on. + /// + /// This method does not return triggers declared on base types. + /// It is useful when iterating over all entity types to avoid processing the same trigger more than once. + /// Use to also return triggers declared on base types. + /// + public static IEnumerable GetDeclaredTriggers(this IEntityType entityType) + => Trigger.GetDeclaredTriggers(entityType).Cast(); + + #endregion Trigger } diff --git a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs index 2d53ff66db6..d95a4b7a096 100644 --- a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore; /// public static class RelationalModelExtensions { + #region Default schema + /// /// Returns the default schema to use for the model, or if none has been set. /// @@ -58,6 +60,8 @@ public static void SetDefaultSchema(this IMutableModel model, string? value) public static ConfigurationSource? GetDefaultSchemaConfigurationSource(this IConventionModel model) => model.FindAnnotation(RelationalAnnotationNames.DefaultSchema)?.GetConfigurationSource(); + #endregion Default schema + /// /// Returns the database model. /// @@ -74,6 +78,8 @@ public static IRelationalModel GetRelationalModel(this IModel model) return databaseModel; } + #region Max identifier length + /// /// Returns the maximum length allowed for store identifiers. /// @@ -112,6 +118,10 @@ public static void SetMaxIdentifierLength(this IMutableModel model, int? length) public static ConfigurationSource? GetMaxIdentifierLengthConfigurationSource(this IConventionModel model) => model.FindAnnotation(RelationalAnnotationNames.MaxIdentifierLength)?.GetConfigurationSource(); + #endregion Max identifier length + + #region Sequence + /// /// Finds a sequence with the given name. /// @@ -269,6 +279,10 @@ public static IEnumerable GetSequences(this IConventionMode public static IEnumerable GetSequences(this IReadOnlyModel model) => Sequence.GetSequences(model); + #endregion Sequence + + #region DbFunction + /// /// Finds a function that is mapped to the method represented by the given . /// @@ -467,6 +481,10 @@ public static IEnumerable GetDbFunctions(this IConvention public static IEnumerable GetDbFunctions(this IModel model) => DbFunction.GetDbFunctions(model); + #endregion DbFunction + + #region Collation + /// /// Returns the database collation. /// @@ -509,4 +527,6 @@ public static void SetCollation(this IMutableModel model, string? value) /// The configuration source for the collation. public static ConfigurationSource? GetCollationConfigurationSource(this IConventionModel model) => model.FindAnnotation(RelationalAnnotationNames.Collation)?.GetConfigurationSource(); + + #endregion Collation } diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index fc68d71a418..b1066abdcfe 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Infrastructure; @@ -56,6 +57,7 @@ public override void Validate(IModel model, IDiagnosticsLogger @@ -291,6 +293,7 @@ protected virtual void ValidateSharedTableCompatibility( ValidateSharedForeignKeysCompatibility(mappedTypes, table, logger); ValidateSharedIndexesCompatibility(mappedTypes, table, logger); ValidateSharedCheckConstraintCompatibility(mappedTypes, table, logger); + ValidateSharedTriggerCompatibility(mappedTypes, table, logger); // Validate optional dependents if (mappedTypes.Count == 1) @@ -1191,7 +1194,7 @@ protected virtual void ValidateCompatible( => key.AreCompatible(duplicateKey, storeObject, shouldThrow: true); /// - /// Validates the compatibility of check constraint in a given shared table. + /// Validates the compatibility of check constraints in a given shared table. /// /// The mapped entity types. /// The identifier of the store object. @@ -1223,8 +1226,8 @@ protected virtual void ValidateSharedCheckConstraintCompatibility( /// /// Validates the compatibility of two check constraints with the same name. /// - /// An check constraints. - /// Another check constraints. + /// A check constraint. + /// Another check constraint. /// The name of the check constraint. /// The identifier of the store object. /// The logger to use. @@ -1236,6 +1239,53 @@ protected virtual void ValidateCompatible( IDiagnosticsLogger logger) => CheckConstraint.AreCompatible(checkConstraint, duplicateCheckConstraint, storeObject, shouldThrow: true); + /// + /// Validates the compatibility of triggers in a given shared table. + /// + /// The mapped entity types. + /// The identifier of the store object. + /// The logger to use. + protected virtual void ValidateSharedTriggerCompatibility( + IReadOnlyList mappedTypes, + in StoreObjectIdentifier storeObject, + IDiagnosticsLogger logger) + { + var triggerMappings = new Dictionary(); + foreach (var trigger in mappedTypes.SelectMany(et => et.GetDeclaredTriggers())) + { + var triggerName = trigger.GetName(storeObject); + if (triggerName == null) + { + continue; + } + + if (!triggerMappings.TryGetValue(triggerName, out var duplicateTrigger)) + { + triggerMappings[triggerName] = trigger; + continue; + } + + ValidateCompatible(trigger, duplicateTrigger, triggerName, storeObject, logger); + } + } + + /// + /// Validates the compatibility of two trigger with the same name. + /// + /// A trigger. + /// Another trigger. + /// The name of the trigger. + /// The identifier of the store object. + /// The logger to use. + protected virtual void ValidateCompatible( + ITrigger trigger, + ITrigger duplicateTrigger, + string indexName, + in StoreObjectIdentifier storeObject, + IDiagnosticsLogger logger) + { + } + /// /// Validates the mapping/configuration of inheritance in the model. /// @@ -1478,8 +1528,7 @@ protected virtual void ValidatePropertyOverrides( } /// - /// Validates that the properties of any one index are - /// all mapped to columns on at least one common table. + /// Validates that the properties of any one index are all mapped to columns on at least one common table. /// /// The model to validate. /// The logger to use. @@ -1581,6 +1630,43 @@ protected virtual void ValidateIndexProperties( } } + /// + /// Validates that the triggers are unambiguously mapped to exactly one table. + /// + /// The model to validate. + /// The logger to use. + protected virtual void ValidateTriggers( + IModel model, + IDiagnosticsLogger logger) + { + foreach (var entityType in model.GetEntityTypes()) + { + var tableName = entityType.GetTableName(); + var tableSchema = entityType.GetSchema(); + + foreach (var trigger in entityType.GetDeclaredTriggers()) + { + if (tableName is null) + { + throw new InvalidOperationException( + RelationalStrings.TriggerOnUnmappedEntityType(trigger.ModelName, entityType.DisplayName())); + } + + if ((trigger.TableName != tableName) + || (trigger.TableSchema is not null && trigger.TableSchema != tableSchema)) + { + throw new InvalidOperationException( + RelationalStrings.TriggerWithMismatchedTable( + trigger.ModelName, + (trigger.TableName!, trigger.TableSchema).FormatTable(), + entityType.DisplayName(), + entityType.GetSchemaQualifiedTableName()) + ); + } + } + } + } + /// /// Throws an with a message containing provider-specific information, when /// available, indicating possible reasons why the property cannot be mapped. diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionCheckConstraintBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionCheckConstraintBuilder.cs index be8d2be4c08..a1240ea6ddc 100644 --- a/src/EFCore.Relational/Metadata/Builders/IConventionCheckConstraintBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/IConventionCheckConstraintBuilder.cs @@ -22,10 +22,7 @@ public interface IConventionCheckConstraintBuilder : IConventionAnnotatableBuild /// /// The database name of the check constraint. /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// + /// The same builder instance if the configuration was applied, otherwise. IConventionCheckConstraintBuilder? HasName(string? name, bool fromDataAnnotation = false); /// diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionTriggerBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionTriggerBuilder.cs new file mode 100644 index 00000000000..e53400cc700 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/IConventionTriggerBuilder.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides an API point for provider-specific extensions for configuring a . +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IConventionTriggerBuilder : IConventionAnnotatableBuilder +{ + /// + /// The trigger being configured. + /// + new IConventionTrigger Metadata { get; } + + /// + /// Sets the database name of the trigger. + /// + /// The database name of the trigger. + /// Indicates whether the configuration was specified using a data annotation. + /// The same builder instance if the configuration was applied, otherwise. + IConventionTriggerBuilder? HasName(string? name, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given name can be set for the trigger. + /// + /// The database name of the trigger. + /// Indicates whether the configuration was specified using a data annotation. + /// if the database name can be set for the trigger. + bool CanSetName(string? name, bool fromDataAnnotation = false); +} diff --git a/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs b/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs index 8a74222b55d..9dd2053f25b 100644 --- a/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; +using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -60,6 +61,22 @@ public virtual TableBuilder ExcludeFromMigrations(bool excluded = true) return this; } + /// + /// Configures a database trigger on the table. + /// + /// The name of the trigger. + /// A builder that can be used to configure the database trigger. + /// + /// See Database triggers for more information and examples. + /// + public virtual TriggerBuilder HasTrigger(string name) + => new((Trigger)InternalTriggerBuilder.HasTrigger( + (IConventionEntityType)Metadata, + name, + Name, + Schema, + ConfigurationSource.Explicit)!); + #region Hidden System.Object members /// diff --git a/src/EFCore.Relational/Metadata/Builders/TriggerBuilder.cs b/src/EFCore.Relational/Metadata/Builders/TriggerBuilder.cs new file mode 100644 index 00000000000..de61d080c62 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/TriggerBuilder.cs @@ -0,0 +1,107 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides an API point for provider-specific extensions for configuring a . +/// +/// +/// See Database triggers for more information and examples. +/// +public class TriggerBuilder : IInfrastructure +{ + /// + /// Creates a new builder for the given . + /// + /// The to configure. + public TriggerBuilder(IMutableTrigger trigger) + => Builder = ((Trigger)trigger).Builder; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual InternalTriggerBuilder Builder { [DebuggerStepThrough] get; } + + /// + IConventionTriggerBuilder IInfrastructure.Instance + { + [DebuggerStepThrough] + get => Builder; + } + + /// + /// The trigger being configured. + /// + public virtual IMutableTrigger Metadata + => Builder.Metadata; + + /// + /// Sets the database name of the trigger. + /// + /// + /// See Database triggers for more information and examples. + /// + /// The database name of the trigger. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual TriggerBuilder HasName(string name) + { + Builder.HasName(name, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Adds or updates an annotation on the trigger. If an annotation with the key specified in + /// already exists, its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual TriggerBuilder HasAnnotation(string annotation, object? value) + { + Check.NotEmpty(annotation, nameof(annotation)); + + Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit); + + return this; + } + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectEqualsIsObjectEquals + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore.Relational/Metadata/Conventions/CheckConstraintConvention.cs b/src/EFCore.Relational/Metadata/Conventions/CheckConstraintConvention.cs index 37160c2c9c0..6c05c901dcc 100644 --- a/src/EFCore.Relational/Metadata/Conventions/CheckConstraintConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/CheckConstraintConvention.cs @@ -6,8 +6,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// -/// A convention that ensures that the check constraints on the derived types are compatible with -/// the check constraints on the base type. And also ensures that the declaring type is current. +/// A convention that ensures that the check constraints on the derived types are compatible with the check constraints on the base +/// type. And also ensures that the declaring type is current. /// /// /// See Model building conventions for more information and examples. diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs index 39b9570d35c..3937f1281e1 100644 --- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs +++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs @@ -65,18 +65,21 @@ public override ConventionSet CreateConventionSet() conventionSet.PropertyAddedConventions.Add(relationalCommentAttributeConvention); var checkConstraintConvention = new CheckConstraintConvention(Dependencies, RelationalDependencies); + var triggerConvention = new TriggerConvention(Dependencies, RelationalDependencies); var tableNameFromDbSetConvention = new TableNameFromDbSetConvention(Dependencies, RelationalDependencies); conventionSet.EntityTypeAddedConventions.Add(new RelationalTableAttributeConvention(Dependencies, RelationalDependencies)); conventionSet.EntityTypeAddedConventions.Add( new RelationalTableCommentAttributeConvention(Dependencies, RelationalDependencies)); conventionSet.EntityTypeAddedConventions.Add(tableNameFromDbSetConvention); conventionSet.EntityTypeAddedConventions.Add(checkConstraintConvention); + conventionSet.EntityTypeAddedConventions.Add(triggerConvention); ValueGenerationConvention valueGenerationConvention = new RelationalValueGenerationConvention(Dependencies, RelationalDependencies); ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, valueGenerationConvention); conventionSet.EntityTypeBaseTypeChangedConventions.Add(tableNameFromDbSetConvention); conventionSet.EntityTypeBaseTypeChangedConventions.Add(checkConstraintConvention); + conventionSet.EntityTypeBaseTypeChangedConventions.Add(triggerConvention); ReplaceConvention(conventionSet.ForeignKeyPropertiesChangedConventions, valueGenerationConvention); diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs index 06f382ebf18..4fde4f93489 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs @@ -137,6 +137,23 @@ protected override void ProcessEntityTypeAnnotations( annotations[RelationalAnnotationNames.ViewSchema] = entityType.GetViewSchema(); annotations[RelationalAnnotationNames.SqlQuery] = entityType.GetSqlQuery(); annotations[RelationalAnnotationNames.FunctionName] = entityType.GetFunctionName(); + + if (annotations.TryGetValue(RelationalAnnotationNames.Triggers, out var triggers)) + { + var runtimeTriggers = new SortedDictionary(StringComparer.Ordinal); + foreach (var (key, trigger) in (SortedDictionary)triggers!) + { + var runtimeTrigger = Create(trigger, runtimeEntityType); + runtimeTriggers[key] = runtimeTrigger; + + CreateAnnotations( + trigger, runtimeTrigger, + static (convention, annotations, source, target, runtime) + => convention.ProcessTriggerAnnotations(annotations, source, target, runtime)); + } + + annotations[RelationalAnnotationNames.Triggers] = runtimeTriggers; + } } } @@ -367,4 +384,22 @@ protected override void ProcessForeignKeyAnnotations( annotations.Remove(RelationalAnnotationNames.ForeignKeyMappings); } } + + private static RuntimeTrigger Create(ITrigger trigger, RuntimeEntityType runtimeEntityType) + => new(runtimeEntityType, trigger.ModelName, trigger.Name, trigger.TableName, trigger.TableSchema); + + /// + /// Updates the function annotations that will be set on the read-only object. + /// + /// The annotations to be processed. + /// The source trigger. + /// The target trigger that will contain the annotations. + /// Indicates whether the given annotations are runtime annotations. + protected virtual void ProcessTriggerAnnotations( + Dictionary annotations, + ITrigger trigger, + RuntimeTrigger runtimeTrigger, + bool runtime) + { + } } diff --git a/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs b/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs index cbcb433d86c..653887063b7 100644 --- a/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs @@ -51,6 +51,7 @@ public virtual void ProcessModelFinalizing( var foreignKeys = new Dictionary(); var indexes = new Dictionary(); var checkConstraints = new Dictionary<(string, string?), IConventionCheckConstraint>(); + var triggers = new Dictionary(); foreach (var ((tableName, schema), conventionEntityTypes) in tables) { columns.Clear(); @@ -67,6 +68,11 @@ public virtual void ProcessModelFinalizing( checkConstraints.Clear(); } + if (!TriggersUniqueAcrossTables) + { + triggers.Clear(); + } + var storeObject = StoreObjectIdentifier.Table(tableName, schema); foreach (var entityType in conventionEntityTypes) { @@ -75,22 +81,29 @@ public virtual void ProcessModelFinalizing( TryUniquifyForeignKeyNames(entityType, foreignKeys, storeObject, maxLength); TryUniquifyIndexNames(entityType, indexes, storeObject, maxLength); TryUniquifyCheckConstraintNames(entityType, checkConstraints, storeObject, maxLength); + TryUniquifyTriggerNames(entityType, triggers, storeObject, maxLength); } } } /// - /// Gets a value indicating whether the index names should be unique across tables. + /// Gets a value indicating whether index names should be unique across tables. /// protected virtual bool IndexesUniqueAcrossTables => true; /// - /// Gets a value indicating whether the index names should be unique across tables. + /// Gets a value indicating whether check constraint names should be unique across tables. /// protected virtual bool CheckConstraintsUniqueAcrossTables => true; + /// + /// Gets a value indicating whether trigger names should be unique across tables. + /// + protected virtual bool TriggersUniqueAcrossTables + => true; + private static void TryUniquifyTableNames( IConventionModel model, Dictionary<(string Name, string? Schema), List> tables, @@ -545,8 +558,7 @@ private void TryUniquifyCheckConstraintNames( continue; } - var newOtherConstraintName = TryUniquify( - otherCheckConstraint, constraintName, storeObject.Schema, checkConstraints, maxLength); + var newOtherConstraintName = TryUniquify(otherCheckConstraint, constraintName, storeObject.Schema, checkConstraints, maxLength); if (newOtherConstraintName != null) { checkConstraints[(constraintName, storeObject.Schema)] = checkConstraint; @@ -584,4 +596,74 @@ protected virtual bool AreCompatible( return null; } + + private void TryUniquifyTriggerNames( + IConventionEntityType entityType, + Dictionary triggers, + in StoreObjectIdentifier storeObject, + int maxLength) + { + foreach (var trigger in entityType.GetDeclaredTriggers()) + { + var triggerName = trigger.GetName(storeObject); + if (triggerName == null) + { + continue; + } + + if (!triggers.TryGetValue(triggerName, out var otherTrigger)) + { + triggers[triggerName] = trigger; + continue; + } + + if (AreCompatible(trigger, otherTrigger, storeObject)) + { + continue; + } + + var newTriggerName = TryUniquify(trigger, triggerName, triggers, maxLength); + if (newTriggerName != null) + { + triggers[newTriggerName] = trigger; + continue; + } + + var newOtherTrigger = TryUniquify(otherTrigger, triggerName, triggers, maxLength); + if (newOtherTrigger != null) + { + triggers[triggerName] = trigger; + triggers[newOtherTrigger] = otherTrigger; + } + } + } + + /// + /// Gets a value indicating whether two triggers with the same name are compatible. + /// + /// A trigger. + /// Another trigger. + /// The identifier of the store object. + /// if compatible + protected virtual bool AreCompatible( + IReadOnlyTrigger trigger, + IReadOnlyTrigger duplicateTrigger, + in StoreObjectIdentifier storeObject) + => true; + + private static string? TryUniquify( + IConventionTrigger trigger, + string triggerName, + Dictionary triggers, + int maxLength) + { + if (trigger.Builder.CanSetName(null)) + { + triggerName = Uniquifier.Uniquify(triggerName, triggers, n => n, maxLength); + trigger.Builder.HasName(triggerName); + return triggerName; + } + + return null; + } } diff --git a/src/EFCore.Relational/Metadata/Conventions/TriggerConvention.cs b/src/EFCore.Relational/Metadata/Conventions/TriggerConvention.cs new file mode 100644 index 00000000000..01cd41333e7 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Conventions/TriggerConvention.cs @@ -0,0 +1,162 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// A convention that ensures that the triggers on the derived types are compatible with the triggers on the base type. +/// And also ensures that the declaring type is current. +/// +/// +/// See Model building conventions for more information and examples. +/// +public class TriggerConvention : IEntityTypeBaseTypeChangedConvention, IEntityTypeAddedConvention +{ + /// + /// Creates a new instance of . + /// + /// Parameter object containing dependencies for this convention. + /// Parameter object containing relational dependencies for this convention. + public TriggerConvention( + ProviderConventionSetBuilderDependencies dependencies, + RelationalConventionSetBuilderDependencies relationalDependencies) + { + Dependencies = dependencies; + RelationalDependencies = relationalDependencies; + } + + /// + /// Dependencies for this service. + /// + protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } + + /// + /// Relational provider-specific dependencies for this service. + /// + protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; } + + /// + public virtual void ProcessEntityTypeAdded( + IConventionEntityTypeBuilder entityTypeBuilder, + IConventionContext context) + { + var entityType = entityTypeBuilder.Metadata; + if (!entityType.HasSharedClrType) + { + return; + } + + List? triggersToReattach = null; + foreach (var trigger in entityType.GetTriggers()) + { + if (trigger.EntityType == entityType) + { + continue; + } + + triggersToReattach ??= new List(); + + triggersToReattach.Add(trigger); + } + + if (triggersToReattach == null) + { + return; + } + + foreach (var trigger in triggersToReattach) + { + var removedTrigger = entityType.RemoveTrigger(trigger.ModelName); + if (removedTrigger != null) + { + Trigger.MergeInto(entityType, removedTrigger); + } + } + } + + /// + public virtual void ProcessEntityTypeBaseTypeChanged( + IConventionEntityTypeBuilder entityTypeBuilder, + IConventionEntityType? newBaseType, + IConventionEntityType? oldBaseType, + IConventionContext context) + { + var entityType = entityTypeBuilder.Metadata; + if (newBaseType != null) + { + var configurationSource = entityType.GetBaseTypeConfigurationSource(); + var baseTriggers = newBaseType.GetTriggers().ToDictionary(c => c.ModelName); + List? triggersToBeDetached = null; + List? triggersToBeRemoved = null; + foreach (var trigger in entityType.GetDerivedTypesInclusive().SelectMany(et => et.GetDeclaredTriggers())) + { + if (baseTriggers.TryGetValue(trigger.ModelName, out var baseTrigger) + && baseTrigger.GetConfigurationSource().Overrides(trigger.GetConfigurationSource()) + && !AreCompatible(trigger, baseTrigger)) + { + if (baseTrigger.GetConfigurationSource() == ConfigurationSource.Explicit + && configurationSource == ConfigurationSource.Explicit + && trigger.GetConfigurationSource() == ConfigurationSource.Explicit) + { + throw new InvalidOperationException( + RelationalStrings.DuplicateTrigger( + trigger.ModelName, + trigger.EntityType.DisplayName(), + baseTrigger.EntityType.DisplayName())); + } + + triggersToBeRemoved ??= new List(); + + triggersToBeRemoved.Add(trigger); + continue; + } + + if (baseTrigger != null) + { + triggersToBeDetached ??= new List(); + + triggersToBeDetached.Add(trigger); + } + } + + if (triggersToBeRemoved != null) + { + foreach (var checkConstraintToBeRemoved in triggersToBeRemoved) + { + checkConstraintToBeRemoved.EntityType.RemoveTrigger(checkConstraintToBeRemoved.ModelName); + } + } + + if (triggersToBeDetached != null) + { + foreach (var triggerToBeDetached in triggersToBeDetached) + { + var baseTrigger = baseTriggers[triggerToBeDetached.ModelName]; + Trigger.MergeInto(triggerToBeDetached, baseTrigger); + + triggerToBeDetached.EntityType.RemoveTrigger(triggerToBeDetached.ModelName); + } + } + } + } + + private static bool AreCompatible(IConventionTrigger checkConstraint, IConventionTrigger baseTrigger) + { + var baseTable = StoreObjectIdentifier.Create(baseTrigger.EntityType, StoreObjectType.Table); + if (baseTable == null) + { + return true; + } + + if (checkConstraint.GetName(baseTable.Value) != baseTrigger.GetName(baseTable.Value) + && checkConstraint.GetNameConfigurationSource() is ConfigurationSource nameConfigurationSource + && !nameConfigurationSource.OverridesStrictly(baseTrigger.GetNameConfigurationSource())) + { + return false; + } + + return true; + } +} diff --git a/src/EFCore.Relational/Metadata/IConventionTrigger.cs b/src/EFCore.Relational/Metadata/IConventionTrigger.cs new file mode 100644 index 00000000000..b0ced885611 --- /dev/null +++ b/src/EFCore.Relational/Metadata/IConventionTrigger.cs @@ -0,0 +1,44 @@ +// 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; + +/// +/// Represents a database sequence in the model. +/// +/// +/// See Database triggers for more information and examples. +/// +public interface IConventionTrigger : IReadOnlyTrigger, IConventionAnnotatable +{ + /// + /// Gets the builder that can be used to configure this trigger. + /// + /// If the trigger has been removed from the model. + new IConventionTriggerBuilder Builder { get; } + + /// + /// Gets the on which this trigger is defined. + /// + new IConventionEntityType EntityType { get; } + + /// + /// Gets the configuration source for this trigger. + /// + /// The configuration source for this trigger. + ConfigurationSource GetConfigurationSource(); + + /// + /// Sets the name of the trigger in the database. + /// + /// The name of the trigger in the database. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + string? SetName(string? name, bool fromDataAnnotation = false); + + /// + /// Gets the configuration source for the database name. + /// + /// The configuration source for the database name. + ConfigurationSource? GetNameConfigurationSource(); +} diff --git a/src/EFCore.Relational/Metadata/IMutableTrigger.cs b/src/EFCore.Relational/Metadata/IMutableTrigger.cs new file mode 100644 index 00000000000..037aec2bd8a --- /dev/null +++ b/src/EFCore.Relational/Metadata/IMutableTrigger.cs @@ -0,0 +1,28 @@ +// 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; + +/// +/// Represents a database trigger on a table. +/// +/// +/// +/// Since triggers features vary across databases, this is mainly an extension point for providers to add their own annotations. +/// +/// +/// See Database triggers for more information and examples. +/// +/// +public interface IMutableTrigger : IReadOnlyTrigger, IMutableAnnotatable +{ + /// + /// Gets the on which this trigger is defined. + /// + new IMutableEntityType EntityType { get; } + + /// + /// Gets or sets the name of the trigger in the database. + /// + new string? Name { get; set; } +} diff --git a/src/EFCore.Relational/Metadata/IReadOnlyTrigger.cs b/src/EFCore.Relational/Metadata/IReadOnlyTrigger.cs new file mode 100644 index 00000000000..9b27689154f --- /dev/null +++ b/src/EFCore.Relational/Metadata/IReadOnlyTrigger.cs @@ -0,0 +1,70 @@ +// 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; + +/// +/// Represents a database trigger on a table. +/// +/// +/// +/// Since triggers features vary across databases, this is mainly an extension point for providers to add their own annotations. +/// +/// +/// See Database triggers for more information and examples. +/// +/// +public interface IReadOnlyTrigger : IReadOnlyAnnotatable +{ + /// + /// Gets the name of the trigger in the model. + /// + string ModelName { get; } + + /// + /// Gets the database name of the trigger. + /// + string? Name { get; } + + /// + /// Gets the name of the table on which this trigger is defined. + /// + string TableName { get; } + + /// + /// Gets the schema of the table on which this trigger is defined. + /// + string? TableSchema { get; } + + /// + /// Returns the default database name that would be used for this trigger. + /// + /// The default name that would be used for this trigger. + string? GetDefaultName() + { + var table = StoreObjectIdentifier.Create(EntityType, StoreObjectType.Table); + return !table.HasValue ? null : GetDefaultName(table.Value); + } + + /// + /// Gets the database name of the trigger. + /// + /// The identifier of the store object. + /// The database name of the trigger for the given store object. + string? GetName(in StoreObjectIdentifier storeObject); + + /// + /// Returns the default database name that would be used for this trigger. + /// + /// The identifier of the store object. + /// The default name that would be used for this trigger. + string? GetDefaultName(in StoreObjectIdentifier storeObject) + => storeObject.StoreObjectType == StoreObjectType.Table + ? Uniquifier.Truncate(ModelName, EntityType.Model.GetMaxIdentifierLength()) + : null; + + /// + /// Gets the entity type on which this trigger is defined. + /// + IReadOnlyEntityType EntityType { get; } +} diff --git a/src/EFCore.Relational/Metadata/IRelationalAnnotationProvider.cs b/src/EFCore.Relational/Metadata/IRelationalAnnotationProvider.cs index 29286682c29..6e8cdb7a00e 100644 --- a/src/EFCore.Relational/Metadata/IRelationalAnnotationProvider.cs +++ b/src/EFCore.Relational/Metadata/IRelationalAnnotationProvider.cs @@ -131,4 +131,12 @@ public interface IRelationalAnnotationProvider /// The annotations. /// Whether the model should contain design-time configuration. IEnumerable For(ICheckConstraint checkConstraint, bool designTime); + + /// + /// Gets provider-specific annotations for the given . + /// + /// The trigger. + /// The annotations. + /// Whether the model should contain design-time configuration. + IEnumerable For(ITrigger trigger, bool designTime); } diff --git a/src/EFCore.Relational/Metadata/ITable.cs b/src/EFCore.Relational/Metadata/ITable.cs index 21ba462211d..cd8a0b342c6 100644 --- a/src/EFCore.Relational/Metadata/ITable.cs +++ b/src/EFCore.Relational/Metadata/ITable.cs @@ -53,6 +53,11 @@ public interface ITable : ITableBase /// IEnumerable CheckConstraints { get; } + /// + /// Gets the triggers for this table. + /// + IEnumerable Triggers { get; } + /// /// Gets the comment for this table. /// diff --git a/src/EFCore.Relational/Metadata/ITrigger.cs b/src/EFCore.Relational/Metadata/ITrigger.cs new file mode 100644 index 00000000000..75434b0c9de --- /dev/null +++ b/src/EFCore.Relational/Metadata/ITrigger.cs @@ -0,0 +1,58 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a database trigger on a table. +/// +/// +/// +/// Since triggers features vary across databases, this is mainly an extension point for providers to add their own annotations. +/// +/// +/// See Database triggers for more information and examples. +/// +/// +public interface ITrigger : IReadOnlyTrigger, IAnnotatable +{ + /// + /// Gets the entity type on which this trigger is defined. + /// + new IEntityType EntityType { get; } + + /// + /// + /// Creates a human-readable representation of the given metadata. + /// + /// + /// Warning: Do not rely on the format of the returned string. + /// It is designed for debugging only and may change arbitrarily between releases. + /// + /// + /// Options for generating the string. + /// The number of indent spaces to use before each new line. + /// A human-readable representation. + string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0) + { + var builder = new StringBuilder(); + var indentString = new string(' ', indent); + + builder + .Append(indentString) + .Append("Trigger: ") + .Append(ModelName); + + if ((options & MetadataDebugStringOptions.SingleLine) == 0) + { + if ((options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(AnnotationsToDebugString(indent: indent + 2)); + } + } + + return builder.ToString(); + } +} diff --git a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs index 3d04662e534..e21dc834c69 100644 --- a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs +++ b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs @@ -288,7 +288,12 @@ public virtual string? Name set => SetName(value, ConfigurationSource.Explicit); } - /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public virtual string? GetName(in StoreObjectIdentifier storeObject) { if (storeObject.StoreObjectType != StoreObjectType.Table) diff --git a/src/EFCore.Relational/Metadata/Internal/InternalTriggerBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalTriggerBuilder.cs new file mode 100644 index 00000000000..247cffa8b28 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/InternalTriggerBuilder.cs @@ -0,0 +1,184 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class InternalTriggerBuilder : AnnotatableBuilder, IConventionTriggerBuilder +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public InternalTriggerBuilder(Trigger trigger, IConventionModelBuilder modelBuilder) + : base(trigger, modelBuilder) + { + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IConventionTriggerBuilder? HasName(string? name, ConfigurationSource configurationSource) + { + if (CanSetName(name, configurationSource)) + { + Metadata.SetName(name, configurationSource); + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetName(string? name, ConfigurationSource configurationSource) + => configurationSource.Overrides(Metadata.GetNameConfigurationSource()) + || Metadata.Name == name; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static IConventionTrigger? HasTrigger( + IConventionEntityType entityType, + string name, + string? tableName, + string? tableSchema, + ConfigurationSource configurationSource) + { + List? triggersToBeDetached = null; + var trigger = entityType.FindTrigger(name); + if (trigger != null) + { + if (trigger.TableName == tableName && trigger.TableSchema == tableSchema) + { + ((Trigger)trigger).UpdateConfigurationSource(configurationSource); + return trigger; + } + + if (!configurationSource.Overrides(trigger.GetConfigurationSource())) + { + return null; + } + + entityType.RemoveTrigger(name); + trigger = null; + } + else + { + foreach (var derivedType in entityType.GetDerivedTypes()) + { + var derivedTrigger = + (IConventionTrigger?)Trigger.FindDeclaredTrigger(derivedType, name); + if (derivedTrigger == null) + { + continue; + } + + if ((derivedTrigger.TableName != tableName || derivedTrigger.TableSchema != tableSchema) + && !configurationSource.Overrides(derivedTrigger.GetConfigurationSource())) + { + return null; + } + + triggersToBeDetached ??= new List(); + + triggersToBeDetached.Add(derivedTrigger); + } + } + + List? detachedTriggers = null; + if (triggersToBeDetached != null) + { + detachedTriggers = new List(); + foreach (var triggerToBeDetached in triggersToBeDetached) + { + detachedTriggers.Add( + triggerToBeDetached.EntityType.RemoveTrigger(triggerToBeDetached.ModelName)!); + } + } + + trigger = new Trigger((IMutableEntityType)entityType, name, tableName, tableSchema, configurationSource); + + if (detachedTriggers != null) + { + foreach (var detachedTrigger in detachedTriggers) + { + Trigger.MergeInto(detachedTrigger, trigger); + } + } + + return trigger; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static bool CanHaveTrigger( + IConventionEntityType entityType, + string name, + string? tableName, + string? tableSchema, + ConfigurationSource configurationSource) + { + if (entityType.FindTrigger(name) is IConventionTrigger trigger) + { + return (trigger.TableName == tableName + && trigger.TableSchema == tableSchema) + || configurationSource.Overrides(trigger.GetConfigurationSource()); + } + + foreach (var derivedType in entityType.GetDerivedTypes()) + { + var derivedTrigger = (IConventionTrigger?)Trigger.FindDeclaredTrigger(derivedType, name); + if (derivedTrigger == null) + { + continue; + } + + if ((derivedTrigger.TableName != tableName + || derivedTrigger.TableSchema != tableSchema) + && !configurationSource.Overrides(derivedTrigger.GetConfigurationSource())) + { + return false; + } + } + + return true; + } + + IConventionTrigger IConventionTriggerBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + [DebuggerStepThrough] + IConventionTriggerBuilder? IConventionTriggerBuilder.HasName(string? name, bool fromDataAnnotation) + => HasName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + bool IConventionTriggerBuilder.CanSetName(string? name, bool fromDataAnnotation) + => CanSetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index a5210d6171e..2a2e6c775e5 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -151,7 +151,7 @@ public static IRelationalModel Create( foreach (var table in databaseModel.Tables.Values) { PopulateRowInternalForeignKeys(table); - PopulateConstraints(table, designTime); + PopulateTableConfiguration(table, designTime); if (relationalAnnotationProvider != null) { @@ -179,11 +179,15 @@ public static IRelationalModel Create( { foreach (var checkConstraint in table.CheckConstraints.Values) { - checkConstraint.AddAnnotations( - relationalAnnotationProvider.For(checkConstraint, designTime)); + checkConstraint.AddAnnotations(relationalAnnotationProvider.For(checkConstraint, designTime)); } } + foreach (var trigger in table.Triggers.Values) + { + ((AnnotatableBase)trigger).AddAnnotations(relationalAnnotationProvider.For(trigger, designTime)); + } + table.AddAnnotations(relationalAnnotationProvider.For(table, designTime)); } } @@ -783,7 +787,7 @@ private static StoreFunction GetOrCreateStoreFunction(IRuntimeDbFunction dbFunct return storeFunction; } - private static void PopulateConstraints(Table table, bool designTime) + private static void PopulateTableConfiguration(Table table, bool designTime) { var storeObject = StoreObjectIdentifier.Table(table.Name, table.Schema); foreach (var entityTypeMapping in ((ITable)table).EntityTypeMappings) @@ -1017,6 +1021,23 @@ private static void PopulateConstraints(Table table, bool designTime) } } } + + foreach (var trigger in entityType.GetTriggers()) + { + var name = trigger.GetName(storeObject); + if (name == null) + { + continue; + } + + Check.DebugAssert(trigger.TableName == table.Name, "Mismatch in trigger table name"); + Check.DebugAssert(trigger.TableSchema is null || trigger.TableSchema == table.Schema, "Mismatch in trigger table schema"); + + if (!table.Triggers.ContainsKey(name)) + { + table.Triggers.Add(name, trigger); + } + } } } diff --git a/src/EFCore.Relational/Metadata/Internal/Table.cs b/src/EFCore.Relational/Metadata/Internal/Table.cs index 2ed34a918d8..7dd967f32a1 100644 --- a/src/EFCore.Relational/Metadata/Internal/Table.cs +++ b/src/EFCore.Relational/Metadata/Internal/Table.cs @@ -122,6 +122,15 @@ public virtual UniqueConstraint? PrimaryKey public virtual SortedDictionary CheckConstraints { get; } = new(); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual SortedDictionary Triggers { get; } + = new(); + /// public virtual bool IsExcludedFromMigrations => EntityTypeMappings.First().EntityType.IsTableExcludedFromMigrations(); @@ -191,7 +200,14 @@ IEnumerable ITable.CheckConstraints ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) : CheckConstraints.Values; } - + + /// + IEnumerable ITable.Triggers + { + [DebuggerStepThrough] + get => Triggers.Values; + } + /// [DebuggerStepThrough] IColumn? ITable.FindColumn(string name) diff --git a/src/EFCore.Relational/Metadata/Internal/Trigger.cs b/src/EFCore.Relational/Metadata/Internal/Trigger.cs new file mode 100644 index 00000000000..58600cb3db9 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/Trigger.cs @@ -0,0 +1,466 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class Trigger : ConventionAnnotatable, IMutableTrigger, IConventionTrigger, ITrigger +{ + private string? _name; + private string? _tableName; + private string? _tableSchema; + private InternalTriggerBuilder? _builder; + + private ConfigurationSource _configurationSource; + private ConfigurationSource? _nameConfigurationSource; + private ConfigurationSource? _tableNameConfigurationSource; + private ConfigurationSource? _tableSchemaConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public Trigger( + IMutableEntityType entityType, + string name, + string? tableName, + string? tableSchema, + ConfigurationSource configurationSource) + { + EntityType = entityType; + ModelName = name; + _tableName = tableName; + _tableSchema = tableSchema; + _configurationSource = configurationSource; + + var triggers = GetTriggersDictionary(entityType); + if (triggers == null) + { + triggers = new SortedDictionary(StringComparer.Ordinal); + entityType.SetOrRemoveAnnotation(RelationalAnnotationNames.Triggers, triggers); + } + + if (triggers.ContainsKey(name)) + { + throw new InvalidOperationException( + RelationalStrings.DuplicateTrigger( + name, entityType.DisplayName(), entityType.DisplayName())); + } + + var baseTrigger = entityType.BaseType?.FindTrigger(name); + if (baseTrigger != null) + { + throw new InvalidOperationException( + RelationalStrings.DuplicateTrigger( + name, entityType.DisplayName(), baseTrigger.EntityType.DisplayName())); + } + + foreach (var derivedType in entityType.GetDerivedTypes()) + { + var derivedTrigger = FindTrigger(derivedType, name); + if (derivedTrigger != null) + { + throw new InvalidOperationException( + RelationalStrings.DuplicateTrigger( + name, entityType.DisplayName(), derivedTrigger.EntityType.DisplayName())); + } + } + + if (entityType.GetTableName() is null) + { + throw new InvalidOperationException(RelationalStrings.TriggerOnUnmappedEntityType(name, entityType.DisplayName())); + } + + EnsureMutable(); + + triggers.Add(name, this); + + _builder = new InternalTriggerBuilder(this, ((IConventionModel)entityType.Model).Builder); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static IEnumerable GetDeclaredTriggers(IReadOnlyEntityType entityType) + => GetTriggersDictionary(entityType)?.Values ?? Enumerable.Empty(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static IEnumerable GetTriggers(IReadOnlyEntityType entityType) + => entityType.BaseType != null + ? GetTriggers(entityType.BaseType).Concat(GetDeclaredTriggers(entityType)) + : GetDeclaredTriggers(entityType); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static IReadOnlyTrigger? FindTrigger( + IReadOnlyEntityType entityType, + string name) + => entityType.BaseType?.FindTrigger(name) ?? FindDeclaredTrigger(entityType, name); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static IReadOnlyTrigger? FindDeclaredTrigger(IReadOnlyEntityType entityType, string name) + { + var triggers = (SortedDictionary?)entityType[RelationalAnnotationNames.Triggers]; + + return triggers is not null && triggers.TryGetValue(name, out var trigger) + ? trigger + : null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static Trigger? RemoveTrigger(IMutableEntityType entityType, string name) + { + var triggers = (SortedDictionary?)entityType[RelationalAnnotationNames.Triggers]; + if (triggers == null + || !triggers.TryGetValue(name, out var trigger)) + { + return null; + } + + var mutableTrigger = (Trigger)trigger; + triggers.Remove(name); + mutableTrigger.SetRemovedFromModel(); + + return mutableTrigger; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static void MergeInto(IConventionEntityType entityType, IConventionTrigger detachedTrigger) + { + var newTrigger = new Trigger( + (IMutableEntityType)entityType, + detachedTrigger.ModelName, + detachedTrigger.TableName, + detachedTrigger.TableSchema, + detachedTrigger.GetConfigurationSource()); + + MergeInto(detachedTrigger, newTrigger); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static void MergeInto(IConventionTrigger detachedTrigger, IConventionTrigger existingTrigger) + { + var nameConfigurationSource = detachedTrigger.GetNameConfigurationSource(); + if (nameConfigurationSource != null) + { + ((InternalTriggerBuilder)existingTrigger.Builder).HasName( + detachedTrigger.Name, nameConfigurationSource.Value); + } + + ((InternalTriggerBuilder)existingTrigger.Builder).MergeAnnotationsFrom( + (Trigger)detachedTrigger); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalTriggerBuilder Builder + { + [DebuggerStepThrough] + get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsInModel + => _builder is not null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void SetRemovedFromModel() + => _builder = null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyEntityType EntityType { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string ModelName { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string? Name + { + get => EntityType.GetTableName() == null + ? null + : _name ?? ((IReadOnlyTrigger)this).GetDefaultName(); + set => SetName(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string? GetName(in StoreObjectIdentifier storeObject) + { + if (storeObject.StoreObjectType != StoreObjectType.Table) + { + return null; + } + + foreach (var containingType in EntityType.GetDerivedTypesInclusive()) + { + if (StoreObjectIdentifier.Create(containingType, storeObject.StoreObjectType) == storeObject) + { + return _name ?? ((IReadOnlyTrigger)this).GetDefaultName(storeObject); + } + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string? SetName(string? name, ConfigurationSource configurationSource) + { + EnsureMutable(); + + _name = name; + + _nameConfigurationSource = configurationSource.Max(_nameConfigurationSource); + + return name; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetNameConfigurationSource() + => _nameConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string TableName + { + get => _tableName ?? EntityType.GetTableName()!; + set => SetTableName(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string? SetTableName(string? tableName, ConfigurationSource configurationSource) + { + EnsureMutable(); + + _tableName = tableName; + + _tableNameConfigurationSource = configurationSource.Max(_tableNameConfigurationSource); + + return tableName; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetTableNameConfigurationSource() + => _tableNameConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string? TableSchema + { + get => _tableSchema; + set => SetTableSchema(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string? SetTableSchema(string? tableSchema, ConfigurationSource configurationSource) + { + EnsureMutable(); + + _tableSchema = tableSchema; + + _tableSchemaConfigurationSource = configurationSource.Max(_tableSchemaConfigurationSource); + + return tableSchema; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetTableSchemaConfigurationSource() + => _tableSchemaConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource GetConfigurationSource() + => _configurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void UpdateConfigurationSource(ConfigurationSource configurationSource) + => _configurationSource = _configurationSource.Max(configurationSource); + + private static SortedDictionary? GetTriggersDictionary(IReadOnlyEntityType entityType) + => (SortedDictionary?)entityType[RelationalAnnotationNames.Triggers]; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override string ToString() + => ((ITrigger)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + /// + /// 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. + /// + IConventionEntityType IConventionTrigger.EntityType + { + [DebuggerStepThrough] + get => (IConventionEntityType)EntityType; + } + + /// + /// 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. + /// + IMutableEntityType IMutableTrigger.EntityType + { + [DebuggerStepThrough] + get => (IMutableEntityType)EntityType; + } + + /// + /// 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. + /// + IEntityType ITrigger.EntityType + { + [DebuggerStepThrough] + get => (IEntityType)EntityType; + } + + /// + /// 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. + /// + IConventionTriggerBuilder IConventionTrigger.Builder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + string? IConventionTrigger.SetName(string? name, bool fromDataAnnotation) + => SetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs index 2e40c172622..4885a3d648b 100644 --- a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs @@ -129,6 +129,11 @@ public static class RelationalAnnotationNames /// public const string DbFunctions = Prefix + "DbFunctions"; + /// + /// The name for trigger annotation. + /// + public const string Triggers = Prefix + "Triggers"; + /// /// The name for the annotation containing the maximum length for database identifiers. /// diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationProvider.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationProvider.cs index e7276ac2f2d..09c879111ad 100644 --- a/src/EFCore.Relational/Metadata/RelationalAnnotationProvider.cs +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationProvider.cs @@ -89,4 +89,8 @@ public virtual IEnumerable For(ISequence sequence, bool designTime) /// public virtual IEnumerable For(ICheckConstraint checkConstraint, bool designTime) => Enumerable.Empty(); + + /// + public virtual IEnumerable For(ITrigger trigger, bool designTime) + => Enumerable.Empty(); } diff --git a/src/EFCore.Relational/Metadata/RuntimeTrigger.cs b/src/EFCore.Relational/Metadata/RuntimeTrigger.cs new file mode 100644 index 00000000000..ed95ba9e6e3 --- /dev/null +++ b/src/EFCore.Relational/Metadata/RuntimeTrigger.cs @@ -0,0 +1,58 @@ +// 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; + +/// +/// Represents a database trigger on a table. +/// +/// +/// See Database triggers for more information and examples. +/// +public class RuntimeTrigger : AnnotatableBase, ITrigger +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public RuntimeTrigger( + RuntimeEntityType entityType, + string modelName, + string? name, + string tableName, + string? tableSchema) + { + EntityType = entityType; + ModelName = modelName; + Name = name; + TableName = tableName; + TableSchema = tableSchema; + } + + /// + public virtual string ModelName { get; } + + /// + /// Gets the database name of the trigger. + /// + public virtual string? Name { get; } + + /// + public virtual string? GetName(in StoreObjectIdentifier storeObject) + => Name; + + /// + public virtual string TableName { get; } + + /// + public virtual string? TableSchema { get; } + + /// + public virtual IEntityType EntityType { get; } + + /// + IReadOnlyEntityType IReadOnlyTrigger.EntityType + => EntityType; +} diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 27aad4c18e4..b3727c74382 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -549,6 +549,14 @@ public static string DuplicateKeyTableMismatch(object? keyProperties1, object? e GetString("DuplicateKeyTableMismatch", nameof(keyProperties1), nameof(entityType1), nameof(keyProperties2), nameof(entityType2), nameof(keyName), nameof(table1), nameof(table2)), keyProperties1, entityType1, keyProperties2, entityType2, keyName, table1, table2); + /// + /// The trigger '{trigger}' cannot be added to the entity type '{entityType}' because another trigger with the same name already exists on entity type '{conflictingEntityType}'. + /// + public static string DuplicateTrigger(object? trigger, object? entityType, object? conflictingEntityType) + => string.Format( + GetString("DuplicateTrigger", nameof(trigger), nameof(entityType), nameof(conflictingEntityType)), + trigger, entityType, conflictingEntityType); + /// /// Either {param1} or {param2} must be null. /// @@ -1175,6 +1183,22 @@ public static string TransactionAssociatedWithDifferentConnection public static string TransactionSuppressedMigrationInUserTransaction => GetString("TransactionSuppressedMigrationInUserTransaction"); + /// + /// Trigger '{trigger}' cannot be defined on entity type '{entityType}' since that entity type isn't mapped to a database table. See https://aka.ms/efcore-docs-triggers for more information on triggers. + /// + public static string TriggerOnUnmappedEntityType(object? trigger, object? entityType) + => string.Format( + GetString("TriggerOnUnmappedEntityType", nameof(trigger), nameof(entityType)), + trigger, entityType); + + /// + /// Trigger '{trigger}' with table '{triggerTable}' is defined on entity type '{entityType}', which is mapped to table '{entityTable}'. See https://aka.ms/efcore-docs-triggers for more information on triggers. + /// + public static string TriggerWithMismatchedTable(object? trigger, object? triggerTable, object? entityType, object? entityTable) + => string.Format( + GetString("TriggerWithMismatchedTable", nameof(trigger), nameof(triggerTable), nameof(entityType), nameof(entityTable)), + trigger, triggerTable, entityType, entityTable); + /// /// Unable to bind '{memberType}.{member}' to an entity projection of '{entityType}'. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 6a36f8c0084..6e09693e958 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -319,6 +319,9 @@ The keys {keyProperties1} on '{entityType1}' and {keyProperties2} on '{entityType2}' are both mapped to '{keyName}', but on different tables ('{table1}' and '{table2}'). + + The trigger '{trigger}' cannot be added to the entity type '{entityType}' because another trigger with the same name already exists on entity type '{conflictingEntityType}'. + Either {param1} or {param2} must be null. @@ -815,6 +818,12 @@ User transaction is not supported with a TransactionSuppressed migrations. + + Trigger '{trigger}' cannot be defined on entity type '{entityType}' since that entity type isn't mapped to a database table. See https://aka.ms/efcore-docs-triggers for more information on triggers. + + + Trigger '{trigger}' for table '{triggerTable}' is defined on entity type '{entityType}', which is mapped to table '{entityTable}'. See https://aka.ms/efcore-docs-triggers for more information on triggers. + Unable to bind '{memberType}.{member}' to an entity projection of '{entityType}'. diff --git a/src/EFCore.Relational/Update/AffectedCountModificationCommandBatch.cs b/src/EFCore.Relational/Update/AffectedCountModificationCommandBatch.cs index 14fb6e3bdc3..88b0101dfb3 100644 --- a/src/EFCore.Relational/Update/AffectedCountModificationCommandBatch.cs +++ b/src/EFCore.Relational/Update/AffectedCountModificationCommandBatch.cs @@ -158,45 +158,50 @@ protected override async Task ConsumeAsync( /// Consumes the data reader created by , /// propagating values back into the . /// - /// The ordinal of the command being consumed. + /// The ordinal of the first result set being consumed. /// The data reader. - /// The ordinal of the next command that must be consumed. - protected virtual int ConsumeResultSetWithPropagation(int commandIndex, RelationalDataReader reader) + /// The ordinal of the next result set that must be consumed. + protected virtual int ConsumeResultSetWithPropagation(int startResultSetIndex, RelationalDataReader reader) { + var resultSetIndex = startResultSetIndex; var rowsAffected = 0; do { - var tableModification = ModificationCommands[commandIndex]; - Check.DebugAssert(tableModification.RequiresResultPropagation, "RequiresResultPropagation is false"); - if (!reader.Read()) { var expectedRowsAffected = rowsAffected + 1; - while (++commandIndex < CommandResultSet.Count - && CommandResultSet[commandIndex - 1] == ResultSetMapping.NotLastInResultSet) + while (++resultSetIndex < CommandResultSet.Count + && CommandResultSet[resultSetIndex - 1] == ResultSetMapping.NotLastInResultSet) { expectedRowsAffected++; } - ThrowAggregateUpdateConcurrencyException(commandIndex, expectedRowsAffected, rowsAffected); + ThrowAggregateUpdateConcurrencyException(resultSetIndex, expectedRowsAffected, rowsAffected); } + var tableModification = ModificationCommands[ + ResultsPositionalMappingEnabled?.Length > resultSetIndex && ResultsPositionalMappingEnabled[resultSetIndex] + ? startResultSetIndex + reader.DbDataReader.GetInt32(reader.DbDataReader.FieldCount - 1) + : resultSetIndex]; + + Check.DebugAssert(tableModification.RequiresResultPropagation, "RequiresResultPropagation is false"); + var valueBufferFactory = CreateValueBufferFactory(tableModification.ColumnModifications); tableModification.PropagateResults(valueBufferFactory.Create(reader.DbDataReader)); rowsAffected++; } - while (++commandIndex < CommandResultSet.Count - && CommandResultSet[commandIndex - 1] == ResultSetMapping.NotLastInResultSet); + while (++resultSetIndex < CommandResultSet.Count + && CommandResultSet[resultSetIndex - 1] == ResultSetMapping.NotLastInResultSet); - return commandIndex; + return resultSetIndex; } /// /// Consumes the data reader created by , /// propagating values back into the . /// - /// The ordinal of the command being consumed. + /// The ordinal of the first result set being consumed. /// The data reader. /// A to observe while waiting for the task to complete. /// @@ -205,37 +210,42 @@ protected virtual int ConsumeResultSetWithPropagation(int commandIndex, Relation /// /// If the is canceled. protected virtual async Task ConsumeResultSetWithPropagationAsync( - int commandIndex, + int startResultSetIndex, RelationalDataReader reader, CancellationToken cancellationToken) { + var resultSetIndex = startResultSetIndex; var rowsAffected = 0; do { - var tableModification = ModificationCommands[commandIndex]; - Check.DebugAssert(tableModification.RequiresResultPropagation, "RequiresResultPropagation is false"); - if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) { var expectedRowsAffected = rowsAffected + 1; - while (++commandIndex < CommandResultSet.Count - && CommandResultSet[commandIndex - 1] == ResultSetMapping.NotLastInResultSet) + while (++resultSetIndex < CommandResultSet.Count + && CommandResultSet[resultSetIndex - 1] == ResultSetMapping.NotLastInResultSet) { expectedRowsAffected++; } - ThrowAggregateUpdateConcurrencyException(commandIndex, expectedRowsAffected, rowsAffected); + ThrowAggregateUpdateConcurrencyException(resultSetIndex, expectedRowsAffected, rowsAffected); } + var tableModification = ModificationCommands[ + ResultsPositionalMappingEnabled?.Length > resultSetIndex && ResultsPositionalMappingEnabled[resultSetIndex] + ? startResultSetIndex + reader.DbDataReader.GetInt32(reader.DbDataReader.FieldCount - 1) + : resultSetIndex]; + + Check.DebugAssert(tableModification.RequiresResultPropagation, "RequiresResultPropagation is false"); + var valueBufferFactory = CreateValueBufferFactory(tableModification.ColumnModifications); tableModification.PropagateResults(valueBufferFactory.Create(reader.DbDataReader)); rowsAffected++; } - while (++commandIndex < CommandResultSet.Count - && CommandResultSet[commandIndex - 1] == ResultSetMapping.NotLastInResultSet); + while (++resultSetIndex < CommandResultSet.Count + && CommandResultSet[resultSetIndex - 1] == ResultSetMapping.NotLastInResultSet); - return commandIndex; + return resultSetIndex; } /// diff --git a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs index 9bd866d77dd..7c5457b00fd 100644 --- a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs +++ b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs @@ -27,7 +27,7 @@ public CommandBatchPreparer(CommandBatchPreparerDependencies dependencies) { _minBatchSize = dependencies.Options.Extensions.OfType().FirstOrDefault()?.MinBatchSize - ?? 4; + ?? 1; Dependencies = dependencies; if (dependencies.LoggingOptions.IsSensitiveDataLoggingEnabled) diff --git a/src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs b/src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs index 1a35f6aa66c..7ada76329fe 100644 --- a/src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs +++ b/src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections; using System.Text; namespace Microsoft.EntityFrameworkCore.Update; @@ -66,6 +67,13 @@ public override IReadOnlyList ModificationCommands /// protected virtual IList CommandResultSet { get; } = new List(); + /// + /// When rows with database-generated values are returned in non-deterministic ordering, it is necessary to project out a synthetic + /// position value, in order to look up the correct and propagate the values. When this array + /// isn't , it determines whether the current result row contains such a position value. + /// + protected virtual BitArray? ResultsPositionalMappingEnabled { get; set; } + /// /// Adds the given insert/update/delete to the batch. /// diff --git a/src/EFCore.Relational/Update/UpdateSqlGenerator.cs b/src/EFCore.Relational/Update/UpdateSqlGenerator.cs index f1768588955..5ba908b3037 100644 --- a/src/EFCore.Relational/Update/UpdateSqlGenerator.cs +++ b/src/EFCore.Relational/Update/UpdateSqlGenerator.cs @@ -68,20 +68,11 @@ public virtual ResultSetMapping AppendInsertOperation( var writeOperations = operations.Where(o => o.IsWrite).ToList(); var readOperations = operations.Where(o => o.IsRead).ToList(); - AppendInsertCommand(commandStringBuilder, name, schema, writeOperations); - - if (readOperations.Count > 0) - { - var keyOperations = operations.Where(o => o.IsKey).ToList(); - - requiresTransaction = true; - - return AppendSelectAffectedCommand(commandStringBuilder, name, schema, readOperations, keyOperations, commandPosition); - } + AppendInsertCommand(commandStringBuilder, name, schema, writeOperations, readOperations); requiresTransaction = false; - return ResultSetMapping.NoResultSet; + return readOperations.Count > 0 ? ResultSetMapping.LastInResultSet : ResultSetMapping.NoResultSet; } /// @@ -153,16 +144,19 @@ public virtual ResultSetMapping AppendDeleteOperation( /// The builder to which the SQL should be appended. /// The name of the table. /// The table schema, or to use the default schema. - /// The operations for each column. + /// The operations with the values to insert for each column. + /// The operations for column values to be read back. protected virtual void AppendInsertCommand( StringBuilder commandStringBuilder, string name, string? schema, - IReadOnlyList writeOperations) + IReadOnlyList writeOperations, + IReadOnlyList readOperations) { AppendInsertCommandHeader(commandStringBuilder, name, schema, writeOperations); AppendValuesHeader(commandStringBuilder, writeOperations); AppendValues(commandStringBuilder, name, schema, writeOperations); + AppendReturningClause(commandStringBuilder, readOperations); commandStringBuilder.AppendLine(SqlGenerationHelper.StatementTerminator); } @@ -412,6 +406,27 @@ protected virtual void AppendValues( } } + /// + /// Appends a clause used to return generated values from an INSERT or UPDATE statement. + /// + /// The builder to which the SQL should be appended. + /// The operations for column values to be read back. + protected virtual void AppendReturningClause( + StringBuilder commandStringBuilder, + IReadOnlyList operations) + { + if (operations.Count > 0) + { + commandStringBuilder + .AppendLine() + .Append("RETURNING ") + .AppendJoin( + operations, + SqlGenerationHelper, + (sb, o, helper) => helper.DelimitIdentifier(sb, o.ColumnName)); + } + } + /// /// Appends a WHERE clause. /// diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs index cd412d4d001..9efc6efc08b 100644 --- a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs +++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs @@ -211,6 +211,18 @@ public static string NonKeyValueGeneration(object? property, object? entityType) public static string NoSavepointRelease => GetString("NoSavepointRelease"); + /// + /// Could not save changes because the target table has computed column with a function that performs data access. Please configure your entity type accordingly, see https://aka.ms/efcore-docs-sqlserver-save-changes-and-computed-columns for more information. + /// + public static string SaveChangesFailedBecauseOfComputedColumnWithFunction + => GetString("SaveChangesFailedBecauseOfComputedColumnWithFunction"); + + /// + /// Could not save changes because the target table has database triggers. Please configure your entity type accordingly, see https://aka.ms/efcore-docs-sqlserver-save-changes-and-triggers for more information. + /// + public static string SaveChangesFailedBecauseOfTriggers + => GetString("SaveChangesFailedBecauseOfTriggers"); + /// /// SQL Server sequences cannot be used to generate values for the property '{property}' on entity type '{entityType}' because the property type is '{propertyType}'. Sequences can only be used with integer properties. /// diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx index 6c2891b2744..79abd10b741 100644 --- a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx +++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx @@ -284,6 +284,12 @@ SQL Server does not support releasing a savepoint. + + Could not save changes because the target table has computed column with a function that performs data access. Please configure your entity type accordingly, see https://aka.ms/efcore-docs-sqlserver-save-changes-and-computed-columns for more information. + + + Could not save changes because the target table has database triggers. Please configure your entity type accordingly, see https://aka.ms/efcore-docs-sqlserver-save-changes-and-triggers for more information. + SQL Server sequences cannot be used to generate values for the property '{property}' on entity type '{entityType}' because the property type is '{propertyType}'. Sequences can only be used with integer properties. diff --git a/src/EFCore.SqlServer/Update/Internal/ISqlServerUpdateSqlGenerator.cs b/src/EFCore.SqlServer/Update/Internal/ISqlServerUpdateSqlGenerator.cs index c7f92fb002a..583aebebdfd 100644 --- a/src/EFCore.SqlServer/Update/Internal/ISqlServerUpdateSqlGenerator.cs +++ b/src/EFCore.SqlServer/Update/Internal/ISqlServerUpdateSqlGenerator.cs @@ -28,6 +28,7 @@ ResultSetMapping AppendBulkInsertOperation( StringBuilder commandStringBuilder, IReadOnlyList modificationCommands, int commandPosition, + out bool resultsContainPositionMapping, out bool requiresTransaction); /// @@ -40,5 +41,5 @@ ResultSetMapping AppendBulkInsertOperation( StringBuilder commandStringBuilder, IReadOnlyList modificationCommands, int commandPosition) - => AppendBulkInsertOperation(commandStringBuilder, modificationCommands, commandPosition, out _); + => AppendBulkInsertOperation(commandStringBuilder, modificationCommands, commandPosition, out _, out _); } diff --git a/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs b/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs index 18e4ee6d501..2d2429f88c6 100644 --- a/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs +++ b/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections; using System.Text; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore.SqlServer.Internal; namespace Microsoft.EntityFrameworkCore.SqlServer.Update.Internal; @@ -153,10 +156,28 @@ private void AppendBulkInsertCommandText(int lastIndex) var wasCachedCommandTextEmpty = IsCachedCommandTextEmpty; var resultSetMapping = UpdateSqlGenerator.AppendBulkInsertOperation( - CachedCommandText, _bulkInsertCommands, lastIndex - _bulkInsertCommands.Count, out var requiresTransaction); + CachedCommandText, _bulkInsertCommands, lastIndex - _bulkInsertCommands.Count, out var resultsContainPositionMapping, + out var requiresTransaction); SetRequiresTransaction(!wasCachedCommandTextEmpty || requiresTransaction); + if (resultsContainPositionMapping) + { + if (ResultsPositionalMappingEnabled is null) + { + ResultsPositionalMappingEnabled = new BitArray(CommandResultSet.Count); + } + else + { + ResultsPositionalMappingEnabled.Length = CommandResultSet.Count; + } + + for (var i = lastIndex - _bulkInsertCommands.Count; i < lastIndex; i++) + { + ResultsPositionalMappingEnabled![i] = true; + } + } + for (var i = lastIndex - _bulkInsertCommands.Count; i < lastIndex; i++) { CommandResultSet[i] = resultSetMapping; @@ -230,4 +251,76 @@ private static bool CanBeInsertedInSameStatement( secondCommand.ColumnModifications.Where(o => o.IsWrite).Select(o => o.ColumnName)) && firstCommand.ColumnModifications.Where(o => o.IsRead).Select(o => o.ColumnName).SequenceEqual( secondCommand.ColumnModifications.Where(o => o.IsRead).Select(o => o.ColumnName)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override void Execute(IRelationalConnection connection) + { + try + { + base.Execute(connection); + } + catch (DbUpdateException e) when (e.InnerException is SqlException { Number: 334 } ) + { + // SQL Server error: The target table '%.*ls' of the DML statement cannot have any enabled triggers if the statement contains an + // OUTPUT clause without INTO clause. + // This occurs when the user hasn't declared in metadata that a table has triggers, but triggers do exist in the database. + // Throw a specialized exception to point the user in the right direction. + throw new DbUpdateException( + SqlServerStrings.SaveChangesFailedBecauseOfTriggers, + e.InnerException, + e.Entries); + } + catch (DbUpdateException e) when (e.InnerException is SqlException { Number: 4186 } ) + { + // SQL Server error: Column '%ls.%.*ls' cannot be referenced in the OUTPUT clause because the column definition contains a + // subquery or references a function that performs user or system data access [...] + // See https://docs.microsoft.com/sql/relational-databases/errors-events/mssqlserver-4186-database-engine-error + throw new DbUpdateException( + SqlServerStrings.SaveChangesFailedBecauseOfComputedColumnWithFunction, + e.InnerException, + e.Entries); + } + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override async Task ExecuteAsync( + IRelationalConnection connection, + CancellationToken cancellationToken = default) + { + try + { + await base.ExecuteAsync(connection, cancellationToken).ConfigureAwait(false); + } + catch (DbUpdateException e) when (e.InnerException is SqlException { Number: 334 } ) + { + // SQL Server error: The target table '%.*ls' of the DML statement cannot have any enabled triggers if the statement contains an + // OUTPUT clause without INTO clause. + // This occurs when the user hasn't declared in metadata that a table has triggers, but triggers do exist in the database. + // Throw a specialized exception to point the user in the right direction. + throw new DbUpdateException( + SqlServerStrings.SaveChangesFailedBecauseOfTriggers, + e.InnerException, + e.Entries); + } + catch (DbUpdateException e) when (e.InnerException is SqlException { Number: 4186 } ) + { + // SQL Server error: Column '%ls.%.*ls' cannot be referenced in the OUTPUT clause because the column definition contains a + // subquery or references a function that performs user or system data access [...] + // See https://docs.microsoft.com/sql/relational-databases/errors-events/mssqlserver-4186-database-engine-error + throw new DbUpdateException( + SqlServerStrings.SaveChangesFailedBecauseOfComputedColumnWithFunction, + e.InnerException, + e.Entries); + } + } } diff --git a/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs b/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs index 1d18cee739b..7d08eb12835 100644 --- a/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs +++ b/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs @@ -26,6 +26,74 @@ public SqlServerUpdateSqlGenerator( { } + /// + /// The minimum number of insertions which are executed using MERGE ... OUTPUT INTO. Below this threshold, multiple batched INSERT + /// statements are more efficient. + /// + protected virtual int MergeIntoMinimumThreshold => 4; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override ResultSetMapping AppendInsertOperation( + StringBuilder commandStringBuilder, + IReadOnlyModificationCommand command, + int commandPosition, + out bool requiresTransaction) + { + // If no database-generated columns need to be read back, just do a simple INSERT (default behavior). + // If there are generated columns but there are no triggers defined on the table, we can do a simple INSERT ... OUTPUT + // (without INTO), which is also the default behavior, doesn't require a transaction and is the most efficient. + if (command.ColumnModifications.All(o => !o.IsRead) + || !command.Entries[0].EntityType.Model.GetRelationalModel().FindTable(command.TableName, command.Schema)!.Triggers.Any()) + { + return base.AppendInsertOperation(commandStringBuilder, command, commandPosition, out requiresTransaction); + } + + // SQL Server doesn't allow INSERT ... OUTPUT on tables with triggers. + // If the only generated column is an IDENTITY, do INSERT+SELECT which is relatively fast. + // Otherwise fall back to INSERT ... OUTPUT INTO @inserted; SELECT ... FROM @inserted. + var table = StoreObjectIdentifier.Table(command.TableName, command.Schema); + + return command.ColumnModifications.All( + o => + !o.IsKey + || !o.IsRead + || o.Property?.GetValueGenerationStrategy(table) == SqlServerValueGenerationStrategy.IdentityColumn) + ? AppendInsertAndSelectOperations(commandStringBuilder, command, commandPosition, out requiresTransaction) + : AppendInsertSingleRowWithOutputInto( + commandStringBuilder, + command, + command.ColumnModifications.Where(o => o.IsKey).ToList(), + command.ColumnModifications.Where(o => o.IsRead).ToList(), + commandPosition, + out requiresTransaction); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override void AppendInsertCommand( + StringBuilder commandStringBuilder, + string name, + string? schema, + IReadOnlyList writeOperations, + IReadOnlyList readOperations) + { + // In SQL Server the OUTPUT clause is placed differently (before the VALUES instead of at the end) + AppendInsertCommandHeader(commandStringBuilder, name, schema, writeOperations); + AppendOutputClause(commandStringBuilder, readOperations); + AppendValuesHeader(commandStringBuilder, writeOperations); + AppendValues(commandStringBuilder, name, schema, writeOperations); + commandStringBuilder.AppendLine(SqlGenerationHelper.StatementTerminator); + } + /// /// 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 @@ -36,33 +104,24 @@ public virtual ResultSetMapping AppendBulkInsertOperation( StringBuilder commandStringBuilder, IReadOnlyList modificationCommands, int commandPosition, + out bool resultsContainPositionMapping, out bool requiresTransaction) { - var table = StoreObjectIdentifier.Table(modificationCommands[0].TableName, modificationCommands[0].Schema); + resultsContainPositionMapping = false; + + var firstCommand = modificationCommands[0]; + if (modificationCommands.Count == 1) { - return modificationCommands[0].ColumnModifications.All( - o => - !o.IsKey - || !o.IsRead - || o.Property?.GetValueGenerationStrategy(table) == SqlServerValueGenerationStrategy.IdentityColumn) - // Do a regular INSERT+SELECT for IDENTITY, but not if there are any non-IDENTITY generated columns - ? AppendInsertOperation(commandStringBuilder, modificationCommands[0], commandPosition, out requiresTransaction) - // If we have a non-identity generated column, do INSERT ... OUTPUT INTO @inserted; SELECT ... FROM @inserted - : AppendInsertOperationWithServerKeys( - commandStringBuilder, - modificationCommands[0], - modificationCommands[0].ColumnModifications.Where(o => o.IsKey).ToList(), - modificationCommands[0].ColumnModifications.Where(o => o.IsRead).ToList(), - commandPosition, - out requiresTransaction); + return AppendInsertOperation(commandStringBuilder, firstCommand, commandPosition, out requiresTransaction); } - var readOperations = modificationCommands[0].ColumnModifications.Where(o => o.IsRead).ToList(); - var writeOperations = modificationCommands[0].ColumnModifications.Where(o => o.IsWrite).ToList(); - var keyOperations = modificationCommands[0].ColumnModifications.Where(o => o.IsKey).ToList(); + var table = StoreObjectIdentifier.Table(firstCommand.TableName, modificationCommands[0].Schema); + + var readOperations = firstCommand.ColumnModifications.Where(o => o.IsRead).ToList(); + var writeOperations = firstCommand.ColumnModifications.Where(o => o.IsWrite).ToList(); + var keyOperations = firstCommand.ColumnModifications.Where(o => o.IsKey).ToList(); - var defaultValuesOnly = writeOperations.Count == 0; var writableOperations = modificationCommands[0].ColumnModifications .Where(o => o.Property?.GetValueGenerationStrategy(table) != SqlServerValueGenerationStrategy.IdentityColumn @@ -70,43 +129,48 @@ public virtual ResultSetMapping AppendBulkInsertOperation( && o.Property?.GetColumnType() is not "rowversion" and not "timestamp") .ToList(); - if (defaultValuesOnly) + if (writeOperations.Count == 0) { - if (writableOperations.Count == 0 - || readOperations.Count == 0) + // We have no values to write; MERGE and multi-row INSERT cannot be used without writing at least a single column. + // But as long as there's at least one writable column (non-identity/computed), we can use it to send DEFAULT in a multi-row + // INSERT. + if (writableOperations.Count > 0) { - requiresTransaction = modificationCommands.Count > 1; - foreach (var modification in modificationCommands) + if (writableOperations.Count > 1) { - AppendInsertOperation(commandStringBuilder, modification, commandPosition, out var localRequiresTransaction); - requiresTransaction = requiresTransaction || localRequiresTransaction; + writableOperations.RemoveRange(1, writableOperations.Count - 1); } return readOperations.Count == 0 - ? ResultSetMapping.NoResultSet - : ResultSetMapping.LastInResultSet; + ? AppendInsertMultipleDefaultRows( + commandStringBuilder, modificationCommands, writableOperations, out requiresTransaction) + : AppendInsertMultipleDefaultRowsWithOutputInto( + commandStringBuilder, modificationCommands, commandPosition, writableOperations, keyOperations, readOperations, + out requiresTransaction); } - if (writableOperations.Count > 1) + // There are no writeable columns, fall back to sending multiple single-row INSERTs (there is no way to insert multiple + // all-default rows in a single INSERT). + requiresTransaction = modificationCommands.Count > 1; + foreach (var modification in modificationCommands) { - writableOperations.RemoveRange(1, writableOperations.Count - 1); + AppendInsertOperation(commandStringBuilder, modification, commandPosition++, out var localRequiresTransaction); + requiresTransaction = requiresTransaction || localRequiresTransaction; } + + return readOperations.Count == 0 + ? ResultSetMapping.NoResultSet + : ResultSetMapping.LastInResultSet; } if (readOperations.Count == 0) { - return AppendBulkInsertWithoutServerValues( + // We have no values to read, just use a plain old multi-row INSERT. + return AppendInsertMultipleRows( commandStringBuilder, modificationCommands, writeOperations, out requiresTransaction); } - if (defaultValuesOnly) - { - return AppendBulkInsertWithServerValuesOnly( - commandStringBuilder, modificationCommands, commandPosition, writableOperations, keyOperations, readOperations, - out requiresTransaction); - } - - if (modificationCommands[0].Entries.SelectMany(e => e.EntityType.GetAllBaseTypesInclusive()) + if (firstCommand.Entries.SelectMany(e => e.EntityType.GetAllBaseTypesInclusive()) .Any(e => e.IsMemoryOptimized())) { requiresTransaction = modificationCommands.Count > 1; @@ -123,7 +187,7 @@ public virtual ResultSetMapping AppendBulkInsertOperation( { foreach (var modification in modificationCommands) { - AppendInsertOperationWithServerKeys( + AppendInsertSingleRowWithOutputInto( commandStringBuilder, modification, keyOperations, readOperations, commandPosition++, out var localRequiresTransaction); requiresTransaction = requiresTransaction || localRequiresTransaction; @@ -133,12 +197,75 @@ public virtual ResultSetMapping AppendBulkInsertOperation( return ResultSetMapping.LastInResultSet; } - return AppendBulkInsertWithServerValues( + // We default to using MERGE ... OUTPUT (without INTO), projecting back a synthetic _Position column to know the order back + // at the client and propagate database-generated values correctly. However, if any triggers are defined, OUTPUT without INTO + // doesn't work. + if (!firstCommand.Entries[0].EntityType.Model.GetRelationalModel().FindTable(firstCommand.TableName, firstCommand.Schema)! + .Triggers.Any()) + { + // MERGE ... OUTPUT returns rows whose ordering isn't guaranteed. So this technique projects back a position int with each row, + // to allow mapping the rows back for value propagation. + resultsContainPositionMapping = true; + + return AppendMergeWithOutput( + commandStringBuilder, modificationCommands, writeOperations, readOperations, out requiresTransaction); + } + + // We have a trigger, so can't use a simple OUTPUT clause. + // If we have an IDENTITY column, then multiple batched SELECT+INSERTs are faster up to a certain threshold (4), and then + // MERGE ... OUTPUT INTO is faster. + if (modificationCommands.Count < MergeIntoMinimumThreshold + && firstCommand.ColumnModifications.All( + o => + !o.IsKey + || !o.IsRead + || o.Property?.GetValueGenerationStrategy(table) == SqlServerValueGenerationStrategy.IdentityColumn)) + { + requiresTransaction = true; + + foreach (var command in modificationCommands) + { + AppendInsertAndSelectOperations(commandStringBuilder, command, commandPosition++, out _); + } + + return ResultSetMapping.LastInResultSet; + } + + return AppendMergeWithOutputInto( commandStringBuilder, modificationCommands, commandPosition, writeOperations, keyOperations, readOperations, out requiresTransaction); } - private ResultSetMapping AppendBulkInsertWithoutServerValues( + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected virtual ResultSetMapping AppendInsertAndSelectOperations( + StringBuilder commandStringBuilder, + IReadOnlyModificationCommand command, + int commandPosition, + out bool requiresTransaction) + { + var name = command.TableName; + var schema = command.Schema; + var operations = command.ColumnModifications; + + var writeOperations = operations.Where(o => o.IsWrite).ToList(); + var readOperations = operations.Where(o => o.IsRead).ToList(); + var keyOperations = operations.Where(o => o.IsKey).ToList(); + + Check.DebugAssert(readOperations.Count > 0, "AppendInsertAndSelectOperations called without any read operations"); + + requiresTransaction = true; + + AppendInsertCommand(commandStringBuilder, name, schema, writeOperations, readOperations: Array.Empty()); + + return AppendSelectAffectedCommand(commandStringBuilder, name, schema, readOperations, keyOperations, commandPosition); + } + + private ResultSetMapping AppendInsertMultipleRows( StringBuilder commandStringBuilder, IReadOnlyList modificationCommands, List writeOperations, @@ -155,8 +282,7 @@ private ResultSetMapping AppendBulkInsertWithoutServerValues( for (var i = 1; i < modificationCommands.Count; i++) { commandStringBuilder.AppendLine(","); - AppendValues( - commandStringBuilder, name, schema, modificationCommands[i].ColumnModifications.Where(o => o.IsWrite).ToList()); + AppendValues(commandStringBuilder, name, schema, modificationCommands[i].ColumnModifications.Where(o => o.IsWrite).ToList()); } commandStringBuilder.AppendLine(SqlGenerationHelper.StatementTerminator); @@ -172,7 +298,36 @@ private ResultSetMapping AppendBulkInsertWithoutServerValues( private const string PositionColumnDeclaration = "[" + PositionColumnName + "] [int]"; private const string FullPositionColumnName = ToInsertTableAlias + "." + PositionColumnName; - private ResultSetMapping AppendBulkInsertWithServerValues( + private ResultSetMapping AppendMergeWithOutput( + StringBuilder commandStringBuilder, + IReadOnlyList modificationCommands, + List writeOperations, + List readOperations, + out bool requiresTransaction) + { + var name = modificationCommands[0].TableName; + var schema = modificationCommands[0].Schema; + + AppendMergeCommandHeader( + commandStringBuilder, + name, + schema, + ToInsertTableAlias, + modificationCommands, + writeOperations, + PositionColumnName); + AppendOutputClause( + commandStringBuilder, + readOperations, + FullPositionColumnName); + commandStringBuilder.AppendLine(SqlGenerationHelper.StatementTerminator); + + requiresTransaction = false; + + return ResultSetMapping.NotLastInResultSet; + } + + private ResultSetMapping AppendMergeWithOutputInto( StringBuilder commandStringBuilder, IReadOnlyList modificationCommands, int commandPosition, @@ -199,7 +354,7 @@ private ResultSetMapping AppendBulkInsertWithServerValues( modificationCommands, writeOperations, PositionColumnName); - AppendOutputClause( + AppendOutputIntoClause( commandStringBuilder, keyOperations, InsertedTableBaseName, @@ -216,7 +371,34 @@ private ResultSetMapping AppendBulkInsertWithServerValues( return ResultSetMapping.NotLastInResultSet; } - private ResultSetMapping AppendBulkInsertWithServerValuesOnly( + private ResultSetMapping AppendInsertMultipleDefaultRows( + StringBuilder commandStringBuilder, + IReadOnlyList modificationCommands, + List writeableOperations, + out bool requiresTransaction) + { + Check.DebugAssert(writeableOperations.Count > 0, $"writeableOperations.Count is {writeableOperations.Count}"); + + var name = modificationCommands[0].TableName; + var schema = modificationCommands[0].Schema; + + AppendInsertCommandHeader(commandStringBuilder, name, schema, writeableOperations); + AppendValuesHeader(commandStringBuilder, writeableOperations); + AppendValues(commandStringBuilder, name, schema, writeableOperations); + for (var i = 1; i < modificationCommands.Count; i++) + { + commandStringBuilder.AppendLine(","); + AppendValues(commandStringBuilder, name, schema, writeableOperations); + } + + commandStringBuilder.AppendLine(SqlGenerationHelper.StatementTerminator); + + requiresTransaction = false; + + return ResultSetMapping.NoResultSet; + } + + private ResultSetMapping AppendInsertMultipleDefaultRowsWithOutputInto( StringBuilder commandStringBuilder, IReadOnlyList modificationCommands, int commandPosition, @@ -230,7 +412,7 @@ private ResultSetMapping AppendBulkInsertWithServerValuesOnly( var name = modificationCommands[0].TableName; var schema = modificationCommands[0].Schema; AppendInsertCommandHeader(commandStringBuilder, name, schema, writableOperations); - AppendOutputClause(commandStringBuilder, keyOperations, InsertedTableBaseName, commandPosition); + AppendOutputIntoClause(commandStringBuilder, keyOperations, InsertedTableBaseName, commandPosition); AppendValuesHeader(commandStringBuilder, writableOperations); AppendValues(commandStringBuilder, name, schema, writableOperations); for (var i = 1; i < modificationCommands.Count; i++) @@ -291,9 +473,7 @@ private void AppendMergeCommandHeader( commandStringBuilder .Append(')') .AppendLine(" ON 1=0") - .AppendLine("WHEN NOT MATCHED THEN"); - - commandStringBuilder + .AppendLine("WHEN NOT MATCHED THEN") .Append("INSERT ") .Append('(') .AppendJoin( @@ -390,37 +570,66 @@ private static string GetTypeNameForCopy(IProperty property) : typeName; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override void AppendReturningClause( + StringBuilder commandStringBuilder, + IReadOnlyList operations) + => AppendOutputClause(commandStringBuilder, operations); + // ReSharper disable once ParameterTypeCanBeEnumerable.Local private void AppendOutputClause( StringBuilder commandStringBuilder, IReadOnlyList operations, - string tableName, - int tableIndex, string? additionalColumns = null) { - commandStringBuilder - .AppendLine() - .Append("OUTPUT ") - .AppendJoin( - operations, - SqlGenerationHelper, - (sb, o, helper) => - { - sb.Append("INSERTED."); - helper.DelimitIdentifier(sb, o.ColumnName); - }); - - if (additionalColumns != null) + if (operations.Count > 0 || additionalColumns is not null) { commandStringBuilder - .Append(", ").Append(additionalColumns); + .AppendLine() + .Append("OUTPUT ") + .AppendJoin( + operations, + SqlGenerationHelper, + (sb, o, helper) => + { + sb.Append("INSERTED."); + helper.DelimitIdentifier(sb, o.ColumnName); + }); + + if (additionalColumns != null) + { + if (operations.Count > 0) + { + commandStringBuilder.Append(", "); + } + + commandStringBuilder.Append(additionalColumns); + } } + } - commandStringBuilder.AppendLine() - .Append("INTO ").Append(tableName).Append(tableIndex); + private void AppendOutputIntoClause( + StringBuilder commandStringBuilder, + IReadOnlyList operations, + string tableName, + int tableIndex, + string? additionalColumns = null) + { + if (operations.Count > 0 || additionalColumns is not null) + { + AppendOutputClause(commandStringBuilder, operations, additionalColumns); + + commandStringBuilder.AppendLine() + .Append("INTO ").Append(tableName).Append(tableIndex); + } } - private ResultSetMapping AppendInsertOperationWithServerKeys( + private ResultSetMapping AppendInsertSingleRowWithOutputInto( StringBuilder commandStringBuilder, IReadOnlyModificationCommand command, IReadOnlyList keyOperations, @@ -437,7 +646,7 @@ private ResultSetMapping AppendInsertOperationWithServerKeys( AppendDeclareTable(commandStringBuilder, InsertedTableBaseName, commandPosition, keyOperations); AppendInsertCommandHeader(commandStringBuilder, name, schema, writeOperations); - AppendOutputClause(commandStringBuilder, keyOperations, InsertedTableBaseName, commandPosition); + AppendOutputIntoClause(commandStringBuilder, keyOperations, InsertedTableBaseName, commandPosition); AppendValuesHeader(commandStringBuilder, writeOperations); AppendValues(commandStringBuilder, name, schema, writeOperations); commandStringBuilder.Append(SqlGenerationHelper.StatementTerminator); diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index adc6154513b..fe4fd6f5b06 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -79,7 +79,8 @@ public void Test_new_annotations_handled_for_entity_types() RelationalAnnotationNames.TphMappingStrategy, RelationalAnnotationNames.TptMappingStrategy, RelationalAnnotationNames.RelationalModel, - RelationalAnnotationNames.ModelDependencies + RelationalAnnotationNames.ModelDependencies, + RelationalAnnotationNames.Triggers // Appears on entity but requires provider-specific support }; // Add a line here if the code generator is supposed to handle this annotation @@ -207,7 +208,8 @@ public void Test_new_annotations_handled_for_properties() RelationalAnnotationNames.TphMappingStrategy, RelationalAnnotationNames.TptMappingStrategy, RelationalAnnotationNames.RelationalModel, - RelationalAnnotationNames.ModelDependencies + RelationalAnnotationNames.ModelDependencies, + RelationalAnnotationNames.Triggers }; var columnMapping = $@"{_nl}.{nameof(RelationalPropertyBuilderExtensions.HasColumnType)}(""default_int_mapping"")"; diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 285844ce4c9..9489f117095 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -824,6 +824,87 @@ public virtual void CheckConstraint_is_only_stored_in_snapshot_once_for_TPH() Assert.Equal("CK_BaseEntity_AlternateId", constraint.Name); }); + [ConditionalFact] + public virtual void Trigger_is_stored_in_snapshot() + => Test( + builder => + { + builder.Entity() + .ToTable(tb => tb.HasTrigger("SomeTrigger").Metadata["foo"] = "bar"); + builder.Ignore(); + }, + AddBoilerPlate( + GetHeading() + + @" + modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty"", b => + { + b.Property(""Id"") + .ValueGeneratedOnAdd() + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + + b.HasKey(""Id""); + + b.ToTable(""EntityWithOneProperty"", t => + { + t.HasTrigger(""SomeTrigger"") + .HasAnnotation(""foo"", ""bar""); + }); + });"), + o => + { + var trigger = Assert.Single(o.GetEntityTypes().Single().GetTriggers()); + Assert.Equal("SomeTrigger", trigger.Name); + }); + + [ConditionalFact] + public virtual void Triggers_and_ExcludeFromMigrations_are_stored_in_snapshot() + => Test( + builder => + { + builder.Entity() + .ToTable(tb => + { + tb.HasTrigger("SomeTrigger1"); + tb.HasTrigger("SomeTrigger2"); + tb.ExcludeFromMigrations(); + }); + builder.Ignore(); + }, + AddBoilerPlate( + GetHeading() + + @" + modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty"", b => + { + b.Property(""Id"") + .ValueGeneratedOnAdd() + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + + b.HasKey(""Id""); + + b.ToTable(""EntityWithOneProperty"", t => + { + t.ExcludeFromMigrations(); + + t.HasTrigger(""SomeTrigger1""); + t.HasTrigger(""SomeTrigger2""); + }); + });"), + o => + { + var entityType = Assert.Single(o.GetEntityTypes()); + + Assert.True(entityType.IsTableExcludedFromMigrations()); + + Assert.Collection( + entityType.GetTriggers().OrderBy(t => t.Name), + t => Assert.Equal("SomeTrigger1", t.Name), + t => Assert.Equal("SomeTrigger2", t.Name)); + }); + [ConditionalFact] public virtual void ProductVersion_is_stored_in_snapshot() { diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 166946c8d5e..03cdc62567c 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -3495,6 +3495,187 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } } + [ConditionalFact] + public void Triggers() + => Test( + new TriggersContext(), + new CompiledModelCodeGenerationOptions(), + code => Assert.Collection( + code, + c => AssertFileContents( + "TriggersContextModel.cs", + @"// +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + [DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.TriggersContext))] + public partial class TriggersContextModel : RuntimeModel + { + static TriggersContextModel() + { + var model = new TriggersContextModel(); + model.Initialize(); + model.Customize(); + _instance = model; + } + + private static TriggersContextModel _instance; + public static IModel Instance => _instance; + + partial void Initialize(); + + partial void Customize(); + } +} +", + c), + c => AssertFileContents( + "TriggersContextModelBuilder.cs", + @"// +using System; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + public partial class TriggersContextModel + { + partial void Initialize() + { + var data = DataEntityType.Create(this); + + DataEntityType.CreateAnnotations(data); + + AddAnnotation(""Relational:MaxIdentifierLength"", 128); + AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + } + } +} +", + c), + c => AssertFileContents( + "DataEntityType.cs", + @"// +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + internal partial class DataEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+Data"", + typeof(CSharpRuntimeModelCodeGeneratorTest.Data), + baseEntityType); + + var id = runtimeEntityType.AddProperty( + ""Id"", + typeof(int), + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw); + id.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + + var blob = runtimeEntityType.AddProperty( + ""Blob"", + typeof(byte[]), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + blob.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var key = runtimeEntityType.AddKey( + new[] { id }); + runtimeEntityType.SetPrimaryKey(key); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + var triggers = new SortedDictionary(); + + var trigger1 = new RuntimeTrigger( + runtimeEntityType, + ""Trigger1"", + ""Trigger1"", + ""Data"", + null); + + triggers[""Trigger1""] = trigger1; + + var trigger2 = new RuntimeTrigger( + runtimeEntityType, + ""Trigger2"", + ""Trigger2"", + ""Data"", + null); + + triggers[""Trigger2""] = trigger2; + + runtimeEntityType.AddAnnotation(""Relational:Triggers"", triggers); + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); + runtimeEntityType.AddAnnotation(""Relational:Schema"", null); + runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); + runtimeEntityType.AddAnnotation(""Relational:TableName"", ""Data""); + runtimeEntityType.AddAnnotation(""Relational:ViewName"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewSchema"", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", + c)), + model => + { + var dataEntity = model.GetEntityTypes().Single(); + + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => dataEntity.GetCheckConstraints()).Message); + }); + + public class TriggersContext : SqlServerContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity( + eb => + { + eb.Property("Id"); + eb.HasKey("Id"); + + eb.ToTable( + tb => + { + tb.HasTrigger("Trigger1"); + tb.HasTrigger("Trigger2"); + }); + }); + } + } + [ConditionalFact] public void Sqlite() => Test( diff --git a/test/EFCore.Relational.Specification.Tests/TransactionTestBase.cs b/test/EFCore.Relational.Specification.Tests/TransactionTestBase.cs index 2d2beb2d737..5d9d4da8faa 100644 --- a/test/EFCore.Relational.Specification.Tests/TransactionTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/TransactionTestBase.cs @@ -40,7 +40,7 @@ public virtual async Task SaveChanges_can_be_used_with_no_transaction(bool async context.Add( new TransactionCustomer { Id = -77, Name = "Bobble" }); - context.Entry(context.Set().OrderBy(c => c.Id).Last()).State = EntityState.Added; + context.Entry(context.Set().OrderBy(c => c.Id).Last()).State = EntityState.Added; if (async) { @@ -1290,7 +1290,7 @@ public virtual async Task SaveChanges_can_be_used_with_no_savepoint(bool async) } context.Add(new TransactionCustomer { Id = -78, Name = "Hobble" }); - context.Add(new TransactionCustomer { Id = 1, Name = "Gobble" }); // Cause SaveChanges failure + context.Add(new TransactionOrder { Id = 100 }); // Cause SaveChanges failure if (async) { diff --git a/test/EFCore.Relational.Specification.Tests/Update/StoreValueGenerationFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Update/StoreValueGenerationFixtureBase.cs index b0b27159638..b35fea5b9ad 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/StoreValueGenerationFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/StoreValueGenerationFixtureBase.cs @@ -13,6 +13,8 @@ public abstract class StoreValueGenerationFixtureBase : SharedStoreFixtureBase(); + foreach (var name in new[] { nameof(StoreValueGenerationContext.WithNoDatabaseGenerated), @@ -28,15 +30,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con foreach (var name in new[] { nameof(StoreValueGenerationContext.WithSomeDatabaseGenerated), - nameof(StoreValueGenerationContext.WithSomeDatabaseGenerated2), - nameof(StoreValueGenerationContext.WithAllDatabaseGenerated), - nameof(StoreValueGenerationContext.WithAllDatabaseGenerated2) + nameof(StoreValueGenerationContext.WithSomeDatabaseGenerated2) }) { modelBuilder .SharedTypeEntity(name) .Property(w => w.Data1) - .HasComputedColumnSql("80"); + .HasComputedColumnSql(sqlGenerationHelper.DelimitIdentifier(nameof(StoreValueGenerationData.Data2)) + " + 1"); } foreach (var name in new[] @@ -45,6 +45,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con nameof(StoreValueGenerationContext.WithAllDatabaseGenerated2) }) { + modelBuilder + .SharedTypeEntity(name) + .Property(w => w.Data1) + .HasComputedColumnSql("80"); + modelBuilder .SharedTypeEntity(name) .Property(w => w.Data2) diff --git a/test/EFCore.Relational.Specification.Tests/Update/StoreValueGenerationTestBase.cs b/test/EFCore.Relational.Specification.Tests/Update/StoreValueGenerationTestBase.cs index 5b89e789ace..007f32d838c 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/StoreValueGenerationTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/StoreValueGenerationTestBase.cs @@ -336,13 +336,13 @@ protected virtual bool ShouldCreateImplicitTransaction( return true; } - // Deletes don't ever need to bring back database-generated values - if (firstOperationType == EntityState.Deleted) + // Deletes don't ever need to bring back database-generated values, and inserts use the RETURNING clause - no transaction needed + if (firstOperationType is EntityState.Deleted or EntityState.Added) { return false; } - // By default, assume that fetching back database-generated values requires a transaction + // Fetching back database-generated values from an update requires a transaction return generatedValues != GeneratedValues.None; } diff --git a/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs b/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs index 14977de9726..8243c421905 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs @@ -74,65 +74,17 @@ public virtual void AppendDeleteOperation_creates_full_delete_command_text_with_ } [ConditionalFact] - public virtual void AppendInsertOperation_appends_insert_and_select_and_where_if_store_generated_columns_exist() + public virtual void AppendInsertOperation_insert_if_store_generated_columns_exist() { var stringBuilder = new StringBuilder(); var command = CreateInsertCommand(); CreateSqlGenerator().AppendInsertOperation(stringBuilder, command, 0); - AppendInsertOperation_appends_insert_and_select_and_where_if_store_generated_columns_exist_verification(stringBuilder); + AppendInsertOperation_insert_if_store_generated_columns_exist_verification(stringBuilder); } - protected virtual void AppendInsertOperation_appends_insert_and_select_and_where_if_store_generated_columns_exist_verification( - StringBuilder stringBuilder) - => Assert.Equal( - "INSERT INTO " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + " (" - + OpenDelimiter - + "Name" - + CloseDelimiter - + ", " - + OpenDelimiter - + "Quacks" - + CloseDelimiter - + ", " - + OpenDelimiter - + "ConcurrencyToken" - + CloseDelimiter - + ")" - + Environment.NewLine - + "VALUES (@p0, @p1, @p2);" - + Environment.NewLine - + "SELECT " - + OpenDelimiter - + "Id" - + CloseDelimiter - + ", " - + OpenDelimiter - + "Computed" - + CloseDelimiter - + "" - + Environment.NewLine - + "FROM " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + "" - + Environment.NewLine - + "WHERE " - + RowsAffected - + " = 1 AND " - + GetIdentityWhereCondition("Id") - + ";" - + Environment.NewLine - + Environment.NewLine, - stringBuilder.ToString()); + protected abstract void AppendInsertOperation_insert_if_store_generated_columns_exist_verification(StringBuilder stringBuilder); [ConditionalFact] public virtual void @@ -173,217 +125,56 @@ public virtual void } [ConditionalFact] - public virtual void AppendInsertOperation_appends_insert_and_select_store_generated_columns_but_no_identity() + public virtual void AppendInsertOperation_for_store_generated_columns_but_no_identity() { var stringBuilder = new StringBuilder(); var command = CreateInsertCommand(false); CreateSqlGenerator().AppendInsertOperation(stringBuilder, command, 0); - AppendInsertOperation_appends_insert_and_select_store_generated_columns_but_no_identity_verification(stringBuilder); + AppendInsertOperation_for_store_generated_columns_but_no_identity_verification(stringBuilder); } - protected virtual void AppendInsertOperation_appends_insert_and_select_store_generated_columns_but_no_identity_verification( - StringBuilder stringBuilder) - => Assert.Equal( - "INSERT INTO " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + " (" - + OpenDelimiter - + "Id" - + CloseDelimiter - + ", " - + OpenDelimiter - + "Name" - + CloseDelimiter - + ", " - + OpenDelimiter - + "Quacks" - + CloseDelimiter - + ", " - + OpenDelimiter - + "ConcurrencyToken" - + CloseDelimiter - + ")" - + Environment.NewLine - + "VALUES (@p0, @p1, @p2, @p3);" - + Environment.NewLine - + "SELECT " - + OpenDelimiter - + "Computed" - + CloseDelimiter - + "" - + Environment.NewLine - + "FROM " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + "" - + Environment.NewLine - + "WHERE " - + RowsAffected - + " = 1 AND " - + OpenDelimiter - + "Id" - + CloseDelimiter - + " = @p0;" - + Environment.NewLine - + Environment.NewLine, - stringBuilder.ToString()); + protected abstract void AppendInsertOperation_for_store_generated_columns_but_no_identity_verification(StringBuilder stringBuilder); [ConditionalFact] - public virtual void AppendInsertOperation_appends_insert_and_select_for_only_identity() + public virtual void AppendInsertOperation_for_only_identity() { var stringBuilder = new StringBuilder(); var command = CreateInsertCommand(true, false); CreateSqlGenerator().AppendInsertOperation(stringBuilder, command, 0); - AppendInsertOperation_appends_insert_and_select_for_only_identity_verification(stringBuilder); + AppendInsertOperation_for_only_identity_verification(stringBuilder); } - protected virtual void AppendInsertOperation_appends_insert_and_select_for_only_identity_verification(StringBuilder stringBuilder) - => Assert.Equal( - "INSERT INTO " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + " (" - + OpenDelimiter - + "Name" - + CloseDelimiter - + ", " - + OpenDelimiter - + "Quacks" - + CloseDelimiter - + ", " - + OpenDelimiter - + "ConcurrencyToken" - + CloseDelimiter - + ")" - + Environment.NewLine - + "VALUES (@p0, @p1, @p2);" - + Environment.NewLine - + "SELECT " - + OpenDelimiter - + "Id" - + CloseDelimiter - + "" - + Environment.NewLine - + "FROM " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + "" - + Environment.NewLine - + "WHERE " - + RowsAffected - + " = 1 AND " - + GetIdentityWhereCondition("Id") - + ";" - + Environment.NewLine - + Environment.NewLine, - stringBuilder.ToString()); + protected abstract void AppendInsertOperation_for_only_identity_verification(StringBuilder stringBuilder); [ConditionalFact] - public virtual void AppendInsertOperation_appends_insert_and_select_for_all_store_generated_columns() + public virtual void AppendInsertOperation_for_all_store_generated_columns() { var stringBuilder = new StringBuilder(); var command = CreateInsertCommand(true, true, true); CreateSqlGenerator().AppendInsertOperation(stringBuilder, command, 0); - AppendInsertOperation_appends_insert_and_select_for_all_store_generated_columns_verification(stringBuilder); + AppendInsertOperation_for_all_store_generated_columns_verification(stringBuilder); } - protected virtual void AppendInsertOperation_appends_insert_and_select_for_all_store_generated_columns_verification( - StringBuilder stringBuilder) - => Assert.Equal( - "INSERT INTO " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + "" - + Environment.NewLine - + "DEFAULT VALUES;" - + Environment.NewLine - + "SELECT " - + OpenDelimiter - + "Id" - + CloseDelimiter - + ", " - + OpenDelimiter - + "Computed" - + CloseDelimiter - + "" - + Environment.NewLine - + "FROM " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + "" - + Environment.NewLine - + "WHERE " - + RowsAffected - + " = 1 AND " - + GetIdentityWhereCondition("Id") - + ";" - + Environment.NewLine - + Environment.NewLine, - stringBuilder.ToString()); + protected abstract void AppendInsertOperation_for_all_store_generated_columns_verification(StringBuilder stringBuilder); [ConditionalFact] - public virtual void AppendInsertOperation_appends_insert_and_select_for_only_single_identity_columns() + public virtual void AppendInsertOperation_for_only_single_identity_columns() { var stringBuilder = new StringBuilder(); - var command = CreateInsertCommand(true, false, true); + var command = CreateInsertCommand(identityKey: true, isComputed: false, defaultsOnly: true); CreateSqlGenerator().AppendInsertOperation(stringBuilder, command, 0); - AppendInsertOperation_appends_insert_and_select_for_only_single_identity_columns_verification(stringBuilder); + AppendInsertOperation_for_only_single_identity_columns_verification(stringBuilder); } - protected virtual void AppendInsertOperation_appends_insert_and_select_for_only_single_identity_columns_verification( - StringBuilder stringBuilder) - => Assert.Equal( - "INSERT INTO " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + "" - + Environment.NewLine - + "DEFAULT VALUES;" - + Environment.NewLine - + "SELECT " - + OpenDelimiter - + "Id" - + CloseDelimiter - + "" - + Environment.NewLine - + "FROM " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + "" - + Environment.NewLine - + "WHERE " - + RowsAffected - + " = 1 AND " - + GetIdentityWhereCondition("Id") - + ";" - + Environment.NewLine - + Environment.NewLine, - stringBuilder.ToString()); + protected abstract void AppendInsertOperation_for_only_single_identity_columns_verification(StringBuilder stringBuilder); [ConditionalFact] public virtual void AppendUpdateOperation_appends_update_and_select_if_store_generated_columns_exist() @@ -652,6 +443,7 @@ protected IModificationCommand CreateInsertCommand(bool identityKey = true, bool var model = GetDuckModel(); var stateManager = TestHelpers.CreateContextServices(model).GetRequiredService(); var entry = stateManager.GetOrCreateEntry(new Duck()); + entry.SetEntityState(EntityState.Added); var generator = new ParameterNameGenerator(); var duckType = model.FindEntityType(typeof(Duck)); @@ -685,7 +477,7 @@ protected IModificationCommand CreateInsertCommand(bool identityKey = true, bool columnModifications = columnModifications.Where(c => !c.IsWrite).ToArray(); } - return CreateModificationCommand("Ducks", Schema, columnModifications, false); + return CreateModificationCommand("Ducks", Schema, entry, columnModifications, false); } protected IModificationCommand CreateUpdateCommand(bool isComputed = true, bool concurrencyToken = true) @@ -693,6 +485,7 @@ protected IModificationCommand CreateUpdateCommand(bool isComputed = true, bool var model = GetDuckModel(); var stateManager = TestHelpers.CreateContextServices(model).GetRequiredService(); var entry = stateManager.GetOrCreateEntry(new Duck()); + entry.SetEntityState(EntityState.Modified); var generator = new ParameterNameGenerator(); var duckType = model.FindEntityType(typeof(Duck)); @@ -721,7 +514,7 @@ protected IModificationCommand CreateUpdateCommand(bool isComputed = true, bool concurrencyProperty.GetTableColumnMappings().Single().TypeMapping, false, true, false, concurrencyToken, true) }; - return CreateModificationCommand("Ducks", Schema, columnModifications, false); + return CreateModificationCommand("Ducks", Schema, entry, columnModifications, false); } protected IModificationCommand CreateDeleteCommand(bool concurrencyToken = true) @@ -729,6 +522,7 @@ protected IModificationCommand CreateDeleteCommand(bool concurrencyToken = true) var model = GetDuckModel(); var stateManager = TestHelpers.CreateContextServices(model).GetRequiredService(); var entry = stateManager.GetOrCreateEntry(new Duck()); + entry.SetEntityState(EntityState.Deleted); var generator = new ParameterNameGenerator(); var duckType = model.FindEntityType(typeof(Duck)); @@ -745,7 +539,7 @@ protected IModificationCommand CreateDeleteCommand(bool concurrencyToken = true) concurrencyProperty.GetTableColumnMappings().Single().TypeMapping, false, false, false, concurrencyToken, true) }; - return CreateModificationCommand("Ducks", Schema, columnModifications, false); + return CreateModificationCommand("Ducks", Schema, entry, columnModifications, false); } protected abstract TestHelpers TestHelpers { get; } @@ -769,6 +563,7 @@ protected class Duck private IModificationCommand CreateModificationCommand( string name, string schema, + InternalEntityEntry entry, IReadOnlyList columnModifications, bool sensitiveLoggingEnabled) { @@ -777,6 +572,8 @@ private IModificationCommand CreateModificationCommand( var modificationCommand = CreateMutableModificationCommandFactory().CreateModificationCommand( modificationCommandParameters); + modificationCommand.AddEntry(entry, mainEntry: true); + foreach (var columnModification in columnModifications) { modificationCommand.AddColumnModification(columnModification); diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index 9ce26453f6b..1666ad475ca 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -2446,6 +2446,20 @@ public virtual void Detects_duplicate_column_orders() VerifyWarning(definition.GenerateMessage("Animal", "'Id', 'Name'"), modelBuilder, LogLevel.Error); } + [ConditionalFact] + public virtual void Detects_triggers_on_unmapped_entity_types() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity( + x => + { + x.ToTable(tb => tb.HasTrigger("Animal_Trigger")); + x.ToTable(name: null); + }); + + VerifyError(RelationalStrings.TriggerOnUnmappedEntityType("Animal_Trigger", "Animal"), modelBuilder); + } + protected override void SetBaseType(IMutableEntityType entityType, IMutableEntityType baseEntityType) { base.SetBaseType(entityType, baseEntityType); diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs index 1959792ec76..5a5878377ad 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs @@ -651,6 +651,160 @@ public void Base_check_constraint_overrides_derived_one_after_base_is_set() Assert.Same(baseEntityType.GetCheckConstraints().Single(), derivedEntityType.GetCheckConstraints().Single()); } + [ConditionalFact] + public void Can_create_trigger() + { + var modelBuilder = CreateConventionModelBuilder(); + var entityType = modelBuilder.Entity().Metadata; + + modelBuilder + .Entity() + .ToTable(tb => tb.HasTrigger("Customer_Trigger")); + + var trigger = entityType.FindTrigger("Customer_Trigger"); + + Assert.NotNull(trigger); + Assert.Same(entityType, trigger.EntityType); + Assert.Equal("Customer_Trigger", trigger.ModelName); + Assert.Equal("Customer_Trigger", trigger.Name); + } + + [ConditionalFact] + public void Can_create_trigger_with_duplicate_name_replaces_existing() + { + var modelBuilder = CreateConventionModelBuilder(); + var entityType = modelBuilder.Entity().Metadata; + + modelBuilder + .Entity() + .ToTable(tb => tb.HasTrigger("Customer_Trigger").HasName("Table1")); + + modelBuilder + .Entity() + .ToTable(tb => tb.HasTrigger("Customer_Trigger").HasName("Table2")); + + var trigger = entityType.FindTrigger("Customer_Trigger"); + + Assert.NotNull(trigger); + Assert.Equal(entityType, trigger.EntityType); + Assert.Equal("Customer_Trigger", trigger.ModelName); + Assert.Equal("Table2", trigger.Name); + } + + [ConditionalFact] + public void Can_access_trigger() + { + var typeBuilder = CreateBuilder().Entity(typeof(Splot), ConfigurationSource.Convention); + IReadOnlyEntityType entityType = typeBuilder.Metadata; + + Assert.NotNull(typeBuilder.HasTrigger("Splew", "Table1", "dbo")); + Assert.Equal("Splew", entityType.GetTriggers().Single().ModelName); + Assert.Equal("Table1", entityType.GetTriggers().Single().TableName); + + Assert.NotNull(typeBuilder.HasTrigger("Splew", "Table2", "dbo", fromDataAnnotation: true)); + Assert.Equal("Splew", entityType.GetTriggers().Single().ModelName); + Assert.Equal("Table2", entityType.GetTriggers().Single().TableName); + + Assert.Null(typeBuilder.HasTrigger("Splew", "Table1", "dbo")); + Assert.Equal("Splew", entityType.GetTriggers().Single().ModelName); + Assert.Equal("Table2", entityType.GetTriggers().Single().TableName); + } + + [ConditionalFact] + public void Base_trigger_overrides_derived_one() + { + var modelBuilder = CreateBuilder(); + + var derivedBuilder = modelBuilder.Entity(typeof(Splow), ConfigurationSource.Convention); + IReadOnlyEntityType derivedEntityType = derivedBuilder.Metadata; + + Assert.NotNull( + derivedBuilder.HasTrigger("Splew", "Table1", "dbo", fromDataAnnotation: true) + .HasName("Splow_Trigger", fromDataAnnotation: true)); + Assert.Equal("Splew", derivedEntityType.GetTriggers().Single().ModelName); + Assert.Equal("Splow_Trigger", derivedEntityType.GetTriggers().Single().Name); + Assert.Equal("Table1", derivedEntityType.GetTriggers().Single().TableName); + Assert.Equal("dbo", derivedEntityType.GetTriggers().Single().TableSchema); + + Assert.True(derivedBuilder.CanHaveTrigger("Splew", "Table1", "dbo")); + Assert.True(derivedBuilder.CanHaveTrigger("Splew", "Table2", "dbo", fromDataAnnotation: true)); + Assert.True(derivedBuilder.CanHaveTrigger("Splew", "Table1", "dbo2", fromDataAnnotation: true)); + Assert.False(derivedBuilder.CanHaveTrigger("Splew", "Table2", "dbo")); + Assert.False(derivedBuilder.CanHaveTrigger("Splew", "Table1", "dbo2")); + Assert.True(derivedBuilder.CanHaveTrigger("Splot", "Table2", "dbo2")); + + var baseBuilder = modelBuilder.Entity(typeof(Splot), ConfigurationSource.DataAnnotation); + IReadOnlyEntityType baseEntityType = baseBuilder.Metadata; + + Assert.True(baseBuilder.CanHaveTrigger("Splew", "Table1", "dbo")); + Assert.True(baseBuilder.CanHaveTrigger("Splew", "Table2", "dbo", fromDataAnnotation: true)); + Assert.True(baseBuilder.CanHaveTrigger("Splew", "Table1", "dbo2", fromDataAnnotation: true)); + Assert.False(baseBuilder.CanHaveTrigger("Splew", "Table2", "dbo")); + Assert.False(baseBuilder.CanHaveTrigger("Splew", "Table1", "dbo2")); + Assert.True(baseBuilder.CanHaveTrigger("Splot", "Table2", "dbo2")); + + Assert.Null(baseBuilder.HasTrigger("Splew", "Table2", "dbo")); + Assert.Empty(baseEntityType.GetTriggers()); + Assert.Equal("Table1", derivedEntityType.GetTriggers().Single().TableName); + + Assert.NotNull( + baseBuilder.HasTrigger("Splew", "Table1", "dbo", fromDataAnnotation: true) + .HasName("Another_Splot_Trigger", fromDataAnnotation: true)); + Assert.Equal("Splew", baseEntityType.GetTriggers().Single().ModelName); + Assert.Equal("Table1", baseEntityType.GetTriggers().Single().TableName); + Assert.Equal("Another_Splot_Trigger", baseEntityType.GetTriggers().Single().Name); + + derivedBuilder.HasBaseType((EntityType)baseEntityType, ConfigurationSource.Convention); + + Assert.Null( + baseBuilder.HasTrigger("Splew", "Table1", "dbo", fromDataAnnotation: true) + .HasName("Yet_Another_Splot_Trigger")); + Assert.Equal("Splew", baseEntityType.GetTriggers().Single().ModelName); + Assert.Equal("Table1", baseEntityType.GetTriggers().Single().TableName); + Assert.Equal("Another_Splot_Trigger", baseEntityType.GetTriggers().Single().Name); + Assert.Empty(derivedEntityType.GetDeclaredTriggers()); + Assert.Same(baseEntityType.GetTriggers().Single(), derivedEntityType.GetTriggers().Single()); + } + + [ConditionalFact] + public void Base_trigger_overrides_derived_one_after_base_is_set() + { + var modelBuilder = CreateBuilder(); + + var derivedBuilder = modelBuilder.Entity(typeof(Splow), ConfigurationSource.Convention); + Assert.NotNull(derivedBuilder.HasBaseType((string)null, ConfigurationSource.DataAnnotation)); + IReadOnlyEntityType derivedEntityType = derivedBuilder.Metadata; + + Assert.NotNull( + derivedBuilder.HasTrigger("Splew", "Table1", "dbo", fromDataAnnotation: true) + .HasName("Splow_Trigger", fromDataAnnotation: true)); + Assert.Equal("Splew", derivedEntityType.GetTriggers().Single().ModelName); + Assert.Equal("Table1", derivedEntityType.GetTriggers().Single().TableName); + Assert.Equal("Splow_Trigger", derivedEntityType.GetTriggers().Single().Name); + + var baseBuilder = modelBuilder.Entity(typeof(Splot), ConfigurationSource.Convention); + IReadOnlyEntityType baseEntityType = baseBuilder.Metadata; + Assert.Null(derivedEntityType.BaseType); + + Assert.NotNull( + baseBuilder.HasTrigger("Splew", "Table1", "dbo", fromDataAnnotation: true) + .HasName("Splot_Trigger", fromDataAnnotation: true)); + Assert.Equal("Splew", baseEntityType.GetTriggers().Single().ModelName); + Assert.Equal("Table1", baseEntityType.GetTriggers().Single().TableName); + Assert.Equal("Splot_Trigger", baseEntityType.GetTriggers().Single().Name); + + Assert.NotNull(derivedBuilder.HasBaseType((EntityType)baseEntityType, ConfigurationSource.DataAnnotation)); + + Assert.Null( + baseBuilder.HasTrigger("Splew", "Table1", "dbo", fromDataAnnotation: true) + .HasName("Splew_Trigger")); + Assert.Equal("Splew", baseEntityType.GetTriggers().Single().ModelName); + Assert.Equal("Table1", baseEntityType.GetTriggers().Single().TableName); + Assert.Equal("Splot_Trigger", baseEntityType.GetTriggers().Single().Name); + Assert.Empty(derivedEntityType.GetDeclaredTriggers()); + Assert.Same(baseEntityType.GetTriggers().Single(), derivedEntityType.GetTriggers().Single()); + } + [ConditionalFact] public void Can_set_discriminator_value_using_property_expression() { diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index 3d95323f8cc..c657323d7ac 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -639,6 +639,11 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) Assert.False(customerTable.IsOptional(extraSpecialCustomerType)); } + var orderTrigger = Assert.Single(orderType.GetTriggers()); + Assert.Equal("Order_Trigger", orderTrigger.Name); + Assert.Equal("Order", orderTrigger.TableName); + Assert.Null(orderTrigger.TableSchema); + var customerPk = specialCustomerType.FindPrimaryKey(); if (mapping == Mapping.TPT) @@ -1030,6 +1035,11 @@ private IRelationalModel CreateTestModel(bool mapToTables = false, bool mapToVie { ob.ToTable("Order"); } + + if (mapToTables || !mapToViews) + { + ob.ToTable(o => o.HasTrigger("Order_Trigger")); + } }); modelBuilder.Entity( diff --git a/test/EFCore.Relational.Tests/Metadata/TriggerTest.cs b/test/EFCore.Relational.Tests/Metadata/TriggerTest.cs new file mode 100644 index 00000000000..d4c5c3dc7c3 --- /dev/null +++ b/test/EFCore.Relational.Tests/Metadata/TriggerTest.cs @@ -0,0 +1,102 @@ +// 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; + +public class TriggerTest +{ + [ConditionalFact] + public void Can_create_trigger_for_default_table() + { + var modelBuilder = CreateConventionModelBuilder(); + var entityType = modelBuilder.Entity().Metadata; + + modelBuilder + .Entity() + .ToTable(tb => tb.HasTrigger("Customer_Trigger")); + + var trigger = entityType.FindTrigger("Customer_Trigger"); + + Assert.NotNull(trigger); + Assert.Same(entityType, trigger.EntityType); + Assert.Equal("Customer_Trigger", trigger.Name); + Assert.Equal("Customer", trigger.TableName); + Assert.Null(trigger.TableSchema); + Assert.Equal(ConfigurationSource.Explicit, ((IConventionTrigger)trigger).GetConfigurationSource()); + } + + [ConditionalFact] + public void Can_create_trigger_for_specific_table() + { + var modelBuilder = CreateConventionModelBuilder(); + var entityType = modelBuilder.Entity().Metadata; + + modelBuilder + .Entity() + .ToTable("CustomerTable", "dbo", tb => tb.HasTrigger("Customer_Trigger")); + + var trigger = entityType.FindTrigger("Customer_Trigger"); + + Assert.NotNull(trigger); + Assert.Same(entityType, trigger.EntityType); + Assert.Equal("Customer_Trigger", trigger.Name); + Assert.Equal("CustomerTable", trigger.TableName); + Assert.Equal("dbo", trigger.TableSchema); + Assert.Equal(ConfigurationSource.Explicit, ((IConventionTrigger)trigger).GetConfigurationSource()); + } + + [ConditionalFact] + public void Create_trigger_on_unmapped_entity_type_throws() + { + var modelBuilder = CreateConventionModelBuilder(); + + var exception = Assert.Throws(() => modelBuilder + .Entity() + .ToTable(null, tb => tb.HasTrigger("Customer_Trigger"))); + + Assert.Equal(RelationalStrings.TriggerOnUnmappedEntityType("Customer_Trigger", "Customer"), exception.Message); + } + + [ConditionalFact] + public void AddTrigger_with_duplicate_names_throws_exception() + { + var entityTypeBuilder = CreateConventionModelBuilder().Entity(); + var entityType = entityTypeBuilder.Metadata; + + entityType.AddTrigger("SomeTrigger", "SomeTable", null); + + Assert.Equal( + RelationalStrings.DuplicateTrigger("SomeTrigger", entityType.DisplayName(), entityType.DisplayName()), + Assert.Throws( + () => entityType.AddTrigger("SomeTrigger", "SomeTable")).Message); + } + + [ConditionalFact] + public void RemoveTrigger_returns_trigger_when_trigger_exists() + { + var entityTypeBuilder = CreateConventionModelBuilder().Entity(); + var entityType = entityTypeBuilder.Metadata; + + var constraint = entityType.AddTrigger("SomeTrigger", "SomeTable"); + + Assert.Same(constraint, entityType.RemoveTrigger("SomeTrigger")); + } + + [ConditionalFact] + public void RemoveTrigger_returns_null_when_trigger_is_missing() + { + var entityTypeBuilder = CreateConventionModelBuilder().Entity(); + var entityType = entityTypeBuilder.Metadata; + + Assert.Null(entityType.RemoveTrigger("SomeTrigger")); + } + + protected virtual ModelBuilder CreateConventionModelBuilder() + => RelationalTestHelpers.Instance.CreateConventionBuilder(); + + private class Customer + { + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs index 2e8d706a704..2116ad5afed 100644 --- a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs +++ b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs @@ -59,8 +59,14 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase { typeof(IReadOnlyCheckConstraint), (typeof(IMutableCheckConstraint), typeof(IConventionCheckConstraint), - null, + typeof(IConventionCheckConstraintBuilder), typeof(ICheckConstraint)) + }, + { + typeof(IReadOnlyTrigger), (typeof(IMutableTrigger), + typeof(IConventionTrigger), + typeof(IConventionTriggerBuilder), + typeof(ITrigger)) } }; @@ -81,7 +87,8 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase typeof(IViewColumnMapping), typeof(ITableIndex), typeof(IForeignKeyConstraint), - typeof(IUniqueConstraint) + typeof(IUniqueConstraint), + typeof(ITrigger) }; public override HashSet FluentApiTypes { get; } = new() diff --git a/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTest.cs b/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTest.cs index 1cb16897891..825deece69c 100644 --- a/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTest.cs +++ b/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTest.cs @@ -16,6 +16,48 @@ protected override IUpdateSqlGenerator CreateSqlGenerator() TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()))); + protected override void AppendInsertOperation_insert_if_store_generated_columns_exist_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"INSERT INTO ""dbo"".""Ducks"" (""Name"", ""Quacks"", ""ConcurrencyToken"") +VALUES (@p0, @p1, @p2) +RETURNING ""Id"", ""Computed""; +", + stringBuilder.ToString()); + + protected override void AppendInsertOperation_for_store_generated_columns_but_no_identity_verification( + StringBuilder stringBuilder) + => AssertBaseline( + @"INSERT INTO ""dbo"".""Ducks"" (""Id"", ""Name"", ""Quacks"", ""ConcurrencyToken"") +VALUES (@p0, @p1, @p2, @p3) +RETURNING ""Computed""; +", + stringBuilder.ToString()); + + protected override void AppendInsertOperation_for_only_identity_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"INSERT INTO ""dbo"".""Ducks"" (""Name"", ""Quacks"", ""ConcurrencyToken"") +VALUES (@p0, @p1, @p2) +RETURNING ""Id""; +", + stringBuilder.ToString()); + + protected override void AppendInsertOperation_for_all_store_generated_columns_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"INSERT INTO ""dbo"".""Ducks"" +DEFAULT VALUES +RETURNING ""Id"", ""Computed""; +", + stringBuilder.ToString()); + + protected override void AppendInsertOperation_for_only_single_identity_columns_verification( + StringBuilder stringBuilder) + => AssertBaseline( + @"INSERT INTO ""dbo"".""Ducks"" +DEFAULT VALUES +RETURNING ""Id""; +", + stringBuilder.ToString()); + protected override TestHelpers TestHelpers => RelationalTestHelpers.Instance; @@ -24,4 +66,7 @@ protected override string RowsAffected protected override string Identity => "provider_specific_identity()"; + + private void AssertBaseline(string expected, string actual) + => Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } diff --git a/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs index b2c5db89b7b..77547e4d679 100644 --- a/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs @@ -195,12 +195,11 @@ public override void DatabaseGeneratedAttribute_autogenerates_values_when_set_to @p5='Third Name' (Size = 4000) @p6='0' (Nullable = true) +SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [Sample] ([MaxLengthProperty], [Name], [RowVersion], [AdditionalDetails_Name], [AdditionalDetails_Value], [Details_Name], [Details_Value]) -VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6); -SELECT [Unique_No] -FROM [Sample] -WHERE @@ROWCOUNT = 1 AND [Unique_No] = scope_identity();"); +OUTPUT INSERTED.[Unique_No] +VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6);"); } public override void MaxLengthAttribute_throws_while_inserting_value_longer_than_max_length() @@ -216,12 +215,11 @@ public override void MaxLengthAttribute_throws_while_inserting_value_longer_than @p5='Third Name' (Size = 4000) @p6='0' (Nullable = true) +SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [Sample] ([MaxLengthProperty], [Name], [RowVersion], [AdditionalDetails_Name], [AdditionalDetails_Value], [Details_Name], [Details_Value]) -VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6); -SELECT [Unique_No] -FROM [Sample] -WHERE @@ROWCOUNT = 1 AND [Unique_No] = scope_identity();", +OUTPUT INSERTED.[Unique_No] +VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6);", // @"@p0='VeryVeryVeryVeryVeryVeryLongString' (Size = 4000) @p1='ValidString' (Nullable = false) (Size = 4000) @@ -231,12 +229,11 @@ FROM [Sample] @p5='Third Name' (Size = 4000) @p6='0' (Nullable = true) +SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [Sample] ([MaxLengthProperty], [Name], [RowVersion], [AdditionalDetails_Name], [AdditionalDetails_Value], [Details_Name], [Details_Value]) -VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6); -SELECT [Unique_No] -FROM [Sample] -WHERE @@ROWCOUNT = 1 AND [Unique_No] = scope_identity();"); +OUTPUT INSERTED.[Unique_No] +VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6);"); } public override void StringLengthAttribute_throws_while_inserting_value_longer_than_max_length() @@ -246,21 +243,19 @@ public override void StringLengthAttribute_throws_while_inserting_value_longer_t AssertSql( @"@p0='ValidString' (Size = 16) +SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [Two] ([Data]) -VALUES (@p0); -SELECT [Id], [Timestamp] -FROM [Two] -WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();", +OUTPUT INSERTED.[Id], INSERTED.[Timestamp] +VALUES (@p0);", // @"@p0='ValidButLongString' (Size = 4000) +SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [Two] ([Data]) -VALUES (@p0); -SELECT [Id], [Timestamp] -FROM [Two] -WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();"); +OUTPUT INSERTED.[Id], INSERTED.[Timestamp] +VALUES (@p0);"); } public override void TimestampAttribute_throws_if_value_in_database_changed() diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index e1c16adb004..115f13931a1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -3985,12 +3985,11 @@ public virtual async Task Multilevel_owned_entities_determine_correct_nullabilit AssertSql( @"@p0='BaseEntity13079' (Nullable = false) (Size = 4000) +SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [BaseEntities] ([Discriminator]) -VALUES (@p0); -SELECT [Id] -FROM [BaseEntities] -WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();"); +OUTPUT INSERTED.[Id] +VALUES (@p0);"); } } @@ -9134,8 +9133,8 @@ public virtual async Task Batch_insert_with_sqlvariant_different_types_12482() @p2='String Value' (Size = 12) (DbType = Object) @p3='2020-01-01T00:00:00.0000000' (Nullable = true) (DbType = Object) +SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]); MERGE [BaseEntities] USING ( VALUES (@p0, 0), (@p1, 1), @@ -9144,11 +9143,7 @@ MERGE [BaseEntities] USING ( WHEN NOT MATCHED THEN INSERT ([Value]) VALUES (i.[Value]) -OUTPUT INSERTED.[Id], i._Position -INTO @inserted0; - -SELECT [i].[Id] FROM @inserted0 i -ORDER BY [i].[_Position];"); +OUTPUT INSERTED.[Id], i._Position;"); } } diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerEndToEndTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerEndToEndTest.cs index 7bc02009588..b41d24206a1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerEndToEndTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerEndToEndTest.cs @@ -707,13 +707,13 @@ public async Task Can_save_changes() Assert.Equal(EntityState.Unchanged, db.Entry(toAdd).State); Assert.DoesNotContain(toDelete, db.ChangeTracker.Entries().Select(e => e.Entity)); - Assert.Equal(5, Fixture.TestSqlLoggerFactory.SqlStatements.Count); + Assert.Equal(4, Fixture.TestSqlLoggerFactory.SqlStatements.Count); Assert.Contains("SELECT", Fixture.TestSqlLoggerFactory.SqlStatements[0]); Assert.Contains("SELECT", Fixture.TestSqlLoggerFactory.SqlStatements[1]); Assert.Contains("@p0='" + deletedId, Fixture.TestSqlLoggerFactory.SqlStatements[2]); Assert.Contains("DELETE", Fixture.TestSqlLoggerFactory.SqlStatements[2]); - Assert.Contains("UPDATE", Fixture.TestSqlLoggerFactory.SqlStatements[3]); - Assert.Contains("INSERT", Fixture.TestSqlLoggerFactory.SqlStatements[4]); + Assert.Contains("UPDATE", Fixture.TestSqlLoggerFactory.SqlStatements[2]); + Assert.Contains("INSERT", Fixture.TestSqlLoggerFactory.SqlStatements[3]); var rows = await testDatabase.ExecuteScalarAsync( $"SELECT Count(*) FROM [dbo].[Blog] WHERE Id = {updatedId} AND Name = 'Blog is Updated'"); diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerQueryTriggersTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerQueryTriggersTest.cs index cff6e3c07ba..24ed01dee06 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerQueryTriggersTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerQueryTriggersTest.cs @@ -97,7 +97,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) eb.Property(e => e.StoreUpdated) .HasDefaultValue(0) .ValueGeneratedOnAddOrUpdate(); - eb.ToTable("UpdatedProducts"); + eb.ToTable("UpdatedProducts", tb => tb.HasTrigger("TRG_InsertUpdateProduct")); }); } diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerTriggersTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerTriggersTest.cs index 6243605c772..344e5b10b1a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerTriggersTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerTriggersTest.cs @@ -109,6 +109,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity( eb => { + eb.ToTable(tb => + { + tb.HasTrigger("TRG_InsertProduct"); + tb.HasTrigger("TRG_UpdateProduct"); + tb.HasTrigger("TRG_DeleteProduct"); + }); eb.Property(e => e.Version) .ValueGeneratedOnAddOrUpdate() .IsConcurrencyToken(); diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.cs index b37b7ada9c0..ea50ad74e94 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.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.Data.SqlClient; +using Microsoft.EntityFrameworkCore.SqlServer.Internal; using Microsoft.EntityFrameworkCore.ValueGeneration.Internal; using NetTopologySuite; using NetTopologySuite.Geometries; @@ -792,6 +794,21 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } } + public class BlogContextComputedColumnWithTriggerMetadata : BlogContextComputedColumn + { + public BlogContextComputedColumnWithTriggerMetadata(string databaseName) + : base(databaseName) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity().ToTable(tb => tb.HasTrigger("SomeTrigger")); + } + } + // #6044 [ConditionalFact] public void Insert_and_update_with_computed_column_with_function() @@ -855,7 +872,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) public void Insert_and_update_with_computed_column_with_querying_function() { using var testStore = SqlServerTestStore.CreateInitialized(DatabaseName); - using (var context = new BlogContextComputedColumn(testStore.Name)) + using (var context = new BlogContextComputedColumnWithTriggerMetadata(testStore.Name)) { context.GetService().CreateTables(); @@ -875,7 +892,7 @@ RETURN @FullName try { - using (var context = new BlogContextComputedColumn(testStore.Name)) + using (var context = new BlogContextComputedColumnWithTriggerMetadata(testStore.Name)) { var blog = context.Add( new FullNameBlog { FirstName = "One", LastName = "Unicorn" }).Entity; @@ -885,7 +902,7 @@ RETURN @FullName Assert.Equal("OneUnicorn", blog.FullName); } - using (var context = new BlogContextComputedColumn(testStore.Name)) + using (var context = new BlogContextComputedColumnWithTriggerMetadata(testStore.Name)) { var blog = context.FullNameBlogs.Single(); @@ -898,7 +915,7 @@ RETURN @FullName Assert.Equal("OnePegasus", blog.FullName); } - using (var context = new BlogContextComputedColumn(testStore.Name)) + using (var context = new BlogContextComputedColumnWithTriggerMetadata(testStore.Name)) { var blog1 = context.Add( new FullNameBlog { FirstName = "Hank", LastName = "Unicorn" }).Entity; @@ -913,12 +930,103 @@ RETURN @FullName } finally { - using var context = new BlogContextComputedColumn(testStore.Name); + using var context = new BlogContextComputedColumnWithTriggerMetadata(testStore.Name); + context.Database.ExecuteSqlRaw("ALTER TABLE dbo.FullNameBlogs DROP COLUMN FullName;"); + context.Database.ExecuteSqlRaw("DROP FUNCTION [dbo].[GetFullName];"); + } + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Insert_with_computed_column_with_function_without_metadata_configuration(bool async) + { + using var testStore = SqlServerTestStore.CreateInitialized(DatabaseName); + using (var context = new BlogContextComputedColumn(testStore.Name)) + { + context.GetService().CreateTables(); + + context.Database.ExecuteSqlRaw("ALTER TABLE dbo.FullNameBlogs DROP COLUMN FullName;"); + + context.Database.ExecuteSqlRaw( + @"CREATE FUNCTION [dbo].[GetFullName](@Id int) +RETURNS nvarchar(max) WITH SCHEMABINDING AS +BEGIN + DECLARE @FullName nvarchar(max); + SELECT @FullName = [FirstName] + [LastName] FROM [dbo].[FullNameBlogs] WHERE [Id] = @Id; + RETURN @FullName +END"); + + context.Database.ExecuteSqlRaw("ALTER TABLE dbo.FullNameBlogs ADD FullName AS [dbo].[GetFullName]([Id]); "); + } + + try + { + using (var context = new BlogContextComputedColumn(testStore.Name)) + { + context.Add(new FullNameBlog()); + + var exception = async + ? await Assert.ThrowsAsync(() => context.SaveChangesAsync()) + : Assert.Throws(() => context.SaveChanges()); + + Assert.Equal(SqlServerStrings.SaveChangesFailedBecauseOfComputedColumnWithFunction, exception.Message); + + var sqlException = Assert.IsType(exception.InnerException); + Assert.Equal(4186, sqlException.Number); + } + } + finally + { + using var context = new BlogContextComputedColumnWithTriggerMetadata(testStore.Name); context.Database.ExecuteSqlRaw("ALTER TABLE dbo.FullNameBlogs DROP COLUMN FullName;"); context.Database.ExecuteSqlRaw("DROP FUNCTION [dbo].[GetFullName];"); } } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Insert_with_trigger_without_metadata_configuration(bool async) + { + // Execute an insert against a table which has a trigger, but which haven't identified as such in our metadata. + // This causes a specialized exception to be thrown, directing users to the relevant docs. + using var testStore = SqlServerTestStore.CreateInitialized(DatabaseName); + using (var context = new BlogContextComputedColumn(testStore.Name)) + { + context.GetService().CreateTables(); + + context.Database.ExecuteSqlRaw( + @"CREATE OR ALTER TRIGGER [FullNameBlogs_Trigger] +ON [FullNameBlogs] +FOR INSERT, UPDATE, DELETE AS +BEGIN + IF @@ROWCOUNT = 0 + return +END"); + } + + try + { + using (var context = new BlogContextComputedColumn(testStore.Name)) + { + context.Add(new FullNameBlog()); + + var exception = async + ? await Assert.ThrowsAsync(() => context.SaveChangesAsync()) + : Assert.Throws(() => context.SaveChanges()); + + Assert.Equal(SqlServerStrings.SaveChangesFailedBecauseOfTriggers, exception.Message); + + var sqlException = Assert.IsType(exception.InnerException); + Assert.Equal(334, sqlException.Number); + } + } + finally + { + using var context = new BlogContextComputedColumn(testStore.Name); + context.Database.ExecuteSqlRaw("DROP TRIGGER [FullNameBlogs_Trigger]"); + } + } + [ConditionalFact] public void Insert_with_client_generated_GUID_key() { @@ -1404,4 +1512,6 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) SqlServerTestStore.CreateConnectionString(_databaseName), b => b.UseNetTopologySuite().ApplyConfiguration()); } + + public static IEnumerable IsAsyncData = new[] { new object[] { false }, new object[] { true } }; } diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/SqlServerUpdateSqlGeneratorTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/SqlServerUpdateSqlGeneratorTest.cs index 6f426db0872..2d18a03898d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Update/SqlServerUpdateSqlGeneratorTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Update/SqlServerUpdateSqlGeneratorTest.cs @@ -31,75 +31,50 @@ public void AppendBatchHeader_should_append_SET_NOCOUNT_ON() Assert.Equal("SET NOCOUNT ON;" + Environment.NewLine, sb.ToString()); } - protected override void AppendInsertOperation_appends_insert_and_select_store_generated_columns_but_no_identity_verification( + protected override void AppendInsertOperation_for_store_generated_columns_but_no_identity_verification( StringBuilder stringBuilder) - => Assert.Equal( - "INSERT INTO [dbo].[Ducks] ([Id], [Name], [Quacks], [ConcurrencyToken])" - + Environment.NewLine - + "VALUES (@p0, @p1, @p2, @p3);" - + Environment.NewLine - + "SELECT [Computed]" - + Environment.NewLine - + "FROM [dbo].[Ducks]" - + Environment.NewLine - + "WHERE @@ROWCOUNT = 1 AND [Id] = @p0;" - + Environment.NewLine - + Environment.NewLine, + => AssertBaseline( + @"INSERT INTO [dbo].[Ducks] ([Id], [Name], [Quacks], [ConcurrencyToken]) +OUTPUT INSERTED.[Computed] +VALUES (@p0, @p1, @p2, @p3); +", stringBuilder.ToString()); - protected override void AppendInsertOperation_appends_insert_and_select_and_where_if_store_generated_columns_exist_verification( - StringBuilder stringBuilder) - => Assert.Equal( - "INSERT INTO [dbo].[Ducks] ([Name], [Quacks], [ConcurrencyToken])" - + Environment.NewLine - + "VALUES (@p0, @p1, @p2);" - + Environment.NewLine - + "SELECT [Id], [Computed]" - + Environment.NewLine - + "FROM [dbo].[Ducks]" - + Environment.NewLine - + "WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();" - + Environment.NewLine - + Environment.NewLine, + protected override void AppendInsertOperation_insert_if_store_generated_columns_exist_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"INSERT INTO [dbo].[Ducks] ([Name], [Quacks], [ConcurrencyToken]) +OUTPUT INSERTED.[Id], INSERTED.[Computed] +VALUES (@p0, @p1, @p2); +", stringBuilder.ToString()); - protected override void AppendInsertOperation_appends_insert_and_select_for_only_single_identity_columns_verification( + protected override void AppendInsertOperation_for_only_single_identity_columns_verification( StringBuilder stringBuilder) => AssertBaseline( @"INSERT INTO [dbo].[Ducks] +OUTPUT INSERTED.[Id] DEFAULT VALUES; -SELECT [Id] -FROM [dbo].[Ducks] -WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity(); - ", stringBuilder.ToString()); - protected override void AppendInsertOperation_appends_insert_and_select_for_only_identity_verification(StringBuilder stringBuilder) + protected override void AppendInsertOperation_for_only_identity_verification(StringBuilder stringBuilder) => AssertBaseline( @"INSERT INTO [dbo].[Ducks] ([Name], [Quacks], [ConcurrencyToken]) +OUTPUT INSERTED.[Id] VALUES (@p0, @p1, @p2); -SELECT [Id] -FROM [dbo].[Ducks] -WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity(); - ", stringBuilder.ToString()); - protected override void AppendInsertOperation_appends_insert_and_select_for_all_store_generated_columns_verification( - StringBuilder stringBuilder) + protected override void AppendInsertOperation_for_all_store_generated_columns_verification(StringBuilder stringBuilder) => AssertBaseline( @"INSERT INTO [dbo].[Ducks] +OUTPUT INSERTED.[Id], INSERTED.[Computed] DEFAULT VALUES; -SELECT [Id], [Computed] -FROM [dbo].[Ducks] -WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity(); - ", stringBuilder.ToString()); [ConditionalFact] - public void AppendBulkInsertOperation_appends_insert_if_store_generated_columns_exist() + public void AppendBulkInsertOperation_appends_merge_if_store_generated_columns_exist() { var stringBuilder = new StringBuilder(); var command = CreateInsertCommand(); @@ -108,20 +83,13 @@ public void AppendBulkInsertOperation_appends_insert_if_store_generated_columns_ var grouping = sqlGenerator.AppendBulkInsertOperation(stringBuilder, new[] { command, command }, 0); AssertBaseline( - @"DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]); -MERGE [dbo].[Ducks] USING ( + @"MERGE [dbo].[Ducks] USING ( VALUES (@p0, @p1, @p2, 0), (@p0, @p1, @p2, 1)) AS i ([Name], [Quacks], [ConcurrencyToken], _Position) ON 1=0 WHEN NOT MATCHED THEN INSERT ([Name], [Quacks], [ConcurrencyToken]) VALUES (i.[Name], i.[Quacks], i.[ConcurrencyToken]) -OUTPUT INSERTED.[Id], i._Position -INTO @inserted0; - -SELECT [t].[Id], [t].[Computed] FROM [dbo].[Ducks] t -INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]) -ORDER BY [i].[_Position]; - +OUTPUT INSERTED.[Id], INSERTED.[Computed], i._Position; ", stringBuilder.ToString()); Assert.Equal(ResultSetMapping.NotLastInResultSet, grouping); @@ -178,12 +146,11 @@ public void AppendBulkInsertOperation_appends_insert_if_no_store_generated_colum var sqlGenerator = (ISqlServerUpdateSqlGenerator)CreateSqlGenerator(); var grouping = sqlGenerator.AppendBulkInsertOperation(stringBuilder, new[] { command, command }, 0); - var expectedText = @"INSERT INTO [dbo].[Ducks] -DEFAULT VALUES; + var expectedText = @"INSERT INTO [dbo].[Ducks] ([Computed]) +VALUES (DEFAULT), +(DEFAULT); "; - AssertBaseline( - expectedText + expectedText, - stringBuilder.ToString()); + AssertBaseline(expectedText, stringBuilder.ToString()); Assert.Equal(ResultSetMapping.NoResultSet, grouping); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationIdentitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationIdentitySqlServerTest.cs index 7bb46716dd1..ec27e33ef3a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationIdentitySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationIdentitySqlServerTest.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.StoreValueGenerationModel; + namespace Microsoft.EntityFrameworkCore.Update; #nullable enable @@ -17,12 +19,31 @@ public StoreValueGenerationIdentitySqlServerTest( // Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - protected override int ShouldExecuteInNumberOfCommands( + protected override bool ShouldCreateImplicitTransaction( EntityState firstOperationType, EntityState? secondOperationType, GeneratedValues generatedValues, - bool withDatabaseGenerated) - => secondOperationType is null ? 1 : 2; + bool withSameEntityType) + { + // Updates with generated values currently use SELECT to retrieve them, and so require transactions + if (firstOperationType == EntityState.Modified && generatedValues != GeneratedValues.None) + { + return true; + } + + // For multiple operations, we specifically optimize multiple insertions of the same entity type with a single command (e.g. MERGE) + // (as long as there are writable columns) + if (firstOperationType is EntityState.Added + && secondOperationType is EntityState.Added + && withSameEntityType + && generatedValues != GeneratedValues.All) + { + return false; + } + + // Other single operations should never be in a transaction (always executed in a single SQL command) + return secondOperationType is not null; + } #region Single operation @@ -33,12 +54,11 @@ public override async Task Add_with_generated_values(bool async) AssertSql( @"@p0='1000' +SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) -VALUES (@p0); -SELECT [Id], [Data1] -FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();"); +OUTPUT INSERTED.[Id], INSERTED.[Data1] +VALUES (@p0);"); } public override async Task Add_with_no_generated_values(bool async) @@ -61,12 +81,11 @@ public override async Task Add_with_all_generated_values(bool async) await base.Add_with_all_generated_values(async); AssertSql( - @"SET NOCOUNT ON; + @"SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; INSERT INTO [WithAllDatabaseGenerated] -DEFAULT VALUES; -SELECT [Id], [Data1], [Data2] -FROM [WithAllDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();"); +OUTPUT INSERTED.[Id], INSERTED.[Data1], INSERTED.[Data2] +DEFAULT VALUES;"); } public override async Task Modify_with_generated_values(bool async) @@ -125,22 +144,17 @@ public override async Task Add_Add_with_same_entity_type_and_generated_values(bo AssertSql( @"@p0='1000' +@p1='1001' +SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) -VALUES (@p0); -SELECT [Id], [Data1] -FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();", - // - @"@p0='1001' - -SET NOCOUNT ON; -INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) -VALUES (@p0); -SELECT [Id], [Data1] -FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();"); +MERGE [WithSomeDatabaseGenerated] USING ( +VALUES (@p0, 0), +(@p1, 1)) AS i ([Data2], _Position) ON 1=0 +WHEN NOT MATCHED THEN +INSERT ([Data2]) +VALUES (i.[Data2]) +OUTPUT INSERTED.[Id], INSERTED.[Data1], i._Position;"); } public override async Task Add_Add_with_same_entity_type_and_no_generated_values(bool async) @@ -151,20 +165,15 @@ public override async Task Add_Add_with_same_entity_type_and_no_generated_values @"@p0='100' @p1='1000' @p2='1000' +@p3='101' +@p4='1001' +@p5='1001' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [WithNoDatabaseGenerated] ([Id], [Data1], [Data2]) -VALUES (@p0, @p1, @p2);", - // - @"@p0='101' -@p1='1001' -@p2='1001' - -SET IMPLICIT_TRANSACTIONS OFF; -SET NOCOUNT ON; -INSERT INTO [WithNoDatabaseGenerated] ([Id], [Data1], [Data2]) -VALUES (@p0, @p1, @p2);"); +VALUES (@p0, @p1, @p2), +(@p3, @p4, @p5);"); } public override async Task Add_Add_with_same_entity_type_and_all_generated_values(bool async) @@ -174,17 +183,11 @@ public override async Task Add_Add_with_same_entity_type_and_all_generated_value AssertSql( @"SET NOCOUNT ON; INSERT INTO [WithAllDatabaseGenerated] +OUTPUT INSERTED.[Id], INSERTED.[Data1], INSERTED.[Data2] DEFAULT VALUES; -SELECT [Id], [Data1], [Data2] -FROM [WithAllDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();", - // - @"SET NOCOUNT ON; INSERT INTO [WithAllDatabaseGenerated] -DEFAULT VALUES; -SELECT [Id], [Data1], [Data2] -FROM [WithAllDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();"); +OUTPUT INSERTED.[Id], INSERTED.[Data1], INSERTED.[Data2] +DEFAULT VALUES;"); } public override async Task Modify_Modify_with_same_entity_type_and_generated_values(bool async) @@ -194,23 +197,21 @@ public override async Task Modify_Modify_with_same_entity_type_and_generated_val AssertSql( @"@p1='1' @p0='1000' +@p3='2' +@p2='1001' SET NOCOUNT ON; UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 WHERE [Id] = @p1; SELECT [Data1] FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = @p1;", - // - @"@p1='2' -@p0='1001' +WHERE @@ROWCOUNT = 1 AND [Id] = @p1; -SET NOCOUNT ON; -UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 -WHERE [Id] = @p1; +UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p2 +WHERE [Id] = @p3; SELECT [Data1] FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = @p1;"); +WHERE @@ROWCOUNT = 1 AND [Id] = @p3;"); } public override async Task Modify_Modify_with_same_entity_type_and_no_generated_values(bool async) @@ -221,21 +222,17 @@ public override async Task Modify_Modify_with_same_entity_type_and_no_generated_ @"@p2='1' @p0='1000' @p1='1000' +@p5='2' +@p3='1001' +@p4='1001' -SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 WHERE [Id] = @p2; -SELECT @@ROWCOUNT;", - // - @"@p2='2' -@p0='1001' -@p1='1001' +SELECT @@ROWCOUNT; -SET IMPLICIT_TRANSACTIONS OFF; -SET NOCOUNT ON; -UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 -WHERE [Id] = @p2; +UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p3, [Data2] = @p4 +WHERE [Id] = @p5; SELECT @@ROWCOUNT;"); } @@ -245,19 +242,15 @@ public override async Task Delete_Delete_with_same_entity_type(bool async) AssertSql( @"@p0='1' +@p1='2' -SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; DELETE FROM [WithSomeDatabaseGenerated] WHERE [Id] = @p0; -SELECT @@ROWCOUNT;", - // - @"@p0='2' +SELECT @@ROWCOUNT; -SET IMPLICIT_TRANSACTIONS OFF; -SET NOCOUNT ON; DELETE FROM [WithSomeDatabaseGenerated] -WHERE [Id] = @p0; +WHERE [Id] = @p1; SELECT @@ROWCOUNT;"); } @@ -271,22 +264,15 @@ public override async Task Add_Add_with_different_entity_types_and_generated_val AssertSql( @"@p0='1000' +@p1='1001' SET NOCOUNT ON; INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) +OUTPUT INSERTED.[Id], INSERTED.[Data1] VALUES (@p0); -SELECT [Id], [Data1] -FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();", - // - @"@p0='1001' - -SET NOCOUNT ON; INSERT INTO [WithSomeDatabaseGenerated2] ([Data2]) -VALUES (@p0); -SELECT [Id], [Data1] -FROM [WithSomeDatabaseGenerated2] -WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();"); +OUTPUT INSERTED.[Id], INSERTED.[Data1] +VALUES (@p1);"); } public override async Task Add_Add_with_different_entity_types_and_no_generated_values(bool async) @@ -297,20 +283,15 @@ public override async Task Add_Add_with_different_entity_types_and_no_generated_ @"@p0='100' @p1='1000' @p2='1000' +@p3='101' +@p4='1001' +@p5='1001' -SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [WithNoDatabaseGenerated] ([Id], [Data1], [Data2]) -VALUES (@p0, @p1, @p2);", - // - @"@p0='101' -@p1='1001' -@p2='1001' - -SET IMPLICIT_TRANSACTIONS OFF; -SET NOCOUNT ON; +VALUES (@p0, @p1, @p2); INSERT INTO [WithNoDatabaseGenerated2] ([Id], [Data1], [Data2]) -VALUES (@p0, @p1, @p2);"); +VALUES (@p3, @p4, @p5);"); } public override async Task Add_Add_with_different_entity_types_and_all_generated_values(bool async) @@ -320,17 +301,11 @@ public override async Task Add_Add_with_different_entity_types_and_all_generated AssertSql( @"SET NOCOUNT ON; INSERT INTO [WithAllDatabaseGenerated] +OUTPUT INSERTED.[Id], INSERTED.[Data1], INSERTED.[Data2] DEFAULT VALUES; -SELECT [Id], [Data1], [Data2] -FROM [WithAllDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();", - // - @"SET NOCOUNT ON; INSERT INTO [WithAllDatabaseGenerated2] -DEFAULT VALUES; -SELECT [Id], [Data1], [Data2] -FROM [WithAllDatabaseGenerated2] -WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();"); +OUTPUT INSERTED.[Id], INSERTED.[Data1], INSERTED.[Data2] +DEFAULT VALUES;"); } public override async Task Modify_Modify_with_different_entity_types_and_generated_values(bool async) @@ -340,23 +315,21 @@ public override async Task Modify_Modify_with_different_entity_types_and_generat AssertSql( @"@p1='1' @p0='1000' +@p3='2' +@p2='1001' SET NOCOUNT ON; UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 WHERE [Id] = @p1; SELECT [Data1] FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = @p1;", - // - @"@p1='2' -@p0='1001' +WHERE @@ROWCOUNT = 1 AND [Id] = @p1; -SET NOCOUNT ON; -UPDATE [WithSomeDatabaseGenerated2] SET [Data2] = @p0 -WHERE [Id] = @p1; +UPDATE [WithSomeDatabaseGenerated2] SET [Data2] = @p2 +WHERE [Id] = @p3; SELECT [Data1] FROM [WithSomeDatabaseGenerated2] -WHERE @@ROWCOUNT = 1 AND [Id] = @p1;"); +WHERE @@ROWCOUNT = 1 AND [Id] = @p3;"); } public override async Task Modify_Modify_with_different_entity_types_and_no_generated_values(bool async) @@ -366,21 +339,17 @@ public override async Task Modify_Modify_with_different_entity_types_and_no_gene @"@p2='1' @p0='1000' @p1='1000' +@p5='2' +@p3='1001' +@p4='1001' -SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 WHERE [Id] = @p2; -SELECT @@ROWCOUNT;", - // - @"@p2='2' -@p0='1001' -@p1='1001' +SELECT @@ROWCOUNT; -SET IMPLICIT_TRANSACTIONS OFF; -SET NOCOUNT ON; -UPDATE [WithNoDatabaseGenerated2] SET [Data1] = @p0, [Data2] = @p1 -WHERE [Id] = @p2; +UPDATE [WithNoDatabaseGenerated2] SET [Data1] = @p3, [Data2] = @p4 +WHERE [Id] = @p5; SELECT @@ROWCOUNT;"); } @@ -390,19 +359,15 @@ public override async Task Delete_Delete_with_different_entity_types(bool async) AssertSql( @"@p0='1' +@p1='2' -SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; DELETE FROM [WithSomeDatabaseGenerated] WHERE [Id] = @p0; -SELECT @@ROWCOUNT;", - // - @"@p0='2' +SELECT @@ROWCOUNT; -SET IMPLICIT_TRANSACTIONS OFF; -SET NOCOUNT ON; DELETE FROM [WithSomeDatabaseGenerated2] -WHERE [Id] = @p0; +WHERE [Id] = @p1; SELECT @@ROWCOUNT;"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationIdentityTriggerSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationIdentityTriggerSqlServerTest.cs new file mode 100644 index 00000000000..2f2fb166ba2 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationIdentityTriggerSqlServerTest.cs @@ -0,0 +1,483 @@ +// 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.Update; + +#nullable enable + +public class StoreValueGenerationIdentityTriggerSqlServerTest : StoreValueGenerationTriggerSqlServerTestBase< + StoreValueGenerationIdentityTriggerSqlServerTest.StoreValueGenerationIdentityWithTriggerSqlServerFixture> +{ + public StoreValueGenerationIdentityTriggerSqlServerTest( + StoreValueGenerationIdentityWithTriggerSqlServerFixture fixture, + ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + // Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + protected override bool ShouldCreateImplicitTransaction( + EntityState firstOperationType, + EntityState? secondOperationType, + GeneratedValues generatedValues, + bool withSameEntityType) + { + // We have triggers, so any insert/update retrieving a database-generated value must be enclosed in a transaction + // (we use INSERT+SELECT or INSERT ... OUTPUT INTO+SELECT) + if (generatedValues is GeneratedValues.Some or GeneratedValues.All + && firstOperationType is EntityState.Added or EntityState.Modified) + { + return true; + } + + if (secondOperationType is null) + { + return false; + } + + // For multiple operations, we specifically optimize multiple insertions of the same entity type with a single MERGE. + return !(firstOperationType is EntityState.Added && secondOperationType is EntityState.Added && withSameEntityType); + } + + #region Single operation + + public override async Task Add_with_generated_values(bool async) + { + await base.Add_with_generated_values(async); + + AssertSql( + @"@p0='1000' + +SET NOCOUNT ON; +INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) +VALUES (@p0); +SELECT [Id], [Data1] +FROM [WithSomeDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();"); + } + + public override async Task Add_with_no_generated_values(bool async) + { + await base.Add_with_no_generated_values(async); + + AssertSql( + @"@p0='100' +@p1='1000' +@p2='1000' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +INSERT INTO [WithNoDatabaseGenerated] ([Id], [Data1], [Data2]) +VALUES (@p0, @p1, @p2);"); + } + + public override async Task Add_with_all_generated_values(bool async) + { + await base.Add_with_all_generated_values(async); + + AssertSql( + @"SET NOCOUNT ON; +INSERT INTO [WithAllDatabaseGenerated] +DEFAULT VALUES; +SELECT [Id], [Data1], [Data2] +FROM [WithAllDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();"); + } + + public override async Task Modify_with_generated_values(bool async) + { + await base.Modify_with_generated_values(async); + + AssertSql( + @"@p1='1' +@p0='1000' + +SET NOCOUNT ON; +UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 +WHERE [Id] = @p1; +SELECT [Data1] +FROM [WithSomeDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = @p1;"); + } + + public override async Task Modify_with_no_generated_values(bool async) + { + await base.Modify_with_no_generated_values(async); + + AssertSql( + @"@p2='1' +@p0='1000' +@p1='1000' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 +WHERE [Id] = @p2; +SELECT @@ROWCOUNT;"); + } + + public override async Task Delete(bool async) + { + await base.Delete(async); + + AssertSql( + @"@p0='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +DELETE FROM [WithSomeDatabaseGenerated] +WHERE [Id] = @p0; +SELECT @@ROWCOUNT;"); + } + + #endregion Single operation + + #region Two operations with same entity type + + public override async Task Add_Add_with_same_entity_type_and_generated_values(bool async) + { + await base.Add_Add_with_same_entity_type_and_generated_values(async); + + AssertSql( + @"@p0='1000' +@p1='1001' + +SET NOCOUNT ON; +INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) +VALUES (@p0); +SELECT [Id], [Data1] +FROM [WithSomeDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity(); + +INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) +VALUES (@p1); +SELECT [Id], [Data1] +FROM [WithSomeDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();"); + } + + public override async Task Add_Add_with_same_entity_type_and_no_generated_values(bool async) + { + await base.Add_Add_with_same_entity_type_and_no_generated_values(async); + + AssertSql( + @"@p0='100' +@p1='1000' +@p2='1000' +@p3='101' +@p4='1001' +@p5='1001' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +INSERT INTO [WithNoDatabaseGenerated] ([Id], [Data1], [Data2]) +VALUES (@p0, @p1, @p2), +(@p3, @p4, @p5);"); + } + + public override async Task Add_Add_with_same_entity_type_and_all_generated_values(bool async) + { + await base.Add_Add_with_same_entity_type_and_all_generated_values(async); + + AssertSql( + @"SET NOCOUNT ON; +INSERT INTO [WithAllDatabaseGenerated] +DEFAULT VALUES; +SELECT [Id], [Data1], [Data2] +FROM [WithAllDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity(); + +INSERT INTO [WithAllDatabaseGenerated] +DEFAULT VALUES; +SELECT [Id], [Data1], [Data2] +FROM [WithAllDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();"); + } + + public override async Task Modify_Modify_with_same_entity_type_and_generated_values(bool async) + { + await base.Modify_Modify_with_same_entity_type_and_generated_values(async); + + AssertSql( + @"@p1='1' +@p0='1000' +@p3='2' +@p2='1001' + +SET NOCOUNT ON; +UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 +WHERE [Id] = @p1; +SELECT [Data1] +FROM [WithSomeDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = @p1; + +UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p2 +WHERE [Id] = @p3; +SELECT [Data1] +FROM [WithSomeDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = @p3;"); + } + + public override async Task Modify_Modify_with_same_entity_type_and_no_generated_values(bool async) + { + await base.Modify_Modify_with_same_entity_type_and_no_generated_values(async); + + AssertSql( + @"@p2='1' +@p0='1000' +@p1='1000' +@p5='2' +@p3='1001' +@p4='1001' + +SET NOCOUNT ON; +UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 +WHERE [Id] = @p2; +SELECT @@ROWCOUNT; + +UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p3, [Data2] = @p4 +WHERE [Id] = @p5; +SELECT @@ROWCOUNT;"); + } + + public override async Task Delete_Delete_with_same_entity_type(bool async) + { + await base.Delete_Delete_with_same_entity_type(async); + + AssertSql( + @"@p0='1' +@p1='2' + +SET NOCOUNT ON; +DELETE FROM [WithSomeDatabaseGenerated] +WHERE [Id] = @p0; +SELECT @@ROWCOUNT; + +DELETE FROM [WithSomeDatabaseGenerated] +WHERE [Id] = @p1; +SELECT @@ROWCOUNT;"); + } + + #endregion Two operations with same entity type + + #region Two operations with different entity types + + public override async Task Add_Add_with_different_entity_types_and_generated_values(bool async) + { + await base.Add_Add_with_different_entity_types_and_generated_values(async); + + AssertSql( + @"@p0='1000' +@p1='1001' + +SET NOCOUNT ON; +INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) +VALUES (@p0); +SELECT [Id], [Data1] +FROM [WithSomeDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity(); + +INSERT INTO [WithSomeDatabaseGenerated2] ([Data2]) +VALUES (@p1); +SELECT [Id], [Data1] +FROM [WithSomeDatabaseGenerated2] +WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();"); + } + + public override async Task Add_Add_with_different_entity_types_and_no_generated_values(bool async) + { + await base.Add_Add_with_different_entity_types_and_no_generated_values(async); + + AssertSql( + @"@p0='100' +@p1='1000' +@p2='1000' +@p3='101' +@p4='1001' +@p5='1001' + +SET NOCOUNT ON; +INSERT INTO [WithNoDatabaseGenerated] ([Id], [Data1], [Data2]) +VALUES (@p0, @p1, @p2); +INSERT INTO [WithNoDatabaseGenerated2] ([Id], [Data1], [Data2]) +VALUES (@p3, @p4, @p5);"); + } + + public override async Task Add_Add_with_different_entity_types_and_all_generated_values(bool async) + { + await base.Add_Add_with_different_entity_types_and_all_generated_values(async); + + AssertSql( + @"SET NOCOUNT ON; +INSERT INTO [WithAllDatabaseGenerated] +DEFAULT VALUES; +SELECT [Id], [Data1], [Data2] +FROM [WithAllDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity(); + +INSERT INTO [WithAllDatabaseGenerated2] +DEFAULT VALUES; +SELECT [Id], [Data1], [Data2] +FROM [WithAllDatabaseGenerated2] +WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();"); + } + + + public override async Task Modify_Modify_with_different_entity_types_and_generated_values(bool async) + { + await base.Modify_Modify_with_different_entity_types_and_generated_values(async); + + AssertSql( + @"@p1='1' +@p0='1000' +@p3='2' +@p2='1001' + +SET NOCOUNT ON; +UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 +WHERE [Id] = @p1; +SELECT [Data1] +FROM [WithSomeDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = @p1; + +UPDATE [WithSomeDatabaseGenerated2] SET [Data2] = @p2 +WHERE [Id] = @p3; +SELECT [Data1] +FROM [WithSomeDatabaseGenerated2] +WHERE @@ROWCOUNT = 1 AND [Id] = @p3;"); + } + + public override async Task Modify_Modify_with_different_entity_types_and_no_generated_values(bool async) + { + await base.Modify_Modify_with_different_entity_types_and_no_generated_values(async); + + AssertSql( + @"@p2='1' +@p0='1000' +@p1='1000' +@p5='2' +@p3='1001' +@p4='1001' + +SET NOCOUNT ON; +UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 +WHERE [Id] = @p2; +SELECT @@ROWCOUNT; + +UPDATE [WithNoDatabaseGenerated2] SET [Data1] = @p3, [Data2] = @p4 +WHERE [Id] = @p5; +SELECT @@ROWCOUNT;"); + } + + public override async Task Delete_Delete_with_different_entity_types(bool async) + { + await base.Delete_Delete_with_different_entity_types(async); + + AssertSql( + @"@p0='1' +@p1='2' + +SET NOCOUNT ON; +DELETE FROM [WithSomeDatabaseGenerated] +WHERE [Id] = @p0; +SELECT @@ROWCOUNT; + +DELETE FROM [WithSomeDatabaseGenerated2] +WHERE [Id] = @p1; +SELECT @@ROWCOUNT;"); + } + + #endregion Two operations with different entity types + + public override async Task Three_Add_use_batched_inserts(bool async) + { + await base.Three_Add_use_batched_inserts(async); + + AssertSql( + @"@p0='0' +@p1='0' +@p2='0' + +SET NOCOUNT ON; +INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) +VALUES (@p0); +SELECT [Id], [Data1] +FROM [WithSomeDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity(); + +INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) +VALUES (@p1); +SELECT [Id], [Data1] +FROM [WithSomeDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity(); + +INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) +VALUES (@p2); +SELECT [Id], [Data1] +FROM [WithSomeDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();"); + } + + protected override async Task Test( + EntityState firstOperationType, + EntityState? secondOperationType, + GeneratedValues generatedValues, + bool async, + bool withSameEntityType = true) + { + await base.Test(firstOperationType, secondOperationType, generatedValues, async, withSameEntityType); + + if (!ShouldCreateImplicitTransaction(firstOperationType, secondOperationType, generatedValues, withSameEntityType)) + { + Assert.Contains("SET IMPLICIT_TRANSACTIONS OFF", Fixture.TestSqlLoggerFactory.SqlStatements[0]); + } + } + + public class StoreValueGenerationIdentityWithTriggerSqlServerFixture : StoreValueGenerationTriggerSqlServerFixture + { + private string? _identityResetCommand; + + protected override string StoreName { get; } = "StoreValueGenerationIdentityWithTriggerTest"; + + protected override ITestStoreFactory TestStoreFactory + => SqlServerTestStoreFactory.Instance; + + public override void Reseed() + { + using var context = CreateContext(); + Clean(context); + Seed(context); + } + + protected override void Clean(DbContext context) + { + base.Clean(context); + + // Reset the IDENTITY values since we assert on them + context.Database.ExecuteSqlRaw(GetIdentityResetCommand()); + } + + private string GetIdentityResetCommand() + { + if (_identityResetCommand is not null) + { + return _identityResetCommand; + } + + var context = CreateContext(); + var builder = new StringBuilder(); + + var tablesWithIdentity = context.Model.GetEntityTypes() + .Where(e => e.GetProperties().Any(p => p.GetValueGenerationStrategy() == SqlServerValueGenerationStrategy.IdentityColumn)) + .Select(e => e.GetTableName()); + + foreach (var table in tablesWithIdentity) + { + builder.AppendLine($"DBCC CHECKIDENT ('{table}', RESEED, 0);"); + } + + return _identityResetCommand = builder.ToString(); + } + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationSequenceSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationSequenceSqlServerTest.cs index eb65ef829db..f56a6747fc9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationSequenceSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationSequenceSqlServerTest.cs @@ -19,12 +19,31 @@ public StoreValueGenerationSequenceSqlServerTest( // Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - protected override int ShouldExecuteInNumberOfCommands( + protected override bool ShouldCreateImplicitTransaction( EntityState firstOperationType, EntityState? secondOperationType, GeneratedValues generatedValues, - bool withDatabaseGenerated) - => secondOperationType is null ? 1 : 2; + bool withSameEntityType) + { + // Updates with generated values currently use SELECT to retrieve them, and so require transactions + if (firstOperationType == EntityState.Modified && generatedValues != GeneratedValues.None) + { + return true; + } + + // For multiple operations, we specifically optimize multiple insertions of the same entity type with a single command (e.g. MERGE) + // (as long as there are writable columns) + if (firstOperationType is EntityState.Added + && secondOperationType is EntityState.Added + && withSameEntityType + && generatedValues != GeneratedValues.All) + { + return false; + } + + // Other single operations should never be in a transaction (always executed in a single SQL command) + return secondOperationType is not null; + } #region Single operation @@ -35,14 +54,11 @@ public override async Task Add_with_generated_values(bool async) AssertSql( @"@p0='1000' +SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -DECLARE @inserted0 TABLE ([Id] int); INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) -OUTPUT INSERTED.[Id] -INTO @inserted0 -VALUES (@p0); -SELECT [t].[Id], [t].[Data1] FROM [WithSomeDatabaseGenerated] t -INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]);"); +OUTPUT INSERTED.[Id], INSERTED.[Data1] +VALUES (@p0);"); } public override async Task Add_with_no_generated_values(bool async) @@ -65,14 +81,11 @@ public override async Task Add_with_all_generated_values(bool async) await base.Add_with_all_generated_values(async); AssertSql( - @"SET NOCOUNT ON; -DECLARE @inserted0 TABLE ([Id] int); + @"SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; INSERT INTO [WithAllDatabaseGenerated] -OUTPUT INSERTED.[Id] -INTO @inserted0 -DEFAULT VALUES; -SELECT [t].[Id], [t].[Data1], [t].[Data2] FROM [WithAllDatabaseGenerated] t -INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]);"); +OUTPUT INSERTED.[Id], INSERTED.[Data1], INSERTED.[Data2] +DEFAULT VALUES;"); } public override async Task Modify_with_generated_values(bool async) @@ -131,26 +144,17 @@ public override async Task Add_Add_with_same_entity_type_and_generated_values(bo AssertSql( @"@p0='1000' +@p1='1001' +SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -DECLARE @inserted0 TABLE ([Id] int); -INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) -OUTPUT INSERTED.[Id] -INTO @inserted0 -VALUES (@p0); -SELECT [t].[Id], [t].[Data1] FROM [WithSomeDatabaseGenerated] t -INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]);", - // - @"@p0='1001' - -SET NOCOUNT ON; -DECLARE @inserted0 TABLE ([Id] int); -INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) -OUTPUT INSERTED.[Id] -INTO @inserted0 -VALUES (@p0); -SELECT [t].[Id], [t].[Data1] FROM [WithSomeDatabaseGenerated] t -INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]);"); +MERGE [WithSomeDatabaseGenerated] USING ( +VALUES (@p0, 0), +(@p1, 1)) AS i ([Data2], _Position) ON 1=0 +WHEN NOT MATCHED THEN +INSERT ([Data2]) +VALUES (i.[Data2]) +OUTPUT INSERTED.[Id], INSERTED.[Data1], i._Position;"); } public override async Task Add_Add_with_same_entity_type_and_no_generated_values(bool async) @@ -161,20 +165,15 @@ public override async Task Add_Add_with_same_entity_type_and_no_generated_values @"@p0='100' @p1='1000' @p2='1000' +@p3='101' +@p4='1001' +@p5='1001' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [WithNoDatabaseGenerated] ([Id], [Data1], [Data2]) -VALUES (@p0, @p1, @p2);", - // - @"@p0='101' -@p1='1001' -@p2='1001' - -SET IMPLICIT_TRANSACTIONS OFF; -SET NOCOUNT ON; -INSERT INTO [WithNoDatabaseGenerated] ([Id], [Data1], [Data2]) -VALUES (@p0, @p1, @p2);"); +VALUES (@p0, @p1, @p2), +(@p3, @p4, @p5);"); } public override async Task Add_Add_with_same_entity_type_and_all_generated_values(bool async) @@ -184,19 +183,11 @@ public override async Task Add_Add_with_same_entity_type_and_all_generated_value AssertSql( @"SET NOCOUNT ON; DECLARE @inserted0 TABLE ([Id] int); -INSERT INTO [WithAllDatabaseGenerated] -OUTPUT INSERTED.[Id] -INTO @inserted0 -DEFAULT VALUES; -SELECT [t].[Id], [t].[Data1], [t].[Data2] FROM [WithAllDatabaseGenerated] t -INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]);", - // - @"SET NOCOUNT ON; -DECLARE @inserted0 TABLE ([Id] int); -INSERT INTO [WithAllDatabaseGenerated] +INSERT INTO [WithAllDatabaseGenerated] ([Id]) OUTPUT INSERTED.[Id] INTO @inserted0 -DEFAULT VALUES; +VALUES (DEFAULT), +(DEFAULT); SELECT [t].[Id], [t].[Data1], [t].[Data2] FROM [WithAllDatabaseGenerated] t INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]);"); } @@ -208,23 +199,21 @@ public override async Task Modify_Modify_with_same_entity_type_and_generated_val AssertSql( @"@p1='5' @p0='1000' +@p3='6' +@p2='1001' SET NOCOUNT ON; UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 WHERE [Id] = @p1; SELECT [Data1] FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = @p1;", - // - @"@p1='6' -@p0='1001' +WHERE @@ROWCOUNT = 1 AND [Id] = @p1; -SET NOCOUNT ON; -UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 -WHERE [Id] = @p1; +UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p2 +WHERE [Id] = @p3; SELECT [Data1] FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = @p1;"); +WHERE @@ROWCOUNT = 1 AND [Id] = @p3;"); } public override async Task Modify_Modify_with_same_entity_type_and_no_generated_values(bool async) @@ -235,21 +224,17 @@ public override async Task Modify_Modify_with_same_entity_type_and_no_generated_ @"@p2='1' @p0='1000' @p1='1000' +@p5='2' +@p3='1001' +@p4='1001' -SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 WHERE [Id] = @p2; -SELECT @@ROWCOUNT;", - // - @"@p2='2' -@p0='1001' -@p1='1001' +SELECT @@ROWCOUNT; -SET IMPLICIT_TRANSACTIONS OFF; -SET NOCOUNT ON; -UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 -WHERE [Id] = @p2; +UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p3, [Data2] = @p4 +WHERE [Id] = @p5; SELECT @@ROWCOUNT;"); } @@ -259,19 +244,15 @@ public override async Task Delete_Delete_with_same_entity_type(bool async) AssertSql( @"@p0='5' +@p1='6' -SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; DELETE FROM [WithSomeDatabaseGenerated] WHERE [Id] = @p0; -SELECT @@ROWCOUNT;", - // - @"@p0='6' +SELECT @@ROWCOUNT; -SET IMPLICIT_TRANSACTIONS OFF; -SET NOCOUNT ON; DELETE FROM [WithSomeDatabaseGenerated] -WHERE [Id] = @p0; +WHERE [Id] = @p1; SELECT @@ROWCOUNT;"); } @@ -285,26 +266,15 @@ public override async Task Add_Add_with_different_entity_types_and_generated_val AssertSql( @"@p0='1000' +@p1='1001' SET NOCOUNT ON; -DECLARE @inserted0 TABLE ([Id] int); INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) -OUTPUT INSERTED.[Id] -INTO @inserted0 +OUTPUT INSERTED.[Id], INSERTED.[Data1] VALUES (@p0); -SELECT [t].[Id], [t].[Data1] FROM [WithSomeDatabaseGenerated] t -INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]);", - // - @"@p0='1001' - -SET NOCOUNT ON; -DECLARE @inserted0 TABLE ([Id] int); INSERT INTO [WithSomeDatabaseGenerated2] ([Data2]) -OUTPUT INSERTED.[Id] -INTO @inserted0 -VALUES (@p0); -SELECT [t].[Id], [t].[Data1] FROM [WithSomeDatabaseGenerated2] t -INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]);"); +OUTPUT INSERTED.[Id], INSERTED.[Data1] +VALUES (@p1);"); } public override async Task Add_Add_with_different_entity_types_and_no_generated_values(bool async) @@ -315,20 +285,15 @@ public override async Task Add_Add_with_different_entity_types_and_no_generated_ @"@p0='100' @p1='1000' @p2='1000' +@p3='101' +@p4='1001' +@p5='1001' -SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [WithNoDatabaseGenerated] ([Id], [Data1], [Data2]) -VALUES (@p0, @p1, @p2);", - // - @"@p0='101' -@p1='1001' -@p2='1001' - -SET IMPLICIT_TRANSACTIONS OFF; -SET NOCOUNT ON; +VALUES (@p0, @p1, @p2); INSERT INTO [WithNoDatabaseGenerated2] ([Id], [Data1], [Data2]) -VALUES (@p0, @p1, @p2);"); +VALUES (@p3, @p4, @p5);"); } public override async Task Add_Add_with_different_entity_types_and_all_generated_values(bool async) @@ -337,22 +302,12 @@ public override async Task Add_Add_with_different_entity_types_and_all_generated AssertSql( @"SET NOCOUNT ON; -DECLARE @inserted0 TABLE ([Id] int); INSERT INTO [WithAllDatabaseGenerated] -OUTPUT INSERTED.[Id] -INTO @inserted0 +OUTPUT INSERTED.[Id], INSERTED.[Data1], INSERTED.[Data2] DEFAULT VALUES; -SELECT [t].[Id], [t].[Data1], [t].[Data2] FROM [WithAllDatabaseGenerated] t -INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]);", - // - @"SET NOCOUNT ON; -DECLARE @inserted0 TABLE ([Id] int); INSERT INTO [WithAllDatabaseGenerated2] -OUTPUT INSERTED.[Id] -INTO @inserted0 -DEFAULT VALUES; -SELECT [t].[Id], [t].[Data1], [t].[Data2] FROM [WithAllDatabaseGenerated2] t -INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]);"); +OUTPUT INSERTED.[Id], INSERTED.[Data1], INSERTED.[Data2] +DEFAULT VALUES;"); } public override async Task Modify_Modify_with_different_entity_types_and_generated_values(bool async) @@ -362,23 +317,21 @@ public override async Task Modify_Modify_with_different_entity_types_and_generat AssertSql( @"@p1='5' @p0='1000' +@p3='8' +@p2='1001' SET NOCOUNT ON; UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 WHERE [Id] = @p1; SELECT [Data1] FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = @p1;", - // - @"@p1='8' -@p0='1001' +WHERE @@ROWCOUNT = 1 AND [Id] = @p1; -SET NOCOUNT ON; -UPDATE [WithSomeDatabaseGenerated2] SET [Data2] = @p0 -WHERE [Id] = @p1; +UPDATE [WithSomeDatabaseGenerated2] SET [Data2] = @p2 +WHERE [Id] = @p3; SELECT [Data1] FROM [WithSomeDatabaseGenerated2] -WHERE @@ROWCOUNT = 1 AND [Id] = @p1;"); +WHERE @@ROWCOUNT = 1 AND [Id] = @p3;"); } public override async Task Modify_Modify_with_different_entity_types_and_no_generated_values(bool async) @@ -388,21 +341,17 @@ public override async Task Modify_Modify_with_different_entity_types_and_no_gene @"@p2='1' @p0='1000' @p1='1000' +@p5='2' +@p3='1001' +@p4='1001' -SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 WHERE [Id] = @p2; -SELECT @@ROWCOUNT;", - // - @"@p2='2' -@p0='1001' -@p1='1001' +SELECT @@ROWCOUNT; -SET IMPLICIT_TRANSACTIONS OFF; -SET NOCOUNT ON; -UPDATE [WithNoDatabaseGenerated2] SET [Data1] = @p0, [Data2] = @p1 -WHERE [Id] = @p2; +UPDATE [WithNoDatabaseGenerated2] SET [Data1] = @p3, [Data2] = @p4 +WHERE [Id] = @p5; SELECT @@ROWCOUNT;"); } @@ -412,19 +361,15 @@ public override async Task Delete_Delete_with_different_entity_types(bool async) AssertSql( @"@p0='5' +@p1='8' -SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; DELETE FROM [WithSomeDatabaseGenerated] WHERE [Id] = @p0; -SELECT @@ROWCOUNT;", - // - @"@p0='8' +SELECT @@ROWCOUNT; -SET IMPLICIT_TRANSACTIONS OFF; -SET NOCOUNT ON; DELETE FROM [WithSomeDatabaseGenerated2] -WHERE [Id] = @p0; +WHERE [Id] = @p1; SELECT @@ROWCOUNT;"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationSequenceTriggerSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationSequenceTriggerSqlServerTest.cs new file mode 100644 index 00000000000..8c1d5e85ce9 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationSequenceTriggerSqlServerTest.cs @@ -0,0 +1,491 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore.SqlServer.Internal; +using Microsoft.EntityFrameworkCore.TestModels.StoreValueGenerationModel; + +namespace Microsoft.EntityFrameworkCore.Update; + +#nullable enable + +public class StoreValueGenerationSequenceTriggerSqlServerTest : StoreValueGenerationTriggerSqlServerTestBase< + StoreValueGenerationSequenceTriggerSqlServerTest.StoreValueGenerationSequenceWithTriggerSqlServerFixture> +{ + public StoreValueGenerationSequenceTriggerSqlServerTest( + StoreValueGenerationSequenceWithTriggerSqlServerFixture fixture, + ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + // Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + protected override bool ShouldCreateImplicitTransaction( + EntityState firstOperationType, + EntityState? secondOperationType, + GeneratedValues generatedValues, + bool withSameEntityType) + { + // We have triggers, so any insert/update retrieving a database-generated value must be enclosed in a transaction + // (we use INSERT+SELECT or INSERT ... OUTPUT INTO+SELECT) + if (generatedValues is GeneratedValues.Some or GeneratedValues.All + && firstOperationType is EntityState.Added or EntityState.Modified) + { + return true; + } + + if (secondOperationType is null) + { + return false; + } + + // For multiple operations, we specifically optimize multiple insertions of the same entity type with a single MERGE. + return !(firstOperationType is EntityState.Added && secondOperationType is EntityState.Added && withSameEntityType); + } + + #region Single operation + + public override async Task Add_with_generated_values(bool async) + { + await base.Add_with_generated_values(async); + + AssertSql( + @"@p0='1000' + +SET NOCOUNT ON; +DECLARE @inserted0 TABLE ([Id] int); +INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) +OUTPUT INSERTED.[Id] +INTO @inserted0 +VALUES (@p0); +SELECT [t].[Id], [t].[Data1] FROM [WithSomeDatabaseGenerated] t +INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]);"); + } + + public override async Task Add_with_no_generated_values(bool async) + { + await base.Add_with_no_generated_values(async); + + AssertSql( + @"@p0='100' +@p1='1000' +@p2='1000' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +INSERT INTO [WithNoDatabaseGenerated] ([Id], [Data1], [Data2]) +VALUES (@p0, @p1, @p2);"); + } + + public override async Task Add_with_all_generated_values(bool async) + { + await base.Add_with_all_generated_values(async); + + AssertSql( + @"SET NOCOUNT ON; +DECLARE @inserted0 TABLE ([Id] int); +INSERT INTO [WithAllDatabaseGenerated] +OUTPUT INSERTED.[Id] +INTO @inserted0 +DEFAULT VALUES; +SELECT [t].[Id], [t].[Data1], [t].[Data2] FROM [WithAllDatabaseGenerated] t +INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]);"); + } + + public override async Task Modify_with_generated_values(bool async) + { + await base.Modify_with_generated_values(async); + + AssertSql( + @"@p1='5' +@p0='1000' + +SET NOCOUNT ON; +UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 +WHERE [Id] = @p1; +SELECT [Data1] +FROM [WithSomeDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = @p1;"); + } + + public override async Task Modify_with_no_generated_values(bool async) + { + await base.Modify_with_no_generated_values(async); + + AssertSql( + @"@p2='1' +@p0='1000' +@p1='1000' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 +WHERE [Id] = @p2; +SELECT @@ROWCOUNT;"); + } + + public override async Task Delete(bool async) + { + await base.Delete(async); + + AssertSql( + @"@p0='5' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +DELETE FROM [WithSomeDatabaseGenerated] +WHERE [Id] = @p0; +SELECT @@ROWCOUNT;"); + } + + #endregion Single operation + + #region Two operations with same entity type + + public override async Task Add_Add_with_same_entity_type_and_generated_values(bool async) + { + await base.Add_Add_with_same_entity_type_and_generated_values(async); + + AssertSql( + @"@p0='1000' +@p1='1001' + +SET NOCOUNT ON; +DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]); +MERGE [WithSomeDatabaseGenerated] USING ( +VALUES (@p0, 0), +(@p1, 1)) AS i ([Data2], _Position) ON 1=0 +WHEN NOT MATCHED THEN +INSERT ([Data2]) +VALUES (i.[Data2]) +OUTPUT INSERTED.[Id], i._Position +INTO @inserted0; + +SELECT [t].[Id], [t].[Data1] FROM [WithSomeDatabaseGenerated] t +INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]) +ORDER BY [i].[_Position];"); + } + + public override async Task Add_Add_with_same_entity_type_and_no_generated_values(bool async) + { + await base.Add_Add_with_same_entity_type_and_no_generated_values(async); + + AssertSql( + @"@p0='100' +@p1='1000' +@p2='1000' +@p3='101' +@p4='1001' +@p5='1001' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +INSERT INTO [WithNoDatabaseGenerated] ([Id], [Data1], [Data2]) +VALUES (@p0, @p1, @p2), +(@p3, @p4, @p5);"); + } + + public override async Task Add_Add_with_same_entity_type_and_all_generated_values(bool async) + { + await base.Add_Add_with_same_entity_type_and_all_generated_values(async); + + AssertSql( + @"SET NOCOUNT ON; +DECLARE @inserted0 TABLE ([Id] int); +INSERT INTO [WithAllDatabaseGenerated] ([Id]) +OUTPUT INSERTED.[Id] +INTO @inserted0 +VALUES (DEFAULT), +(DEFAULT); +SELECT [t].[Id], [t].[Data1], [t].[Data2] FROM [WithAllDatabaseGenerated] t +INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]);"); + } + + public override async Task Modify_Modify_with_same_entity_type_and_generated_values(bool async) + { + await base.Modify_Modify_with_same_entity_type_and_generated_values(async); + + AssertSql( + @"@p1='5' +@p0='1000' +@p3='6' +@p2='1001' + +SET NOCOUNT ON; +UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 +WHERE [Id] = @p1; +SELECT [Data1] +FROM [WithSomeDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = @p1; + +UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p2 +WHERE [Id] = @p3; +SELECT [Data1] +FROM [WithSomeDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = @p3;"); + } + + public override async Task Modify_Modify_with_same_entity_type_and_no_generated_values(bool async) + { + await base.Modify_Modify_with_same_entity_type_and_no_generated_values(async); + + AssertSql( + @"@p2='1' +@p0='1000' +@p1='1000' +@p5='2' +@p3='1001' +@p4='1001' + +SET NOCOUNT ON; +UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 +WHERE [Id] = @p2; +SELECT @@ROWCOUNT; + +UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p3, [Data2] = @p4 +WHERE [Id] = @p5; +SELECT @@ROWCOUNT;"); + } + + public override async Task Delete_Delete_with_same_entity_type(bool async) + { + await base.Delete_Delete_with_same_entity_type(async); + + AssertSql( + @"@p0='5' +@p1='6' + +SET NOCOUNT ON; +DELETE FROM [WithSomeDatabaseGenerated] +WHERE [Id] = @p0; +SELECT @@ROWCOUNT; + +DELETE FROM [WithSomeDatabaseGenerated] +WHERE [Id] = @p1; +SELECT @@ROWCOUNT;"); + } + + #endregion Two operations with same entity type + + #region Two operations with different entity types + + public override async Task Add_Add_with_different_entity_types_and_generated_values(bool async) + { + await base.Add_Add_with_different_entity_types_and_generated_values(async); + + AssertSql( + @"@p0='1000' +@p1='1001' + +SET NOCOUNT ON; +DECLARE @inserted0 TABLE ([Id] int); +INSERT INTO [WithSomeDatabaseGenerated] ([Data2]) +OUTPUT INSERTED.[Id] +INTO @inserted0 +VALUES (@p0); +SELECT [t].[Id], [t].[Data1] FROM [WithSomeDatabaseGenerated] t +INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]); + +DECLARE @inserted1 TABLE ([Id] int); +INSERT INTO [WithSomeDatabaseGenerated2] ([Data2]) +OUTPUT INSERTED.[Id] +INTO @inserted1 +VALUES (@p1); +SELECT [t].[Id], [t].[Data1] FROM [WithSomeDatabaseGenerated2] t +INNER JOIN @inserted1 i ON ([t].[Id] = [i].[Id]);"); + } + + public override async Task Add_Add_with_different_entity_types_and_no_generated_values(bool async) + { + await base.Add_Add_with_different_entity_types_and_no_generated_values(async); + + AssertSql( + @"@p0='100' +@p1='1000' +@p2='1000' +@p3='101' +@p4='1001' +@p5='1001' + +SET NOCOUNT ON; +INSERT INTO [WithNoDatabaseGenerated] ([Id], [Data1], [Data2]) +VALUES (@p0, @p1, @p2); +INSERT INTO [WithNoDatabaseGenerated2] ([Id], [Data1], [Data2]) +VALUES (@p3, @p4, @p5);"); + } + + public override async Task Add_Add_with_different_entity_types_and_all_generated_values(bool async) + { + await base.Add_Add_with_different_entity_types_and_all_generated_values(async); + + AssertSql( + @"SET NOCOUNT ON; +DECLARE @inserted0 TABLE ([Id] int); +INSERT INTO [WithAllDatabaseGenerated] +OUTPUT INSERTED.[Id] +INTO @inserted0 +DEFAULT VALUES; +SELECT [t].[Id], [t].[Data1], [t].[Data2] FROM [WithAllDatabaseGenerated] t +INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]); + +DECLARE @inserted1 TABLE ([Id] int); +INSERT INTO [WithAllDatabaseGenerated2] +OUTPUT INSERTED.[Id] +INTO @inserted1 +DEFAULT VALUES; +SELECT [t].[Id], [t].[Data1], [t].[Data2] FROM [WithAllDatabaseGenerated2] t +INNER JOIN @inserted1 i ON ([t].[Id] = [i].[Id]);"); + } + + public override async Task Modify_Modify_with_different_entity_types_and_generated_values(bool async) + { + await base.Modify_Modify_with_different_entity_types_and_generated_values(async); + + AssertSql( + @"@p1='5' +@p0='1000' +@p3='8' +@p2='1001' + +SET NOCOUNT ON; +UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 +WHERE [Id] = @p1; +SELECT [Data1] +FROM [WithSomeDatabaseGenerated] +WHERE @@ROWCOUNT = 1 AND [Id] = @p1; + +UPDATE [WithSomeDatabaseGenerated2] SET [Data2] = @p2 +WHERE [Id] = @p3; +SELECT [Data1] +FROM [WithSomeDatabaseGenerated2] +WHERE @@ROWCOUNT = 1 AND [Id] = @p3;"); + } + + public override async Task Modify_Modify_with_different_entity_types_and_no_generated_values(bool async) + { + await base.Modify_Modify_with_different_entity_types_and_no_generated_values(async); + + AssertSql( + @"@p2='1' +@p0='1000' +@p1='1000' +@p5='2' +@p3='1001' +@p4='1001' + +SET NOCOUNT ON; +UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 +WHERE [Id] = @p2; +SELECT @@ROWCOUNT; + +UPDATE [WithNoDatabaseGenerated2] SET [Data1] = @p3, [Data2] = @p4 +WHERE [Id] = @p5; +SELECT @@ROWCOUNT;"); + } + + public override async Task Delete_Delete_with_different_entity_types(bool async) + { + await base.Delete_Delete_with_different_entity_types(async); + + AssertSql( + @"@p0='5' +@p1='8' + +SET NOCOUNT ON; +DELETE FROM [WithSomeDatabaseGenerated] +WHERE [Id] = @p0; +SELECT @@ROWCOUNT; + +DELETE FROM [WithSomeDatabaseGenerated2] +WHERE [Id] = @p1; +SELECT @@ROWCOUNT;"); + } + + #endregion Two operations with different entity types + + public override async Task Three_Add_use_batched_inserts(bool async) + { + await base.Three_Add_use_batched_inserts(async); + + AssertSql( + @"@p0='0' +@p1='0' +@p2='0' + +SET NOCOUNT ON; +DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]); +MERGE [WithSomeDatabaseGenerated] USING ( +VALUES (@p0, 0), +(@p1, 1), +(@p2, 2)) AS i ([Data2], _Position) ON 1=0 +WHEN NOT MATCHED THEN +INSERT ([Data2]) +VALUES (i.[Data2]) +OUTPUT INSERTED.[Id], i._Position +INTO @inserted0; + +SELECT [t].[Id], [t].[Data1] FROM [WithSomeDatabaseGenerated] t +INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]) +ORDER BY [i].[_Position];"); + } + + protected override async Task Test( + EntityState firstOperationType, + EntityState? secondOperationType, + GeneratedValues generatedValues, + bool async, + bool withSameEntityType = true) + { + await base.Test(firstOperationType, secondOperationType, generatedValues, async, withSameEntityType); + + if (!ShouldCreateImplicitTransaction(firstOperationType, secondOperationType, generatedValues, withSameEntityType)) + { + Assert.Contains("SET IMPLICIT_TRANSACTIONS OFF", Fixture.TestSqlLoggerFactory.SqlStatements[0]); + } + } + + public class StoreValueGenerationSequenceWithTriggerSqlServerFixture : StoreValueGenerationTriggerSqlServerFixture + { + protected override string StoreName { get; } = "StoreValueGenerationSequenceWithTriggerTest"; + + protected override ITestStoreFactory TestStoreFactory + => SqlServerTestStoreFactory.Instance; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + base.OnModelCreating(modelBuilder, context); + + modelBuilder.HasSequence("Ids"); + + foreach (var name in new[] + { + nameof(StoreValueGenerationContext.WithSomeDatabaseGenerated), + nameof(StoreValueGenerationContext.WithSomeDatabaseGenerated2), + nameof(StoreValueGenerationContext.WithAllDatabaseGenerated), + nameof(StoreValueGenerationContext.WithAllDatabaseGenerated2) + }) + { + modelBuilder + .SharedTypeEntity(name) + .Property(w => w.Id) + .HasDefaultValueSql("NEXT VALUE FOR [Ids]"); + } + } + + public override void Reseed() + { + using var context = CreateContext(); + Clean(context); + Seed(context); + } + + protected override void Clean(DbContext context) + { + base.Clean(context); + + // Reset the sequence values since we assert on them + context.Database.ExecuteSqlRaw("ALTER SEQUENCE [Ids] RESTART WITH 1"); + } + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationTriggerSqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationTriggerSqlServerFixture.cs new file mode 100644 index 00000000000..7a24f9483e8 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationTriggerSqlServerFixture.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestModels.StoreValueGenerationModel; + +namespace Microsoft.EntityFrameworkCore.Update; + +public abstract class StoreValueGenerationTriggerSqlServerFixture : StoreValueGenerationFixtureBase +{ + protected override void Seed(StoreValueGenerationContext context) + { + base.Seed(context); + + // Add triggers to all tables + foreach (var table in context.Model.GetEntityTypes().Select(e => e.GetTableName())) + { + context.Database.ExecuteSqlRaw( + $@" +CREATE OR ALTER TRIGGER [{table}_Trigger] +ON [{table}] +FOR INSERT, UPDATE, DELETE AS +BEGIN + IF @@ROWCOUNT = 0 + return +END"); + } + } + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + base.OnModelCreating(modelBuilder, context); + + foreach (var entity in modelBuilder.Model.GetEntityTypes()) + { + modelBuilder.Entity(entity.Name).ToTable(b => b.HasTrigger(entity.GetTableName() + "_Trigger")); + } + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationTriggerSqlServerTestBase.cs b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationTriggerSqlServerTestBase.cs new file mode 100644 index 00000000000..76c888ed1da --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationTriggerSqlServerTestBase.cs @@ -0,0 +1,110 @@ +// 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.StoreValueGenerationModel; + +namespace Microsoft.EntityFrameworkCore.Update; + +public abstract class StoreValueGenerationTriggerSqlServerTestBase : StoreValueGenerationTestBase + where TFixture : StoreValueGenerationTriggerSqlServerFixture +{ + protected StoreValueGenerationTriggerSqlServerTestBase(TFixture fixture) + : base(fixture) + { + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Three_Add_use_batched_inserts(bool async) + { + await using var context = CreateContext(); + + var instances = new StoreValueGenerationData[] { new(), new(), new() }; + context.WithSomeDatabaseGenerated.AddRange(instances[0], instances[1], instances[2]); + + Fixture.ListLoggerFactory.Clear(); + + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } + + Assert.Contains(Fixture.ListLoggerFactory.Log, l => l.Id == RelationalEventId.TransactionStarted); + Assert.Contains(Fixture.ListLoggerFactory.Log, l => l.Id == RelationalEventId.TransactionCommitted); + + Assert.Equal(1, Fixture.ListLoggerFactory.Log.Count(l => l.Id == RelationalEventId.CommandExecuted)); + + context.ChangeTracker.Clear(); + + using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents()) + { + foreach (var instance in instances) + { + Assert.Equal(await context.WithSomeDatabaseGenerated.FindAsync(instance.Id), instance); + } + } + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Four_Add_use_merge_output_into(bool async) + { + await using var context = CreateContext(); + + var instances = new StoreValueGenerationData[] { new(), new(), new(), new() }; + context.WithSomeDatabaseGenerated.AddRange(instances[0], instances[1], instances[2], instances[3]); + + Fixture.ListLoggerFactory.Clear(); + + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } + + Assert.Contains(Fixture.ListLoggerFactory.Log, l => l.Id == RelationalEventId.TransactionStarted); + Assert.Contains(Fixture.ListLoggerFactory.Log, l => l.Id == RelationalEventId.TransactionCommitted); + + Assert.Equal(1, Fixture.ListLoggerFactory.Log.Count(l => l.Id == RelationalEventId.CommandExecuted)); + + context.ChangeTracker.Clear(); + + using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents()) + { + foreach (var instance in instances) + { + Assert.Equal(await context.WithSomeDatabaseGenerated.FindAsync(instance.Id), instance); + } + } + + AssertSql( + @"@p0='0' +@p1='0' +@p2='0' +@p3='0' + +SET NOCOUNT ON; +DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]); +MERGE [WithSomeDatabaseGenerated] USING ( +VALUES (@p0, 0), +(@p1, 1), +(@p2, 2), +(@p3, 3)) AS i ([Data2], _Position) ON 1=0 +WHEN NOT MATCHED THEN +INSERT ([Data2]) +VALUES (i.[Data2]) +OUTPUT INSERTED.[Id], i._Position +INTO @inserted0; + +SELECT [t].[Id], [t].[Data1] FROM [WithSomeDatabaseGenerated] t +INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]) +ORDER BY [i].[_Position];"); + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/UpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/UpdatesSqlServerTest.cs index ac06c242721..1d2c6b2b925 100644 --- a/test/EFCore.SqlServer.FunctionalTests/UpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/UpdatesSqlServerTest.cs @@ -43,23 +43,16 @@ public virtual void Save_with_shared_foreign_key() @"@p0='77' @p1=NULL (Size = 4000) @p2='777' +@p3=NULL (Size = 8000) (DbType = Binary) +@p4='ProductWithBytes' (Nullable = false) (Size = 4000) +@p5=NULL (Size = 4000) -SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [Categories] ([Id], [Name], [PrincipalId]) -VALUES (@p0, @p1, @p2);", - // - @"@p0=NULL (Size = 8000) (DbType = Binary) -@p1='ProductWithBytes' (Nullable = false) (Size = 4000) -@p2=NULL (Size = 4000) - -SET NOCOUNT ON; -DECLARE @inserted0 TABLE ([Id] uniqueidentifier); +VALUES (@p0, @p1, @p2); INSERT INTO [ProductBase] ([Bytes], [Discriminator], [ProductWithBytes_Name]) OUTPUT INSERTED.[Id] -INTO @inserted0 -VALUES (@p0, @p1, @p2); -SELECT [i].[Id] FROM @inserted0 i;"); +VALUES (@p3, @p4, @p5);"); } [ConditionalFact] @@ -68,60 +61,50 @@ public override void Can_add_and_remove_self_refs() base.Can_add_and_remove_self_refs(); AssertContainsSql( - @"@p0='1' (Size = 4000) + @"@p0='1' (Size = 4000) @p1=NULL (DbType = Int32) +SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [Person] ([Name], [ParentId]) -VALUES (@p0, @p1); -SELECT [PersonId] -FROM [Person] -WHERE @@ROWCOUNT = 1 AND [PersonId] = scope_identity();", - // - @"@p0='2' (Size = 4000) -@p1='1' (Nullable = true) - -SET NOCOUNT ON; -INSERT INTO [Person] ([Name], [ParentId]) -VALUES (@p0, @p1); -SELECT [PersonId] -FROM [Person] -WHERE @@ROWCOUNT = 1 AND [PersonId] = scope_identity();", - // - @"@p0='3' (Size = 4000) -@p1='1' (Nullable = true) - -SET NOCOUNT ON; -INSERT INTO [Person] ([Name], [ParentId]) -VALUES (@p0, @p1); -SELECT [PersonId] -FROM [Person] -WHERE @@ROWCOUNT = 1 AND [PersonId] = scope_identity();", - // - @"@p2='4' (Size = 4000) -@p3='2' (Nullable = true) -@p4='5' (Size = 4000) -@p5='2' (Nullable = true) -@p6='6' (Size = 4000) -@p7='3' (Nullable = true) -@p8='7' (Size = 4000) -@p9='3' (Nullable = true) +OUTPUT INSERTED.[PersonId] +VALUES (@p0, @p1);", + // + @"@p2='2' (Size = 4000) +@p3='1' (Nullable = true) +@p4='3' (Size = 4000) +@p5='1' (Nullable = true) +SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -DECLARE @inserted0 TABLE ([PersonId] int, [_Position] [int]); MERGE [Person] USING ( VALUES (@p2, @p3, 0), -(@p4, @p5, 1), -(@p6, @p7, 2), -(@p8, @p9, 3)) AS i ([Name], [ParentId], _Position) ON 1=0 +(@p4, @p5, 1)) AS i ([Name], [ParentId], _Position) ON 1=0 WHEN NOT MATCHED THEN INSERT ([Name], [ParentId]) VALUES (i.[Name], i.[ParentId]) -OUTPUT INSERTED.[PersonId], i._Position -INTO @inserted0; +OUTPUT INSERTED.[PersonId], i._Position;", + // + @"@p6='4' (Size = 4000) +@p7='2' (Nullable = true) +@p8='5' (Size = 4000) +@p9='2' (Nullable = true) +@p10='6' (Size = 4000) +@p11='3' (Nullable = true) +@p12='7' (Size = 4000) +@p13='3' (Nullable = true) -SELECT [i].[PersonId] FROM @inserted0 i -ORDER BY [i].[_Position];"); +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +MERGE [Person] USING ( +VALUES (@p6, @p7, 0), +(@p8, @p9, 1), +(@p10, @p11, 2), +(@p12, @p13, 3)) AS i ([Name], [ParentId], _Position) ON 1=0 +WHEN NOT MATCHED THEN +INSERT ([Name], [ParentId]) +VALUES (i.[Name], i.[ParentId]) +OUTPUT INSERTED.[PersonId], i._Position;"); } public override void Save_replaced_principal() diff --git a/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs index bfa4f379fce..c032c7b032f 100644 --- a/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs @@ -133,10 +133,8 @@ public override void DatabaseGeneratedAttribute_autogenerates_values_when_set_to @p6='0' (Nullable = true) INSERT INTO ""Sample"" (""MaxLengthProperty"", ""Name"", ""RowVersion"", ""AdditionalDetails_Name"", ""AdditionalDetails_Value"", ""Details_Name"", ""Details_Value"") -VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6); -SELECT ""Unique_No"" -FROM ""Sample"" -WHERE changes() = 1 AND ""rowid"" = last_insert_rowid();"); +VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6) +RETURNING ""Unique_No"";"); } // Sqlite does not support length diff --git a/test/EFCore.Sqlite.FunctionalTests/Update/SqliteUpdateSqlGeneratorTest.cs b/test/EFCore.Sqlite.FunctionalTests/Update/SqliteUpdateSqlGeneratorTest.cs index f4cb3dd24bc..94da6a946f2 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Update/SqliteUpdateSqlGeneratorTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Update/SqliteUpdateSqlGeneratorTest.cs @@ -40,4 +40,49 @@ public override void GenerateNextSequenceValueOperation_returns_statement_with_s () => base.GenerateNextSequenceValueOperation_returns_statement_with_sanitized_sequence()); Assert.Equal(SqliteStrings.SequencesNotSupported, ex.Message); } + + protected override void AppendInsertOperation_insert_if_store_generated_columns_exist_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"INSERT INTO ""Ducks"" (""Name"", ""Quacks"", ""ConcurrencyToken"") +VALUES (@p0, @p1, @p2) +RETURNING ""Id"", ""Computed""; +", + stringBuilder.ToString()); + + protected override void AppendInsertOperation_for_store_generated_columns_but_no_identity_verification( + StringBuilder stringBuilder) + => AssertBaseline( + @"INSERT INTO ""Ducks"" (""Id"", ""Name"", ""Quacks"", ""ConcurrencyToken"") +VALUES (@p0, @p1, @p2, @p3) +RETURNING ""Computed""; +", + stringBuilder.ToString()); + + protected override void AppendInsertOperation_for_only_identity_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"INSERT INTO ""Ducks"" (""Name"", ""Quacks"", ""ConcurrencyToken"") +VALUES (@p0, @p1, @p2) +RETURNING ""Id""; +", + stringBuilder.ToString()); + + protected override void AppendInsertOperation_for_all_store_generated_columns_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"INSERT INTO ""Ducks"" +DEFAULT VALUES +RETURNING ""Id"", ""Computed""; +", + stringBuilder.ToString()); + + protected override void AppendInsertOperation_for_only_single_identity_columns_verification( + StringBuilder stringBuilder) + => AssertBaseline( + @"INSERT INTO ""Ducks"" +DEFAULT VALUES +RETURNING ""Id""; +", + stringBuilder.ToString()); + + private void AssertBaseline(string expected, string actual) + => Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Update/StoreValueGenerationSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Update/StoreValueGenerationSqliteTest.cs index 0904be07d53..3aa88b271a9 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Update/StoreValueGenerationSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Update/StoreValueGenerationSqliteTest.cs @@ -12,7 +12,7 @@ public StoreValueGenerationSqliteTest(StoreValueGenerationSqliteFixture fixture, : base(fixture) { fixture.TestSqlLoggerFactory.Clear(); - fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + // fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } protected override int ShouldExecuteInNumberOfCommands( @@ -32,10 +32,8 @@ public override async Task Add_with_generated_values(bool async) @"@p0='1000' INSERT INTO ""WithSomeDatabaseGenerated"" (""Data2"") -VALUES (@p0); -SELECT ""Id"", ""Data1"" -FROM ""WithSomeDatabaseGenerated"" -WHERE changes() = 1 AND ""rowid"" = last_insert_rowid();"); +VALUES (@p0) +RETURNING ""Id"", ""Data1"";"); } public override async Task Add_with_no_generated_values(bool async) @@ -57,10 +55,8 @@ public override async Task Add_with_all_generated_values(bool async) AssertSql( @"INSERT INTO ""WithAllDatabaseGenerated"" -DEFAULT VALUES; -SELECT ""Id"", ""Data1"", ""Data2"" -FROM ""WithAllDatabaseGenerated"" -WHERE changes() = 1 AND ""rowid"" = last_insert_rowid();"); +DEFAULT VALUES +RETURNING ""Id"", ""Data1"", ""Data2"";"); } public override async Task Modify_with_generated_values(bool async) @@ -116,18 +112,14 @@ public override async Task Add_Add_with_same_entity_type_and_generated_values(bo @"@p0='1000' INSERT INTO ""WithSomeDatabaseGenerated"" (""Data2"") -VALUES (@p0); -SELECT ""Id"", ""Data1"" -FROM ""WithSomeDatabaseGenerated"" -WHERE changes() = 1 AND ""rowid"" = last_insert_rowid();", +VALUES (@p0) +RETURNING ""Id"", ""Data1"";", // @"@p0='1001' INSERT INTO ""WithSomeDatabaseGenerated"" (""Data2"") -VALUES (@p0); -SELECT ""Id"", ""Data1"" -FROM ""WithSomeDatabaseGenerated"" -WHERE changes() = 1 AND ""rowid"" = last_insert_rowid();"); +VALUES (@p0) +RETURNING ""Id"", ""Data1"";"); } public override async Task Add_Add_with_same_entity_type_and_no_generated_values(bool async) @@ -156,16 +148,12 @@ public override async Task Add_Add_with_same_entity_type_and_all_generated_value AssertSql( @"INSERT INTO ""WithAllDatabaseGenerated"" -DEFAULT VALUES; -SELECT ""Id"", ""Data1"", ""Data2"" -FROM ""WithAllDatabaseGenerated"" -WHERE changes() = 1 AND ""rowid"" = last_insert_rowid();", +DEFAULT VALUES +RETURNING ""Id"", ""Data1"", ""Data2"";", // @"INSERT INTO ""WithAllDatabaseGenerated"" -DEFAULT VALUES; -SELECT ""Id"", ""Data1"", ""Data2"" -FROM ""WithAllDatabaseGenerated"" -WHERE changes() = 1 AND ""rowid"" = last_insert_rowid();"); +DEFAULT VALUES +RETURNING ""Id"", ""Data1"", ""Data2"";"); } public override async Task Modify_Modify_with_same_entity_type_and_generated_values(bool async) @@ -244,18 +232,14 @@ public override async Task Add_Add_with_different_entity_types_and_generated_val @"@p0='1000' INSERT INTO ""WithSomeDatabaseGenerated"" (""Data2"") -VALUES (@p0); -SELECT ""Id"", ""Data1"" -FROM ""WithSomeDatabaseGenerated"" -WHERE changes() = 1 AND ""rowid"" = last_insert_rowid();", +VALUES (@p0) +RETURNING ""Id"", ""Data1"";", // @"@p0='1001' INSERT INTO ""WithSomeDatabaseGenerated2"" (""Data2"") -VALUES (@p0); -SELECT ""Id"", ""Data1"" -FROM ""WithSomeDatabaseGenerated2"" -WHERE changes() = 1 AND ""rowid"" = last_insert_rowid();"); +VALUES (@p0) +RETURNING ""Id"", ""Data1"";"); } public override async Task Add_Add_with_different_entity_types_and_no_generated_values(bool async) @@ -284,16 +268,12 @@ public override async Task Add_Add_with_different_entity_types_and_all_generated AssertSql( @"INSERT INTO ""WithAllDatabaseGenerated"" -DEFAULT VALUES; -SELECT ""Id"", ""Data1"", ""Data2"" -FROM ""WithAllDatabaseGenerated"" -WHERE changes() = 1 AND ""rowid"" = last_insert_rowid();", +DEFAULT VALUES +RETURNING ""Id"", ""Data1"", ""Data2"";", // @"INSERT INTO ""WithAllDatabaseGenerated2"" -DEFAULT VALUES; -SELECT ""Id"", ""Data1"", ""Data2"" -FROM ""WithAllDatabaseGenerated2"" -WHERE changes() = 1 AND ""rowid"" = last_insert_rowid();"); +DEFAULT VALUES +RETURNING ""Id"", ""Data1"", ""Data2"";"); } public override async Task Modify_Modify_with_different_entity_types_and_generated_values(bool async) From 587de3000b9d8d9c4f4456b2799b1a1dd989321f Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 17 Mar 2022 15:42:16 +0200 Subject: [PATCH 037/143] Support SQL Server UTF8 strings (#27634) Closes #25798 --- .../Internal/SqlServerStringTypeMapping.cs | 36 +- .../BuiltInDataTypesSqlServerTest.cs | 476 +++++++++++++----- .../TestUtilities/SqlServerCondition.cs | 1 + .../SqlServerConditionAttribute.cs | 5 + .../TestUtilities/TestEnvironment.cs | 31 ++ .../Storage/SqlServerTypeMappingTest.cs | 42 +- 6 files changed, 458 insertions(+), 133 deletions(-) diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerStringTypeMapping.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerStringTypeMapping.cs index 77c4da4b79e..ed8f75818ed 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerStringTypeMapping.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerStringTypeMapping.cs @@ -18,6 +18,7 @@ public class SqlServerStringTypeMapping : StringTypeMapping private const int UnicodeMax = 4000; private const int AnsiMax = 8000; + private readonly bool _isUtf16; private readonly SqlDbType? _sqlDbType; private readonly int _maxSpecificSize; private readonly int _maxSize; @@ -38,7 +39,7 @@ public SqlServerStringTypeMapping( : this( new RelationalTypeMappingParameters( new CoreTypeMappingParameters(typeof(string)), - storeType ?? GetStoreName(unicode, fixedLength), + storeType ?? GetDefaultStoreName(unicode, fixedLength), storeTypePostfix ?? StoreTypePostfix.Size, GetDbType(unicode, fixedLength), unicode, @@ -48,7 +49,7 @@ public SqlServerStringTypeMapping( { } - private static string GetStoreName(bool unicode, bool fixedLength) + private static string GetDefaultStoreName(bool unicode, bool fixedLength) => unicode ? fixedLength ? "nchar" : "nvarchar" : fixedLength @@ -84,6 +85,7 @@ protected SqlServerStringTypeMapping(RelationalTypeMappingParameters parameters, _maxSize = AnsiMax; } + _isUtf16 = parameters.Unicode && parameters.StoreType.StartsWith("n", StringComparison.OrdinalIgnoreCase); _sqlDbType = sqlDbType; } @@ -93,7 +95,23 @@ protected SqlServerStringTypeMapping(RelationalTypeMappingParameters parameters, /// The parameters for this mapping. /// The newly created mapping. protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) - => new SqlServerStringTypeMapping(parameters, _sqlDbType); + { + if (parameters.Unicode) + { + parameters = new( + parameters.CoreParameters, + parameters.StoreType, + parameters.StoreTypePostfix, + GetDbType(parameters.Unicode, parameters.FixedLength), + parameters.Unicode, + parameters.Size, + parameters.FixedLength, + parameters.Precision, + parameters.Scale); + } + + return new SqlServerStringTypeMapping(parameters, _sqlDbType); + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -184,7 +202,7 @@ protected override string GenerateNonNullSqlLiteral(object value) { AddConcatOperatorIfNeeded(); - if (IsUnicode) + if (_isUtf16) { builder.Append('N'); } @@ -206,7 +224,7 @@ protected override string GenerateNonNullSqlLiteral(object value) AddConcatOperatorIfNeeded(); - if (IsUnicode) + if (_isUtf16) { builder.Append('n'); } @@ -222,7 +240,7 @@ protected override string GenerateNonNullSqlLiteral(object value) { AddConcatOperatorIfNeeded(); - if (IsUnicode) + if (_isUtf16) { builder.Append('N'); } @@ -245,7 +263,7 @@ protected override string GenerateNonNullSqlLiteral(object value) { AddConcatOperatorIfNeeded(); - if (IsUnicode) + if (_isUtf16) { builder.Append('N'); } @@ -275,7 +293,7 @@ protected override string GenerateNonNullSqlLiteral(object value) if (builder.Length == 0) { - if (IsUnicode) + if (_isUtf16) { builder.Append('N'); } @@ -292,7 +310,7 @@ void AddConcatOperatorIfNeeded() if (!castApplied) { builder.Append(" AS "); - if (IsUnicode) + if (_isUtf16) { builder.Append('n'); } diff --git a/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs index 1b9ea4c2a2f..29bb00abd3c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs @@ -10,7 +10,7 @@ // ReSharper disable PossibleInvalidOperationException namespace Microsoft.EntityFrameworkCore; -[SqlServerCondition(SqlServerCondition.IsNotSqlAzure)] +[SqlServerCondition(SqlServerCondition.IsNotSqlAzure | SqlServerCondition.SupportsUtf8)] public class BuiltInDataTypesSqlServerTest : BuiltInDataTypesTestBase { private static readonly string _eol = Environment.NewLine; @@ -688,24 +688,29 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types() @p35='887876' (DbType = Object) @p36='Bang!' (Nullable = false) (Size = 5) (DbType = Object) @p37='Your' (Nullable = false) (Size = 8000) (DbType = AnsiString) -@p38='strong' (Nullable = false) (Size = 8000) (DbType = AnsiString) -@p39='help' (Nullable = false) (Size = 4000) -@p40='anyone!' (Nullable = false) (Size = 4000) -@p41='Gumball Rules OK!' (Nullable = false) (Size = 4000) -@p42='" +@p38='And now' (Nullable = false) (Size = 4000) +@p39='strong' (Nullable = false) (Size = 8000) (DbType = AnsiString) +@p40='this...' (Nullable = false) (Size = 4000) +@p41='help' (Nullable = false) (Size = 4000) +@p42='anyone!' (Nullable = false) (Size = 4000) +@p43='Gumball Rules OK!' (Nullable = false) (Size = 4000) +@p44='" + entity.StringAsNvarcharMax + @"' (Nullable = false) (Size = -1) -@p43='Gumball Rules!' (Nullable = false) (Size = 8000) (DbType = AnsiString) -@p44='" +@p45='Gumball Rules!' (Nullable = false) (Size = 8000) (DbType = AnsiString) +@p46='" + entity.StringAsVarcharMax + @"' (Nullable = false) (Size = -1) (DbType = AnsiString) -@p45='11:15:12' -@p46='65535' -@p47='-1' -@p48='4294967295' -@p49='-1' +@p47='" + + entity.StringAsVarcharMaxUtf8 + + @"' (Nullable = false) (Size = -1) +@p48='11:15:12' +@p49='65535' @p50='-1' -@p51='18446744073709551615' (Precision = 20)", +@p51='4294967295' +@p52='-1' +@p53='-1' +@p54='18446744073709551615' (Precision = 20)", parameters, ignoreLineEndingDifferences: true); @@ -744,11 +749,14 @@ private static void AssertMappedDataTypes(MappedDataTypes entity, int id) Assert.Equal(new DateTime(2019, 1, 2, 14, 11, 12), entity.DateTimeAsDatetime); Assert.Equal(new TimeSpan(11, 15, 12), entity.TimeSpanAsTime); Assert.Equal(expected.StringAsVarcharMax, entity.StringAsVarcharMax); - Assert.Equal("Your", entity.StringAsAsCharVaryingMax); + Assert.Equal("Your", entity.StringAsCharVaryingMax); Assert.Equal("strong", entity.StringAsCharacterVaryingMax); Assert.Equal(expected.StringAsNvarcharMax, entity.StringAsNvarcharMax); Assert.Equal("help", entity.StringAsNationalCharVaryingMax); Assert.Equal("anyone!", entity.StringAsNationalCharacterVaryingMax); + Assert.Equal(expected.StringAsVarcharMaxUtf8, entity.StringAsVarcharMaxUtf8); + Assert.Equal("And now", entity.StringAsCharVaryingMaxUtf8); + Assert.Equal("this...", entity.StringAsCharacterVaryingMaxUtf8); Assert.Equal("Gumball Rules!", entity.StringAsText); Assert.Equal("Gumball Rules OK!", entity.StringAsNtext); Assert.Equal(new byte[] { 89, 90, 91, 92 }, entity.BytesAsVarbinaryMax); @@ -801,11 +809,14 @@ private static MappedDataTypes CreateMappedDataTypes(int id) DateTimeAsDatetime = new DateTime(2019, 1, 2, 14, 11, 12), TimeSpanAsTime = new TimeSpan(11, 15, 12), StringAsVarcharMax = string.Concat(Enumerable.Repeat("C", 8001)), - StringAsAsCharVaryingMax = "Your", + StringAsCharVaryingMax = "Your", StringAsCharacterVaryingMax = "strong", StringAsNvarcharMax = string.Concat(Enumerable.Repeat("D", 4001)), StringAsNationalCharVaryingMax = "help", StringAsNationalCharacterVaryingMax = "anyone!", + StringAsVarcharMaxUtf8 = string.Concat(Enumerable.Repeat("E", 4001)), + StringAsCharVaryingMaxUtf8 = "And now", + StringAsCharacterVaryingMaxUtf8 = "this...", StringAsText = "Gumball Rules!", StringAsNtext = "Gumball Rules OK!", BytesAsVarbinaryMax = new byte[] { 89, 90, 91, 92 }, @@ -1048,20 +1059,23 @@ public virtual void Can_insert_and_read_back_all_mapped_nullable_data_types() @p35='887876' (Nullable = true) (DbType = Object) @p36='Bang!' (Size = 5) (DbType = Object) @p37='Your' (Size = 8000) (DbType = AnsiString) -@p38='strong' (Size = 8000) (DbType = AnsiString) -@p39='help' (Size = 4000) -@p40='anyone!' (Size = 4000) -@p41='Gumball Rules OK!' (Size = 4000) -@p42='don't' (Size = 4000) -@p43='Gumball Rules!' (Size = 8000) (DbType = AnsiString) -@p44='C' (Size = 8000) (DbType = AnsiString) -@p45='11:15:12' (Nullable = true) -@p46='65535' (Nullable = true) -@p47='-1' (Nullable = true) -@p48='4294967295' (Nullable = true) -@p49='-1' (Nullable = true) +@p38='And now' (Size = 4000) +@p39='strong' (Size = 8000) (DbType = AnsiString) +@p40='this...' (Size = 4000) +@p41='help' (Size = 4000) +@p42='anyone!' (Size = 4000) +@p43='Gumball Rules OK!' (Size = 4000) +@p44='don't' (Size = 4000) +@p45='Gumball Rules!' (Size = 8000) (DbType = AnsiString) +@p46='C' (Size = 8000) (DbType = AnsiString) +@p47='short' (Size = 4000) +@p48='11:15:12' (Nullable = true) +@p49='65535' (Nullable = true) @p50='-1' (Nullable = true) -@p51='18446744073709551615' (Nullable = true) (Precision = 20)", +@p51='4294967295' (Nullable = true) +@p52='-1' (Nullable = true) +@p53='-1' (Nullable = true) +@p54='18446744073709551615' (Nullable = true) (Precision = 20)", parameters, ignoreLineEndingDifferences: true); @@ -1158,6 +1172,9 @@ private static MappedNullableDataTypes CreateMappedNullableDataTypes(int id) StringAsNvarcharMax = "don't", StringAsNationalCharVaryingMax = "help", StringAsNationalCharacterVaryingMax = "anyone!", + StringAsVarcharMaxUtf8 = "short", + StringAsCharVaryingMaxUtf8 = "And now", + StringAsCharacterVaryingMaxUtf8 = "this...", StringAsText = "Gumball Rules!", StringAsNtext = "Gumball Rules OK!", BytesAsVarbinaryMax = new byte[] { 89, 90, 91, 92 }, @@ -1236,20 +1253,23 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_set_to_null() @p35=NULL (DbType = Object) @p36=NULL (DbType = Object) @p37=NULL (Size = 8000) (DbType = AnsiString) -@p38=NULL (Size = 8000) (DbType = AnsiString) -@p39=NULL (Size = 4000) +@p38=NULL (Size = 4000) +@p39=NULL (Size = 8000) (DbType = AnsiString) @p40=NULL (Size = 4000) @p41=NULL (Size = 4000) @p42=NULL (Size = 4000) -@p43=NULL (Size = 8000) (DbType = AnsiString) -@p44=NULL (Size = 8000) (DbType = AnsiString) -@p45=NULL (DbType = Time) -@p46=NULL (DbType = Int32) -@p47=NULL (DbType = Int16) -@p48=NULL (DbType = Int64) +@p43=NULL (Size = 4000) +@p44=NULL (Size = 4000) +@p45=NULL (Size = 8000) (DbType = AnsiString) +@p46=NULL (Size = 8000) (DbType = AnsiString) +@p47=NULL (Size = 4000) +@p48=NULL (DbType = Time) @p49=NULL (DbType = Int32) -@p50=NULL (DbType = Int64) -@p51=NULL (Precision = 20) (DbType = Decimal)", +@p50=NULL (DbType = Int16) +@p51=NULL (DbType = Int64) +@p52=NULL (DbType = Int32) +@p53=NULL (DbType = Int64) +@p54=NULL (Precision = 20) (DbType = Decimal)", parameters, ignoreLineEndingDifferences: true); @@ -1338,15 +1358,20 @@ public virtual void Can_insert_and_read_back_all_mapped_sized_data_types() @p8='D' (Size = 3) @p9='A' (Size = 3) (DbType = AnsiString) @p10='Wor' (Size = 3) (DbType = AnsiStringFixedLength) -@p11='Thr' (Size = 3) (DbType = AnsiString) -@p12='Lon' (Size = 3) (DbType = AnsiStringFixedLength) -@p13='Let' (Size = 3) (DbType = AnsiString) -@p14='The' (Size = 3) -@p15='Squ' (Size = 3) (DbType = StringFixedLength) -@p16='Col' (Size = 3) -@p17='Won' (Size = 3) (DbType = StringFixedLength) -@p18='Int' (Size = 3) -@p19='Tha' (Size = 3) (DbType = AnsiString)", +@p11='Wha' (Size = 3) (DbType = StringFixedLength) +@p12='Thr' (Size = 3) (DbType = AnsiString) +@p13='tex' (Size = 3) +@p14='Lon' (Size = 3) (DbType = AnsiStringFixedLength) +@p15='doe' (Size = 3) (DbType = StringFixedLength) +@p16='Let' (Size = 3) (DbType = AnsiString) +@p17='men' (Size = 3) +@p18='The' (Size = 3) +@p19='Squ' (Size = 3) (DbType = StringFixedLength) +@p20='Col' (Size = 3) +@p21='Won' (Size = 3) (DbType = StringFixedLength) +@p22='Int' (Size = 3) +@p23='Tha' (Size = 3) (DbType = AnsiString) +@p24='the' (Size = 3)", parameters, ignoreLineEndingDifferences: true); @@ -1369,6 +1394,11 @@ private static void AssertMappedSizedDataTypes(MappedSizedDataTypes entity, int Assert.Equal("Int", entity.StringAsNvarchar3); Assert.Equal("The", entity.StringAsNationalCharVarying3); Assert.Equal("Col", entity.StringAsNationalCharacterVarying3); + Assert.Equal("Wha", entity.StringAsChar3Utf8); + Assert.Equal("doe", entity.StringAsCharacter3Utf8); + Assert.Equal("the", entity.StringAsVarchar3Utf8); + Assert.Equal("tex", entity.StringAsCharVarying3Utf8); + Assert.Equal("men", entity.StringAsCharacterVarying3Utf8); Assert.Equal(new byte[] { 10, 11, 12 }, entity.BytesAsBinary3); Assert.Equal(new byte[] { 11, 12, 13 }, entity.BytesAsVarbinary3); Assert.Equal(new byte[] { 12, 13, 14 }, entity.BytesAsBinaryVarying3); @@ -1394,6 +1424,11 @@ private static MappedSizedDataTypes CreateMappedSizedDataTypes(int id) StringAsNvarchar3 = "Int", StringAsNationalCharVarying3 = "The", StringAsNationalCharacterVarying3 = "Col", + StringAsChar3Utf8 = "Wha", + StringAsCharacter3Utf8 = "doe", + StringAsVarchar3Utf8 = "the", + StringAsCharVarying3Utf8 = "tex", + StringAsCharacterVarying3Utf8 = "men", BytesAsBinary3 = new byte[] { 10, 11, 12 }, BytesAsVarbinary3 = new byte[] { 11, 12, 13 }, BytesAsBinaryVarying3 = new byte[] { 12, 13, 14 }, @@ -1428,15 +1463,20 @@ public virtual void Can_insert_and_read_back_nulls_for_all_mapped_sized_data_typ @p8=NULL (Size = 3) @p9=NULL (Size = 3) (DbType = AnsiString) @p10=NULL (Size = 3) (DbType = AnsiStringFixedLength) -@p11=NULL (Size = 3) (DbType = AnsiString) -@p12=NULL (Size = 3) (DbType = AnsiStringFixedLength) -@p13=NULL (Size = 3) (DbType = AnsiString) -@p14=NULL (Size = 3) +@p11=NULL (Size = 3) (DbType = StringFixedLength) +@p12=NULL (Size = 3) (DbType = AnsiString) +@p13=NULL (Size = 3) +@p14=NULL (Size = 3) (DbType = AnsiStringFixedLength) @p15=NULL (Size = 3) (DbType = StringFixedLength) -@p16=NULL (Size = 3) -@p17=NULL (Size = 3) (DbType = StringFixedLength) +@p16=NULL (Size = 3) (DbType = AnsiString) +@p17=NULL (Size = 3) @p18=NULL (Size = 3) -@p19=NULL (Size = 3) (DbType = AnsiString)", +@p19=NULL (Size = 3) (DbType = StringFixedLength) +@p20=NULL (Size = 3) +@p21=NULL (Size = 3) (DbType = StringFixedLength) +@p22=NULL (Size = 3) +@p23=NULL (Size = 3) (DbType = AnsiString) +@p24=NULL (Size = 3)", parameters, ignoreLineEndingDifferences: true); @@ -1459,6 +1499,11 @@ private static void AssertNullMappedSizedDataTypes(MappedSizedDataTypes entity, Assert.Null(entity.StringAsNvarchar3); Assert.Null(entity.StringAsNationalCharVarying3); Assert.Null(entity.StringAsNationalCharacterVarying3); + Assert.Null(entity.StringAsChar3Utf8); + Assert.Null(entity.StringAsCharacter3Utf8); + Assert.Null(entity.StringAsVarchar3Utf8); + Assert.Null(entity.StringAsCharVarying3Utf8); + Assert.Null(entity.StringAsCharacterVarying3Utf8); Assert.Null(entity.BytesAsBinary3); Assert.Null(entity.BytesAsVarbinary3); Assert.Null(entity.BytesAsBinaryVarying3); @@ -1493,15 +1538,20 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_sized_separat @p8='D' (Size = 3) @p9='A' (Size = 3) (DbType = AnsiString) @p10='Wor' (Size = 3) (DbType = AnsiStringFixedLength) -@p11='Thr' (Size = 3) (DbType = AnsiString) -@p12='Lon' (Size = 3) (DbType = AnsiStringFixedLength) -@p13='Let' (Size = 3) (DbType = AnsiString) -@p14='The' (Size = 3) -@p15='Squ' (Size = 3) (DbType = StringFixedLength) -@p16='Col' (Size = 3) -@p17='Won' (Size = 3) (DbType = StringFixedLength) -@p18='Int' (Size = 3) -@p19='Tha' (Size = 3) (DbType = AnsiString)", +@p11='Wha' (Size = 3) (DbType = AnsiStringFixedLength) +@p12='Thr' (Size = 3) (DbType = AnsiString) +@p13='tex' (Size = 3) (DbType = AnsiString) +@p14='Lon' (Size = 3) (DbType = AnsiStringFixedLength) +@p15='doe' (Size = 3) (DbType = AnsiStringFixedLength) +@p16='Let' (Size = 3) (DbType = AnsiString) +@p17='men' (Size = 3) (DbType = AnsiString) +@p18='The' (Size = 3) +@p19='Squ' (Size = 3) (DbType = StringFixedLength) +@p20='Col' (Size = 3) +@p21='Won' (Size = 3) (DbType = StringFixedLength) +@p22='Int' (Size = 3) +@p23='Tha' (Size = 3) (DbType = AnsiString) +@p24='the' (Size = 3) (DbType = AnsiString)", parameters, ignoreLineEndingDifferences: true); @@ -1549,6 +1599,11 @@ private static MappedSizedSeparatelyDataTypes CreateMappedSizedSeparatelyDataTyp StringAsNvarchar3 = "Int", StringAsNationalCharVarying3 = "The", StringAsNationalCharacterVarying3 = "Col", + StringAsChar3Utf8 = "Wha", + StringAsCharacter3Utf8 = "doe", + StringAsVarchar3Utf8 = "the", + StringAsCharVarying3Utf8 = "tex", + StringAsCharacterVarying3Utf8 = "men", BytesAsBinary3 = new byte[] { 10, 11, 12 }, BytesAsVarbinary3 = new byte[] { 11, 12, 13 }, BytesAsBinaryVarying3 = new byte[] { 12, 13, 14 }, @@ -1862,20 +1917,23 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_with_identity @p35='887876' (DbType = Object) @p36='Bang!' (Nullable = false) (Size = 5) (DbType = Object) @p37='Your' (Nullable = false) (Size = 8000) (DbType = AnsiString) -@p38='strong' (Nullable = false) (Size = 8000) (DbType = AnsiString) -@p39='help' (Nullable = false) (Size = 4000) -@p40='anyone!' (Nullable = false) (Size = 4000) -@p41='Gumball Rules OK!' (Nullable = false) (Size = 4000) -@p42='don't' (Nullable = false) (Size = 4000) -@p43='Gumball Rules!' (Nullable = false) (Size = 8000) (DbType = AnsiString) -@p44='C' (Nullable = false) (Size = 8000) (DbType = AnsiString) -@p45='11:15:12' -@p46='65535' -@p47='-1' -@p48='4294967295' -@p49='-1' +@p38='And now' (Nullable = false) (Size = 4000) +@p39='strong' (Nullable = false) (Size = 8000) (DbType = AnsiString) +@p40='this...' (Nullable = false) (Size = 4000) +@p41='help' (Nullable = false) (Size = 4000) +@p42='anyone!' (Nullable = false) (Size = 4000) +@p43='Gumball Rules OK!' (Nullable = false) (Size = 4000) +@p44='don't' (Nullable = false) (Size = 4000) +@p45='Gumball Rules!' (Nullable = false) (Size = 8000) (DbType = AnsiString) +@p46='C' (Nullable = false) (Size = 8000) (DbType = AnsiString) +@p47='short' (Nullable = false) (Size = 4000) +@p48='11:15:12' +@p49='65535' @p50='-1' -@p51='18446744073709551615' (Precision = 20)", +@p51='4294967295' +@p52='-1' +@p53='-1' +@p54='18446744073709551615' (Precision = 20)", parameters, ignoreLineEndingDifferences: true); @@ -1915,6 +1973,9 @@ private static void AssertMappedDataTypesWithIdentity(MappedDataTypesWithIdentit Assert.Equal("don't", entity.StringAsNvarcharMax); Assert.Equal("help", entity.StringAsNationalCharVaryingMax); Assert.Equal("anyone!", entity.StringAsNationalCharacterVaryingMax); + Assert.Equal("short", entity.StringAsVarcharMaxUtf8); + Assert.Equal("And now", entity.StringAsCharVaryingMaxUtf8); + Assert.Equal("this...", entity.StringAsCharacterVaryingMaxUtf8); Assert.Equal("Gumball Rules!", entity.StringAsText); Assert.Equal("Gumball Rules OK!", entity.StringAsNtext); Assert.Equal(new byte[] { 89, 90, 91, 92 }, entity.BytesAsVarbinaryMax); @@ -1972,6 +2033,9 @@ private static MappedDataTypesWithIdentity CreateMappedDataTypesWithIdentity(int StringAsNvarcharMax = "don't", StringAsNationalCharVaryingMax = "help", StringAsNationalCharacterVaryingMax = "anyone!", + StringAsVarcharMaxUtf8 = "short", + StringAsCharVaryingMaxUtf8 = "And now", + StringAsCharacterVaryingMaxUtf8 = "this...", StringAsText = "Gumball Rules!", StringAsNtext = "Gumball Rules OK!", BytesAsVarbinaryMax = new byte[] { 89, 90, 91, 92 }, @@ -2050,20 +2114,23 @@ public virtual void Can_insert_and_read_back_all_mapped_nullable_data_types_with @p35='887876' (Nullable = true) (DbType = Object) @p36='Bang!' (Size = 5) (DbType = Object) @p37='Your' (Size = 8000) (DbType = AnsiString) -@p38='strong' (Size = 8000) (DbType = AnsiString) -@p39='help' (Size = 4000) -@p40='anyone!' (Size = 4000) -@p41='Gumball Rules OK!' (Size = 4000) -@p42='don't' (Size = 4000) -@p43='Gumball Rules!' (Size = 8000) (DbType = AnsiString) -@p44='C' (Size = 8000) (DbType = AnsiString) -@p45='11:15:12' (Nullable = true) -@p46='65535' (Nullable = true) -@p47='4294967295' (Nullable = true) -@p48='-1' (Nullable = true) -@p49='-1' (Nullable = true) -@p50='18446744073709551615' (Nullable = true) (Precision = 20) -@p51='-1' (Nullable = true)", +@p38='And now' (Size = 4000) +@p39='strong' (Size = 8000) (DbType = AnsiString) +@p40='this...' (Size = 4000) +@p41='help' (Size = 4000) +@p42='anyone!' (Size = 4000) +@p43='Gumball Rules OK!' (Size = 4000) +@p44='don't' (Size = 4000) +@p45='Gumball Rules!' (Size = 8000) (DbType = AnsiString) +@p46='C' (Size = 8000) (DbType = AnsiString) +@p47='short' (Size = 4000) +@p48='11:15:12' (Nullable = true) +@p49='65535' (Nullable = true) +@p50='4294967295' (Nullable = true) +@p51='-1' (Nullable = true) +@p52='-1' (Nullable = true) +@p53='18446744073709551615' (Nullable = true) (Precision = 20) +@p54='-1' (Nullable = true)", parameters, ignoreLineEndingDifferences: true); @@ -2160,6 +2227,9 @@ private static MappedNullableDataTypesWithIdentity CreateMappedNullableDataTypes StringAsNvarcharMax = "don't", StringAsNationalCharVaryingMax = "help", StringAsNationalCharacterVaryingMax = "anyone!", + StringAsVarcharMaxUtf8 = "short", + StringAsCharVaryingMaxUtf8 = "And now", + StringAsCharacterVaryingMaxUtf8 = "this...", StringAsText = "Gumball Rules!", StringAsNtext = "Gumball Rules OK!", BytesAsVarbinaryMax = new byte[] { 89, 90, 91, 92 }, @@ -2238,20 +2308,23 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_set_to_null_w @p35=NULL (DbType = Object) @p36=NULL (DbType = Object) @p37=NULL (Size = 8000) (DbType = AnsiString) -@p38=NULL (Size = 8000) (DbType = AnsiString) -@p39=NULL (Size = 4000) +@p38=NULL (Size = 4000) +@p39=NULL (Size = 8000) (DbType = AnsiString) @p40=NULL (Size = 4000) @p41=NULL (Size = 4000) @p42=NULL (Size = 4000) -@p43=NULL (Size = 8000) (DbType = AnsiString) -@p44=NULL (Size = 8000) (DbType = AnsiString) -@p45=NULL (DbType = Time) -@p46=NULL (DbType = Int32) -@p47=NULL (DbType = Int64) -@p48=NULL (DbType = Int32) -@p49=NULL (DbType = Int64) -@p50=NULL (Precision = 20) (DbType = Decimal) -@p51=NULL (DbType = Int16)", +@p43=NULL (Size = 4000) +@p44=NULL (Size = 4000) +@p45=NULL (Size = 8000) (DbType = AnsiString) +@p46=NULL (Size = 8000) (DbType = AnsiString) +@p47=NULL (Size = 4000) +@p48=NULL (DbType = Time) +@p49=NULL (DbType = Int32) +@p50=NULL (DbType = Int64) +@p51=NULL (DbType = Int32) +@p52=NULL (DbType = Int64) +@p53=NULL (Precision = 20) (DbType = Decimal) +@p54=NULL (DbType = Int16)", parameters, ignoreLineEndingDifferences: true); @@ -2343,15 +2416,20 @@ public virtual void Can_insert_and_read_back_all_mapped_sized_data_types_with_id @p8='A' (Size = 3) (DbType = AnsiString) @p9='77' @p10='Wor' (Size = 3) (DbType = AnsiStringFixedLength) -@p11='Thr' (Size = 3) (DbType = AnsiString) -@p12='Lon' (Size = 3) (DbType = AnsiStringFixedLength) -@p13='Let' (Size = 3) (DbType = AnsiString) -@p14='The' (Size = 3) -@p15='Squ' (Size = 3) (DbType = StringFixedLength) -@p16='Col' (Size = 3) -@p17='Won' (Size = 3) (DbType = StringFixedLength) -@p18='Int' (Size = 3) -@p19='Tha' (Size = 3) (DbType = AnsiString)", +@p11='Wha' (Size = 3) (DbType = StringFixedLength) +@p12='Thr' (Size = 3) (DbType = AnsiString) +@p13='tex' (Size = 3) +@p14='Lon' (Size = 3) (DbType = AnsiStringFixedLength) +@p15='doe' (Size = 3) (DbType = StringFixedLength) +@p16='Let' (Size = 3) (DbType = AnsiString) +@p17='men' (Size = 3) +@p18='The' (Size = 3) +@p19='Squ' (Size = 3) (DbType = StringFixedLength) +@p20='Col' (Size = 3) +@p21='Won' (Size = 3) (DbType = StringFixedLength) +@p22='Int' (Size = 3) +@p23='Tha' (Size = 3) (DbType = AnsiString) +@p24='the' (Size = 3)", parameters, ignoreLineEndingDifferences: true); @@ -2374,6 +2452,11 @@ private static void AssertMappedSizedDataTypesWithIdentity(MappedSizedDataTypesW Assert.Equal("Int", entity.StringAsNvarchar3); Assert.Equal("The", entity.StringAsNationalCharVarying3); Assert.Equal("Col", entity.StringAsNationalCharacterVarying3); + Assert.Equal("Wha", entity.StringAsChar3Utf8); + Assert.Equal("doe", entity.StringAsCharacter3Utf8); + Assert.Equal("the", entity.StringAsVarchar3Utf8); + Assert.Equal("tex", entity.StringAsCharVarying3Utf8); + Assert.Equal("men", entity.StringAsCharacterVarying3Utf8); Assert.Equal(new byte[] { 10, 11, 12 }, entity.BytesAsBinary3); Assert.Equal(new byte[] { 11, 12, 13 }, entity.BytesAsVarbinary3); Assert.Equal(new byte[] { 12, 13, 14 }, entity.BytesAsBinaryVarying3); @@ -2399,6 +2482,11 @@ private static MappedSizedDataTypesWithIdentity CreateMappedSizedDataTypesWithId StringAsNvarchar3 = "Int", StringAsNationalCharVarying3 = "The", StringAsNationalCharacterVarying3 = "Col", + StringAsChar3Utf8 = "Wha", + StringAsCharacter3Utf8 = "doe", + StringAsVarchar3Utf8 = "the", + StringAsCharVarying3Utf8 = "tex", + StringAsCharacterVarying3Utf8 = "men", BytesAsBinary3 = new byte[] { 10, 11, 12 }, BytesAsVarbinary3 = new byte[] { 11, 12, 13 }, BytesAsBinaryVarying3 = new byte[] { 12, 13, 14 }, @@ -2433,15 +2521,20 @@ public virtual void Can_insert_and_read_back_nulls_for_all_mapped_sized_data_typ @p8=NULL (Size = 3) (DbType = AnsiString) @p9='78' @p10=NULL (Size = 3) (DbType = AnsiStringFixedLength) -@p11=NULL (Size = 3) (DbType = AnsiString) -@p12=NULL (Size = 3) (DbType = AnsiStringFixedLength) -@p13=NULL (Size = 3) (DbType = AnsiString) -@p14=NULL (Size = 3) +@p11=NULL (Size = 3) (DbType = StringFixedLength) +@p12=NULL (Size = 3) (DbType = AnsiString) +@p13=NULL (Size = 3) +@p14=NULL (Size = 3) (DbType = AnsiStringFixedLength) @p15=NULL (Size = 3) (DbType = StringFixedLength) -@p16=NULL (Size = 3) -@p17=NULL (Size = 3) (DbType = StringFixedLength) +@p16=NULL (Size = 3) (DbType = AnsiString) +@p17=NULL (Size = 3) @p18=NULL (Size = 3) -@p19=NULL (Size = 3) (DbType = AnsiString)", +@p19=NULL (Size = 3) (DbType = StringFixedLength) +@p20=NULL (Size = 3) +@p21=NULL (Size = 3) (DbType = StringFixedLength) +@p22=NULL (Size = 3) +@p23=NULL (Size = 3) (DbType = AnsiString) +@p24=NULL (Size = 3)", parameters, ignoreLineEndingDifferences: true); @@ -2464,6 +2557,11 @@ private static void AssertNullMappedSizedDataTypesWithIdentity(MappedSizedDataTy Assert.Null(entity.StringAsNvarchar3); Assert.Null(entity.StringAsNationalCharVarying3); Assert.Null(entity.StringAsNationalCharacterVarying3); + Assert.Null(entity.StringAsChar3Utf8); + Assert.Null(entity.StringAsCharacter3Utf8); + Assert.Null(entity.StringAsVarchar3Utf8); + Assert.Null(entity.StringAsCharVarying3Utf8); + Assert.Null(entity.StringAsCharacterVarying3Utf8); Assert.Null(entity.BytesAsBinary3); Assert.Null(entity.BytesAsVarbinary3); Assert.Null(entity.BytesAsBinaryVarying3); @@ -3044,14 +3142,17 @@ public virtual void Columns_have_expected_data_types() MappedDataTypes.ShortAsSmallint ---> [smallint] [Precision = 5 Scale = 0] MappedDataTypes.SqlVariantInt ---> [sql_variant] [MaxLength = 0] MappedDataTypes.SqlVariantString ---> [sql_variant] [MaxLength = 0] -MappedDataTypes.StringAsAsCharVaryingMax ---> [varchar] [MaxLength = -1] MappedDataTypes.StringAsCharacterVaryingMax ---> [varchar] [MaxLength = -1] +MappedDataTypes.StringAsCharacterVaryingMaxUtf8 ---> [varchar] [MaxLength = -1] +MappedDataTypes.StringAsCharVaryingMax ---> [varchar] [MaxLength = -1] +MappedDataTypes.StringAsCharVaryingMaxUtf8 ---> [varchar] [MaxLength = -1] MappedDataTypes.StringAsNationalCharacterVaryingMax ---> [nvarchar] [MaxLength = -1] MappedDataTypes.StringAsNationalCharVaryingMax ---> [nvarchar] [MaxLength = -1] MappedDataTypes.StringAsNtext ---> [ntext] [MaxLength = 1073741823] MappedDataTypes.StringAsNvarcharMax ---> [nvarchar] [MaxLength = -1] MappedDataTypes.StringAsText ---> [text] [MaxLength = 2147483647] MappedDataTypes.StringAsVarcharMax ---> [varchar] [MaxLength = -1] +MappedDataTypes.StringAsVarcharMaxUtf8 ---> [varchar] [MaxLength = -1] MappedDataTypes.TimeSpanAsTime ---> [time] [Precision = 7] MappedDataTypes.UintAsBigint ---> [bigint] [Precision = 19 Scale = 0] MappedDataTypes.UintAsInt ---> [int] [Precision = 10 Scale = 0] @@ -3098,13 +3199,16 @@ public virtual void Columns_have_expected_data_types() MappedDataTypesWithIdentity.SqlVariantInt ---> [sql_variant] [MaxLength = 0] MappedDataTypesWithIdentity.SqlVariantString ---> [sql_variant] [MaxLength = 0] MappedDataTypesWithIdentity.StringAsCharacterVaryingMax ---> [varchar] [MaxLength = -1] +MappedDataTypesWithIdentity.StringAsCharacterVaryingMaxUtf8 ---> [varchar] [MaxLength = -1] MappedDataTypesWithIdentity.StringAsCharVaryingMax ---> [varchar] [MaxLength = -1] +MappedDataTypesWithIdentity.StringAsCharVaryingMaxUtf8 ---> [varchar] [MaxLength = -1] MappedDataTypesWithIdentity.StringAsNationalCharacterVaryingMax ---> [nvarchar] [MaxLength = -1] MappedDataTypesWithIdentity.StringAsNationalCharVaryingMax ---> [nvarchar] [MaxLength = -1] MappedDataTypesWithIdentity.StringAsNtext ---> [ntext] [MaxLength = 1073741823] MappedDataTypesWithIdentity.StringAsNvarcharMax ---> [nvarchar] [MaxLength = -1] MappedDataTypesWithIdentity.StringAsText ---> [text] [MaxLength = 2147483647] MappedDataTypesWithIdentity.StringAsVarcharMax ---> [varchar] [MaxLength = -1] +MappedDataTypesWithIdentity.StringAsVarcharMaxUtf8 ---> [varchar] [MaxLength = -1] MappedDataTypesWithIdentity.TimeSpanAsTime ---> [time] [Precision = 7] MappedDataTypesWithIdentity.UintAsBigint ---> [bigint] [Precision = 19 Scale = 0] MappedDataTypesWithIdentity.UintAsInt ---> [int] [Precision = 10 Scale = 0] @@ -3150,13 +3254,16 @@ public virtual void Columns_have_expected_data_types() MappedNullableDataTypes.SqlVariantInt ---> [nullable sql_variant] [MaxLength = 0] MappedNullableDataTypes.SqlVariantString ---> [nullable sql_variant] [MaxLength = 0] MappedNullableDataTypes.StringAsCharacterVaryingMax ---> [nullable varchar] [MaxLength = -1] +MappedNullableDataTypes.StringAsCharacterVaryingMaxUtf8 ---> [nullable varchar] [MaxLength = -1] MappedNullableDataTypes.StringAsCharVaryingMax ---> [nullable varchar] [MaxLength = -1] +MappedNullableDataTypes.StringAsCharVaryingMaxUtf8 ---> [nullable varchar] [MaxLength = -1] MappedNullableDataTypes.StringAsNationalCharacterVaryingMax ---> [nullable nvarchar] [MaxLength = -1] MappedNullableDataTypes.StringAsNationalCharVaryingMax ---> [nullable nvarchar] [MaxLength = -1] MappedNullableDataTypes.StringAsNtext ---> [nullable ntext] [MaxLength = 1073741823] MappedNullableDataTypes.StringAsNvarcharMax ---> [nullable nvarchar] [MaxLength = -1] MappedNullableDataTypes.StringAsText ---> [nullable text] [MaxLength = 2147483647] MappedNullableDataTypes.StringAsVarcharMax ---> [nullable varchar] [MaxLength = -1] +MappedNullableDataTypes.StringAsVarcharMaxUtf8 ---> [nullable varchar] [MaxLength = -1] MappedNullableDataTypes.TimeSpanAsTime ---> [nullable time] [Precision = 7] MappedNullableDataTypes.UintAsBigint ---> [nullable bigint] [Precision = 19 Scale = 0] MappedNullableDataTypes.UintAsInt ---> [nullable int] [Precision = 10 Scale = 0] @@ -3203,13 +3310,16 @@ public virtual void Columns_have_expected_data_types() MappedNullableDataTypesWithIdentity.SqlVariantInt ---> [nullable sql_variant] [MaxLength = 0] MappedNullableDataTypesWithIdentity.SqlVariantString ---> [nullable sql_variant] [MaxLength = 0] MappedNullableDataTypesWithIdentity.StringAsCharacterVaryingMax ---> [nullable varchar] [MaxLength = -1] +MappedNullableDataTypesWithIdentity.StringAsCharacterVaryingMaxUtf8 ---> [nullable varchar] [MaxLength = -1] MappedNullableDataTypesWithIdentity.StringAsCharVaryingMax ---> [nullable varchar] [MaxLength = -1] +MappedNullableDataTypesWithIdentity.StringAsCharVaryingMaxUtf8 ---> [nullable varchar] [MaxLength = -1] MappedNullableDataTypesWithIdentity.StringAsNationalCharacterVaryingMax ---> [nullable nvarchar] [MaxLength = -1] MappedNullableDataTypesWithIdentity.StringAsNationalCharVaryingMax ---> [nullable nvarchar] [MaxLength = -1] MappedNullableDataTypesWithIdentity.StringAsNtext ---> [nullable ntext] [MaxLength = 1073741823] MappedNullableDataTypesWithIdentity.StringAsNvarcharMax ---> [nullable nvarchar] [MaxLength = -1] MappedNullableDataTypesWithIdentity.StringAsText ---> [nullable text] [MaxLength = 2147483647] MappedNullableDataTypesWithIdentity.StringAsVarcharMax ---> [nullable varchar] [MaxLength = -1] +MappedNullableDataTypesWithIdentity.StringAsVarcharMaxUtf8 ---> [nullable varchar] [MaxLength = -1] MappedNullableDataTypesWithIdentity.TimeSpanAsTime ---> [nullable time] [Precision = 7] MappedNullableDataTypesWithIdentity.UintAsBigint ---> [nullable bigint] [Precision = 19 Scale = 0] MappedNullableDataTypesWithIdentity.UintAsInt ---> [nullable int] [Precision = 10 Scale = 0] @@ -3275,15 +3385,20 @@ public virtual void Columns_have_expected_data_types() MappedSizedDataTypes.CharAsVarchar3 ---> [nullable varchar] [MaxLength = 3] MappedSizedDataTypes.Id ---> [int] [Precision = 10 Scale = 0] MappedSizedDataTypes.StringAsChar3 ---> [nullable char] [MaxLength = 3] +MappedSizedDataTypes.StringAsChar3Utf8 ---> [nullable char] [MaxLength = 3] MappedSizedDataTypes.StringAsCharacter3 ---> [nullable char] [MaxLength = 3] +MappedSizedDataTypes.StringAsCharacter3Utf8 ---> [nullable char] [MaxLength = 3] MappedSizedDataTypes.StringAsCharacterVarying3 ---> [nullable varchar] [MaxLength = 3] +MappedSizedDataTypes.StringAsCharacterVarying3Utf8 ---> [nullable varchar] [MaxLength = 3] MappedSizedDataTypes.StringAsCharVarying3 ---> [nullable varchar] [MaxLength = 3] +MappedSizedDataTypes.StringAsCharVarying3Utf8 ---> [nullable varchar] [MaxLength = 3] MappedSizedDataTypes.StringAsNationalCharacter3 ---> [nullable nchar] [MaxLength = 3] MappedSizedDataTypes.StringAsNationalCharacterVarying3 ---> [nullable nvarchar] [MaxLength = 3] MappedSizedDataTypes.StringAsNationalCharVarying3 ---> [nullable nvarchar] [MaxLength = 3] MappedSizedDataTypes.StringAsNchar3 ---> [nullable nchar] [MaxLength = 3] MappedSizedDataTypes.StringAsNvarchar3 ---> [nullable nvarchar] [MaxLength = 3] MappedSizedDataTypes.StringAsVarchar3 ---> [nullable varchar] [MaxLength = 3] +MappedSizedDataTypes.StringAsVarchar3Utf8 ---> [nullable varchar] [MaxLength = 3] MappedSizedDataTypesWithIdentity.BytesAsBinary3 ---> [nullable binary] [MaxLength = 3] MappedSizedDataTypesWithIdentity.BytesAsBinaryVarying3 ---> [nullable varbinary] [MaxLength = 3] MappedSizedDataTypesWithIdentity.BytesAsVarbinary3 ---> [nullable varbinary] [MaxLength = 3] @@ -3296,15 +3411,20 @@ public virtual void Columns_have_expected_data_types() MappedSizedDataTypesWithIdentity.Id ---> [int] [Precision = 10 Scale = 0] MappedSizedDataTypesWithIdentity.Int ---> [int] [Precision = 10 Scale = 0] MappedSizedDataTypesWithIdentity.StringAsChar3 ---> [nullable char] [MaxLength = 3] +MappedSizedDataTypesWithIdentity.StringAsChar3Utf8 ---> [nullable char] [MaxLength = 3] MappedSizedDataTypesWithIdentity.StringAsCharacter3 ---> [nullable char] [MaxLength = 3] +MappedSizedDataTypesWithIdentity.StringAsCharacter3Utf8 ---> [nullable char] [MaxLength = 3] MappedSizedDataTypesWithIdentity.StringAsCharacterVarying3 ---> [nullable varchar] [MaxLength = 3] +MappedSizedDataTypesWithIdentity.StringAsCharacterVarying3Utf8 ---> [nullable varchar] [MaxLength = 3] MappedSizedDataTypesWithIdentity.StringAsCharVarying3 ---> [nullable varchar] [MaxLength = 3] +MappedSizedDataTypesWithIdentity.StringAsCharVarying3Utf8 ---> [nullable varchar] [MaxLength = 3] MappedSizedDataTypesWithIdentity.StringAsNationalCharacter3 ---> [nullable nchar] [MaxLength = 3] MappedSizedDataTypesWithIdentity.StringAsNationalCharacterVarying3 ---> [nullable nvarchar] [MaxLength = 3] MappedSizedDataTypesWithIdentity.StringAsNationalCharVarying3 ---> [nullable nvarchar] [MaxLength = 3] MappedSizedDataTypesWithIdentity.StringAsNchar3 ---> [nullable nchar] [MaxLength = 3] MappedSizedDataTypesWithIdentity.StringAsNvarchar3 ---> [nullable nvarchar] [MaxLength = 3] MappedSizedDataTypesWithIdentity.StringAsVarchar3 ---> [nullable varchar] [MaxLength = 3] +MappedSizedDataTypesWithIdentity.StringAsVarchar3Utf8 ---> [nullable varchar] [MaxLength = 3] MappedSizedSeparatelyDataTypes.BytesAsBinary3 ---> [nullable binary] [MaxLength = 3] MappedSizedSeparatelyDataTypes.BytesAsBinaryVarying3 ---> [nullable varbinary] [MaxLength = 3] MappedSizedSeparatelyDataTypes.BytesAsVarbinary3 ---> [nullable varbinary] [MaxLength = 3] @@ -3316,15 +3436,20 @@ public virtual void Columns_have_expected_data_types() MappedSizedSeparatelyDataTypes.CharAsVarchar3 ---> [nullable varchar] [MaxLength = 3] MappedSizedSeparatelyDataTypes.Id ---> [int] [Precision = 10 Scale = 0] MappedSizedSeparatelyDataTypes.StringAsChar3 ---> [nullable char] [MaxLength = 3] +MappedSizedSeparatelyDataTypes.StringAsChar3Utf8 ---> [nullable char] [MaxLength = 3] MappedSizedSeparatelyDataTypes.StringAsCharacter3 ---> [nullable char] [MaxLength = 3] +MappedSizedSeparatelyDataTypes.StringAsCharacter3Utf8 ---> [nullable char] [MaxLength = 3] MappedSizedSeparatelyDataTypes.StringAsCharacterVarying3 ---> [nullable varchar] [MaxLength = 3] +MappedSizedSeparatelyDataTypes.StringAsCharacterVarying3Utf8 ---> [nullable varchar] [MaxLength = 3] MappedSizedSeparatelyDataTypes.StringAsCharVarying3 ---> [nullable varchar] [MaxLength = 3] +MappedSizedSeparatelyDataTypes.StringAsCharVarying3Utf8 ---> [nullable varchar] [MaxLength = 3] MappedSizedSeparatelyDataTypes.StringAsNationalCharacter3 ---> [nullable nchar] [MaxLength = 3] MappedSizedSeparatelyDataTypes.StringAsNationalCharacterVarying3 ---> [nullable nvarchar] [MaxLength = 3] MappedSizedSeparatelyDataTypes.StringAsNationalCharVarying3 ---> [nullable nvarchar] [MaxLength = 3] MappedSizedSeparatelyDataTypes.StringAsNchar3 ---> [nullable nchar] [MaxLength = 3] MappedSizedSeparatelyDataTypes.StringAsNvarchar3 ---> [nullable nvarchar] [MaxLength = 3] MappedSizedSeparatelyDataTypes.StringAsVarchar3 ---> [nullable varchar] [MaxLength = 3] +MappedSizedSeparatelyDataTypes.StringAsVarchar3Utf8 ---> [nullable varchar] [MaxLength = 3] MappedSquareDataTypes.BoolAsBit ---> [bit] MappedSquareDataTypes.ByteAsTinyint ---> [tinyint] [Precision = 3 Scale = 0] MappedSquareDataTypes.BytesAsImage ---> [image] [MaxLength = 2147483647] @@ -3560,6 +3685,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con { b.HasKey(e => e.Int); b.Property(e => e.Int).ValueGeneratedNever(); + b.Property(e => e.StringAsVarcharMaxUtf8).UseCollation("LATIN1_GENERAL_100_CI_AS_SC_UTF8"); + b.Property(e => e.StringAsCharVaryingMaxUtf8).UseCollation("LATIN1_GENERAL_100_CI_AS_SC_UTF8"); + b.Property(e => e.StringAsCharacterVaryingMaxUtf8).UseCollation("LATIN1_GENERAL_100_CI_AS_SC_UTF8"); }); modelBuilder.Entity( @@ -3619,6 +3747,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b.Property(e => e.StringAsNvarchar3).HasMaxLength(3); b.Property(e => e.StringAsNationalCharVarying3).HasMaxLength(3); b.Property(e => e.StringAsNationalCharacterVarying3).HasMaxLength(3); + b.Property(e => e.StringAsChar3Utf8).HasMaxLength(3).UseCollation("LATIN1_GENERAL_100_CI_AS_SC_UTF8"); + b.Property(e => e.StringAsCharacter3Utf8).HasMaxLength(3).UseCollation("LATIN1_GENERAL_100_CI_AS_SC_UTF8"); + b.Property(e => e.StringAsVarchar3Utf8).HasMaxLength(3).UseCollation("LATIN1_GENERAL_100_CI_AS_SC_UTF8"); + b.Property(e => e.StringAsCharVarying3Utf8).HasMaxLength(3).UseCollation("LATIN1_GENERAL_100_CI_AS_SC_UTF8"); + b.Property(e => e.StringAsCharacterVarying3Utf8).HasMaxLength(3).UseCollation("LATIN1_GENERAL_100_CI_AS_SC_UTF8"); b.Property(e => e.BytesAsBinary3).HasMaxLength(3); b.Property(e => e.BytesAsVarbinary3).HasMaxLength(3); b.Property(e => e.BytesAsBinaryVarying3).HasMaxLength(3); @@ -3762,7 +3895,7 @@ protected class MappedDataTypes public string StringAsVarcharMax { get; set; } [Column(TypeName = "char varying(max)")] - public string StringAsAsCharVaryingMax { get; set; } + public string StringAsCharVaryingMax { get; set; } [Column(TypeName = "character varying(max)")] public string StringAsCharacterVaryingMax { get; set; } @@ -3776,6 +3909,18 @@ protected class MappedDataTypes [Column(TypeName = "national character varying(max)")] public string StringAsNationalCharacterVaryingMax { get; set; } + [Column(TypeName = "varchar(max)")] + [Unicode] + public string StringAsVarcharMaxUtf8 { get; set; } + + [Column(TypeName = "char varying(max)")] + [Unicode] + public string StringAsCharVaryingMaxUtf8 { get; set; } + + [Column(TypeName = "character varying(max)")] + [Unicode] + public string StringAsCharacterVaryingMaxUtf8 { get; set; } + [Column(TypeName = "text")] public string StringAsText { get; set; } @@ -4018,6 +4163,26 @@ protected class MappedSizedDataTypes [Column(TypeName = "national character varying(3)")] public string StringAsNationalCharacterVarying3 { get; set; } + [Column(TypeName = "char(3)")] + [Unicode] + public string StringAsChar3Utf8 { get; set; } + + [Column(TypeName = "character(3)")] + [Unicode] + public string StringAsCharacter3Utf8 { get; set; } + + [Column(TypeName = "varchar(3)")] + [Unicode] + public string StringAsVarchar3Utf8 { get; set; } + + [Column(TypeName = "char varying(3)")] + [Unicode] + public string StringAsCharVarying3Utf8 { get; set; } + + [Column(TypeName = "character varying(3)")] + [Unicode] + public string StringAsCharacterVarying3Utf8 { get; set; } + [Column(TypeName = "binary(3)")] public byte[] BytesAsBinary3 { get; set; } @@ -4080,6 +4245,21 @@ protected class MappedSizedSeparatelyDataTypes [Column(TypeName = "national character varying")] public string StringAsNationalCharacterVarying3 { get; set; } + [Column(TypeName = "char")] + public string StringAsChar3Utf8 { get; set; } + + [Column(TypeName = "character")] + public string StringAsCharacter3Utf8 { get; set; } + + [Column(TypeName = "varchar")] + public string StringAsVarchar3Utf8 { get; set; } + + [Column(TypeName = "char varying")] + public string StringAsCharVarying3Utf8 { get; set; } + + [Column(TypeName = "character varying")] + public string StringAsCharacterVarying3Utf8 { get; set; } + [Column(TypeName = "binary")] public byte[] BytesAsBinary3 { get; set; } @@ -4294,6 +4474,18 @@ protected class MappedNullableDataTypes [Column(TypeName = "national character varying(max)")] public string StringAsNationalCharacterVaryingMax { get; set; } + [Column(TypeName = "varchar(max)")] + [Unicode] + public string StringAsVarcharMaxUtf8 { get; set; } + + [Column(TypeName = "char varying(max)")] + [Unicode] + public string StringAsCharVaryingMaxUtf8 { get; set; } + + [Column(TypeName = "character varying(max)")] + [Unicode] + public string StringAsCharacterVaryingMaxUtf8 { get; set; } + [Column(TypeName = "text")] public string StringAsText { get; set; } @@ -4455,6 +4647,18 @@ protected class MappedDataTypesWithIdentity [Column(TypeName = "national character varying(max)")] public string StringAsNationalCharacterVaryingMax { get; set; } + [Column(TypeName = "varchar(max)")] + [Unicode] + public string StringAsVarcharMaxUtf8 { get; set; } + + [Column(TypeName = "char varying(max)")] + [Unicode] + public string StringAsCharVaryingMaxUtf8 { get; set; } + + [Column(TypeName = "character varying(max)")] + [Unicode] + public string StringAsCharacterVaryingMaxUtf8 { get; set; } + [Column(TypeName = "text")] public string StringAsText { get; set; } @@ -4569,6 +4773,26 @@ protected class MappedSizedDataTypesWithIdentity [Column(TypeName = "national character varying(3)")] public string StringAsNationalCharacterVarying3 { get; set; } + [Column(TypeName = "char(3)")] + [Unicode] + public string StringAsChar3Utf8 { get; set; } + + [Column(TypeName = "character(3)")] + [Unicode] + public string StringAsCharacter3Utf8 { get; set; } + + [Column(TypeName = "varchar(3)")] + [Unicode] + public string StringAsVarchar3Utf8 { get; set; } + + [Column(TypeName = "char varying(3)")] + [Unicode] + public string StringAsCharVarying3Utf8 { get; set; } + + [Column(TypeName = "character varying(3)")] + [Unicode] + public string StringAsCharacterVarying3Utf8 { get; set; } + [Column(TypeName = "binary(3)")] public byte[] BytesAsBinary3 { get; set; } @@ -4730,6 +4954,18 @@ protected class MappedNullableDataTypesWithIdentity [Column(TypeName = "national character varying(max)")] public string StringAsNationalCharacterVaryingMax { get; set; } + [Column(TypeName = "varchar(max)")] + [Unicode] + public string StringAsVarcharMaxUtf8 { get; set; } + + [Column(TypeName = "char varying(max)")] + [Unicode] + public string StringAsCharVaryingMaxUtf8 { get; set; } + + [Column(TypeName = "character varying(max)")] + [Unicode] + public string StringAsCharacterVaryingMaxUtf8 { get; set; } + [Column(TypeName = "text")] public string StringAsText { get; set; } diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerCondition.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerCondition.cs index 420e3809216..5466a13c8bd 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerCondition.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerCondition.cs @@ -15,4 +15,5 @@ public enum SqlServerCondition SupportsFullTextSearch = 1 << 6, SupportsOnlineIndexes = 1 << 7, SupportsTemporalTablesCascadeDelete = 1 << 8, + SupportsUtf8 = 1 << 9 } diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerConditionAttribute.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerConditionAttribute.cs index 80ff24b17f8..d6d9a9882ad 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerConditionAttribute.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerConditionAttribute.cs @@ -67,6 +67,11 @@ public ValueTask IsMetAsync() isMet &= TestEnvironment.IsTemporalTablesCascadeDeleteSupported; } + if (Conditions.HasFlag(SqlServerCondition.SupportsUtf8)) + { + isMet &= TestEnvironment.IsUtf8Supported; + } + return new ValueTask(isMet); } diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestEnvironment.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestEnvironment.cs index 00e442a59b4..64d6392f9bf 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestEnvironment.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestEnvironment.cs @@ -37,6 +37,8 @@ public static class TestEnvironment private static bool? _supportsTemporalTablesCascadeDelete; + private static bool? _supportsUtf8; + private static byte? _productMajorVersion; private static int? _engineEdition; @@ -234,6 +236,35 @@ public static bool IsTemporalTablesCascadeDeleteSupported } } + public static bool IsUtf8Supported + { + get + { + if (!IsConfigured) + { + return false; + } + + if (_supportsUtf8.HasValue) + { + return _supportsUtf8.Value; + } + + try + { + _productMajorVersion = GetProductMajorVersion(); + + _supportsUtf8 = _productMajorVersion >= 15 || IsSqlAzure; + } + catch (PlatformNotSupportedException) + { + _supportsUtf8 = false; + } + + return _supportsUtf8.Value; + } + } + public static string ElasticPoolName { get; } = Config["ElasticPoolName"]; public static bool? GetFlag(string key) diff --git a/test/EFCore.SqlServer.Tests/Storage/SqlServerTypeMappingTest.cs b/test/EFCore.SqlServer.Tests/Storage/SqlServerTypeMappingTest.cs index 631d3a70531..44f0af461ff 100644 --- a/test/EFCore.SqlServer.Tests/Storage/SqlServerTypeMappingTest.cs +++ b/test/EFCore.SqlServer.Tests/Storage/SqlServerTypeMappingTest.cs @@ -313,6 +313,38 @@ public override void String_literal_generated_correctly() Test_GenerateSqlLiteral_helper(GetMapping("varchar(max)"), "Text", "'Text'"); } + [ConditionalFact] + public virtual void String_Utf8() + { + var typeMappingSource = GetTypeMappingSource(); + + var utf8StringMapping = typeMappingSource.FindMapping(typeof(string), "varchar(max)", unicode: true)!; + Assert.Same(typeof(string), utf8StringMapping.ClrType); + Assert.Equal("varchar(max)", utf8StringMapping.StoreType); + Assert.True(utf8StringMapping.IsUnicode); + Test_GenerateSqlLiteral_helper(utf8StringMapping, "Text", "'Text'"); + + using var command = CreateTestCommand(); + var parameter = utf8StringMapping.CreateParameter(command, "foo", "hello"); + Assert.Equal(DbType.String, parameter.DbType); + } + + [ConditionalFact] + public virtual void Char_Utf8() + { + var typeMappingSource = GetTypeMappingSource(); + + var utf8StringMapping = typeMappingSource.FindMapping(typeof(char), "varchar(max)", unicode: true)!; + Assert.Same(typeof(char), utf8StringMapping.ClrType); + Assert.Equal("varchar(max)", utf8StringMapping.StoreType); + Assert.True(utf8StringMapping.IsUnicode); + Test_GenerateSqlLiteral_helper(utf8StringMapping, "T", "'T'"); + + using var command = CreateTestCommand(); + var parameter = utf8StringMapping.CreateParameter(command, "foo", "h"); + Assert.Equal(DbType.String, parameter.DbType); + } + [ConditionalFact] public virtual void DateOnly_code_literal_generated_correctly() { @@ -340,10 +372,12 @@ public virtual void TimeOnly_code_literal_generated_correctly() } public static RelationalTypeMapping GetMapping(string type) - => new SqlServerTypeMappingSource( - TestServiceFactory.Instance.Create(), - TestServiceFactory.Instance.Create()) - .FindMapping(type); + => GetTypeMappingSource().FindMapping(type); + + public static SqlServerTypeMappingSource GetTypeMappingSource() + => new( + TestServiceFactory.Instance.Create(), + TestServiceFactory.Instance.Create()); protected virtual void Test_GenerateCodeLiteral_helper( RelationalTypeMapping typeMapping, From 3ad8b2e9080567908fd58b69bc81589e99ed81ca Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Thu, 17 Mar 2022 16:21:32 +0000 Subject: [PATCH 038/143] Ensure FK properties have nullable-appropriate value comparers (#27654) --- ...yExpressionTranslatingExpressionVisitor.cs | 72 ++++++++++++++++--- ...yableMethodTranslatingExpressionVisitor.cs | 7 +- .../Internal/ExpressionExtensions.cs | 6 +- src/EFCore/Metadata/Internal/Property.cs | 62 ++++++++++++++-- .../BuiltInDataTypesTestBase.cs | 6 +- .../CustomConvertersTestBase.cs | 8 ++- .../StoreGeneratedTestBase.cs | 7 +- .../ModelBuilding/NonRelationshipTestBase.cs | 2 +- 8 files changed, 143 insertions(+), 27 deletions(-) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index 145c4687168..25957b2b815 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -240,19 +240,73 @@ static Expression RemoveConvert(Expression e) var property = FindProperty(newLeft) ?? FindProperty(newRight); var comparer = property?.GetValueComparer(); - if (comparer != null - && comparer.Type.IsAssignableFrom(newLeft.Type) - && comparer.Type.IsAssignableFrom(newRight.Type)) + if (comparer != null) { - if (binaryExpression.NodeType == ExpressionType.Equal) + MethodInfo? objectEquals = null; + MethodInfo? exactMatch = null; + + var converter = property?.GetValueConverter(); + foreach (var candidate in comparer + .GetType() + .GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Where( + m => m.Name == "Equals" && m.GetParameters().Length == 2) + .ToList()) { - return comparer.ExtractEqualsBody(newLeft, newRight); - } + var parameters = candidate.GetParameters(); + var leftType = parameters[0].ParameterType; + var rightType = parameters[1].ParameterType; - if (binaryExpression.NodeType == ExpressionType.NotEqual) - { - return Expression.IsFalse(comparer.ExtractEqualsBody(newLeft, newRight)); + if (leftType == typeof(object) + && rightType == typeof(object)) + { + objectEquals = candidate; + continue; + } + + var matchingLeft = leftType.IsAssignableFrom(newLeft.Type) + ? newLeft + : converter != null && leftType.IsAssignableFrom(converter.ModelClrType) + ? ReplacingExpressionVisitor.Replace( + converter.ConvertFromProviderExpression.Parameters.Single(), + newLeft, + converter.ConvertFromProviderExpression.Body) + : null; + + var matchingRight = rightType.IsAssignableFrom(newRight.Type) + ? newRight + : converter != null && rightType.IsAssignableFrom(converter.ModelClrType) + ? ReplacingExpressionVisitor.Replace( + converter.ConvertFromProviderExpression.Parameters.Single(), + newRight, + converter.ConvertFromProviderExpression.Body) + : null; + + if (matchingLeft != null && matchingRight != null) + { + exactMatch = candidate; + newLeft = matchingLeft; + newRight = matchingRight; + break; + } } + + var equalsExpression = + exactMatch != null + ? Expression.Call( + Expression.Constant(comparer, comparer.GetType()), + exactMatch, + newLeft, + newRight) + : Expression.Call( + Expression.Constant(comparer, comparer.GetType()), + objectEquals!, + Expression.Convert(newLeft, typeof(object)), + Expression.Convert(newRight, typeof(object))); + + return binaryExpression.NodeType == ExpressionType.NotEqual + ? Expression.IsFalse(equalsExpression) + : equalsExpression; } } diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index 00f06a3641f..ebfe4334114 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -594,10 +594,11 @@ private static bool ProcessJoinCondition( } if (joinCondition is MethodCallExpression methodCallExpression - && methodCallExpression.Method.IsStatic - && methodCallExpression.Method.DeclaringType == typeof(object) && methodCallExpression.Method.Name == nameof(object.Equals) - && methodCallExpression.Arguments.Count == 2) + && methodCallExpression.Arguments.Count == 2 + && ((methodCallExpression.Method.IsStatic + && methodCallExpression.Method.DeclaringType == typeof(object)) + || typeof(ValueComparer).IsAssignableFrom(methodCallExpression.Method.DeclaringType))) { leftExpressions.Add(methodCallExpression.Arguments[0]); rightExpressions.Add(methodCallExpression.Arguments[1]); diff --git a/src/EFCore/Extensions/Internal/ExpressionExtensions.cs b/src/EFCore/Extensions/Internal/ExpressionExtensions.cs index fe8b95eb478..173f010df74 100644 --- a/src/EFCore/Extensions/Internal/ExpressionExtensions.cs +++ b/src/EFCore/Extensions/Internal/ExpressionExtensions.cs @@ -42,12 +42,12 @@ public static Expression MakeHasDefaultValue( } var property = propertyBase as IReadOnlyProperty; - var clrType = propertyBase?.ClrType ?? currentValueExpression.Type; var comparer = property?.GetValueComparer() - ?? ValueComparer.CreateDefault(clrType, favorStructuralComparisons: false); + ?? ValueComparer.CreateDefault( + propertyBase?.ClrType ?? currentValueExpression.Type, favorStructuralComparisons: false); return comparer.ExtractEqualsBody( - comparer.Type != clrType + comparer.Type != currentValueExpression.Type ? Expression.Convert(currentValueExpression, comparer.Type) : currentValueExpression, Expression.Default(comparer.Type)); diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index ba6e9478699..28396b2d384 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.cs @@ -811,8 +811,8 @@ public virtual CoreTypeMapping? TypeMapping /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual ValueComparer? GetValueComparer() - => GetValueComparer(null) - ?? TypeMapping?.Comparer; + => ToNullableComparer(GetValueComparer(null) + ?? TypeMapping?.Comparer); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -821,8 +821,62 @@ public virtual CoreTypeMapping? TypeMapping /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual ValueComparer? GetKeyValueComparer() - => GetValueComparer(null) - ?? TypeMapping?.KeyComparer; + => ToNullableComparer(GetValueComparer(null) + ?? TypeMapping?.KeyComparer); + + private ValueComparer? ToNullableComparer(ValueComparer? valueComparer) + { + if (valueComparer == null + || !ClrType.IsNullableValueType() + || valueComparer.Type.IsNullableValueType()) + { + return valueComparer; + } + + var newEqualsParam1 = Expression.Parameter(ClrType, "v1"); + var newEqualsParam2 = Expression.Parameter(ClrType, "v2"); + var newHashCodeParam = Expression.Parameter(ClrType, "v"); + var newSnapshotParam = Expression.Parameter(ClrType, "v"); + var hasValueMethod = ClrType.GetMethod("get_HasValue")!; + var v1HasValue = Expression.Parameter(typeof(bool), "v1HasValue"); + var v2HasValue = Expression.Parameter(typeof(bool), "v2HasValue"); + + return (ValueComparer)Activator.CreateInstance( + typeof(ValueComparer<>).MakeGenericType(ClrType), + Expression.Lambda( + Expression.Block( + typeof(bool), + new[] { v1HasValue, v2HasValue }, + Expression.Assign(v1HasValue, Expression.Call(newEqualsParam1, hasValueMethod)), + Expression.Assign(v2HasValue, Expression.Call(newEqualsParam2, hasValueMethod)), + Expression.OrElse( + Expression.AndAlso( + v1HasValue, + Expression.AndAlso( + v2HasValue, + valueComparer.ExtractEqualsBody( + Expression.Convert(newEqualsParam1, valueComparer.Type), + Expression.Convert(newEqualsParam2, valueComparer.Type)))), + Expression.AndAlso( + Expression.Not(v1HasValue), + Expression.Not(v2HasValue)))), + newEqualsParam1, newEqualsParam2), + Expression.Lambda( + Expression.Condition( + Expression.Call(newHashCodeParam, hasValueMethod), + valueComparer.ExtractHashCodeBody( + Expression.Convert(newHashCodeParam, valueComparer.Type)), + Expression.Constant(0, typeof(int))), + newHashCodeParam), + Expression.Lambda( + Expression.Condition( + Expression.Call(newSnapshotParam, hasValueMethod), + Expression.Convert( + valueComparer.ExtractSnapshotBody( + Expression.Convert(newSnapshotParam, valueComparer.Type)), ClrType), + Expression.Default(ClrType)), + newSnapshotParam))!; + } private ValueComparer? GetValueComparer(HashSet? checkedProperties) { diff --git a/test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs b/test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs index 46dd9c8011d..e901b0b2b77 100644 --- a/test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs +++ b/test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs @@ -1686,8 +1686,7 @@ public virtual void Can_insert_and_read_back_all_nullable_data_types_with_values TestNullableDouble = -1.23456789, TestNullableDecimal = -1234567890.01M, TestNullableDateTime = DateTime.Parse("01/01/2000 12:34:56").ToUniversalTime(), - TestNullableDateTimeOffset = - new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.FromHours(-8.0)).ToUniversalTime(), + TestNullableDateTimeOffset = new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.FromHours(-8.0)), TestNullableTimeSpan = new TimeSpan(0, 10, 9, 8, 7), TestNullableSingle = -1.234F, TestNullableBoolean = false, @@ -1723,8 +1722,7 @@ public virtual void Can_insert_and_read_back_all_nullable_data_types_with_values AssertEqualIfMapped(entityType, -1.23456789, () => dt.TestNullableDouble); AssertEqualIfMapped(entityType, -1234567890.01M, () => dt.TestNullableDecimal); AssertEqualIfMapped(entityType, DateTime.Parse("01/01/2000 12:34:56").ToUniversalTime(), () => dt.TestNullableDateTime); - AssertEqualIfMapped( - entityType, new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.FromHours(-8.0)).ToUniversalTime(), + AssertEqualIfMapped(entityType, new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.FromHours(-8.0)), () => dt.TestNullableDateTimeOffset); AssertEqualIfMapped(entityType, new TimeSpan(0, 10, 9, 8, 7), () => dt.TestNullableTimeSpan); AssertEqualIfMapped(entityType, -1.234F, () => dt.TestNullableSingle); diff --git a/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs b/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs index 853809a2ba0..870a0086f1e 100644 --- a/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs +++ b/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs @@ -204,6 +204,12 @@ private Email(string value) _value = value; } + public override bool Equals(object obj) + => _value == ((Email)obj)?._value; + + public override int GetHashCode() + => _value.GetHashCode(); + public static Email Create(string value) => new(value); @@ -1069,7 +1075,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b.Property(nameof(BuiltInNullableDataTypes.TestNullableDateTimeOffset)).HasConversion( new ValueConverter( v => v.Value.ToUnixTimeMilliseconds(), - v => DateTimeOffset.FromUnixTimeMilliseconds(v))); + v => DateTimeOffset.FromUnixTimeMilliseconds(v).ToOffset(TimeSpan.FromHours(-8.0)))); b.Property(nameof(BuiltInNullableDataTypes.TestNullableDouble)).HasConversion( new ValueConverter( diff --git a/test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs b/test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs index e3997b74ed8..fa76f62917f 100644 --- a/test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs +++ b/test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs @@ -1569,7 +1569,7 @@ public int NullableAsNonNullable public int? NonNullableAsNullable { get => _nonNullableAsNullable; - set => _nonNullableAsNullable = (int)value; + set => _nonNullableAsNullable = value ?? 0; } } @@ -1930,7 +1930,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con { b.Property(e => e.Id).HasField("_id"); b.Property(e => e.NullableAsNonNullable).HasField("_nullableAsNonNullable").ValueGeneratedOnAddOrUpdate(); - b.Property(e => e.NonNullableAsNullable).HasField("_nonNullableAsNullable").ValueGeneratedOnAddOrUpdate(); + b.Property(e => e.NonNullableAsNullable) + .HasField("_nonNullableAsNullable") + .ValueGeneratedOnAddOrUpdate() + .UsePropertyAccessMode(PropertyAccessMode.Property); }); modelBuilder.Entity(); diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs index d69d92909eb..a7603a2a5ce 100644 --- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs @@ -991,7 +991,7 @@ public virtual void Value_converter_configured_on_non_nullable_type_is_applied() var wierd = entityType.FindProperty("Wierd"); Assert.IsType>(wierd.GetValueConverter()); - Assert.IsType>(wierd.GetValueComparer()); + Assert.IsType>(wierd.GetValueComparer()); } [ConditionalFact] From b7518d5c746bfa9517f53b15850ad499e54399a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Mar 2022 21:01:31 +0000 Subject: [PATCH 039/143] Bump NetTopologySuite.IO.SqlServerBytes from 2.0.0 to 2.1.0 (#27518) Bumps [NetTopologySuite.IO.SqlServerBytes](https://github.com/NetTopologySuite/NetTopologySuite.IO.SqlServerBytes) from 2.0.0 to 2.1.0. - [Release notes](https://github.com/NetTopologySuite/NetTopologySuite.IO.SqlServerBytes/releases) - [Commits](https://github.com/NetTopologySuite/NetTopologySuite.IO.SqlServerBytes/compare/v2.0.0...v2.1.0) --- updated-dependencies: - dependency-name: NetTopologySuite.IO.SqlServerBytes dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/EFCore.SqlServer.NTS/EFCore.SqlServer.NTS.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EFCore.SqlServer.NTS/EFCore.SqlServer.NTS.csproj b/src/EFCore.SqlServer.NTS/EFCore.SqlServer.NTS.csproj index 263e30d9138..5cd741f9962 100644 --- a/src/EFCore.SqlServer.NTS/EFCore.SqlServer.NTS.csproj +++ b/src/EFCore.SqlServer.NTS/EFCore.SqlServer.NTS.csproj @@ -57,7 +57,7 @@ - + From e0a4b8885f68a4077c6c15085bd1b304d4e6f4b7 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Fri, 18 Mar 2022 09:54:25 +0000 Subject: [PATCH 040/143] Revert "feat (codespaces) - Add initial support for codespaces (#27483)" (#27655) --- .devcontainer/Dockerfile | 12 ----- .devcontainer/devcontainer.json | 45 ---------------- .devcontainer/docker-compose.yml | 35 ------------- .devcontainer/mssql/installSQLtools.sh | 15 ------ .devcontainer/mssql/postCreateCommand.sh | 67 ------------------------ .vscode/tasks.json | 56 -------------------- 6 files changed, 230 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 .devcontainer/docker-compose.yml delete mode 100644 .devcontainer/mssql/installSQLtools.sh delete mode 100644 .devcontainer/mssql/postCreateCommand.sh delete mode 100644 .vscode/tasks.json diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 0d69c885b2e..00000000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -# [Choice] .NET version: 6.0-focal, 5.0-focal, 3.1-focal -ARG VARIANT="6.0-focal" -FROM mcr.microsoft.com/vscode/devcontainers/dotnet:0-${VARIANT} - -# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 -ARG NODE_VERSION="none" -RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi - -# Install SQL Tools: SQLPackage and sqlcmd -COPY mssql/installSQLtools.sh installSQLtools.sh -RUN bash ./installSQLtools.sh \ - && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 1034072677b..00000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,45 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.222.0/containers/dotnet-mssql -{ - "name": "C# (.NET) and MS SQL", - "dockerComposeFile": "docker-compose.yml", - "service": "app", - "workspaceFolder": "/workspace", - "settings": { - "mssql.connections": [ - { - "server": "localhost,1433", - "database": "master", - "authenticationType": "SqlLogin", - "user": "sa", - "password": "Pass@word", - "emptyPasswordInput": false, - "savePassword": true, - "profileName": "mssql-container", - "trustServerCertificate": true - } - ], - "omnisharp.enableRoslynAnalyzers": true, - "omnisharp.enableEditorConfigSupport": true, - "omnisharp.enableImportCompletion": true, - "omnisharp.useModernNet": true, - "omnisharp.enableMsBuildLoadProjectsOnDemand": true - }, - "extensions": [ - "ms-dotnettools.csharp", - "ms-mssql.mssql", - "EditorConfig.EditorConfig", - "k--kato.docomment", - ], - "remoteEnv": { - "PATH": "${containerWorkspaceFolder}/.dotnet:${containerEnv:PATH}", - "DOTNET_MULTILEVEL_LOOKUP": "0", - "TARGET": "net7.0", - "DOTNET_WATCH_SUPPRESS_LAUNCH_BROWSER": "true", - "Test__SqlServer__DefaultConnection ": "Server=localhost;Database=master;User Id=sa;password=Pass@word;Trusted_Connection=False;MultipleActiveResultSets=true;" - }, - "postCreateCommand": "bash .devcontainer/mssql/postCreateCommand.sh 'Pass@word' './bin/Debug/' './.devcontainer/mssql/'", - "features": { - "powershell": "latest" - } -} \ No newline at end of file diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml deleted file mode 100644 index 12a0ae525a3..00000000000 --- a/.devcontainer/docker-compose.yml +++ /dev/null @@ -1,35 +0,0 @@ -version: '3' - -services: - app: - build: - context: . - dockerfile: Dockerfile - args: - # Update 'VARIANT' to pick a version of .NET: 3.1-focal, 5.0-focal, 6.0-focal - VARIANT: "6.0-focal" - # Optional version of Node.js - NODE_VERSION: "none" - - volumes: - - ..:/workspace:cached - - # Overrides default command so things don't shut down after the process ends. - command: sleep infinity - - # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. - network_mode: service:db - # Uncomment the next line to use a non-root user for all processes. - # user: vscode - - # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. - # (Adding the "ports" property to this file will not forward from a Codespace.) - - db: - image: mcr.microsoft.com/mssql/server:2019-latest - restart: unless-stopped - environment: - SA_PASSWORD: Pass@word - ACCEPT_EULA: Y - # Add "forwardPorts": ["1433"] to **devcontainer.json** to forward MSSQL locally. - # (Adding the "ports" property to this file will not forward from a Codespace.) diff --git a/.devcontainer/mssql/installSQLtools.sh b/.devcontainer/mssql/installSQLtools.sh deleted file mode 100644 index d45e44b2e97..00000000000 --- a/.devcontainer/mssql/installSQLtools.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -echo "Installing mssql-tools" -curl -sSL https://packages.microsoft.com/keys/microsoft.asc | (OUT=$(apt-key add - 2>&1) || echo $OUT) -DISTRO=$(lsb_release -is | tr '[:upper:]' '[:lower:]') -CODENAME=$(lsb_release -cs) -echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-${DISTRO}-${CODENAME}-prod ${CODENAME} main" > /etc/apt/sources.list.d/microsoft.list -apt-get update -ACCEPT_EULA=Y apt-get -y install unixodbc-dev msodbcsql17 libunwind8 mssql-tools - -echo "Installing sqlpackage" -curl -sSL -o sqlpackage.zip "https://aka.ms/sqlpackage-linux" -mkdir /opt/sqlpackage -unzip sqlpackage.zip -d /opt/sqlpackage -rm sqlpackage.zip -chmod a+x /opt/sqlpackage/sqlpackage diff --git a/.devcontainer/mssql/postCreateCommand.sh b/.devcontainer/mssql/postCreateCommand.sh deleted file mode 100644 index 4aac9edeb29..00000000000 --- a/.devcontainer/mssql/postCreateCommand.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/bash -dacpac="false" -sqlfiles="false" -SApassword=$1 -dacpath=$2 -sqlpath=$3 - -echo "SELECT * FROM SYS.DATABASES" | dd of=testsqlconnection.sql -for i in {1..60}; -do - /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $SApassword -d master -i testsqlconnection.sql > /dev/null - if [ $? -eq 0 ] - then - echo "SQL server ready" - break - else - echo "Not ready yet..." - sleep 1 - fi -done -rm testsqlconnection.sql - -for f in $dacpath/* -do - if [ $f == $dacpath/*".dacpac" ] - then - dacpac="true" - echo "Found dacpac $f" - fi -done - -for f in $sqlpath/* -do - if [ $f == $sqlpath/*".sql" ] - then - sqlfiles="true" - echo "Found SQL file $f" - fi -done - -if [ $sqlfiles == "true" ] -then - for f in $sqlpath/* - do - if [ $f == $sqlpath/*".sql" ] - then - echo "Executing $f" - /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $SApassword -d master -i $f - fi - done -fi - -if [ $dacpac == "true" ] -then - for f in $dacpath/* - do - if [ $f == $dacpath/*".dacpac" ] - then - dbname=$(basename $f ".dacpac") - echo "Deploying dacpac $f" - /opt/sqlpackage/sqlpackage /Action:Publish /SourceFile:$f /TargetServerName:localhost /TargetDatabaseName:$dbname /TargetUser:sa /TargetPassword:$SApassword - fi - done -fi - -echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc -source ~/.bashrc \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index b264106bb75..00000000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "Restore projects", - "type": "shell", - "command": "./restore.sh", - "windows": { - "command": ".\\restore.cmd" - }, - "group": "build", - "presentation": { - "reveal": "always", - "panel": "new" - } - }, - { - "label": "Build entire repository", - "type": "shell", - "command": "./build.sh", - "windows": { - "command": ".\\build.cmd" - }, - "group": "build", - "presentation": { - "reveal": "always", - "panel": "new" - } - }, - { - "label": "Build and run all tests", - "type": "shell", - "command": "/test.sh -test", - "windows": { - "command": ".\\test.cmd -test" - }, - "group": "test", - "presentation": { - "reveal": "always", - "panel": "new" - } - }, - { - "label": "Pack assets", - "type": "shell", - "command": "./eng/build.sh --pack", - "windows": { - "command": ".\\eng\\build.cmd -pack" - }, - "presentation": { - "reveal": "always", - "panel": "new" - } - } - ] -} \ No newline at end of file From e1db60abeb7f175079cd03d56740511f0c8e53bf Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 18 Mar 2022 14:33:45 +0200 Subject: [PATCH 041/143] Use RETURNING/OUTPUT clause for UPDATE/DELETE (#27663) Closes #27547 --- .../AffectedCountModificationCommandBatch.cs | 3 +- .../Update/UpdateSqlGenerator.cs | 69 ++--- .../Internal/SqlServerUpdateSqlGenerator.cs | 145 ++++++++++- .../Internal/SqliteUpdateSqlGenerator.cs | 20 -- .../Update/StoreValueGenerationTestBase.cs | 17 +- .../Update/UpdateSqlGeneratorTestBase.cs | 242 ++---------------- .../FakeProvider/FakeSqlGenerator.cs | 12 - .../Update/UpdateSqlGeneratorTest.cs | 48 ++++ .../DataAnnotationSqlServerTest.cs | 8 +- .../OptimisticConcurrencySqlServerTest.cs | 4 +- ...eteMappingInheritanceQuerySqlServerTest.cs | 8 +- .../Query/InheritanceQuerySqlServerTest.cs | 8 +- .../Query/TPTInheritanceQuerySqlServerTest.cs | 16 +- .../TPTTableSplittingSqlServerTest.cs | 8 +- .../TableSplittingSqlServerTest.cs | 8 +- .../Update/SqlServerUpdateSqlGeneratorTest.cs | 50 ++++ ...oreValueGenerationIdentitySqlServerTest.cs | 78 ++---- ...eGenerationIdentityTriggerSqlServerTest.cs | 2 +- ...oreValueGenerationSequenceSqlServerTest.cs | 71 ++--- ...eGenerationSequenceTriggerSqlServerTest.cs | 2 +- .../UpdatesSqlServerTest.cs | 24 +- .../DataAnnotationSqliteTest.cs | 8 +- .../SqliteMigrationsSqlGeneratorTest.cs | 125 ++++----- .../OptimisticConcurrencySqliteTest.cs | 4 +- .../Update/SqliteUpdateSqlGeneratorTest.cs | 50 ++++ .../Update/StoreValueGenerationSqliteTest.cs | 70 +++-- 26 files changed, 531 insertions(+), 569 deletions(-) diff --git a/src/EFCore.Relational/Update/AffectedCountModificationCommandBatch.cs b/src/EFCore.Relational/Update/AffectedCountModificationCommandBatch.cs index 88b0101dfb3..beae8113198 100644 --- a/src/EFCore.Relational/Update/AffectedCountModificationCommandBatch.cs +++ b/src/EFCore.Relational/Update/AffectedCountModificationCommandBatch.cs @@ -5,8 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Update; /// /// -/// A for providers which append an SQL query to find out -/// how many rows were affected (see ). +/// A for providers which return values to find out how many rows were affected. /// /// /// This type is typically used by database providers; it is generally not used in application code. diff --git a/src/EFCore.Relational/Update/UpdateSqlGenerator.cs b/src/EFCore.Relational/Update/UpdateSqlGenerator.cs index 5ba908b3037..0d18aff8a37 100644 --- a/src/EFCore.Relational/Update/UpdateSqlGenerator.cs +++ b/src/EFCore.Relational/Update/UpdateSqlGenerator.cs @@ -97,20 +97,13 @@ public virtual ResultSetMapping AppendUpdateOperation( var conditionOperations = operations.Where(o => o.IsCondition).ToList(); var readOperations = operations.Where(o => o.IsRead).ToList(); - AppendUpdateCommand(commandStringBuilder, name, schema, writeOperations, conditionOperations); - - if (readOperations.Count > 0) - { - var keyOperations = operations.Where(o => o.IsKey).ToList(); - - requiresTransaction = true; - - return AppendSelectAffectedCommand(commandStringBuilder, name, schema, readOperations, keyOperations, commandPosition); - } - requiresTransaction = false; - return AppendSelectAffectedCountCommand(commandStringBuilder, name, schema, commandPosition); + AppendUpdateCommand( + commandStringBuilder, name, schema, writeOperations, readOperations, conditionOperations, + additionalReadValues: readOperations.Count == 0 ? "1" : null); + + return ResultSetMapping.LastInResultSet; } /// @@ -131,11 +124,12 @@ public virtual ResultSetMapping AppendDeleteOperation( var schema = command.Schema; var conditionOperations = command.ColumnModifications.Where(o => o.IsCondition).ToList(); - AppendDeleteCommand(commandStringBuilder, name, schema, conditionOperations); - requiresTransaction = false; - return AppendSelectAffectedCountCommand(commandStringBuilder, name, schema, commandPosition); + AppendDeleteCommand( + commandStringBuilder, name, schema, Array.Empty(), conditionOperations, additionalReadValues: "1"); + + return ResultSetMapping.LastInResultSet; } /// @@ -167,16 +161,21 @@ protected virtual void AppendInsertCommand( /// The name of the table. /// The table schema, or to use the default schema. /// The operations for each column. + /// The operations for column values to be read back. /// The operations used to generate the WHERE clause for the update. + /// Additional values to be read back. protected virtual void AppendUpdateCommand( StringBuilder commandStringBuilder, string name, string? schema, IReadOnlyList writeOperations, - IReadOnlyList conditionOperations) + IReadOnlyList readOperations, + IReadOnlyList conditionOperations, + string? additionalReadValues = null) { AppendUpdateCommandHeader(commandStringBuilder, name, schema, writeOperations); AppendWhereClause(commandStringBuilder, conditionOperations); + AppendReturningClause(commandStringBuilder, readOperations, additionalReadValues); commandStringBuilder.AppendLine(SqlGenerationHelper.StatementTerminator); } @@ -186,33 +185,23 @@ protected virtual void AppendUpdateCommand( /// The builder to which the SQL should be appended. /// The name of the table. /// The table schema, or to use the default schema. + /// The operations for column values to be read back. /// The operations used to generate the WHERE clause for the delete. + /// Additional values to be read back. protected virtual void AppendDeleteCommand( StringBuilder commandStringBuilder, string name, string? schema, - IReadOnlyList conditionOperations) + IReadOnlyList readOperations, + IReadOnlyList conditionOperations, + string? additionalReadValues = null) { AppendDeleteCommandHeader(commandStringBuilder, name, schema); AppendWhereClause(commandStringBuilder, conditionOperations); + AppendReturningClause(commandStringBuilder, readOperations, additionalReadValues); commandStringBuilder.AppendLine(SqlGenerationHelper.StatementTerminator); } - /// - /// Appends a SQL command for selecting the number of rows affected. - /// - /// The builder to which the SQL should be appended. - /// The name of the table. - /// The table schema, or to use the default schema. - /// The ordinal of the command for which rows affected it being returned. - /// The for this command. - protected virtual ResultSetMapping AppendSelectAffectedCountCommand( - StringBuilder commandStringBuilder, - string name, - string? schema, - int commandPosition) - => ResultSetMapping.NoResultSet; - /// /// Appends a SQL command for selecting affected data. /// @@ -411,11 +400,13 @@ protected virtual void AppendValues( /// /// The builder to which the SQL should be appended. /// The operations for column values to be read back. + /// Additional values to be read back. protected virtual void AppendReturningClause( StringBuilder commandStringBuilder, - IReadOnlyList operations) + IReadOnlyList operations, + string? additionalValues = null) { - if (operations.Count > 0) + if (operations.Count > 0 || additionalValues is not null) { commandStringBuilder .AppendLine() @@ -424,6 +415,16 @@ protected virtual void AppendReturningClause( operations, SqlGenerationHelper, (sb, o, helper) => helper.DelimitIdentifier(sb, o.ColumnName)); + + if (additionalValues is not null) + { + if (operations.Count > 0) + { + commandStringBuilder.Append(", "); + } + + commandStringBuilder.Append(additionalValues); + } } } diff --git a/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs b/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs index 7d08eb12835..c633fc85db5 100644 --- a/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs +++ b/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs @@ -47,8 +47,7 @@ public override ResultSetMapping AppendInsertOperation( // If no database-generated columns need to be read back, just do a simple INSERT (default behavior). // If there are generated columns but there are no triggers defined on the table, we can do a simple INSERT ... OUTPUT // (without INTO), which is also the default behavior, doesn't require a transaction and is the most efficient. - if (command.ColumnModifications.All(o => !o.IsRead) - || !command.Entries[0].EntityType.Model.GetRelationalModel().FindTable(command.TableName, command.Schema)!.Triggers.Any()) + if (command.ColumnModifications.All(o => !o.IsRead) || !HasAnyTriggers(command)) { return base.AppendInsertOperation(commandStringBuilder, command, commandPosition, out requiresTransaction); } @@ -94,6 +93,124 @@ protected override void AppendInsertCommand( commandStringBuilder.AppendLine(SqlGenerationHelper.StatementTerminator); } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override ResultSetMapping AppendUpdateOperation( + StringBuilder commandStringBuilder, + IReadOnlyModificationCommand command, + int commandPosition, + out bool requiresTransaction) + { + // We normally do a simple UPDATE with an OUTPUT clause (either for the generated columns, or for "1" for concurrency checking). + // However, if there are triggers defined, OUTPUT (without INTO) is not supported, so we do UPDATE+SELECT. + if (!HasAnyTriggers(command)) + { + return base.AppendUpdateOperation(commandStringBuilder, command, commandPosition, out requiresTransaction); + } + + var name = command.TableName; + var schema = command.Schema; + var operations = command.ColumnModifications; + + var writeOperations = operations.Where(o => o.IsWrite).ToList(); + var conditionOperations = operations.Where(o => o.IsCondition).ToList(); + var readOperations = operations.Where(o => o.IsRead).ToList(); + + AppendUpdateCommand(commandStringBuilder, name, schema, writeOperations, Array.Empty(), conditionOperations); + + if (readOperations.Count > 0) + { + var keyOperations = operations.Where(o => o.IsKey).ToList(); + + requiresTransaction = true; + + return AppendSelectAffectedCommand(commandStringBuilder, name, schema, readOperations, keyOperations, commandPosition); + } + + requiresTransaction = false; + + return AppendSelectAffectedCountCommand(commandStringBuilder, name, schema, commandPosition); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override void AppendUpdateCommand( + StringBuilder commandStringBuilder, + string name, + string? schema, + IReadOnlyList writeOperations, + IReadOnlyList readOperations, + IReadOnlyList conditionOperations, + string? additionalReadValues = null) + { + // In SQL Server the OUTPUT clause is placed differently (before the WHERE instead of at the end) + AppendUpdateCommandHeader(commandStringBuilder, name, schema, writeOperations); + AppendOutputClause(commandStringBuilder, readOperations, additionalReadValues); + AppendWhereClause(commandStringBuilder, conditionOperations); + commandStringBuilder.AppendLine(SqlGenerationHelper.StatementTerminator); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override ResultSetMapping AppendDeleteOperation( + StringBuilder commandStringBuilder, + IReadOnlyModificationCommand command, + int commandPosition, + out bool requiresTransaction) + { + // We normally do a simple DELETE, with an OUTPUT clause emitting "1" for concurrency checking. + // However, if there are triggers defined, OUTPUT (without INTO) is not supported, so we do UPDATE+SELECT. + if (!HasAnyTriggers(command)) + { + return base.AppendDeleteOperation(commandStringBuilder, command, commandPosition, out requiresTransaction); + } + + var name = command.TableName; + var schema = command.Schema; + var operations = command.ColumnModifications; + + var conditionOperations = operations.Where(o => o.IsCondition).ToList(); + + requiresTransaction = false; + + AppendDeleteCommand(commandStringBuilder, name, schema, Array.Empty(), conditionOperations); + + return AppendSelectAffectedCountCommand(commandStringBuilder, name, schema, commandPosition); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override void AppendDeleteCommand( + StringBuilder commandStringBuilder, + string name, + string? schema, + IReadOnlyList readOperations, + IReadOnlyList conditionOperations, + string? additionalReadValues = null) + { + // In SQL Server the OUTPUT clause is placed differently (before the WHERE instead of at the end) + AppendDeleteCommandHeader(commandStringBuilder, name, schema); + AppendOutputClause(commandStringBuilder, readOperations, additionalReadValues); + AppendWhereClause(commandStringBuilder, conditionOperations); + commandStringBuilder.AppendLine(SqlGenerationHelper.StatementTerminator); + } + /// /// 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 @@ -200,8 +317,7 @@ public virtual ResultSetMapping AppendBulkInsertOperation( // We default to using MERGE ... OUTPUT (without INTO), projecting back a synthetic _Position column to know the order back // at the client and propagate database-generated values correctly. However, if any triggers are defined, OUTPUT without INTO // doesn't work. - if (!firstCommand.Entries[0].EntityType.Model.GetRelationalModel().FindTable(firstCommand.TableName, firstCommand.Schema)! - .Triggers.Any()) + if (!HasAnyTriggers(firstCommand)) { // MERGE ... OUTPUT returns rows whose ordering isn't guaranteed. So this technique projects back a position int with each row, // to allow mapping the rows back for value propagation. @@ -578,16 +694,17 @@ private static string GetTypeNameForCopy(IProperty property) /// protected override void AppendReturningClause( StringBuilder commandStringBuilder, - IReadOnlyList operations) - => AppendOutputClause(commandStringBuilder, operations); + IReadOnlyList operations, + string? additionalValues = null) + => AppendOutputClause(commandStringBuilder, operations, additionalValues); // ReSharper disable once ParameterTypeCanBeEnumerable.Local private void AppendOutputClause( StringBuilder commandStringBuilder, IReadOnlyList operations, - string? additionalColumns = null) + string? additionalReadValues = null) { - if (operations.Count > 0 || additionalColumns is not null) + if (operations.Count > 0 || additionalReadValues is not null) { commandStringBuilder .AppendLine() @@ -601,14 +718,14 @@ private void AppendOutputClause( helper.DelimitIdentifier(sb, o.ColumnName); }); - if (additionalColumns != null) + if (additionalReadValues is not null) { if (operations.Count > 0) { commandStringBuilder.Append(", "); } - commandStringBuilder.Append(additionalColumns); + commandStringBuilder.Append(additionalReadValues); } } } @@ -728,7 +845,7 @@ private ResultSetMapping AppendSelectCommand( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - protected override ResultSetMapping AppendSelectAffectedCountCommand( + protected virtual ResultSetMapping AppendSelectAffectedCountCommand( StringBuilder commandStringBuilder, string name, string? schema, @@ -790,4 +907,10 @@ protected override void AppendRowsAffectedWhereCondition(StringBuilder commandSt => commandStringBuilder .Append("@@ROWCOUNT = ") .Append(expectedRowsAffected.ToString(CultureInfo.InvariantCulture)); + + private static bool HasAnyTriggers(IReadOnlyModificationCommand command) + // Data seeding doesn't provide any entries, so we we don't know if the table has triggers; assume it does to generate SQL + // that works everywhere. + => command.Entries.Count == 0 + || command.Entries[0].EntityType.Model.GetRelationalModel().FindTable(command.TableName, command.Schema)!.Triggers.Any(); } diff --git a/src/EFCore.Sqlite.Core/Update/Internal/SqliteUpdateSqlGenerator.cs b/src/EFCore.Sqlite.Core/Update/Internal/SqliteUpdateSqlGenerator.cs index e92eb82e089..5ea20d8b67a 100644 --- a/src/EFCore.Sqlite.Core/Update/Internal/SqliteUpdateSqlGenerator.cs +++ b/src/EFCore.Sqlite.Core/Update/Internal/SqliteUpdateSqlGenerator.cs @@ -38,26 +38,6 @@ protected override void AppendIdentityWhereCondition(StringBuilder commandString .Append("last_insert_rowid()"); } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - protected override ResultSetMapping AppendSelectAffectedCountCommand( - StringBuilder commandStringBuilder, - string name, - string? schema, - int commandPosition) - { - commandStringBuilder - .Append("SELECT changes()") - .AppendLine(SqlGenerationHelper.StatementTerminator) - .AppendLine(); - - return ResultSetMapping.LastInResultSet; - } - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/test/EFCore.Relational.Specification.Tests/Update/StoreValueGenerationTestBase.cs b/test/EFCore.Relational.Specification.Tests/Update/StoreValueGenerationTestBase.cs index 007f32d838c..f790bf1219f 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/StoreValueGenerationTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/StoreValueGenerationTestBase.cs @@ -329,22 +329,7 @@ protected virtual bool ShouldCreateImplicitTransaction( EntityState? secondOperationType, GeneratedValues generatedValues, bool withSameEntityType) - { - // By default, two changes require a transaction - if (secondOperationType is not null) - { - return true; - } - - // Deletes don't ever need to bring back database-generated values, and inserts use the RETURNING clause - no transaction needed - if (firstOperationType is EntityState.Deleted or EntityState.Added) - { - return false; - } - - // Fetching back database-generated values from an update requires a transaction - return generatedValues != GeneratedValues.None; - } + => secondOperationType is not null; /// /// Providers can override this to specify how many commands (batches) are used to execute the update. diff --git a/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs b/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs index 8243c421905..d0544ef6232 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs @@ -17,28 +17,11 @@ public virtual void AppendDeleteOperation_creates_full_delete_command_text() CreateSqlGenerator().AppendDeleteOperation(stringBuilder, command, 0); - Assert.Equal( - "DELETE FROM " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + "" - + Environment.NewLine - + "WHERE " - + OpenDelimiter - + "Id" - + CloseDelimiter - + " = @p0;" - + Environment.NewLine - + "SELECT " - + RowsAffected - + ";" - + Environment.NewLine - + Environment.NewLine, - stringBuilder.ToString()); + AppendDeleteOperation_creates_full_delete_command_text_verification(stringBuilder); } + protected abstract void AppendDeleteOperation_creates_full_delete_command_text_verification(StringBuilder stringBuilder); + [ConditionalFact] public virtual void AppendDeleteOperation_creates_full_delete_command_text_with_concurrency_check() { @@ -47,32 +30,12 @@ public virtual void AppendDeleteOperation_creates_full_delete_command_text_with_ CreateSqlGenerator().AppendDeleteOperation(stringBuilder, command, 0); - Assert.Equal( - "DELETE FROM " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + "" - + Environment.NewLine - + "WHERE " - + OpenDelimiter - + "Id" - + CloseDelimiter - + " = @p0 AND " - + OpenDelimiter - + "ConcurrencyToken" - + CloseDelimiter - + " IS NULL;" - + Environment.NewLine - + "SELECT " - + RowsAffected - + ";" - + Environment.NewLine - + Environment.NewLine, - stringBuilder.ToString()); + AppendDeleteOperation_creates_full_delete_command_text_with_concurrency_check_verification(stringBuilder); } + protected abstract void AppendDeleteOperation_creates_full_delete_command_text_with_concurrency_check_verification( + StringBuilder stringBuilder); + [ConditionalFact] public virtual void AppendInsertOperation_insert_if_store_generated_columns_exist() { @@ -177,114 +140,31 @@ public virtual void AppendInsertOperation_for_only_single_identity_columns() protected abstract void AppendInsertOperation_for_only_single_identity_columns_verification(StringBuilder stringBuilder); [ConditionalFact] - public virtual void AppendUpdateOperation_appends_update_and_select_if_store_generated_columns_exist() + public virtual void AppendUpdateOperation_if_store_generated_columns_exist() { var stringBuilder = new StringBuilder(); var command = CreateUpdateCommand(); CreateSqlGenerator().AppendUpdateOperation(stringBuilder, command, 0); - AppendUpdateOperation_appends_update_and_select_if_store_generated_columns_exist_verification(stringBuilder); + AppendUpdateOperation_if_store_generated_columns_exist_verification(stringBuilder); } - protected virtual void AppendUpdateOperation_appends_update_and_select_if_store_generated_columns_exist_verification( - StringBuilder stringBuilder) - => Assert.Equal( - "UPDATE " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + " SET " - + OpenDelimiter - + "Name" - + CloseDelimiter - + " = @p0, " - + OpenDelimiter - + "Quacks" - + CloseDelimiter - + " = @p1, " - + OpenDelimiter - + "ConcurrencyToken" - + CloseDelimiter - + " = @p2" - + Environment.NewLine - + "WHERE " - + OpenDelimiter - + "Id" - + CloseDelimiter - + " = @p3 AND " - + OpenDelimiter - + "ConcurrencyToken" - + CloseDelimiter - + " IS NULL;" - + Environment.NewLine - + "SELECT " - + OpenDelimiter - + "Computed" - + CloseDelimiter - + "" - + Environment.NewLine - + "FROM " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + "" - + Environment.NewLine - + "WHERE " - + RowsAffected - + " = 1 AND " - + OpenDelimiter - + "Id" - + CloseDelimiter - + " = @p3;" - + Environment.NewLine - + Environment.NewLine, - stringBuilder.ToString()); + protected abstract void AppendUpdateOperation_if_store_generated_columns_exist_verification(StringBuilder stringBuilder); [ConditionalFact] - public virtual void AppendUpdateOperation_appends_update_and_select_rowcount_if_store_generated_columns_dont_exist() + public virtual void AppendUpdateOperation_if_store_generated_columns_dont_exist() { var stringBuilder = new StringBuilder(); var command = CreateUpdateCommand(false, false); CreateSqlGenerator().AppendUpdateOperation(stringBuilder, command, 0); - Assert.Equal( - "UPDATE " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + " SET " - + OpenDelimiter - + "Name" - + CloseDelimiter - + " = @p0, " - + OpenDelimiter - + "Quacks" - + CloseDelimiter - + " = @p1, " - + OpenDelimiter - + "ConcurrencyToken" - + CloseDelimiter - + " = @p2" - + Environment.NewLine - + "WHERE " - + OpenDelimiter - + "Id" - + CloseDelimiter - + " = @p3;" - + Environment.NewLine - + "SELECT " - + RowsAffected - + ";" - + Environment.NewLine - + Environment.NewLine, - stringBuilder.ToString()); + AppendUpdateOperation_if_store_generated_columns_dont_exist_verification(stringBuilder); } + protected abstract void AppendUpdateOperation_if_store_generated_columns_dont_exist_verification(StringBuilder stringBuilder); + [ConditionalFact] public virtual void AppendUpdateOperation_appends_where_for_concurrency_token() { @@ -293,105 +173,23 @@ public virtual void AppendUpdateOperation_appends_where_for_concurrency_token() CreateSqlGenerator().AppendUpdateOperation(stringBuilder, command, 0); - Assert.Equal( - "UPDATE " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + " SET " - + OpenDelimiter - + "Name" - + CloseDelimiter - + " = @p0, " - + OpenDelimiter - + "Quacks" - + CloseDelimiter - + " = @p1, " - + OpenDelimiter - + "ConcurrencyToken" - + CloseDelimiter - + " = @p2" - + Environment.NewLine - + "WHERE " - + OpenDelimiter - + "Id" - + CloseDelimiter - + " = @p3 AND " - + OpenDelimiter - + "ConcurrencyToken" - + CloseDelimiter - + " IS NULL;" - + Environment.NewLine - + "SELECT " - + RowsAffected - + ";" - + Environment.NewLine - + Environment.NewLine, - stringBuilder.ToString()); + AppendUpdateOperation_appends_where_for_concurrency_token_verification(stringBuilder); } + protected abstract void AppendUpdateOperation_appends_where_for_concurrency_token_verification(StringBuilder stringBuilder); + [ConditionalFact] - public virtual void AppendUpdateOperation_appends_select_for_computed_property() + public virtual void AppendUpdateOperation_for_computed_property() { var stringBuilder = new StringBuilder(); var command = CreateUpdateCommand(true, false); CreateSqlGenerator().AppendUpdateOperation(stringBuilder, command, 0); - AppendUpdateOperation_appends_select_for_computed_property_verification(stringBuilder); + AppendUpdateOperation_for_computed_property_verification(stringBuilder); } - protected virtual void AppendUpdateOperation_appends_select_for_computed_property_verification(StringBuilder stringBuilder) - => Assert.Equal( - "UPDATE " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + " SET " - + OpenDelimiter - + "Name" - + CloseDelimiter - + " = @p0, " - + OpenDelimiter - + "Quacks" - + CloseDelimiter - + " = @p1, " - + OpenDelimiter - + "ConcurrencyToken" - + CloseDelimiter - + " = @p2" - + Environment.NewLine - + "WHERE " - + OpenDelimiter - + "Id" - + CloseDelimiter - + " = @p3;" - + Environment.NewLine - + "SELECT " - + OpenDelimiter - + "Computed" - + CloseDelimiter - + "" - + Environment.NewLine - + "FROM " - + SchemaPrefix - + OpenDelimiter - + "Ducks" - + CloseDelimiter - + "" - + Environment.NewLine - + "WHERE " - + RowsAffected - + " = 1 AND " - + OpenDelimiter - + "Id" - + CloseDelimiter - + " = @p3;" - + Environment.NewLine - + Environment.NewLine, - stringBuilder.ToString()); + protected abstract void AppendUpdateOperation_for_computed_property_verification(StringBuilder stringBuilder); [ConditionalFact] public virtual void GenerateNextSequenceValueOperation_returns_statement_with_sanitized_sequence() diff --git a/test/EFCore.Relational.Tests/TestUtilities/FakeProvider/FakeSqlGenerator.cs b/test/EFCore.Relational.Tests/TestUtilities/FakeProvider/FakeSqlGenerator.cs index 3a45ae9629b..84e737066f4 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/FakeProvider/FakeSqlGenerator.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/FakeProvider/FakeSqlGenerator.cs @@ -57,18 +57,6 @@ protected override void AppendIdentityWhereCondition(StringBuilder commandString .Append(" = ") .Append("provider_specific_identity()"); - protected override ResultSetMapping AppendSelectAffectedCountCommand( - StringBuilder commandStringBuilder, - string name, - string schema, - int commandPosition) - { - commandStringBuilder - .Append("SELECT provider_specific_rowcount();").Append(Environment.NewLine).Append(Environment.NewLine); - - return ResultSetMapping.LastInResultSet; - } - protected override void AppendRowsAffectedWhereCondition(StringBuilder commandStringBuilder, int expectedRowsAffected) => commandStringBuilder .Append("provider_specific_rowcount() = ").Append(expectedRowsAffected); diff --git a/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTest.cs b/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTest.cs index 825deece69c..34c232b0733 100644 --- a/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTest.cs +++ b/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTest.cs @@ -16,6 +16,22 @@ protected override IUpdateSqlGenerator CreateSqlGenerator() TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()))); + protected override void AppendDeleteOperation_creates_full_delete_command_text_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"DELETE FROM ""dbo"".""Ducks"" +WHERE ""Id"" = @p0 +RETURNING 1; +", + stringBuilder.ToString()); + + protected override void AppendDeleteOperation_creates_full_delete_command_text_with_concurrency_check_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"DELETE FROM ""dbo"".""Ducks"" +WHERE ""Id"" = @p0 AND ""ConcurrencyToken"" IS NULL +RETURNING 1; +", + stringBuilder.ToString()); + protected override void AppendInsertOperation_insert_if_store_generated_columns_exist_verification(StringBuilder stringBuilder) => AssertBaseline( @"INSERT INTO ""dbo"".""Ducks"" (""Name"", ""Quacks"", ""ConcurrencyToken"") @@ -55,6 +71,38 @@ protected override void AppendInsertOperation_for_only_single_identity_columns_v @"INSERT INTO ""dbo"".""Ducks"" DEFAULT VALUES RETURNING ""Id""; +", + stringBuilder.ToString()); + + protected override void AppendUpdateOperation_if_store_generated_columns_exist_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"UPDATE ""dbo"".""Ducks"" SET ""Name"" = @p0, ""Quacks"" = @p1, ""ConcurrencyToken"" = @p2 +WHERE ""Id"" = @p3 AND ""ConcurrencyToken"" IS NULL +RETURNING ""Computed""; +", + stringBuilder.ToString()); + + protected override void AppendUpdateOperation_if_store_generated_columns_dont_exist_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"UPDATE ""dbo"".""Ducks"" SET ""Name"" = @p0, ""Quacks"" = @p1, ""ConcurrencyToken"" = @p2 +WHERE ""Id"" = @p3 +RETURNING 1; +", + stringBuilder.ToString()); + + protected override void AppendUpdateOperation_appends_where_for_concurrency_token_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"UPDATE ""dbo"".""Ducks"" SET ""Name"" = @p0, ""Quacks"" = @p1, ""ConcurrencyToken"" = @p2 +WHERE ""Id"" = @p3 AND ""ConcurrencyToken"" IS NULL +RETURNING 1; +", + stringBuilder.ToString()); + + protected override void AppendUpdateOperation_for_computed_property_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"UPDATE ""dbo"".""Ducks"" SET ""Name"" = @p0, ""Quacks"" = @p1, ""ConcurrencyToken"" = @p2 +WHERE ""Id"" = @p3 +RETURNING ""Computed""; ", stringBuilder.ToString()); diff --git a/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs index 77547e4d679..f8cab2a2ace 100644 --- a/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs @@ -167,8 +167,8 @@ FROM [Sample] AS [s] SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [Sample] SET [Name] = @p0, [RowVersion] = @p1 -WHERE [Unique_No] = @p2 AND [RowVersion] = @p3; -SELECT @@ROWCOUNT;", +OUTPUT 1 +WHERE [Unique_No] = @p2 AND [RowVersion] = @p3;", // @"@p2='1' @p0='ChangedData' (Nullable = false) (Size = 4000) @@ -178,8 +178,8 @@ FROM [Sample] AS [s] SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [Sample] SET [Name] = @p0, [RowVersion] = @p1 -WHERE [Unique_No] = @p2 AND [RowVersion] = @p3; -SELECT @@ROWCOUNT;"); +OUTPUT 1 +WHERE [Unique_No] = @p2 AND [RowVersion] = @p3;"); } public override void DatabaseGeneratedAttribute_autogenerates_values_when_set_to_identity() diff --git a/test/EFCore.SqlServer.FunctionalTests/OptimisticConcurrencySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/OptimisticConcurrencySqlServerTest.cs index 469a2b2120b..21b4c363f26 100644 --- a/test/EFCore.SqlServer.FunctionalTests/OptimisticConcurrencySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/OptimisticConcurrencySqlServerTest.cs @@ -169,8 +169,8 @@ FROM [Engines] AS [e] SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [Engines] SET [Name] = @p0 -WHERE [Id] = @p1 AND [EngineSupplierId] = @p2 AND [Name] = @p3 AND [StorageLocation_Latitude] = @p4 AND [StorageLocation_Longitude] = @p5; -SELECT @@ROWCOUNT;"); +OUTPUT 1 +WHERE [Id] = @p1 AND [EngineSupplierId] = @p2 AND [Name] = @p3 AND [StorageLocation_Latitude] = @p4 AND [StorageLocation_Longitude] = @p5;"); } private void AssertSql(params string[] expected) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/IncompleteMappingInheritanceQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/IncompleteMappingInheritanceQuerySqlServerTest.cs index 85a40748b6c..98a6c6c600e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/IncompleteMappingInheritanceQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/IncompleteMappingInheritanceQuerySqlServerTest.cs @@ -404,8 +404,8 @@ FROM [Animals] AS [a] SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [Animals] SET [EagleId] = @p0 -WHERE [Species] = @p1; -SELECT @@ROWCOUNT;", +OUTPUT 1 +WHERE [Species] = @p1;", // @"SELECT TOP(2) [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[FoundOn] FROM [Animals] AS [a] @@ -416,8 +416,8 @@ FROM [Animals] AS [a] SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; DELETE FROM [Animals] -WHERE [Species] = @p0; -SELECT @@ROWCOUNT;", +OUTPUT 1 +WHERE [Species] = @p0;", // @"SELECT COUNT(*) FROM [Animals] AS [a] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/InheritanceQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/InheritanceQuerySqlServerTest.cs index 3f58c6ef539..cf4ed45898d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/InheritanceQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/InheritanceQuerySqlServerTest.cs @@ -378,8 +378,8 @@ FROM [Animals] AS [a] SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [Animals] SET [EagleId] = @p0 -WHERE [Species] = @p1; -SELECT @@ROWCOUNT;", +OUTPUT 1 +WHERE [Species] = @p1;", // @"SELECT TOP(2) [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[FoundOn] FROM [Animals] AS [a] @@ -390,8 +390,8 @@ FROM [Animals] AS [a] SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; DELETE FROM [Animals] -WHERE [Species] = @p0; -SELECT @@ROWCOUNT;", +OUTPUT 1 +WHERE [Species] = @p0;", // @"SELECT COUNT(*) FROM [Animals] AS [a] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTInheritanceQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTInheritanceQuerySqlServerTest.cs index 6c45fc18098..a31d28b8f50 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTInheritanceQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTInheritanceQuerySqlServerTest.cs @@ -136,8 +136,8 @@ FROM [Animals] AS [a] SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [Birds] SET [EagleId] = @p0 -WHERE [Species] = @p1; -SELECT @@ROWCOUNT;", +OUTPUT 1 +WHERE [Species] = @p1;", // @"SELECT TOP(2) [a].[Species], [a].[CountryId], [a].[Name], [b].[EagleId], [b].[IsFlightless], [k].[FoundOn] FROM [Animals] AS [a] @@ -150,24 +150,24 @@ FROM [Animals] AS [a] SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; DELETE FROM [Kiwi] -WHERE [Species] = @p0; -SELECT @@ROWCOUNT;", +OUTPUT 1 +WHERE [Species] = @p0;", // @"@p1='Apteryx owenii' (Nullable = false) (Size = 100) SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; DELETE FROM [Birds] -WHERE [Species] = @p1; -SELECT @@ROWCOUNT;", +OUTPUT 1 +WHERE [Species] = @p1;", // @"@p2='Apteryx owenii' (Nullable = false) (Size = 100) SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; DELETE FROM [Animals] -WHERE [Species] = @p2; -SELECT @@ROWCOUNT;", +OUTPUT 1 +WHERE [Species] = @p2;", // @"SELECT COUNT(*) FROM [Animals] AS [a] diff --git a/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs index 4f9bbb9011a..518ec2d7f57 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs @@ -190,8 +190,8 @@ public override async Task Can_change_dependent_instance_non_derived() SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [Vehicles] SET [Operator_Name] = @p0 -WHERE [Name] = @p1; -SELECT @@ROWCOUNT;", +OUTPUT 1 +WHERE [Name] = @p1;", // @"@p2='Trek Pro Fit Madone 6 Series' (Nullable = false) (Size = 450) @p3='Repair' (Size = 4000) @@ -233,8 +233,8 @@ public override async Task Can_change_principal_instance_non_derived() SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [Vehicles] SET [SeatingCapacity] = @p0 -WHERE [Name] = @p1; -SELECT @@ROWCOUNT;", +OUTPUT 1 +WHERE [Name] = @p1;", // @"SELECT TOP(2) [v].[Name], [v].[SeatingCapacity], [c].[AttachedVehicleName], CASE WHEN [c].[Name] IS NOT NULL THEN N'CompositeVehicle' diff --git a/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs index 4fa4ba61080..30f4308c715 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs @@ -213,8 +213,8 @@ public override async Task Can_change_dependent_instance_non_derived() SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [Vehicles] SET [Operator_Discriminator] = @p0, [LicenseType] = @p1, [Operator_Name] = @p2 -WHERE [Name] = @p3; -SELECT @@ROWCOUNT;", +OUTPUT 1 +WHERE [Name] = @p3;", // @"SELECT TOP(2) [v].[Name], [v].[Discriminator], [v].[SeatingCapacity], [v].[AttachedVehicleName], [t].[Name], [t].[Operator_Discriminator], [t].[Operator_Name], [t].[LicenseType] FROM [Vehicles] AS [v] @@ -237,8 +237,8 @@ public override async Task Can_change_principal_instance_non_derived() SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [Vehicles] SET [SeatingCapacity] = @p0 -WHERE [Name] = @p1; -SELECT @@ROWCOUNT;", +OUTPUT 1 +WHERE [Name] = @p1;", // @"SELECT TOP(2) [v].[Name], [v].[Discriminator], [v].[SeatingCapacity], [v].[AttachedVehicleName], [t].[Name], [t].[Operator_Discriminator], [t].[Operator_Name], [t].[LicenseType] FROM [Vehicles] AS [v] diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/SqlServerUpdateSqlGeneratorTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/SqlServerUpdateSqlGeneratorTest.cs index 2d18a03898d..b57726caabc 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Update/SqlServerUpdateSqlGeneratorTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Update/SqlServerUpdateSqlGeneratorTest.cs @@ -154,6 +154,56 @@ public void AppendBulkInsertOperation_appends_insert_if_no_store_generated_colum Assert.Equal(ResultSetMapping.NoResultSet, grouping); } + protected override void AppendUpdateOperation_for_computed_property_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"UPDATE [dbo].[Ducks] SET [Name] = @p0, [Quacks] = @p1, [ConcurrencyToken] = @p2 +OUTPUT INSERTED.[Computed] +WHERE [Id] = @p3; +", + stringBuilder.ToString()); + + protected override void AppendUpdateOperation_if_store_generated_columns_exist_verification( + StringBuilder stringBuilder) + => AssertBaseline( + @"UPDATE [dbo].[Ducks] SET [Name] = @p0, [Quacks] = @p1, [ConcurrencyToken] = @p2 +OUTPUT INSERTED.[Computed] +WHERE [Id] = @p3 AND [ConcurrencyToken] IS NULL; +", + stringBuilder.ToString()); + + protected override void AppendUpdateOperation_if_store_generated_columns_dont_exist_verification( + StringBuilder stringBuilder) + => AssertBaseline( + @"UPDATE [dbo].[Ducks] SET [Name] = @p0, [Quacks] = @p1, [ConcurrencyToken] = @p2 +OUTPUT 1 +WHERE [Id] = @p3; +", + stringBuilder.ToString()); + + protected override void AppendUpdateOperation_appends_where_for_concurrency_token_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"UPDATE [dbo].[Ducks] SET [Name] = @p0, [Quacks] = @p1, [ConcurrencyToken] = @p2 +OUTPUT 1 +WHERE [Id] = @p3 AND [ConcurrencyToken] IS NULL; +", + stringBuilder.ToString()); + + protected override void AppendDeleteOperation_creates_full_delete_command_text_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"DELETE FROM [dbo].[Ducks] +OUTPUT 1 +WHERE [Id] = @p0; +", + stringBuilder.ToString()); + + protected override void AppendDeleteOperation_creates_full_delete_command_text_with_concurrency_check_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"DELETE FROM [dbo].[Ducks] +OUTPUT 1 +WHERE [Id] = @p0 AND [ConcurrencyToken] IS NULL; +", + stringBuilder.ToString()); + protected override string RowsAffected => "@@ROWCOUNT"; diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationIdentitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationIdentitySqlServerTest.cs index ec27e33ef3a..b68da2be60e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationIdentitySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationIdentitySqlServerTest.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.EntityFrameworkCore.TestModels.StoreValueGenerationModel; - namespace Microsoft.EntityFrameworkCore.Update; #nullable enable @@ -25,12 +23,6 @@ protected override bool ShouldCreateImplicitTransaction( GeneratedValues generatedValues, bool withSameEntityType) { - // Updates with generated values currently use SELECT to retrieve them, and so require transactions - if (firstOperationType == EntityState.Modified && generatedValues != GeneratedValues.None) - { - return true; - } - // For multiple operations, we specifically optimize multiple insertions of the same entity type with a single command (e.g. MERGE) // (as long as there are writable columns) if (firstOperationType is EntityState.Added @@ -96,12 +88,11 @@ public override async Task Modify_with_generated_values(bool async) @"@p1='1' @p0='1000' +SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 -WHERE [Id] = @p1; -SELECT [Data1] -FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = @p1;"); +OUTPUT INSERTED.[Data1] +WHERE [Id] = @p1;"); } public override async Task Modify_with_no_generated_values(bool async) @@ -116,8 +107,8 @@ public override async Task Modify_with_no_generated_values(bool async) SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 -WHERE [Id] = @p2; -SELECT @@ROWCOUNT;"); +OUTPUT 1 +WHERE [Id] = @p2;"); } public override async Task Delete(bool async) @@ -130,8 +121,8 @@ public override async Task Delete(bool async) SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; DELETE FROM [WithSomeDatabaseGenerated] -WHERE [Id] = @p0; -SELECT @@ROWCOUNT;"); +OUTPUT 1 +WHERE [Id] = @p0;"); } #endregion Single operation @@ -202,16 +193,11 @@ public override async Task Modify_Modify_with_same_entity_type_and_generated_val SET NOCOUNT ON; UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 +OUTPUT INSERTED.[Data1] WHERE [Id] = @p1; -SELECT [Data1] -FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = @p1; - UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p2 -WHERE [Id] = @p3; -SELECT [Data1] -FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = @p3;"); +OUTPUT INSERTED.[Data1] +WHERE [Id] = @p3;"); } public override async Task Modify_Modify_with_same_entity_type_and_no_generated_values(bool async) @@ -228,12 +214,11 @@ public override async Task Modify_Modify_with_same_entity_type_and_no_generated_ SET NOCOUNT ON; UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 +OUTPUT 1 WHERE [Id] = @p2; -SELECT @@ROWCOUNT; - UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p3, [Data2] = @p4 -WHERE [Id] = @p5; -SELECT @@ROWCOUNT;"); +OUTPUT 1 +WHERE [Id] = @p5;"); } public override async Task Delete_Delete_with_same_entity_type(bool async) @@ -246,12 +231,11 @@ public override async Task Delete_Delete_with_same_entity_type(bool async) SET NOCOUNT ON; DELETE FROM [WithSomeDatabaseGenerated] +OUTPUT 1 WHERE [Id] = @p0; -SELECT @@ROWCOUNT; - DELETE FROM [WithSomeDatabaseGenerated] -WHERE [Id] = @p1; -SELECT @@ROWCOUNT;"); +OUTPUT 1 +WHERE [Id] = @p1;"); } #endregion Two operations with same entity type @@ -320,23 +304,19 @@ public override async Task Modify_Modify_with_different_entity_types_and_generat SET NOCOUNT ON; UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 +OUTPUT INSERTED.[Data1] WHERE [Id] = @p1; -SELECT [Data1] -FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = @p1; - UPDATE [WithSomeDatabaseGenerated2] SET [Data2] = @p2 -WHERE [Id] = @p3; -SELECT [Data1] -FROM [WithSomeDatabaseGenerated2] -WHERE @@ROWCOUNT = 1 AND [Id] = @p3;"); +OUTPUT INSERTED.[Data1] +WHERE [Id] = @p3;"); } public override async Task Modify_Modify_with_different_entity_types_and_no_generated_values(bool async) { await base.Modify_Modify_with_different_entity_types_and_no_generated_values(async); -AssertSql( - @"@p2='1' + + AssertSql( + @"@p2='1' @p0='1000' @p1='1000' @p5='2' @@ -345,12 +325,11 @@ public override async Task Modify_Modify_with_different_entity_types_and_no_gene SET NOCOUNT ON; UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 +OUTPUT 1 WHERE [Id] = @p2; -SELECT @@ROWCOUNT; - UPDATE [WithNoDatabaseGenerated2] SET [Data1] = @p3, [Data2] = @p4 -WHERE [Id] = @p5; -SELECT @@ROWCOUNT;"); +OUTPUT 1 +WHERE [Id] = @p5;"); } public override async Task Delete_Delete_with_different_entity_types(bool async) @@ -363,12 +342,11 @@ public override async Task Delete_Delete_with_different_entity_types(bool async) SET NOCOUNT ON; DELETE FROM [WithSomeDatabaseGenerated] +OUTPUT 1 WHERE [Id] = @p0; -SELECT @@ROWCOUNT; - DELETE FROM [WithSomeDatabaseGenerated2] -WHERE [Id] = @p1; -SELECT @@ROWCOUNT;"); +OUTPUT 1 +WHERE [Id] = @p1;"); } #endregion Two operations with different entity types diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationIdentityTriggerSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationIdentityTriggerSqlServerTest.cs index 2f2fb166ba2..2dffa61a266 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationIdentityTriggerSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationIdentityTriggerSqlServerTest.cs @@ -24,7 +24,7 @@ protected override bool ShouldCreateImplicitTransaction( bool withSameEntityType) { // We have triggers, so any insert/update retrieving a database-generated value must be enclosed in a transaction - // (we use INSERT+SELECT or INSERT ... OUTPUT INTO+SELECT) + // (e.g. we use INSERT/UPDATE+SELECT or INSERT ... OUTPUT INTO+SELECT) if (generatedValues is GeneratedValues.Some or GeneratedValues.All && firstOperationType is EntityState.Added or EntityState.Modified) { diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationSequenceSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationSequenceSqlServerTest.cs index f56a6747fc9..b32fade1836 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationSequenceSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationSequenceSqlServerTest.cs @@ -25,12 +25,6 @@ protected override bool ShouldCreateImplicitTransaction( GeneratedValues generatedValues, bool withSameEntityType) { - // Updates with generated values currently use SELECT to retrieve them, and so require transactions - if (firstOperationType == EntityState.Modified && generatedValues != GeneratedValues.None) - { - return true; - } - // For multiple operations, we specifically optimize multiple insertions of the same entity type with a single command (e.g. MERGE) // (as long as there are writable columns) if (firstOperationType is EntityState.Added @@ -96,12 +90,11 @@ public override async Task Modify_with_generated_values(bool async) @"@p1='5' @p0='1000' +SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 -WHERE [Id] = @p1; -SELECT [Data1] -FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = @p1;"); +OUTPUT INSERTED.[Data1] +WHERE [Id] = @p1;"); } public override async Task Modify_with_no_generated_values(bool async) @@ -116,8 +109,8 @@ public override async Task Modify_with_no_generated_values(bool async) SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 -WHERE [Id] = @p2; -SELECT @@ROWCOUNT;"); +OUTPUT 1 +WHERE [Id] = @p2;"); } public override async Task Delete(bool async) @@ -130,8 +123,8 @@ public override async Task Delete(bool async) SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; DELETE FROM [WithSomeDatabaseGenerated] -WHERE [Id] = @p0; -SELECT @@ROWCOUNT;"); +OUTPUT 1 +WHERE [Id] = @p0;"); } #endregion Single operation @@ -204,16 +197,11 @@ public override async Task Modify_Modify_with_same_entity_type_and_generated_val SET NOCOUNT ON; UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 +OUTPUT INSERTED.[Data1] WHERE [Id] = @p1; -SELECT [Data1] -FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = @p1; - UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p2 -WHERE [Id] = @p3; -SELECT [Data1] -FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = @p3;"); +OUTPUT INSERTED.[Data1] +WHERE [Id] = @p3;"); } public override async Task Modify_Modify_with_same_entity_type_and_no_generated_values(bool async) @@ -230,12 +218,11 @@ public override async Task Modify_Modify_with_same_entity_type_and_no_generated_ SET NOCOUNT ON; UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 +OUTPUT 1 WHERE [Id] = @p2; -SELECT @@ROWCOUNT; - UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p3, [Data2] = @p4 -WHERE [Id] = @p5; -SELECT @@ROWCOUNT;"); +OUTPUT 1 +WHERE [Id] = @p5;"); } public override async Task Delete_Delete_with_same_entity_type(bool async) @@ -248,12 +235,11 @@ public override async Task Delete_Delete_with_same_entity_type(bool async) SET NOCOUNT ON; DELETE FROM [WithSomeDatabaseGenerated] +OUTPUT 1 WHERE [Id] = @p0; -SELECT @@ROWCOUNT; - DELETE FROM [WithSomeDatabaseGenerated] -WHERE [Id] = @p1; -SELECT @@ROWCOUNT;"); +OUTPUT 1 +WHERE [Id] = @p1;"); } #endregion Two operations with same entity type @@ -322,16 +308,11 @@ public override async Task Modify_Modify_with_different_entity_types_and_generat SET NOCOUNT ON; UPDATE [WithSomeDatabaseGenerated] SET [Data2] = @p0 +OUTPUT INSERTED.[Data1] WHERE [Id] = @p1; -SELECT [Data1] -FROM [WithSomeDatabaseGenerated] -WHERE @@ROWCOUNT = 1 AND [Id] = @p1; - UPDATE [WithSomeDatabaseGenerated2] SET [Data2] = @p2 -WHERE [Id] = @p3; -SELECT [Data1] -FROM [WithSomeDatabaseGenerated2] -WHERE @@ROWCOUNT = 1 AND [Id] = @p3;"); +OUTPUT INSERTED.[Data1] +WHERE [Id] = @p3;"); } public override async Task Modify_Modify_with_different_entity_types_and_no_generated_values(bool async) @@ -347,12 +328,11 @@ public override async Task Modify_Modify_with_different_entity_types_and_no_gene SET NOCOUNT ON; UPDATE [WithNoDatabaseGenerated] SET [Data1] = @p0, [Data2] = @p1 +OUTPUT 1 WHERE [Id] = @p2; -SELECT @@ROWCOUNT; - UPDATE [WithNoDatabaseGenerated2] SET [Data1] = @p3, [Data2] = @p4 -WHERE [Id] = @p5; -SELECT @@ROWCOUNT;"); +OUTPUT 1 +WHERE [Id] = @p5;"); } public override async Task Delete_Delete_with_different_entity_types(bool async) @@ -365,12 +345,11 @@ public override async Task Delete_Delete_with_different_entity_types(bool async) SET NOCOUNT ON; DELETE FROM [WithSomeDatabaseGenerated] +OUTPUT 1 WHERE [Id] = @p0; -SELECT @@ROWCOUNT; - DELETE FROM [WithSomeDatabaseGenerated2] -WHERE [Id] = @p1; -SELECT @@ROWCOUNT;"); +OUTPUT 1 +WHERE [Id] = @p1;"); } #endregion Two operations with different entity types diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationSequenceTriggerSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationSequenceTriggerSqlServerTest.cs index 8c1d5e85ce9..055a0351ca8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationSequenceTriggerSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Update/StoreValueGenerationSequenceTriggerSqlServerTest.cs @@ -28,7 +28,7 @@ protected override bool ShouldCreateImplicitTransaction( bool withSameEntityType) { // We have triggers, so any insert/update retrieving a database-generated value must be enclosed in a transaction - // (we use INSERT+SELECT or INSERT ... OUTPUT INTO+SELECT) + // (e.g. we use INSERT/UPDATE+SELECT or INSERT ... OUTPUT INTO+SELECT) if (generatedValues is GeneratedValues.Some or GeneratedValues.All && firstOperationType is EntityState.Added or EntityState.Modified) { diff --git a/test/EFCore.SqlServer.FunctionalTests/UpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/UpdatesSqlServerTest.cs index 1d2c6b2b925..5e33fb7b297 100644 --- a/test/EFCore.SqlServer.FunctionalTests/UpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/UpdatesSqlServerTest.cs @@ -111,15 +111,33 @@ public override void Save_replaced_principal() { base.Save_replaced_principal(); - AssertContainsSql( + AssertSql( + @"SELECT TOP(2) [c].[Id], [c].[Name], [c].[PrincipalId] +FROM [Categories] AS [c]", + // + @"@__category_PrincipalId_0='778' (Nullable = true) + +SELECT [p].[Id], [p].[Discriminator], [p].[DependentId], [p].[Name], [p].[Price] +FROM [ProductBase] AS [p] +WHERE [p].[Discriminator] = N'Product' AND [p].[DependentId] = @__category_PrincipalId_0", + // @"@p1='78' @p0='New Category' (Size = 4000) SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; UPDATE [Categories] SET [Name] = @p0 -WHERE [Id] = @p1; -SELECT @@ROWCOUNT;"); +OUTPUT 1 +WHERE [Id] = @p1;", + // + @"SELECT TOP(2) [c].[Id], [c].[Name], [c].[PrincipalId] +FROM [Categories] AS [c]", + // + @"@__category_PrincipalId_0='778' (Nullable = true) + +SELECT [p].[Id], [p].[Discriminator], [p].[DependentId], [p].[Name], [p].[Price] +FROM [ProductBase] AS [p] +WHERE [p].[Discriminator] = N'Product' AND [p].[DependentId] = @__category_PrincipalId_0"); } public override void Identifiers_are_generated_correctly() diff --git a/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs index c032c7b032f..d92736fdf73 100644 --- a/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs @@ -106,8 +106,8 @@ public override void ConcurrencyCheckAttribute_throws_if_value_in_database_chang @p3='00000001-0000-0000-0000-000000000001' UPDATE ""Sample"" SET ""Name"" = @p0, ""RowVersion"" = @p1 -WHERE ""Unique_No"" = @p2 AND ""RowVersion"" = @p3; -SELECT changes();", +WHERE ""Unique_No"" = @p2 AND ""RowVersion"" = @p3 +RETURNING 1;", // @"@p2='1' @p0='ChangedData' (Nullable = false) (Size = 11) @@ -115,8 +115,8 @@ public override void ConcurrencyCheckAttribute_throws_if_value_in_database_chang @p3='00000001-0000-0000-0000-000000000001' UPDATE ""Sample"" SET ""Name"" = @p0, ""RowVersion"" = @p1 -WHERE ""Unique_No"" = @p2 AND ""RowVersion"" = @p3; -SELECT changes();"); +WHERE ""Unique_No"" = @p2 AND ""RowVersion"" = @p3 +RETURNING 1;"); } public override void DatabaseGeneratedAttribute_autogenerates_values_when_set_to_identity() diff --git a/test/EFCore.Sqlite.FunctionalTests/Migrations/SqliteMigrationsSqlGeneratorTest.cs b/test/EFCore.Sqlite.FunctionalTests/Migrations/SqliteMigrationsSqlGeneratorTest.cs index 3857debf8b4..921372b163d 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Migrations/SqliteMigrationsSqlGeneratorTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Migrations/SqliteMigrationsSqlGeneratorTest.cs @@ -428,25 +428,20 @@ public override void DeleteDataOperation_all_args() AssertSql( @"DELETE FROM ""People"" -WHERE ""First Name"" = 'Hodor'; -SELECT changes(); - +WHERE ""First Name"" = 'Hodor' +RETURNING 1; DELETE FROM ""People"" -WHERE ""First Name"" = 'Daenerys'; -SELECT changes(); - +WHERE ""First Name"" = 'Daenerys' +RETURNING 1; DELETE FROM ""People"" -WHERE ""First Name"" = 'John'; -SELECT changes(); - +WHERE ""First Name"" = 'John' +RETURNING 1; DELETE FROM ""People"" -WHERE ""First Name"" = 'Arya'; -SELECT changes(); - +WHERE ""First Name"" = 'Arya' +RETURNING 1; DELETE FROM ""People"" -WHERE ""First Name"" = 'Harry'; -SELECT changes(); - +WHERE ""First Name"" = 'Harry' +RETURNING 1; "); } @@ -456,25 +451,20 @@ public override void DeleteDataOperation_all_args_composite() AssertSql( @"DELETE FROM ""People"" -WHERE ""First Name"" = 'Hodor' AND ""Last Name"" IS NULL; -SELECT changes(); - +WHERE ""First Name"" = 'Hodor' AND ""Last Name"" IS NULL +RETURNING 1; DELETE FROM ""People"" -WHERE ""First Name"" = 'Daenerys' AND ""Last Name"" = 'Targaryen'; -SELECT changes(); - +WHERE ""First Name"" = 'Daenerys' AND ""Last Name"" = 'Targaryen' +RETURNING 1; DELETE FROM ""People"" -WHERE ""First Name"" = 'John' AND ""Last Name"" = 'Snow'; -SELECT changes(); - +WHERE ""First Name"" = 'John' AND ""Last Name"" = 'Snow' +RETURNING 1; DELETE FROM ""People"" -WHERE ""First Name"" = 'Arya' AND ""Last Name"" = 'Stark'; -SELECT changes(); - +WHERE ""First Name"" = 'Arya' AND ""Last Name"" = 'Stark' +RETURNING 1; DELETE FROM ""People"" -WHERE ""First Name"" = 'Harry' AND ""Last Name"" = 'Strickland'; -SELECT changes(); - +WHERE ""First Name"" = 'Harry' AND ""Last Name"" = 'Strickland' +RETURNING 1; "); } @@ -484,9 +474,8 @@ public override void DeleteDataOperation_required_args() AssertSql( @"DELETE FROM ""People"" -WHERE ""Last Name"" = 'Snow'; -SELECT changes(); - +WHERE ""Last Name"" = 'Snow' +RETURNING 1; "); } @@ -496,9 +485,8 @@ public override void DeleteDataOperation_required_args_composite() AssertSql( @"DELETE FROM ""People"" -WHERE ""First Name"" = 'John' AND ""Last Name"" = 'Snow'; -SELECT changes(); - +WHERE ""First Name"" = 'John' AND ""Last Name"" = 'Snow' +RETURNING 1; "); } @@ -508,13 +496,11 @@ public override void UpdateDataOperation_all_args() AssertSql( @"UPDATE ""People"" SET ""Birthplace"" = 'Winterfell', ""House Allegiance"" = 'Stark', ""Culture"" = 'Northmen' -WHERE ""First Name"" = 'Hodor'; -SELECT changes(); - +WHERE ""First Name"" = 'Hodor' +RETURNING 1; UPDATE ""People"" SET ""Birthplace"" = 'Dragonstone', ""House Allegiance"" = 'Targaryen', ""Culture"" = 'Valyrian' -WHERE ""First Name"" = 'Daenerys'; -SELECT changes(); - +WHERE ""First Name"" = 'Daenerys' +RETURNING 1; "); } @@ -524,13 +510,11 @@ public override void UpdateDataOperation_all_args_composite() AssertSql( @"UPDATE ""People"" SET ""House Allegiance"" = 'Stark' -WHERE ""First Name"" = 'Hodor' AND ""Last Name"" IS NULL; -SELECT changes(); - +WHERE ""First Name"" = 'Hodor' AND ""Last Name"" IS NULL +RETURNING 1; UPDATE ""People"" SET ""House Allegiance"" = 'Targaryen' -WHERE ""First Name"" = 'Daenerys' AND ""Last Name"" = 'Targaryen'; -SELECT changes(); - +WHERE ""First Name"" = 'Daenerys' AND ""Last Name"" = 'Targaryen' +RETURNING 1; "); } @@ -540,13 +524,11 @@ public override void UpdateDataOperation_all_args_composite_multi() AssertSql( @"UPDATE ""People"" SET ""Birthplace"" = 'Winterfell', ""House Allegiance"" = 'Stark', ""Culture"" = 'Northmen' -WHERE ""First Name"" = 'Hodor' AND ""Last Name"" IS NULL; -SELECT changes(); - +WHERE ""First Name"" = 'Hodor' AND ""Last Name"" IS NULL +RETURNING 1; UPDATE ""People"" SET ""Birthplace"" = 'Dragonstone', ""House Allegiance"" = 'Targaryen', ""Culture"" = 'Valyrian' -WHERE ""First Name"" = 'Daenerys' AND ""Last Name"" = 'Targaryen'; -SELECT changes(); - +WHERE ""First Name"" = 'Daenerys' AND ""Last Name"" = 'Targaryen' +RETURNING 1; "); } @@ -556,9 +538,8 @@ public override void UpdateDataOperation_all_args_multi() AssertSql( @"UPDATE ""People"" SET ""Birthplace"" = 'Dragonstone', ""House Allegiance"" = 'Targaryen', ""Culture"" = 'Valyrian' -WHERE ""First Name"" = 'Daenerys'; -SELECT changes(); - +WHERE ""First Name"" = 'Daenerys' +RETURNING 1; "); } @@ -568,9 +549,8 @@ public override void UpdateDataOperation_required_args() AssertSql( @"UPDATE ""People"" SET ""House Allegiance"" = 'Targaryen' -WHERE ""First Name"" = 'Daenerys'; -SELECT changes(); - +WHERE ""First Name"" = 'Daenerys' +RETURNING 1; "); } @@ -580,9 +560,8 @@ public override void UpdateDataOperation_required_args_composite() AssertSql( @"UPDATE ""People"" SET ""House Allegiance"" = 'Targaryen' -WHERE ""First Name"" = 'Daenerys' AND ""Last Name"" = 'Targaryen'; -SELECT changes(); - +WHERE ""First Name"" = 'Daenerys' AND ""Last Name"" = 'Targaryen' +RETURNING 1; "); } @@ -592,9 +571,8 @@ public override void UpdateDataOperation_required_args_composite_multi() AssertSql( @"UPDATE ""People"" SET ""Birthplace"" = 'Dragonstone', ""House Allegiance"" = 'Targaryen', ""Culture"" = 'Valyrian' -WHERE ""First Name"" = 'Daenerys' AND ""Last Name"" = 'Targaryen'; -SELECT changes(); - +WHERE ""First Name"" = 'Daenerys' AND ""Last Name"" = 'Targaryen' +RETURNING 1; "); } @@ -604,9 +582,8 @@ public override void UpdateDataOperation_required_args_multi() AssertSql( @"UPDATE ""People"" SET ""Birthplace"" = 'Dragonstone', ""House Allegiance"" = 'Targaryen', ""Culture"" = 'Valyrian' -WHERE ""First Name"" = 'Daenerys'; -SELECT changes(); - +WHERE ""First Name"" = 'Daenerys' +RETURNING 1; "); } @@ -616,13 +593,11 @@ public override void UpdateDataOperation_required_args_multiple_rows() AssertSql( @"UPDATE ""People"" SET ""House Allegiance"" = 'Stark' -WHERE ""First Name"" = 'Hodor'; -SELECT changes(); - +WHERE ""First Name"" = 'Hodor' +RETURNING 1; UPDATE ""People"" SET ""House Allegiance"" = 'Targaryen' -WHERE ""First Name"" = 'Daenerys'; -SELECT changes(); - +WHERE ""First Name"" = 'Daenerys' +RETURNING 1; "); } diff --git a/test/EFCore.Sqlite.FunctionalTests/OptimisticConcurrencySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/OptimisticConcurrencySqliteTest.cs index ca7c5154b75..801464aff8f 100644 --- a/test/EFCore.Sqlite.FunctionalTests/OptimisticConcurrencySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/OptimisticConcurrencySqliteTest.cs @@ -46,8 +46,8 @@ ORDER BY ""e"".""Id"" @p5='-122.128101' (Nullable = true) UPDATE ""Engines"" SET ""Name"" = @p0 -WHERE ""Id"" = @p1 AND ""EngineSupplierId"" = @p2 AND ""Name"" = @p3 AND ""StorageLocation_Latitude"" = @p4 AND ""StorageLocation_Longitude"" = @p5; -SELECT changes();"); +WHERE ""Id"" = @p1 AND ""EngineSupplierId"" = @p2 AND ""Name"" = @p3 AND ""StorageLocation_Latitude"" = @p4 AND ""StorageLocation_Longitude"" = @p5 +RETURNING 1;"); } [ConditionalFact(Skip = "Optimistic Offline Lock #2195")] diff --git a/test/EFCore.Sqlite.FunctionalTests/Update/SqliteUpdateSqlGeneratorTest.cs b/test/EFCore.Sqlite.FunctionalTests/Update/SqliteUpdateSqlGeneratorTest.cs index 94da6a946f2..c49b1709151 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Update/SqliteUpdateSqlGeneratorTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Update/SqliteUpdateSqlGeneratorTest.cs @@ -80,6 +80,56 @@ protected override void AppendInsertOperation_for_only_single_identity_columns_v @"INSERT INTO ""Ducks"" DEFAULT VALUES RETURNING ""Id""; +", + stringBuilder.ToString()); + + protected override void AppendUpdateOperation_for_computed_property_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"UPDATE ""Ducks"" SET ""Name"" = @p0, ""Quacks"" = @p1, ""ConcurrencyToken"" = @p2 +WHERE ""Id"" = @p3 +RETURNING ""Computed""; +", + stringBuilder.ToString()); + + protected override void AppendUpdateOperation_if_store_generated_columns_exist_verification( + StringBuilder stringBuilder) + => AssertBaseline( + @"UPDATE ""Ducks"" SET ""Name"" = @p0, ""Quacks"" = @p1, ""ConcurrencyToken"" = @p2 +WHERE ""Id"" = @p3 AND ""ConcurrencyToken"" IS NULL +RETURNING ""Computed""; +", + stringBuilder.ToString()); + + protected override void AppendUpdateOperation_if_store_generated_columns_dont_exist_verification( + StringBuilder stringBuilder) + => AssertBaseline( + @"UPDATE ""Ducks"" SET ""Name"" = @p0, ""Quacks"" = @p1, ""ConcurrencyToken"" = @p2 +WHERE ""Id"" = @p3 +RETURNING 1; +", + stringBuilder.ToString()); + + protected override void AppendUpdateOperation_appends_where_for_concurrency_token_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"UPDATE ""Ducks"" SET ""Name"" = @p0, ""Quacks"" = @p1, ""ConcurrencyToken"" = @p2 +WHERE ""Id"" = @p3 AND ""ConcurrencyToken"" IS NULL +RETURNING 1; +", + stringBuilder.ToString()); + + protected override void AppendDeleteOperation_creates_full_delete_command_text_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"DELETE FROM ""Ducks"" +WHERE ""Id"" = @p0 +RETURNING 1; +", + stringBuilder.ToString()); + + protected override void AppendDeleteOperation_creates_full_delete_command_text_with_concurrency_check_verification(StringBuilder stringBuilder) + => AssertBaseline( + @"DELETE FROM ""Ducks"" +WHERE ""Id"" = @p0 AND ""ConcurrencyToken"" IS NULL +RETURNING 1; ", stringBuilder.ToString()); diff --git a/test/EFCore.Sqlite.FunctionalTests/Update/StoreValueGenerationSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Update/StoreValueGenerationSqliteTest.cs index 3aa88b271a9..c4ece3530f3 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Update/StoreValueGenerationSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Update/StoreValueGenerationSqliteTest.cs @@ -68,10 +68,8 @@ public override async Task Modify_with_generated_values(bool async) @p0='1000' UPDATE ""WithSomeDatabaseGenerated"" SET ""Data2"" = @p0 -WHERE ""Id"" = @p1; -SELECT ""Data1"" -FROM ""WithSomeDatabaseGenerated"" -WHERE changes() = 1 AND ""Id"" = @p1;"); +WHERE ""Id"" = @p1 +RETURNING ""Data1"";"); } public override async Task Modify_with_no_generated_values(bool async) @@ -84,8 +82,8 @@ public override async Task Modify_with_no_generated_values(bool async) @p1='1000' UPDATE ""WithNoDatabaseGenerated"" SET ""Data1"" = @p0, ""Data2"" = @p1 -WHERE ""Id"" = @p2; -SELECT changes();"); +WHERE ""Id"" = @p2 +RETURNING 1;"); } public override async Task Delete(bool async) @@ -96,8 +94,8 @@ public override async Task Delete(bool async) @"@p0='1' DELETE FROM ""WithSomeDatabaseGenerated"" -WHERE ""Id"" = @p0; -SELECT changes();"); +WHERE ""Id"" = @p0 +RETURNING 1;"); } #endregion Single operation @@ -165,19 +163,15 @@ public override async Task Modify_Modify_with_same_entity_type_and_generated_val @p0='1000' UPDATE ""WithSomeDatabaseGenerated"" SET ""Data2"" = @p0 -WHERE ""Id"" = @p1; -SELECT ""Data1"" -FROM ""WithSomeDatabaseGenerated"" -WHERE changes() = 1 AND ""Id"" = @p1;", +WHERE ""Id"" = @p1 +RETURNING ""Data1"";", // @"@p1='2' @p0='1001' UPDATE ""WithSomeDatabaseGenerated"" SET ""Data2"" = @p0 -WHERE ""Id"" = @p1; -SELECT ""Data1"" -FROM ""WithSomeDatabaseGenerated"" -WHERE changes() = 1 AND ""Id"" = @p1;"); +WHERE ""Id"" = @p1 +RETURNING ""Data1"";"); } public override async Task Modify_Modify_with_same_entity_type_and_no_generated_values(bool async) @@ -190,16 +184,16 @@ public override async Task Modify_Modify_with_same_entity_type_and_no_generated_ @p1='1000' UPDATE ""WithNoDatabaseGenerated"" SET ""Data1"" = @p0, ""Data2"" = @p1 -WHERE ""Id"" = @p2; -SELECT changes();", +WHERE ""Id"" = @p2 +RETURNING 1;", // @"@p2='2' @p0='1001' @p1='1001' UPDATE ""WithNoDatabaseGenerated"" SET ""Data1"" = @p0, ""Data2"" = @p1 -WHERE ""Id"" = @p2; -SELECT changes();"); +WHERE ""Id"" = @p2 +RETURNING 1;"); } public override async Task Delete_Delete_with_same_entity_type(bool async) @@ -210,14 +204,14 @@ public override async Task Delete_Delete_with_same_entity_type(bool async) @"@p0='1' DELETE FROM ""WithSomeDatabaseGenerated"" -WHERE ""Id"" = @p0; -SELECT changes();", +WHERE ""Id"" = @p0 +RETURNING 1;", // @"@p0='2' DELETE FROM ""WithSomeDatabaseGenerated"" -WHERE ""Id"" = @p0; -SELECT changes();"); +WHERE ""Id"" = @p0 +RETURNING 1;"); } #endregion Two operations with same entity type @@ -285,19 +279,15 @@ public override async Task Modify_Modify_with_different_entity_types_and_generat @p0='1000' UPDATE ""WithSomeDatabaseGenerated"" SET ""Data2"" = @p0 -WHERE ""Id"" = @p1; -SELECT ""Data1"" -FROM ""WithSomeDatabaseGenerated"" -WHERE changes() = 1 AND ""Id"" = @p1;", +WHERE ""Id"" = @p1 +RETURNING ""Data1"";", // @"@p1='2' @p0='1001' UPDATE ""WithSomeDatabaseGenerated2"" SET ""Data2"" = @p0 -WHERE ""Id"" = @p1; -SELECT ""Data1"" -FROM ""WithSomeDatabaseGenerated2"" -WHERE changes() = 1 AND ""Id"" = @p1;"); +WHERE ""Id"" = @p1 +RETURNING ""Data1"";"); } public override async Task Modify_Modify_with_different_entity_types_and_no_generated_values(bool async) @@ -310,16 +300,16 @@ public override async Task Modify_Modify_with_different_entity_types_and_no_gene @p1='1000' UPDATE ""WithNoDatabaseGenerated"" SET ""Data1"" = @p0, ""Data2"" = @p1 -WHERE ""Id"" = @p2; -SELECT changes();", +WHERE ""Id"" = @p2 +RETURNING 1;", // @"@p2='2' @p0='1001' @p1='1001' UPDATE ""WithNoDatabaseGenerated2"" SET ""Data1"" = @p0, ""Data2"" = @p1 -WHERE ""Id"" = @p2; -SELECT changes();"); +WHERE ""Id"" = @p2 +RETURNING 1;"); } public override async Task Delete_Delete_with_different_entity_types(bool async) @@ -330,14 +320,14 @@ public override async Task Delete_Delete_with_different_entity_types(bool async) @"@p0='1' DELETE FROM ""WithSomeDatabaseGenerated"" -WHERE ""Id"" = @p0; -SELECT changes();", +WHERE ""Id"" = @p0 +RETURNING 1;", // @"@p0='2' DELETE FROM ""WithSomeDatabaseGenerated2"" -WHERE ""Id"" = @p0; -SELECT changes();"); +WHERE ""Id"" = @p0 +RETURNING 1;"); } #endregion Two operations with different entity types From 44f1c243758dbadc38344320b5e47b23427030ec Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 18 Mar 2022 16:10:10 +0200 Subject: [PATCH 042/143] Refactor ReaderModificationCommandBatch (#27584) * Allow flexible/alternative parameter strategies * Insert commands and update SQL immediately Closes #27583 --- .../Storage/IRelationalCommandBuilder.cs | 7 + .../Storage/RelationalCommandBuilder.cs | 66 ++-- .../Update/ColumnModification.cs | 85 ++---- .../Update/IColumnModification.cs | 5 + .../Update/Internal/CommandBatchPreparer.cs | 4 +- .../Update/ModificationCommandBatch.cs | 4 +- .../Update/ReaderModificationCommandBatch.cs | 273 ++++++++--------- .../SingularModificationCommandBatch.cs | 16 +- .../SqlServerModificationCommandBatch.cs | 167 ++++------- .../TestModificationCommandBatch.cs | 4 +- .../ReaderModificationCommandBatchTest.cs | 282 +++++++++++------- .../TestRelationalCommandBuilderFactory.cs | 7 + .../SqlServerDatabaseCreatorTest.cs | 7 + ...rverModificationCommandBatchFactoryTest.cs | 8 +- .../SqlServerModificationCommandBatchTest.cs | 89 ++++-- 15 files changed, 496 insertions(+), 528 deletions(-) diff --git a/src/EFCore.Relational/Storage/IRelationalCommandBuilder.cs b/src/EFCore.Relational/Storage/IRelationalCommandBuilder.cs index 5885ed35511..7a3da9c6924 100644 --- a/src/EFCore.Relational/Storage/IRelationalCommandBuilder.cs +++ b/src/EFCore.Relational/Storage/IRelationalCommandBuilder.cs @@ -30,6 +30,13 @@ public interface IRelationalCommandBuilder /// The same builder instance so that multiple calls can be chained. IRelationalCommandBuilder AddParameter(IRelationalParameter parameter); + /// + /// Removes the parameter with the given index from this command. + /// + /// The index of the parameter to be removed. + /// The same builder instance so that multiple calls can be chained. + IRelationalCommandBuilder RemoveParameterAt(int index); + /// /// The source for s to use. /// diff --git a/src/EFCore.Relational/Storage/RelationalCommandBuilder.cs b/src/EFCore.Relational/Storage/RelationalCommandBuilder.cs index 7f0f3084359..486184be4e5 100644 --- a/src/EFCore.Relational/Storage/RelationalCommandBuilder.cs +++ b/src/EFCore.Relational/Storage/RelationalCommandBuilder.cs @@ -3,19 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Storage; -/// -/// -/// Builds a command to be executed against a relational database. -/// -/// -/// This type is typically used by database providers (and other extensions). It is generally -/// not used in application code. -/// -/// -/// -/// See Implementation of database providers and extensions -/// for more information and examples. -/// +/// public class RelationalCommandBuilder : IRelationalCommandBuilder { private readonly List _parameters = new(); @@ -42,17 +30,12 @@ public RelationalCommandBuilder( /// protected virtual RelationalCommandBuilderDependencies Dependencies { get; } - /// - /// The source for s to use. - /// + /// [Obsolete("Code trying to add parameter should add type mapped parameter using TypeMappingSource directly.")] public virtual IRelationalTypeMappingSource TypeMappingSource => Dependencies.TypeMappingSource; - /// - /// Creates the command. - /// - /// The newly created command. + /// public virtual IRelationalCommand Build() => new RelationalCommand(Dependencies, _commandTextBuilder.ToString(), Parameters); @@ -62,17 +45,11 @@ public virtual IRelationalCommand Build() public override string ToString() => _commandTextBuilder.ToString(); - /// - /// The collection of parameters. - /// + /// public virtual IReadOnlyList Parameters => _parameters; - /// - /// Adds the given parameter to this command. - /// - /// The parameter. - /// The same builder instance so that multiple calls can be chained. + /// public virtual IRelationalCommandBuilder AddParameter(IRelationalParameter parameter) { _parameters.Add(parameter); @@ -80,11 +57,15 @@ public virtual IRelationalCommandBuilder AddParameter(IRelationalParameter param return this; } - /// - /// Appends an object to the command text. - /// - /// The object to be written. - /// The same builder instance so that multiple calls can be chained. + /// + public virtual IRelationalCommandBuilder RemoveParameterAt(int index) + { + _parameters.RemoveAt(index); + + return this; + } + + /// public virtual IRelationalCommandBuilder Append(string value) { _commandTextBuilder.Append(value); @@ -92,10 +73,7 @@ public virtual IRelationalCommandBuilder Append(string value) return this; } - /// - /// Appends a blank line to the command text. - /// - /// The same builder instance so that multiple calls can be chained. + /// public virtual IRelationalCommandBuilder AppendLine() { _commandTextBuilder.AppendLine(); @@ -103,10 +81,7 @@ public virtual IRelationalCommandBuilder AppendLine() return this; } - /// - /// Increments the indent of subsequent lines. - /// - /// The same builder instance so that multiple calls can be chained. + /// public virtual IRelationalCommandBuilder IncrementIndent() { _commandTextBuilder.IncrementIndent(); @@ -114,10 +89,7 @@ public virtual IRelationalCommandBuilder IncrementIndent() return this; } - /// - /// Decrements the indent of subsequent lines. - /// - /// The same builder instance so that multiple calls can be chained. + /// public virtual IRelationalCommandBuilder DecrementIndent() { _commandTextBuilder.DecrementIndent(); @@ -125,9 +97,7 @@ public virtual IRelationalCommandBuilder DecrementIndent() return this; } - /// - /// Gets the length of the command text. - /// + /// public virtual int CommandTextLength => _commandTextBuilder.Length; } diff --git a/src/EFCore.Relational/Update/ColumnModification.cs b/src/EFCore.Relational/Update/ColumnModification.cs index c9da2313950..e6fec449e0f 100644 --- a/src/EFCore.Relational/Update/ColumnModification.cs +++ b/src/EFCore.Relational/Update/ColumnModification.cs @@ -55,100 +55,64 @@ public ColumnModification(in ColumnModificationParameters columnModificationPara UseParameter = _generateParameterName != null; } - /// - /// The that represents the entity that is being modified. - /// + /// public virtual IUpdateEntry? Entry { get; } - /// - /// The property that maps to the column. - /// + /// public virtual IProperty? Property { get; } - /// - /// The relational type mapping for the column. - /// + /// public virtual RelationalTypeMapping? TypeMapping { get; } - /// - /// A value indicating whether the column could contain a null value. - /// + /// public virtual bool? IsNullable { get; } - /// - /// Indicates whether a value must be read from the database for the column. - /// + /// public virtual bool IsRead { get; } - /// - /// Indicates whether a value must be written to the database for the column. - /// + /// public virtual bool IsWrite { get; } - /// - /// Indicates whether the column is used in the WHERE clause when updating. - /// + /// public virtual bool IsCondition { get; } - /// - /// Indicates whether the column is part of a primary or alternate key. - /// + /// public virtual bool IsKey { get; } - /// - /// Indicates whether the original value of the property must be passed as a parameter to the SQL. - /// + /// public virtual bool UseOriginalValueParameter => UseParameter && UseOriginalValue; - /// - /// Indicates whether the current value of the property must be passed as a parameter to the SQL. - /// + /// public virtual bool UseCurrentValueParameter => UseParameter && UseCurrentValue; - /// - /// Indicates whether the original value of the property should be used. - /// + /// public virtual bool UseOriginalValue => IsCondition; - /// - /// Indicates whether the current value of the property should be used. - /// + /// public virtual bool UseCurrentValue => IsWrite; - /// - /// Indicates whether the value of the property must be passed as a parameter to the SQL as opposed to being inlined. - /// + /// public virtual bool UseParameter { get; } - /// - /// The parameter name to use for the current value parameter (), if needed. - /// + /// public virtual string? ParameterName => _parameterName ??= UseCurrentValueParameter ? _generateParameterName!() : null; - /// - /// The parameter name to use for the original value parameter (), if needed. - /// + /// public virtual string? OriginalParameterName => _originalParameterName ??= UseOriginalValueParameter ? _generateParameterName!() : null; - /// - /// The name of the column. - /// + /// public virtual string ColumnName { get; } - /// - /// The database type of the column. - /// + /// public virtual string? ColumnType { get; } - /// - /// The original value of the property mapped to this column. - /// + /// public virtual object? OriginalValue => Entry == null ? _originalValue @@ -156,9 +120,7 @@ public virtual object? OriginalValue ? Entry.GetOriginalValue(Property!) : Entry.SharedIdentityEntry.GetOriginalValue(Property!); - /// - /// Gets or sets the current value of the property mapped to this column. - /// + /// public virtual object? Value { get => Entry == null @@ -186,10 +148,7 @@ public virtual object? Value } } - /// - /// Adds a modification affecting the same database value. - /// - /// The modification for the shared column. + /// public virtual void AddSharedColumnModification(IColumnModification modification) { Check.DebugAssert(Entry is not null, "Entry is not null"); @@ -258,4 +217,8 @@ public virtual void AddSharedColumnModification(IColumnModification modification _sharedColumnModifications.Add(modification); } + + /// + public virtual void ResetParameterNames() + => _parameterName = _originalParameterName = null; } diff --git a/src/EFCore.Relational/Update/IColumnModification.cs b/src/EFCore.Relational/Update/IColumnModification.cs index 0cee072bc33..41b5e9380ce 100644 --- a/src/EFCore.Relational/Update/IColumnModification.cs +++ b/src/EFCore.Relational/Update/IColumnModification.cs @@ -122,4 +122,9 @@ public interface IColumnModification /// /// The modification for the shared column. public void AddSharedColumnModification(IColumnModification modification); + + /// + /// Resets parameter names, so they can be regenerated if the command needs to be re-added to a new batch. + /// + public void ResetParameterNames(); } diff --git a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs index 7c5457b00fd..28e73094e27 100644 --- a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs +++ b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs @@ -74,7 +74,7 @@ public CommandBatchPreparer(CommandBatchPreparerDependencies dependencies) continue; } - if (!batch.AddCommand(modificationCommand)) + if (!batch.TryAddCommand(modificationCommand)) { if (batch.ModificationCommands.Count == 1 || batch.ModificationCommands.Count >= _minBatchSize) @@ -144,7 +144,7 @@ private ModificationCommandBatch StartNewBatch( { parameterNameGenerator.Reset(); var batch = Dependencies.ModificationCommandBatchFactory.Create(); - batch.AddCommand(modificationCommand); + batch.TryAddCommand(modificationCommand); return batch; } diff --git a/src/EFCore.Relational/Update/ModificationCommandBatch.cs b/src/EFCore.Relational/Update/ModificationCommandBatch.cs index 152e665be45..3d97110eb52 100644 --- a/src/EFCore.Relational/Update/ModificationCommandBatch.cs +++ b/src/EFCore.Relational/Update/ModificationCommandBatch.cs @@ -24,14 +24,14 @@ public abstract class ModificationCommandBatch public abstract IReadOnlyList ModificationCommands { get; } /// - /// Adds the given insert/update/delete to the batch. + /// Attempts to adds the given insert/update/delete to the batch. /// /// The command to add. /// /// if the command was successfully added; if there was no /// room in the current batch to add the command and it must instead be added to a new batch. /// - public abstract bool AddCommand(IReadOnlyModificationCommand modificationCommand); + public abstract bool TryAddCommand(IReadOnlyModificationCommand modificationCommand); /// /// Indicates that no more commands will be added to this batch, and prepares it for execution. diff --git a/src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs b/src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs index 7ada76329fe..641d886d0ee 100644 --- a/src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs +++ b/src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs @@ -22,8 +22,10 @@ namespace Microsoft.EntityFrameworkCore.Update; public abstract class ReaderModificationCommandBatch : ModificationCommandBatch { private readonly List _modificationCommands = new(); - private string? _finalCommandText; + private readonly int _batchHeaderLength; + private readonly List _pendingParameterNames = new(); private bool _requiresTransaction = true; + private int _sqlBuilderPosition, _commandResultSetCount, _resultsPositionalMappingEnabledLength; /// /// Creates a new instance. @@ -32,7 +34,12 @@ public abstract class ReaderModificationCommandBatch : ModificationCommandBatch protected ReaderModificationCommandBatch(ModificationCommandBatchFactoryDependencies dependencies) { Dependencies = dependencies; - CachedCommandText = new StringBuilder(); + + RelationalCommandBuilder = dependencies.CommandBuilderFactory.Create(); + + UpdateSqlGenerator = dependencies.UpdateSqlGenerator; + UpdateSqlGenerator.AppendBatchHeader(SqlBuilder); + _batchHeaderLength = SqlBuilder.Length; } /// @@ -43,18 +50,22 @@ protected ReaderModificationCommandBatch(ModificationCommandBatchFactoryDependen /// /// The update SQL generator. /// - protected virtual IUpdateSqlGenerator UpdateSqlGenerator - => Dependencies.UpdateSqlGenerator; + protected virtual IUpdateSqlGenerator UpdateSqlGenerator { get; } + + /// + /// Gets the relational command builder for the commands in the batch. + /// + protected virtual IRelationalCommandBuilder RelationalCommandBuilder { get; } /// - /// Gets or sets the cached command text for the commands in the batch. + /// Gets the command text builder for the commands in the batch. /// - protected virtual StringBuilder CachedCommandText { get; set; } + protected virtual StringBuilder SqlBuilder { get; } = new(); /// - /// The ordinal of the last command for which command text was built. + /// Gets the parameter values for the commands in the batch. /// - protected virtual int LastCachedCommandIndex { get; set; } + protected virtual Dictionary ParameterValues { get; } = new(); /// /// The list of conceptual insert/update/delete s in the batch. @@ -75,66 +86,77 @@ public override IReadOnlyList ModificationCommands protected virtual BitArray? ResultsPositionalMappingEnabled { get; set; } /// - /// Adds the given insert/update/delete to the batch. + /// The store command generated from this batch when is called. /// - /// The command to add. - /// - /// if the command was successfully added; if there was no - /// room in the current batch to add the command and it must instead be added to a new batch. - /// - public override bool AddCommand(IReadOnlyModificationCommand modificationCommand) + protected virtual RawSqlCommand? StoreCommand { get; set; } + + /// + public override bool TryAddCommand(IReadOnlyModificationCommand modificationCommand) { - if (_finalCommandText is not null) + if (StoreCommand is not null) { throw new InvalidOperationException(RelationalStrings.ModificationCommandBatchAlreadyComplete); } - if (ModificationCommands.Count == 0) - { - ResetCommandText(); - } + _sqlBuilderPosition = SqlBuilder.Length; + _commandResultSetCount = CommandResultSet.Count; + _pendingParameterNames.Clear(); + _resultsPositionalMappingEnabledLength = ResultsPositionalMappingEnabled?.Length ?? 0; + + AddCommand(modificationCommand); + _modificationCommands.Add(modificationCommand); - if (!CanAddCommand(modificationCommand)) + // Check if the batch is still valid after having added the command (e.g. have we bypassed a maximum CommandText size?) + // A batch with only one command is always considered valid (otherwise we'd get an endless loop); allow the batch to fail + // server-side. + if (IsValid() || _modificationCommands.Count == 1) { - return false; + return true; } - _modificationCommands.Add(modificationCommand); - CommandResultSet.Add(ResultSetMapping.LastInResultSet); + RollbackLastCommand(); - if (!IsCommandTextValid()) + // The command's column modifications had their parameter names generated, that needs to be rolled back as well. + foreach (var columnModification in modificationCommand.ColumnModifications) { - ResetCommandText(); - _modificationCommands.RemoveAt(_modificationCommands.Count - 1); - CommandResultSet.RemoveAt(CommandResultSet.Count - 1); - return false; + columnModification.ResetParameterNames(); } - return true; + return false; } /// - /// Resets the builder to start building a new batch. + /// Rolls back the last command added. Used when adding a command caused the batch to become invalid (e.g. CommandText too long). /// - protected virtual void ResetCommandText() + protected virtual void RollbackLastCommand() { - CachedCommandText.Clear(); + _modificationCommands.RemoveAt(_modificationCommands.Count - 1); + + SqlBuilder.Length = _sqlBuilderPosition; - UpdateSqlGenerator.AppendBatchHeader(CachedCommandText); - _batchHeaderLength = CachedCommandText.Length; + while (CommandResultSet.Count > _commandResultSetCount) + { + CommandResultSet.RemoveAt(CommandResultSet.Count - 1); + } - SetRequiresTransaction(true); + if (ResultsPositionalMappingEnabled is not null) + { + ResultsPositionalMappingEnabled.Length = _resultsPositionalMappingEnabledLength; + } - LastCachedCommandIndex = -1; - } + foreach (var pendingParameterName in _pendingParameterNames) + { + ParameterValues.Remove(pendingParameterName); - private int _batchHeaderLength; + RelationalCommandBuilder.RemoveParameterAt(RelationalCommandBuilder.Parameters.Count - 1); + } + } /// /// Whether any SQL has already been added to the batch command text. /// - protected virtual bool IsCachedCommandTextEmpty - => CachedCommandText.Length == _batchHeaderLength; + protected virtual bool IsCommandTextEmpty + => SqlBuilder.Length == _batchHeaderLength; /// public override bool RequiresTransaction @@ -147,164 +169,136 @@ public override bool RequiresTransaction protected virtual void SetRequiresTransaction(bool requiresTransaction) => _requiresTransaction = requiresTransaction; - /// - /// Checks whether a new command can be added to the batch. - /// - /// The command to potentially add. - /// if the command can be added; otherwise. - protected abstract bool CanAddCommand(IReadOnlyModificationCommand modificationCommand); - /// /// Checks whether the command text is valid. /// /// if the command text is valid; otherwise. - protected abstract bool IsCommandTextValid(); - - /// - /// Processes all unprocessed commands in the batch, making sure their corresponding SQL is populated in - /// . - /// - protected virtual void UpdateCachedCommandText() - { - for (var i = LastCachedCommandIndex + 1; i < ModificationCommands.Count; i++) - { - UpdateCachedCommandText(i); - } - } + protected abstract bool IsValid(); /// - /// Updates the command text for the command at the given position in the - /// list. + /// Adds Updates the command text for the command at the given position in the list. /// - /// The position of the command to generate command text for. - protected virtual void UpdateCachedCommandText(int commandPosition) + /// The command to add. + protected virtual void AddCommand(IReadOnlyModificationCommand modificationCommand) { - var newModificationCommand = ModificationCommands[commandPosition]; - bool requiresTransaction; - switch (newModificationCommand.EntityState) + var commandPosition = CommandResultSet.Count; + + switch (modificationCommand.EntityState) { case EntityState.Added: - CommandResultSet[commandPosition] = + CommandResultSet.Add( UpdateSqlGenerator.AppendInsertOperation( - CachedCommandText, newModificationCommand, commandPosition, out requiresTransaction); + SqlBuilder, modificationCommand, commandPosition, out requiresTransaction)); break; case EntityState.Modified: - CommandResultSet[commandPosition] = + CommandResultSet.Add( UpdateSqlGenerator.AppendUpdateOperation( - CachedCommandText, newModificationCommand, commandPosition, out requiresTransaction); + SqlBuilder, modificationCommand, commandPosition, out requiresTransaction)); break; case EntityState.Deleted: - CommandResultSet[commandPosition] = + CommandResultSet.Add( UpdateSqlGenerator.AppendDeleteOperation( - CachedCommandText, newModificationCommand, commandPosition, out requiresTransaction); + SqlBuilder, modificationCommand, commandPosition, out requiresTransaction)); break; default: throw new InvalidOperationException( RelationalStrings.ModificationCommandInvalidEntityState( - newModificationCommand.Entries[0].EntityType, - newModificationCommand.EntityState)); + modificationCommand.Entries[0].EntityType, + modificationCommand.EntityState)); } - _requiresTransaction = commandPosition > 0 || requiresTransaction; + AddParameters(modificationCommand); - LastCachedCommandIndex = commandPosition; + _requiresTransaction = commandPosition > 0 || requiresTransaction; } - /// - /// Gets the total number of parameters needed for the batch. - /// - /// The total parameter count. - protected virtual int GetParameterCount() - => ModificationCommands.Sum(c => c.ColumnModifications.Count); - /// public override void Complete() { - UpdateCachedCommandText(); + if (StoreCommand is not null) + { + throw new InvalidOperationException(RelationalStrings.ModificationCommandBatchAlreadyComplete); + } // Some database have a mode where autocommit is off, and so executing a command outside of an explicit transaction implicitly // creates a new transaction (which needs to be explicitly committed). // The below is a hook for allowing providers to turn autocommit on, in case it's off. if (!RequiresTransaction) { - UpdateSqlGenerator.PrependEnsureAutocommit(CachedCommandText); + UpdateSqlGenerator.PrependEnsureAutocommit(SqlBuilder); } - _finalCommandText = CachedCommandText.ToString(); + RelationalCommandBuilder.Append(SqlBuilder.ToString()); + + StoreCommand = new RawSqlCommand(RelationalCommandBuilder.Build(), ParameterValues); } /// - /// Generates a for the batch. + /// Adds parameters for all column modifications in the given to the relational command + /// being built for this batch. /// - /// The command. - protected virtual RawSqlCommand CreateStoreCommand() + /// The modification command for which to add parameters. + protected virtual void AddParameters(IReadOnlyModificationCommand modificationCommand) { - Check.DebugAssert(_finalCommandText is not null, "_finalCommandText is not null, checked in Execute"); + foreach (var columnModification in modificationCommand.ColumnModifications) + { + AddParameter(columnModification); + } + } - var commandBuilder = Dependencies.CommandBuilderFactory - .Create() - .Append(_finalCommandText); + /// + /// Adds a parameter for the given to the relational command being built for this batch. + /// + /// The column modification for which to add parameters. + protected virtual void AddParameter(IColumnModification columnModification) + { + if (columnModification.UseCurrentValueParameter) + { + RelationalCommandBuilder.AddParameter( + columnModification.ParameterName, + Dependencies.SqlGenerationHelper.GenerateParameterName(columnModification.ParameterName), + columnModification.TypeMapping!, + columnModification.IsNullable); - var parameterValues = new Dictionary(GetParameterCount()); + ParameterValues.Add(columnModification.ParameterName, columnModification.Value); - // ReSharper disable once ForCanBeConvertedToForeach - for (var commandIndex = 0; commandIndex < ModificationCommands.Count; commandIndex++) - { - var command = ModificationCommands[commandIndex]; - // ReSharper disable once ForCanBeConvertedToForeach - for (var columnIndex = 0; columnIndex < command.ColumnModifications.Count; columnIndex++) - { - var columnModification = command.ColumnModifications[columnIndex]; - if (columnModification.UseCurrentValueParameter) - { - commandBuilder.AddParameter( - columnModification.ParameterName, - Dependencies.SqlGenerationHelper.GenerateParameterName(columnModification.ParameterName), - columnModification.TypeMapping!, - columnModification.IsNullable); - - parameterValues.Add(columnModification.ParameterName, columnModification.Value); - } - - if (columnModification.UseOriginalValueParameter) - { - commandBuilder.AddParameter( - columnModification.OriginalParameterName, - Dependencies.SqlGenerationHelper.GenerateParameterName(columnModification.OriginalParameterName), - columnModification.TypeMapping!, - columnModification.IsNullable); - - parameterValues.Add(columnModification.OriginalParameterName, columnModification.OriginalValue); - } - } + _pendingParameterNames.Add(columnModification.ParameterName); } - return new RawSqlCommand(commandBuilder.Build(), parameterValues); + if (columnModification.UseOriginalValueParameter) + { + RelationalCommandBuilder.AddParameter( + columnModification.OriginalParameterName, + Dependencies.SqlGenerationHelper.GenerateParameterName(columnModification.OriginalParameterName), + columnModification.TypeMapping!, + columnModification.IsNullable); + + ParameterValues.Add(columnModification.OriginalParameterName, columnModification.OriginalValue); + + _pendingParameterNames.Add(columnModification.OriginalParameterName); + } } /// - /// Executes the command generated by against a - /// database using the given connection. + /// Executes the command generated by this batch against a database using the given connection. /// /// The connection to the database to update. public override void Execute(IRelationalConnection connection) { - if (_finalCommandText is null) + if (StoreCommand is null) { throw new InvalidOperationException(RelationalStrings.ModificationCommandBatchNotComplete); } - var storeCommand = CreateStoreCommand(); - try { - using var dataReader = storeCommand.RelationalCommand.ExecuteReader( + using var dataReader = StoreCommand.RelationalCommand.ExecuteReader( new RelationalCommandParameterObject( connection, - storeCommand.ParameterValues, + StoreCommand.ParameterValues, null, Dependencies.CurrentContext.Context, Dependencies.Logger, CommandSource.SaveChanges)); @@ -320,8 +314,7 @@ public override void Execute(IRelationalConnection connection) } /// - /// Executes the command generated by against a - /// database using the given connection. + /// Executes the command generated by this batch against a database using the given connection. /// /// The connection to the database to update. /// A to observe while waiting for the task to complete. @@ -331,19 +324,17 @@ public override async Task ExecuteAsync( IRelationalConnection connection, CancellationToken cancellationToken = default) { - if (_finalCommandText is null) + if (StoreCommand is null) { throw new InvalidOperationException(RelationalStrings.ModificationCommandBatchNotComplete); } - var storeCommand = CreateStoreCommand(); - try { - var dataReader = await storeCommand.RelationalCommand.ExecuteReaderAsync( + var dataReader = await StoreCommand.RelationalCommand.ExecuteReaderAsync( new RelationalCommandParameterObject( connection, - storeCommand.ParameterValues, + StoreCommand.ParameterValues, null, Dependencies.CurrentContext.Context, Dependencies.Logger, CommandSource.SaveChanges), diff --git a/src/EFCore.Relational/Update/SingularModificationCommandBatch.cs b/src/EFCore.Relational/Update/SingularModificationCommandBatch.cs index 9af3e5e3704..e9763f2ab94 100644 --- a/src/EFCore.Relational/Update/SingularModificationCommandBatch.cs +++ b/src/EFCore.Relational/Update/SingularModificationCommandBatch.cs @@ -28,19 +28,11 @@ public SingularModificationCommandBatch(ModificationCommandBatchFactoryDependenc } /// - /// Only returns if the no command has already been added. - /// - /// The command to potentially add. - /// if no command has already been added. - protected override bool CanAddCommand(IReadOnlyModificationCommand modificationCommand) - => ModificationCommands.Count == 0; - - /// - /// Returns since only a single command is generated so the command text must be valid. + /// Returns only when the batch contains a single command. /// /// - /// + /// when the batch contains a single command, otherwise. /// - protected override bool IsCommandTextValid() - => true; + protected override bool IsValid() + => ModificationCommands.Count == 1; } diff --git a/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs b/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs index 2d2429f88c6..336e318c894 100644 --- a/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs +++ b/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; -using System.Text; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore.SqlServer.Internal; @@ -20,10 +19,8 @@ public class SqlServerModificationCommandBatch : AffectedCountModificationComman private const int MaxScriptLength = 65536 * DefaultNetworkPacketSizeBytes / 2; private const int MaxParameterCount = 2100; private const int MaxRowCount = 1000; - private int _parameterCount = 1; // Implicit parameter for the command text private readonly int _maxBatchSize; - private readonly List _bulkInsertCommands = new(); - private int _commandsLeftToLengthCheck = 50; + private readonly List _pendingBulkInsertCommands = new(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -36,8 +33,7 @@ public SqlServerModificationCommandBatch( int? maxBatchSize) : base(dependencies) { - if (maxBatchSize.HasValue - && maxBatchSize.Value <= 0) + if (maxBatchSize is <= 0) { throw new ArgumentOutOfRangeException(nameof(maxBatchSize), RelationalStrings.InvalidMaxBatchSize(maxBatchSize.Value)); } @@ -60,77 +56,15 @@ public SqlServerModificationCommandBatch( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - protected override bool CanAddCommand(IReadOnlyModificationCommand modificationCommand) + protected override void RollbackLastCommand() { - if (ModificationCommands.Count >= _maxBatchSize) + if (_pendingBulkInsertCommands.Count > 0) { - return false; - } - - var additionalParameterCount = CountParameters(modificationCommand); - - if (_parameterCount + additionalParameterCount >= MaxParameterCount) - { - return false; - } - - _parameterCount += additionalParameterCount; - return true; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - protected override bool IsCommandTextValid() - { - if (--_commandsLeftToLengthCheck < 0) - { - UpdateCachedCommandText(); - var commandTextLength = CachedCommandText.Length; - if (commandTextLength >= MaxScriptLength) - { - return false; - } - - var averageCommandLength = commandTextLength / ModificationCommands.Count; - var expectedAdditionalCommandCapacity = (MaxScriptLength - commandTextLength) / averageCommandLength; - _commandsLeftToLengthCheck = Math.Max(1, expectedAdditionalCommandCapacity / 4); - } - - return true; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - protected override int GetParameterCount() - => _parameterCount; - - private static int CountParameters(IReadOnlyModificationCommand modificationCommand) - { - var parameterCount = 0; - // ReSharper disable once ForCanBeConvertedToForeach - for (var columnIndex = 0; columnIndex < modificationCommand.ColumnModifications.Count; columnIndex++) - { - var columnModification = modificationCommand.ColumnModifications[columnIndex]; - if (columnModification.UseCurrentValueParameter) - { - parameterCount++; - } - - if (columnModification.UseOriginalValueParameter) - { - parameterCount++; - } + _pendingBulkInsertCommands.RemoveAt(_pendingBulkInsertCommands.Count - 1); + return; } - return parameterCount; + base.RollbackLastCommand(); } /// @@ -139,24 +73,25 @@ private static int CountParameters(IReadOnlyModificationCommand modificationComm /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - protected override void ResetCommandText() - { - base.ResetCommandText(); - - _bulkInsertCommands.Clear(); - } + protected override bool IsValid() + => ModificationCommands.Count <= _maxBatchSize + && SqlBuilder.Length < MaxScriptLength + // A single implicit parameter for the command text itself + && ParameterValues.Count + 1 < MaxParameterCount; - private void AppendBulkInsertCommandText(int lastIndex) + private void ApplyPendingBulkInsertCommands() { - if (_bulkInsertCommands.Count == 0) + if (_pendingBulkInsertCommands.Count == 0) { return; } - var wasCachedCommandTextEmpty = IsCachedCommandTextEmpty; + var commandPosition = CommandResultSet.Count; + + var wasCachedCommandTextEmpty = IsCommandTextEmpty; var resultSetMapping = UpdateSqlGenerator.AppendBulkInsertOperation( - CachedCommandText, _bulkInsertCommands, lastIndex - _bulkInsertCommands.Count, out var resultsContainPositionMapping, + SqlBuilder, _pendingBulkInsertCommands, commandPosition, out var resultsContainPositionMapping, out var requiresTransaction); SetRequiresTransaction(!wasCachedCommandTextEmpty || requiresTransaction); @@ -165,27 +100,29 @@ private void AppendBulkInsertCommandText(int lastIndex) { if (ResultsPositionalMappingEnabled is null) { - ResultsPositionalMappingEnabled = new BitArray(CommandResultSet.Count); + ResultsPositionalMappingEnabled = new BitArray(CommandResultSet.Count + _pendingBulkInsertCommands.Count); } else { - ResultsPositionalMappingEnabled.Length = CommandResultSet.Count; + ResultsPositionalMappingEnabled.Length = CommandResultSet.Count + _pendingBulkInsertCommands.Count; } - for (var i = lastIndex - _bulkInsertCommands.Count; i < lastIndex; i++) + for (var i = commandPosition; i < commandPosition + _pendingBulkInsertCommands.Count; i++) { ResultsPositionalMappingEnabled![i] = true; } } - for (var i = lastIndex - _bulkInsertCommands.Count; i < lastIndex; i++) + foreach (var pendingCommand in _pendingBulkInsertCommands) { - CommandResultSet[i] = resultSetMapping; + AddParameters(pendingCommand); + + CommandResultSet.Add(resultSetMapping); } if (resultSetMapping != ResultSetMapping.NoResultSet) { - CommandResultSet[lastIndex - 1] = ResultSetMapping.LastInResultSet; + CommandResultSet[^1] = ResultSetMapping.LastInResultSet; } } @@ -195,50 +132,33 @@ private void AppendBulkInsertCommandText(int lastIndex) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - protected override void UpdateCachedCommandText() - { - base.UpdateCachedCommandText(); - - AppendBulkInsertCommandText(ModificationCommands.Count); - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - protected override void UpdateCachedCommandText(int commandPosition) + protected override void AddCommand(IReadOnlyModificationCommand modificationCommand) { - var newModificationCommand = ModificationCommands[commandPosition]; - - if (newModificationCommand.EntityState == EntityState.Added) + if (modificationCommand.EntityState == EntityState.Added) { - if (_bulkInsertCommands.Count > 0 - && !CanBeInsertedInSameStatement(_bulkInsertCommands[0], newModificationCommand)) + if (_pendingBulkInsertCommands.Count > 0 + && !CanBeInsertedInSameStatement(_pendingBulkInsertCommands[0], modificationCommand)) { // The new Add command cannot be added to the pending bulk insert commands (e.g. different table). // Write out the pending commands before starting a new pending chain. - AppendBulkInsertCommandText(commandPosition); - _bulkInsertCommands.Clear(); + ApplyPendingBulkInsertCommands(); + _pendingBulkInsertCommands.Clear(); } - _bulkInsertCommands.Add(newModificationCommand); - - LastCachedCommandIndex = commandPosition; + _pendingBulkInsertCommands.Add(modificationCommand); } else { // If we have any pending bulk insert commands, write them out before the next non-Add command - if (_bulkInsertCommands.Count > 0) + if (_pendingBulkInsertCommands.Count > 0) { // Note that we don't care about the transactionality of the bulk insert SQL, since there's the additional non-Add // command coming right afterwards, and so a transaction is required in any case. - AppendBulkInsertCommandText(commandPosition); - _bulkInsertCommands.Clear(); + ApplyPendingBulkInsertCommands(); + _pendingBulkInsertCommands.Clear(); } - base.UpdateCachedCommandText(commandPosition); + base.AddCommand(modificationCommand); } } @@ -252,6 +172,19 @@ private static bool CanBeInsertedInSameStatement( && firstCommand.ColumnModifications.Where(o => o.IsRead).Select(o => o.ColumnName).SequenceEqual( secondCommand.ColumnModifications.Where(o => o.IsRead).Select(o => o.ColumnName)); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override void Complete() + { + ApplyPendingBulkInsertCommands(); + + base.Complete(); + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/test/EFCore.Relational.Tests/TestUtilities/TestModificationCommandBatch.cs b/test/EFCore.Relational.Tests/TestUtilities/TestModificationCommandBatch.cs index 121e59c9c59..9d50183e4f0 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/TestModificationCommandBatch.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/TestModificationCommandBatch.cs @@ -15,6 +15,6 @@ public TestModificationCommandBatch( _maxBatchSize = maxBatchSize ?? 1; } - protected override bool CanAddCommand(IReadOnlyModificationCommand modificationCommand) - => ModificationCommands.Count < _maxBatchSize; + protected override bool IsValid() + => ModificationCommands.Count <= _maxBatchSize; } diff --git a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs index f4848969fe0..8bcd49c6c92 100644 --- a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs +++ b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs @@ -15,55 +15,116 @@ namespace Microsoft.EntityFrameworkCore.Update; public class ReaderModificationCommandBatchTest { [ConditionalFact] - public void AddCommand_adds_command_if_possible() + public void AddCommand_adds_command_if_batch_is_valid() { - var command = CreateModificationCommand("T1", null, true, columnModifications: null); - - var batch = new ModificationCommandBatchFake(); - batch.AddCommand(command); - batch.ShouldAddCommand = true; - batch.ShouldValidateSql = true; - - batch.AddCommand(command); - batch.Complete(); - - Assert.Equal(2, batch.ModificationCommands.Count); - Assert.Same(command, batch.ModificationCommands[0]); - Assert.Equal("..", batch.CommandText); - } + var parameterNameGenerator = new ParameterNameGenerator(); - [ConditionalFact] - public void AddCommand_does_not_add_command_if_not_possible() - { - var command = CreateModificationCommand("T1", null, true, columnModifications: null); + var entry1 = CreateEntry(EntityState.Modified); + var property1 = entry1.EntityType.FindProperty("Name")!; + var command1 = CreateModificationCommand( + "T1", + null, + true, + new[] + { + new ColumnModificationParameters( + entry1, + property1, + property1.GetTableColumnMappings().Single().Column, + parameterNameGenerator.GenerateNext, + property1.GetTableColumnMappings().Single().TypeMapping, + false, true, false, false, true) + }); + + var entry2 = CreateEntry(EntityState.Modified); + var property2 = entry1.EntityType.FindProperty("Name")!; + var command2 = CreateModificationCommand( + "T2", + null, + true, + new[] + { + new ColumnModificationParameters( + entry2, + property2, + property2.GetTableColumnMappings().Single().Column, + parameterNameGenerator.GenerateNext, + property2.GetTableColumnMappings().Single().TypeMapping, + false, true, false, false, true) + }); var batch = new ModificationCommandBatchFake(); - batch.AddCommand(command); - batch.ShouldAddCommand = false; - batch.ShouldValidateSql = true; - - batch.AddCommand(command); + batch.ShouldBeValid = true; + Assert.True(batch.TryAddCommand(command1)); + Assert.True(batch.TryAddCommand(command2)); batch.Complete(); - Assert.Equal(1, batch.ModificationCommands.Count); - Assert.Equal(".", batch.CommandText); + Assert.Collection(batch.ModificationCommands, + m => Assert.Same(command1, m), + m => Assert.Same(command2, m)); + + Assert.Equal(@"UPDATE ""T1"" SET ""Col2"" = @p0 +RETURNING 1; +UPDATE ""T2"" SET ""Col2"" = @p1 +RETURNING 1; +", + batch.CommandText, + ignoreLineEndingDifferences: true); } [ConditionalFact] - public void AddCommand_does_not_add_command_if_resulting_sql_is_invalid() + public void AddCommand_does_not_add_command_batch_is_invalid() { - var command = CreateModificationCommand("T1", null, true, columnModifications: null); + var parameterNameGenerator = new ParameterNameGenerator(); + + var entry1 = CreateEntry(EntityState.Modified); + var property1 = entry1.EntityType.FindProperty("Name")!; + var command1 = CreateModificationCommand( + "T1", + null, + true, + new[] + { + new ColumnModificationParameters( + entry1, + property1, + property1.GetTableColumnMappings().Single().Column, + parameterNameGenerator.GenerateNext, + property1.GetTableColumnMappings().Single().TypeMapping, + false, true, false, false, true) + }); + + var entry2 = CreateEntry(EntityState.Modified); + var property2 = entry1.EntityType.FindProperty("Name")!; + var command2 = CreateModificationCommand( + "T2", + null, + true, + new[] + { + new ColumnModificationParameters( + entry2, + property2, + property2.GetTableColumnMappings().Single().Column, + parameterNameGenerator.GenerateNext, + property2.GetTableColumnMappings().Single().TypeMapping, + false, true, false, false, true) + }); var batch = new ModificationCommandBatchFake(); - batch.AddCommand(command); - batch.ShouldAddCommand = true; - batch.ShouldValidateSql = false; + Assert.True(batch.TryAddCommand(command1)); + batch.ShouldBeValid = false; - batch.AddCommand(command); + Assert.False(batch.TryAddCommand(command2)); batch.Complete(); - Assert.Equal(1, batch.ModificationCommands.Count); - Assert.Equal(".", batch.CommandText); + Assert.Same(command1, Assert.Single(batch.ModificationCommands)); + + Assert.Equal(@"UPDATE ""T1"" SET ""Col2"" = @p0 +RETURNING 1; +", + batch.CommandText, + ignoreLineEndingDifferences: true); } [ConditionalFact] @@ -74,16 +135,12 @@ public void UpdateCommandText_compiles_inserts() var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); - var fakeSqlGenerator = new FakeSqlGenerator( - RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService()); - var batch = new ModificationCommandBatchFake(fakeSqlGenerator); - batch.AddCommand(command); + var batch = new ModificationCommandBatchFake(); + batch.TryAddCommand(command); batch.Complete(); - batch.UpdateCachedCommandTextBase(0); - - Assert.Equal(1, fakeSqlGenerator.AppendBatchHeaderCalls); - Assert.Equal(1, fakeSqlGenerator.AppendInsertOperationCalls); + Assert.Equal(1, batch.FakeSqlGenerator.AppendBatchHeaderCalls); + Assert.Equal(1, batch.FakeSqlGenerator.AppendInsertOperationCalls); } [ConditionalFact] @@ -94,16 +151,12 @@ public void UpdateCommandText_compiles_updates() var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); - var fakeSqlGenerator = new FakeSqlGenerator( - RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService()); - var batch = new ModificationCommandBatchFake(fakeSqlGenerator); - batch.AddCommand(command); - - batch.UpdateCachedCommandTextBase(0); + var batch = new ModificationCommandBatchFake(); + batch.TryAddCommand(command); batch.Complete(); - Assert.Equal(1, fakeSqlGenerator.AppendBatchHeaderCalls); - Assert.Equal(1, fakeSqlGenerator.AppendUpdateOperationCalls); + Assert.Equal(1, batch.FakeSqlGenerator.AppendBatchHeaderCalls); + Assert.Equal(1, batch.FakeSqlGenerator.AppendUpdateOperationCalls); } [ConditionalFact] @@ -114,16 +167,12 @@ public void UpdateCommandText_compiles_deletes() var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); - var fakeSqlGenerator = new FakeSqlGenerator( - RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService()); - var batch = new ModificationCommandBatchFake(fakeSqlGenerator); - batch.AddCommand(command); - - batch.UpdateCachedCommandTextBase(0); + var batch = new ModificationCommandBatchFake(); + batch.TryAddCommand(command); batch.Complete(); - Assert.Equal(1, fakeSqlGenerator.AppendBatchHeaderCalls); - Assert.Equal(1, fakeSqlGenerator.AppendDeleteOperationCalls); + Assert.Equal(1, batch.FakeSqlGenerator.AppendBatchHeaderCalls); + Assert.Equal(1, batch.FakeSqlGenerator.AppendDeleteOperationCalls); } [ConditionalFact] @@ -131,25 +180,25 @@ public void UpdateCommandText_compiles_multiple_commands() { var entry = CreateEntry(EntityState.Added); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); - command.AddEntry(entry, true); + var parameterNameGenerator = new ParameterNameGenerator(); + var command1 = CreateModificationCommand("T1", null, parameterNameGenerator.GenerateNext, true, null); + command1.AddEntry(entry, true); + var command2 = CreateModificationCommand("T1", null, parameterNameGenerator.GenerateNext, true, null); + command2.AddEntry(entry, true); - var fakeSqlGenerator = new FakeSqlGenerator( - RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService()); - var batch = new ModificationCommandBatchFake(fakeSqlGenerator); - batch.AddCommand(command); - batch.AddCommand(command); + var batch = new ModificationCommandBatchFake(); + batch.TryAddCommand(command1); + batch.TryAddCommand(command2); batch.Complete(); - Assert.Equal("..", batch.CommandText); - - Assert.Equal(1, fakeSqlGenerator.AppendBatchHeaderCalls); + Assert.Equal(1, batch.FakeSqlGenerator.AppendBatchHeaderCalls); + Assert.Equal(2, batch.FakeSqlGenerator.AppendInsertOperationCalls); } [ConditionalFact] public async Task ExecuteAsync_executes_batch_commands_and_consumes_reader() { - var entry = CreateEntry(EntityState.Added); + var entry = CreateEntry(EntityState.Added, generateKeyValues: true); var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); @@ -159,7 +208,7 @@ public async Task ExecuteAsync_executes_batch_commands_and_consumes_reader() var connection = CreateConnection(dbDataReader); var batch = new ModificationCommandBatchFake(); - batch.AddCommand(command); + batch.TryAddCommand(command); batch.Complete(); await batch.ExecuteAsync(connection); @@ -182,7 +231,7 @@ public async Task ExecuteAsync_saves_store_generated_values() new[] { "Col1" }, new List { new object[] { 42 } })); var batch = new ModificationCommandBatchFake(); - batch.AddCommand(command); + batch.TryAddCommand(command); batch.Complete(); await batch.ExecuteAsync(connection); @@ -206,7 +255,7 @@ public async Task ExecuteAsync_saves_store_generated_values_on_non_key_columns() new[] { "Col1", "Col2" }, new List { new object[] { 42, "FortyTwo" } })); var batch = new ModificationCommandBatchFake(); - batch.AddCommand(command); + batch.TryAddCommand(command); batch.Complete(); await batch.ExecuteAsync(connection); @@ -219,7 +268,7 @@ public async Task ExecuteAsync_saves_store_generated_values_on_non_key_columns() public async Task ExecuteAsync_saves_store_generated_values_when_updating() { var entry = CreateEntry( - EntityState.Modified, generateKeyValues: true, computeNonKeyValue: true); + EntityState.Modified, generateKeyValues: true, overrideKeyValues: true, computeNonKeyValue: true); var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); @@ -229,7 +278,7 @@ public async Task ExecuteAsync_saves_store_generated_values_when_updating() new[] { "Col2" }, new List { new object[] { "FortyTwo" } })); var batch = new ModificationCommandBatchFake(); - batch.AddCommand(command); + batch.TryAddCommand(command); batch.Complete(); await batch.ExecuteAsync(connection); @@ -253,7 +302,7 @@ public async Task Exception_not_thrown_for_more_than_one_row_returned_for_single new List { new object[] { 42 }, new object[] { 43 } })); var batch = new ModificationCommandBatchFake(); - batch.AddCommand(command); + batch.TryAddCommand(command); batch.Complete(); await batch.ExecuteAsync(connection); @@ -266,7 +315,7 @@ public async Task Exception_not_thrown_for_more_than_one_row_returned_for_single [InlineData(true)] public async Task Exception_thrown_if_rows_returned_for_command_without_store_generated_values_is_not_1(bool async) { - var entry = CreateEntry(EntityState.Added); + var entry = CreateEntry(EntityState.Modified); var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); @@ -276,7 +325,7 @@ public async Task Exception_thrown_if_rows_returned_for_command_without_store_ge new[] { "Col1" }, new List { new object[] { 42 } })); var batch = new ModificationCommandBatchFake(); - batch.AddCommand(command); + batch.TryAddCommand(command); batch.Complete(); var exception = async @@ -301,7 +350,7 @@ public async Task Exception_thrown_if_no_rows_returned_for_command_with_store_ge CreateFakeDataReader(new[] { "Col1" }, new List())); var batch = new ModificationCommandBatchFake(); - batch.AddCommand(command); + batch.TryAddCommand(command); batch.Complete(); var exception = async @@ -329,7 +378,7 @@ public async Task DbException_is_wrapped_with_DbUpdateException(bool async) executeReader: (c, b) => throw originalException)); var batch = new ModificationCommandBatchFake(); - batch.AddCommand(command); + batch.TryAddCommand(command); batch.Complete(); var actualException = async @@ -357,7 +406,7 @@ public async Task OperationCanceledException_is_not_wrapped_with_DbUpdateExcepti executeReader: (c, b) => throw originalException)); var batch = new ModificationCommandBatchFake(); - batch.AddCommand(command); + batch.TryAddCommand(command); batch.Complete(); var actualException = async @@ -377,7 +426,7 @@ public void CreateStoreCommand_creates_parameters_for_each_ModificationCommand() var batch = new ModificationCommandBatchFake(); var parameterNameGenerator = new ParameterNameGenerator(); - batch.AddCommand( + batch.TryAddCommand( CreateModificationCommand( "T1", null, @@ -393,7 +442,7 @@ public void CreateStoreCommand_creates_parameters_for_each_ModificationCommand() false, true, false, false, true) })); - batch.AddCommand( + batch.TryAddCommand( CreateModificationCommand( "T1", null, @@ -411,7 +460,7 @@ public void CreateStoreCommand_creates_parameters_for_each_ModificationCommand() batch.Complete(); - var storeCommand = batch.CreateStoreCommandBase(); + var storeCommand = batch.StoreCommand; Assert.Equal(2, storeCommand.RelationalCommand.Parameters.Count); Assert.Equal("p0", storeCommand.RelationalCommand.Parameters[0].InvariantName); @@ -431,7 +480,7 @@ public void PopulateParameters_creates_parameter_for_write_ModificationCommand() var batch = new ModificationCommandBatchFake(); var parameterNameGenerator = new ParameterNameGenerator(); - batch.AddCommand( + batch.TryAddCommand( CreateModificationCommand( "T1", null, @@ -450,7 +499,7 @@ public void PopulateParameters_creates_parameter_for_write_ModificationCommand() batch.Complete(); - var storeCommand = batch.CreateStoreCommandBase(); + var storeCommand = batch.StoreCommand; Assert.Equal(1, storeCommand.RelationalCommand.Parameters.Count); Assert.Equal("p0", storeCommand.RelationalCommand.Parameters[0].InvariantName); @@ -468,7 +517,7 @@ public void PopulateParameters_creates_parameter_for_condition_ModificationComma var batch = new ModificationCommandBatchFake(); var parameterNameGenerator = new ParameterNameGenerator(); - batch.AddCommand( + batch.TryAddCommand( CreateModificationCommand( "T1", null, @@ -487,7 +536,7 @@ public void PopulateParameters_creates_parameter_for_condition_ModificationComma batch.Complete(); - var storeCommand = batch.CreateStoreCommandBase(); + var storeCommand = batch.StoreCommand; Assert.Equal(1, storeCommand.RelationalCommand.Parameters.Count); Assert.Equal("p0", storeCommand.RelationalCommand.Parameters[0].InvariantName); @@ -505,7 +554,7 @@ public void PopulateParameters_creates_parameters_for_write_and_condition_Modifi var batch = new ModificationCommandBatchFake(); var parameterNameGenerator = new ParameterNameGenerator(); - batch.AddCommand( + batch.TryAddCommand( CreateModificationCommand( "T1", null, @@ -524,7 +573,7 @@ public void PopulateParameters_creates_parameters_for_write_and_condition_Modifi batch.Complete(); - var storeCommand = batch.CreateStoreCommandBase(); + var storeCommand = batch.StoreCommand; Assert.Equal(2, storeCommand.RelationalCommand.Parameters.Count); Assert.Equal("p0", storeCommand.RelationalCommand.Parameters[0].InvariantName); @@ -544,7 +593,7 @@ public void PopulateParameters_does_not_create_parameter_for_read_ModificationCo var batch = new ModificationCommandBatchFake(); var parameterNameGenerator = new ParameterNameGenerator(); - batch.AddCommand( + batch.TryAddCommand( CreateModificationCommand( "T1", null, @@ -563,7 +612,7 @@ public void PopulateParameters_does_not_create_parameter_for_read_ModificationCo batch.Complete(); - var storeCommand = batch.CreateStoreCommandBase(); + var storeCommand = batch.StoreCommand; Assert.Equal(0, storeCommand.RelationalCommand.Parameters.Count); } @@ -597,12 +646,17 @@ private static IModel BuildModel(bool generateKeyValues, bool computeNonKeyValue private static InternalEntityEntry CreateEntry( EntityState entityState, bool generateKeyValues = false, + bool overrideKeyValues = false, bool computeNonKeyValue = false) { var model = BuildModel(generateKeyValues, computeNonKeyValue); return RelationalTestHelpers.Instance.CreateInternalEntry( - model, entityState, new T1 { Id = 1, Name = computeNonKeyValue ? null : "Test" }); + model, entityState, new T1 + { + Id = overrideKeyValues ? 1 : default, + Name = computeNonKeyValue ? null : "Test" + }); } private static FakeDbDataReader CreateFakeDataReader(string[] columnNames = null, IList results = null) @@ -615,12 +669,14 @@ private static FakeDbDataReader CreateFakeDataReader(string[] columnNames = null private class ModificationCommandBatchFake : AffectedCountModificationCommandBatch { - public ModificationCommandBatchFake( - IUpdateSqlGenerator sqlGenerator = null) + private readonly FakeSqlGenerator _fakeSqlGenerator; + + public ModificationCommandBatchFake(IUpdateSqlGenerator sqlGenerator = null) : base(CreateDependencies(sqlGenerator)) { - ShouldAddCommand = true; - ShouldValidateSql = true; + ShouldBeValid = true; + + _fakeSqlGenerator = Dependencies.UpdateSqlGenerator as FakeSqlGenerator; } private static ModificationCommandBatchFactoryDependencies CreateDependencies( @@ -632,6 +688,10 @@ private static ModificationCommandBatchFactoryDependencies CreateDependencies( var logger = new FakeRelationalCommandDiagnosticsLogger(); + sqlGenerator ??= new FakeSqlGenerator( + RelationalTestHelpers.Instance.CreateContextServices() + .GetRequiredService()); + return new ModificationCommandBatchFactoryDependencies( new RelationalCommandBuilderFactory( new RelationalCommandBuilderDependencies( @@ -639,10 +699,7 @@ private static ModificationCommandBatchFactoryDependencies CreateDependencies( new ExceptionDetector())), new RelationalSqlGenerationHelper( new RelationalSqlGenerationHelperDependencies()), - sqlGenerator - ?? new FakeSqlGenerator( - RelationalTestHelpers.Instance.CreateContextServices() - .GetRequiredService()), + sqlGenerator, new TypedRelationalValueBufferFactoryFactory( new RelationalValueBufferFactoryDependencies( typeMappingSource, @@ -651,27 +708,20 @@ private static ModificationCommandBatchFactoryDependencies CreateDependencies( logger); } - public string CommandText - => CachedCommandText.ToString(); - - public bool ShouldAddCommand { get; set; } - protected override bool CanAddCommand(IReadOnlyModificationCommand modificationCommand) - => ShouldAddCommand; - - public bool ShouldValidateSql { get; set; } + public string CommandText + => SqlBuilder.ToString(); - protected override bool IsCommandTextValid() - => ShouldValidateSql; + public bool ShouldBeValid { get; set; } - protected override void UpdateCachedCommandText(int commandIndex) - => CachedCommandText.Append("."); + protected override bool IsValid() + => ShouldBeValid; - public void UpdateCachedCommandTextBase(int commandIndex) - => base.UpdateCachedCommandText(commandIndex); + public new RawSqlCommand StoreCommand + => base.StoreCommand; - public RawSqlCommand CreateStoreCommandBase() - => CreateStoreCommand(); + public FakeSqlGenerator FakeSqlGenerator + => _fakeSqlGenerator ?? throw new InvalidOperationException("Not using FakeSqlGenerator"); } private class FakeDbContext : DbContext diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs index a1144359e67..0a9b2685713 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs @@ -40,6 +40,13 @@ public IRelationalCommandBuilder AddParameter(IRelationalParameter parameter) return this; } + public IRelationalCommandBuilder RemoveParameterAt(int index) + { + _parameters.RemoveAt(index); + + return this; + } + [Obsolete("Code trying to add parameter should add type mapped parameter using TypeMappingSource directly.")] public IRelationalTypeMappingSource TypeMappingSource => Dependencies.TypeMappingSource; diff --git a/test/EFCore.SqlServer.Tests/SqlServerDatabaseCreatorTest.cs b/test/EFCore.SqlServer.Tests/SqlServerDatabaseCreatorTest.cs index fb625df70a0..a5a9ed19b75 100644 --- a/test/EFCore.SqlServer.Tests/SqlServerDatabaseCreatorTest.cs +++ b/test/EFCore.SqlServer.Tests/SqlServerDatabaseCreatorTest.cs @@ -184,6 +184,13 @@ public IRelationalCommandBuilder AddParameter(IRelationalParameter parameter) return this; } + public IRelationalCommandBuilder RemoveParameterAt(int index) + { + _parameters.RemoveAt(index); + + return this; + } + public IRelationalTypeMappingSource TypeMappingSource => null; diff --git a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs index bfd00295b34..e172e013bbc 100644 --- a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs +++ b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs @@ -45,8 +45,8 @@ public void Uses_MaxBatchSize_specified_in_SqlServerOptionsExtension() var batch = factory.Create(); - Assert.True(batch.AddCommand(CreateModificationCommand("T1", null, false))); - Assert.False(batch.AddCommand(CreateModificationCommand("T1", null, false))); + Assert.True(batch.TryAddCommand(CreateModificationCommand("T1", null, false))); + Assert.False(batch.TryAddCommand(CreateModificationCommand("T1", null, false))); } [ConditionalFact] @@ -83,8 +83,8 @@ public void MaxBatchSize_is_optional() var batch = factory.Create(); - Assert.True(batch.AddCommand(CreateModificationCommand("T1", null, false))); - Assert.True(batch.AddCommand(CreateModificationCommand("T1", null, false))); + Assert.True(batch.TryAddCommand(CreateModificationCommand("T1", null, false))); + Assert.True(batch.TryAddCommand(CreateModificationCommand("T1", null, false))); } private class FakeDbContext : DbContext diff --git a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs index adc2169c793..606372df527 100644 --- a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs +++ b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs @@ -14,13 +14,58 @@ public class SqlServerModificationCommandBatchTest [ConditionalFact] public void AddCommand_returns_false_when_max_batch_size_is_reached() { - var typeMapper = new SqlServerTypeMappingSource( - TestServiceFactory.Instance.Create(), - TestServiceFactory.Instance.Create()); + var batch = CreateBatch(maxBatchSize: 1); - var logger = new FakeRelationalCommandDiagnosticsLogger(); + var firstCommand = CreateModificationCommand("T1", null, false); + Assert.True(batch.TryAddCommand(firstCommand)); + Assert.False(batch.TryAddCommand(CreateModificationCommand("T1", null, false))); + + Assert.Same(firstCommand, Assert.Single(batch.ModificationCommands)); + } + + [ConditionalFact] + public void AddCommand_returns_false_when_max_parameters_are_reached() + { + var typeMapper = CreateTypeMappingSource(); + var intMapping = typeMapper.FindMapping(typeof(int)); + var paramIndex = 0; - var batch = new SqlServerModificationCommandBatch( + var batch = CreateBatch(); + + var command = CreateModificationCommand("T1", null, false); + for (var i = 0; i < 2098; i++) + { + command.AddColumnModification(CreateModificationParameters("col" + i)); + } + Assert.True(batch.TryAddCommand(command)); + + var secondCommand = CreateModificationCommand("T2", null, false); + secondCommand.AddColumnModification(CreateModificationParameters("col")); + Assert.False(batch.TryAddCommand(secondCommand)); + Assert.Same(command, Assert.Single(batch.ModificationCommands)); + Assert.Equal(2098, batch.ParameterValues.Count); + + ColumnModificationParameters CreateModificationParameters(string columnName) + => new() + { + ColumnName = columnName, + ColumnType = "integer", + TypeMapping = intMapping, + IsWrite = true, + OriginalValue = 8, + GenerateParameterName = () => "p" + paramIndex++ + }; + } + + private class FakeDbContext : DbContext + { + } + + private static TestSqlServerModificationCommandBatch CreateBatch(int? maxBatchSize = null) + { + var typeMapper = CreateTypeMappingSource(); + + return new TestSqlServerModificationCommandBatch( new ModificationCommandBatchFactoryDependencies( new RelationalCommandBuilderFactory( new RelationalCommandBuilderDependencies( @@ -37,32 +82,30 @@ public void AddCommand_returns_false_when_max_batch_size_is_reached() new RelationalValueBufferFactoryDependencies( typeMapper, new CoreSingletonOptions())), new CurrentDbContext(new FakeDbContext()), - logger), - 1); - - Assert.True( - batch.AddCommand( - CreateModificationCommand("T1", null, false))); - Assert.False( - batch.AddCommand( - CreateModificationCommand("T1", null, false))); + new FakeRelationalCommandDiagnosticsLogger()), + maxBatchSize); } - private class FakeDbContext : DbContext - { - } + private static SqlServerTypeMappingSource CreateTypeMappingSource() + => new( + TestServiceFactory.Instance.Create(), + TestServiceFactory.Instance.Create()); private static IModificationCommand CreateModificationCommand( string name, string schema, bool sensitiveLoggingEnabled) - { - var modificationCommandParameters = new ModificationCommandParameters( - name, schema, sensitiveLoggingEnabled); + => new ModificationCommandFactory().CreateModificationCommand( + new ModificationCommandParameters(name, schema, sensitiveLoggingEnabled)); - var modificationCommand = new ModificationCommandFactory().CreateModificationCommand( - modificationCommandParameters); + private class TestSqlServerModificationCommandBatch : SqlServerModificationCommandBatch + { + public TestSqlServerModificationCommandBatch(ModificationCommandBatchFactoryDependencies dependencies, int? maxBatchSize) + : base(dependencies, maxBatchSize) + { + } - return modificationCommand; + public new Dictionary ParameterValues + => base.ParameterValues; } } From a2fed36d74211b0d77bc0eec965477ff44a70736 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 13:04:44 +0000 Subject: [PATCH 043/143] Update dependencies from https://github.com/dotnet/runtime build 20220321.1 (#27675) [main] Update dependencies from dotnet/runtime --- eng/Version.Details.xml | 44 ++++++++++++++++++++--------------------- eng/Versions.props | 22 ++++++++++----------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index f728cf1405d..43ea530cfca 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,49 +1,49 @@ - + https://github.com/dotnet/runtime - accf6fb6bf18f822fbbd227b1b4e31c65001a4ff + eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 - + https://github.com/dotnet/runtime - accf6fb6bf18f822fbbd227b1b4e31c65001a4ff + eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 - + https://github.com/dotnet/runtime - accf6fb6bf18f822fbbd227b1b4e31c65001a4ff + eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 - + https://github.com/dotnet/runtime - accf6fb6bf18f822fbbd227b1b4e31c65001a4ff + eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 - + https://github.com/dotnet/runtime - accf6fb6bf18f822fbbd227b1b4e31c65001a4ff + eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 - + https://github.com/dotnet/runtime - accf6fb6bf18f822fbbd227b1b4e31c65001a4ff + eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 - + https://github.com/dotnet/runtime - accf6fb6bf18f822fbbd227b1b4e31c65001a4ff + eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 - + https://github.com/dotnet/runtime - accf6fb6bf18f822fbbd227b1b4e31c65001a4ff + eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 - + https://github.com/dotnet/runtime - accf6fb6bf18f822fbbd227b1b4e31c65001a4ff + eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 - + https://github.com/dotnet/runtime - accf6fb6bf18f822fbbd227b1b4e31c65001a4ff + eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 - + https://github.com/dotnet/runtime - accf6fb6bf18f822fbbd227b1b4e31c65001a4ff + eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 diff --git a/eng/Versions.props b/eng/Versions.props index 0b6a905eaa8..dbb453c374f 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -15,17 +15,17 @@ False - 7.0.0-preview.3.22163.2 - 7.0.0-preview.3.22163.2 - 7.0.0-preview.3.22163.2 - 7.0.0-preview.3.22163.2 - 7.0.0-preview.3.22163.2 - 7.0.0-preview.3.22163.2 - 7.0.0-preview.3.22163.2 - 7.0.0-preview.3.22163.2 - 7.0.0-preview.3.22163.2 - 7.0.0-preview.3.22163.2 - 7.0.0-preview.3.22163.2 + 7.0.0-preview.3.22171.1 + 7.0.0-preview.3.22171.1 + 7.0.0-preview.3.22171.1 + 7.0.0-preview.3.22171.1 + 7.0.0-preview.3.22171.1 + 7.0.0-preview.3.22171.1 + 7.0.0-preview.3.22171.1 + 7.0.0-preview.3.22171.1 + 7.0.0-preview.3.22171.1 + 7.0.0-preview.3.22171.1 + 7.0.0-preview.3.22171.1 4.0.1 From d0f7646f7f6750e81b35f65c77d50eaf9c48a6a0 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 13:15:22 +0000 Subject: [PATCH 044/143] Update dependencies from https://github.com/dotnet/arcade build 20220318.2 (#27676) [main] Update dependencies from dotnet/arcade --- eng/Version.Details.xml | 8 ++++---- eng/common/cross/build-rootfs.sh | 4 ++-- eng/common/cross/toolchain.cmake | 16 +++++++++++++++- eng/common/retain-build.ps1 | 8 +++----- global.json | 4 ++-- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 43ea530cfca..4ae406c0e92 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -47,13 +47,13 @@ - + https://github.com/dotnet/arcade - 81001b45bd54f9223905bf55f6ed0125273580fa + bafd55901b50d6fc3507c8ed96a7777fcca1796f - + https://github.com/dotnet/arcade - 81001b45bd54f9223905bf55f6ed0125273580fa + bafd55901b50d6fc3507c8ed96a7777fcca1796f diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index 7e4be9a0ccf..e784c9c005a 100755 --- a/eng/common/cross/build-rootfs.sh +++ b/eng/common/cross/build-rootfs.sh @@ -194,8 +194,8 @@ while :; do __LLDB_Package="liblldb-6.0-dev" ;; tizen) - if [ "$__BuildArch" != "arm" ] && [ "$__BuildArch" != "armel" ] && [ "$__BuildArch" != "arm64" ]; then - echo "Tizen is available only for arm, armel and arm64." + if [ "$__BuildArch" != "arm" ] && [ "$__BuildArch" != "armel" ] && [ "$__BuildArch" != "arm64" ] && [ "$__BuildArch" != "x86" ] ; then + echo "Tizen is available only for arm, armel, arm64 and x86." usage; exit 1; fi diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake index 9fd345bde6d..eaeeab38fa1 100644 --- a/eng/common/cross/toolchain.cmake +++ b/eng/common/cross/toolchain.cmake @@ -63,6 +63,9 @@ elseif(TARGET_ARCH_NAME STREQUAL "s390x") elseif(TARGET_ARCH_NAME STREQUAL "x86") set(CMAKE_SYSTEM_PROCESSOR i686) set(TOOLCHAIN "i686-linux-gnu") + if(TIZEN) + set(TIZEN_TOOLCHAIN "i586-tizen-linux-gnu/9.2.0") + endif() elseif (FREEBSD) set(CMAKE_SYSTEM_PROCESSOR "x86_64") set(triple "x86_64-unknown-freebsd12") @@ -91,6 +94,10 @@ if(TIZEN) include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/) include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/aarch64-tizen-linux-gnu) endif() + if(TARGET_ARCH_NAME STREQUAL "x86") + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/) + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/i586-tizen-linux-gnu) + endif() endif() if(ANDROID) @@ -197,6 +204,13 @@ elseif(TARGET_ARCH_NAME STREQUAL "arm64") endif() elseif(TARGET_ARCH_NAME STREQUAL "x86") add_toolchain_linker_flag(-m32) + + if(TIZEN) + add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + endif() elseif(ILLUMOS) add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib/amd64") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/amd64/lib") @@ -232,7 +246,7 @@ elseif(TARGET_ARCH_NAME STREQUAL "x86") endif() if(TIZEN) - if(TARGET_ARCH_NAME MATCHES "^(arm|armel|arm64)$") + if(TARGET_ARCH_NAME MATCHES "^(arm|armel|arm64|x86)$") add_compile_options(-Wno-deprecated-declarations) # compile-time option add_compile_options(-D__extern_always_inline=inline) # compile-time option endif() diff --git a/eng/common/retain-build.ps1 b/eng/common/retain-build.ps1 index e08fc227b21..e7ba975adeb 100644 --- a/eng/common/retain-build.ps1 +++ b/eng/common/retain-build.ps1 @@ -8,8 +8,6 @@ Param( $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 -. $PSScriptRoot\tools.ps1 - function Get-AzDOHeaders( [string] $token) @@ -38,10 +36,10 @@ function Update-BuildRetention( Write-Host "Updated retention settings for build ${buildId}." } catch { - Write-PipelineTelemetryError -Category "Build" -Message "Failed to update retention settings for build: $_.Exception.Response.StatusDescription" - ExitWithExitCode 1 + Write-Error "Failed to update retention settings for build: $_.Exception.Response.StatusDescription" + exit 1 } } Update-BuildRetention -azdoOrgUri $azdoOrgUri -azdoProject $azdoProject -buildId $buildId -token $token -ExitWithExitCode 0 +exit 0 diff --git a/global.json b/global.json index 9916afa3473..79922c436e6 100644 --- a/global.json +++ b/global.json @@ -18,7 +18,7 @@ "rollForward": "latestMajor" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22157.6", - "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22157.6" + "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22168.2", + "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22168.2" } } From bb95ab77aac45cea4dbe8defd9760c0a9c3d2fb0 Mon Sep 17 00:00:00 2001 From: Doug Bunting <6431421+dougbu@users.noreply.github.com> Date: Tue, 22 Mar 2022 17:14:17 -0700 Subject: [PATCH 045/143] Update branding to preview4 (#27683) --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index dbb453c374f..1939ca3b716 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -2,7 +2,7 @@ 7.0.0 preview - 3 + 4 False true + https://github.com/dotnet/runtime - eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 + e24f66dff0770eee344038da8c12476d8c450c41 - + https://github.com/dotnet/runtime - eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 + e24f66dff0770eee344038da8c12476d8c450c41 - + https://github.com/dotnet/runtime - eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 + e24f66dff0770eee344038da8c12476d8c450c41 - + https://github.com/dotnet/runtime - eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 + e24f66dff0770eee344038da8c12476d8c450c41 - + https://github.com/dotnet/runtime - eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 + e24f66dff0770eee344038da8c12476d8c450c41 https://github.com/dotnet/runtime eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 - + https://github.com/dotnet/runtime - eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 + e24f66dff0770eee344038da8c12476d8c450c41 https://github.com/dotnet/runtime eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 - + https://github.com/dotnet/runtime - eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 + e24f66dff0770eee344038da8c12476d8c450c41 - + https://github.com/dotnet/runtime - eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 + e24f66dff0770eee344038da8c12476d8c450c41 - + https://github.com/dotnet/runtime - eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 + e24f66dff0770eee344038da8c12476d8c450c41 diff --git a/eng/Versions.props b/eng/Versions.props index 1939ca3b716..ab60226b68f 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -15,17 +15,17 @@ False - 7.0.0-preview.3.22171.1 - 7.0.0-preview.3.22171.1 - 7.0.0-preview.3.22171.1 - 7.0.0-preview.3.22171.1 - 7.0.0-preview.3.22171.1 - 7.0.0-preview.3.22171.1 - 7.0.0-preview.3.22171.1 + 7.0.0-preview.2.22152.2 + 7.0.0-preview.2.22152.2 + 7.0.0-preview.2.22152.2 + 7.0.0-preview.2.22152.2 + 7.0.0-preview.2.22152.2 + 7.0.0-preview.2.22152.2 + 7.0.0-preview.2.22152.2 7.0.0-preview.3.22171.1 - 7.0.0-preview.3.22171.1 - 7.0.0-preview.3.22171.1 - 7.0.0-preview.3.22171.1 + 7.0.0-preview.2.22152.2 + 7.0.0-preview.3.22163.2 + 7.0.0-preview.2.22152.2 4.0.1 diff --git a/eng/helix.proj b/eng/helix.proj index 43c84deccaa..68d80d146d5 100644 --- a/eng/helix.proj +++ b/eng/helix.proj @@ -56,7 +56,7 @@ - net6.0 + net7.0 netcoreapp2.0 2.4.2-pre.9 diff --git a/global.json b/global.json index 606e7148fcf..b945df16521 100644 --- a/global.json +++ b/global.json @@ -2,13 +2,8 @@ "tools": { "dotnet": "7.0.100-preview.2.22153.17", "runtimes": { - "dotnet": [ - "3.1.23", - "5.0.15" - ], "aspnetcore": [ - "3.1.23", - "5.0.15" + "7.0.0-preview.2.22153.2" ] } }, diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteBlob.cs b/src/Microsoft.Data.Sqlite.Core/SqliteBlob.cs index 25b211f6206..bbcb2a263ef 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteBlob.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteBlob.cs @@ -183,7 +183,7 @@ public override int Read(byte[] buffer, int offset, int count) /// the buffer if that many bytes are not currently available, or zero (0) if the end of the stream has been /// reached. /// -#if NET +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER public override int Read(Span buffer) #else public virtual int Read(Span buffer) @@ -257,7 +257,7 @@ public override void Write(byte[] buffer, int offset, int count) /// /// A region of memory. This method copies the contents of this region to the current stream. /// -#if NET +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER public override void Write(ReadOnlySpan buffer) #else public virtual void Write(ReadOnlySpan buffer) diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs b/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs index 8e6f5c983e9..f1db5bdf2cc 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs @@ -101,7 +101,7 @@ public override void Rollback() RollbackInternal(); } -#if NET +#if NET5_0_OR_GREATER /// public override bool SupportsSavepoints => true; #endif @@ -111,7 +111,7 @@ public override void Rollback() /// established to be rolled back, restoring the transaction state to what it was at the time of the savepoint. /// /// The name of the savepoint to be created. -#if NET +#if NET5_0_OR_GREATER public override void Save(string savepointName) #else public virtual void Save(string savepointName) @@ -139,7 +139,7 @@ public virtual void Save(string savepointName) /// Rolls back all commands that were executed after the specified savepoint was established. /// /// The name of the savepoint to roll back to. -#if NET +#if NET5_0_OR_GREATER public override void Rollback(string savepointName) #else public virtual void Rollback(string savepointName) @@ -168,7 +168,7 @@ public virtual void Rollback(string savepointName) /// reclaim some resources before the transaction ends. /// /// The name of the savepoint to release. -#if NET +#if NET5_0_OR_GREATER public override void Release(string savepointName) #else public virtual void Release(string savepointName) diff --git a/src/Microsoft.Data.Sqlite.Core/Utilities/AllowNullAttribute.cs b/src/Microsoft.Data.Sqlite.Core/Utilities/AllowNullAttribute.cs index f0da84d5cd3..73be1e0f899 100644 --- a/src/Microsoft.Data.Sqlite.Core/Utilities/AllowNullAttribute.cs +++ b/src/Microsoft.Data.Sqlite.Core/Utilities/AllowNullAttribute.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#if !NET +#if !(NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER) namespace System.Diagnostics.CodeAnalysis { diff --git a/src/Microsoft.Data.Sqlite.Core/Utilities/MemberNotNullAttribute.cs b/src/Microsoft.Data.Sqlite.Core/Utilities/MemberNotNullAttribute.cs index a6bed8247ce..e977d80e0fa 100644 --- a/src/Microsoft.Data.Sqlite.Core/Utilities/MemberNotNullAttribute.cs +++ b/src/Microsoft.Data.Sqlite.Core/Utilities/MemberNotNullAttribute.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#if !NET +#if !NET5_0_OR_GREATER namespace System.Diagnostics.CodeAnalysis { diff --git a/src/ef/NotNullIfNotNullAttribute.cs b/src/ef/NotNullIfNotNullAttribute.cs index ff18062e54c..49ee73d01b5 100644 --- a/src/ef/NotNullIfNotNullAttribute.cs +++ b/src/ef/NotNullIfNotNullAttribute.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#if !NETCOREAPP3_0_OR_GREATER +#if !(NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER) namespace System.Diagnostics.CodeAnalysis { diff --git a/test/EFCore.Analyzers.Tests/EFCore.Analyzers.Tests.csproj b/test/EFCore.Analyzers.Tests/EFCore.Analyzers.Tests.csproj index cd39ac99e98..4e570d4b04a 100644 --- a/test/EFCore.Analyzers.Tests/EFCore.Analyzers.Tests.csproj +++ b/test/EFCore.Analyzers.Tests/EFCore.Analyzers.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.EntityFrameworkCore.Analyzers.Tests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.Analyzers.Tests/TestUtilities/DiagnosticAnalyzerTestBase.cs b/test/EFCore.Analyzers.Tests/TestUtilities/DiagnosticAnalyzerTestBase.cs index 9c8dc1d92e5..bc4d40526c0 100644 --- a/test/EFCore.Analyzers.Tests/TestUtilities/DiagnosticAnalyzerTestBase.cs +++ b/test/EFCore.Analyzers.Tests/TestUtilities/DiagnosticAnalyzerTestBase.cs @@ -61,7 +61,8 @@ var compilationWithAnalyzers = compilation .WithOptions( compilation.Options.WithSpecificDiagnosticOptions( - analyzer.SupportedDiagnostics.ToDictionary(d => d.Id, d => ReportDiagnostic.Default))) + compilation.Options.SpecificDiagnosticOptions + .AddRange(analyzer.SupportedDiagnostics.ToDictionary(d => d.Id, d => ReportDiagnostic.Default)))) .WithAnalyzers(ImmutableArray.Create(analyzer)); var diagnostics = analyzerDiagnosticsOnly @@ -98,6 +99,10 @@ var metadataReferences .WithCompilationOptions( new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary, + specificDiagnosticOptions: new Dictionary + { + { "CS1701", ReportDiagnostic.Suppress } + }, nullableContextOptions: NullableContextOptions.Enable)); } } diff --git a/test/EFCore.AspNet.InMemory.FunctionalTests/EFCore.AspNet.InMemory.FunctionalTests.csproj b/test/EFCore.AspNet.InMemory.FunctionalTests/EFCore.AspNet.InMemory.FunctionalTests.csproj index 56914b4708a..2b6183a103a 100644 --- a/test/EFCore.AspNet.InMemory.FunctionalTests/EFCore.AspNet.InMemory.FunctionalTests.csproj +++ b/test/EFCore.AspNet.InMemory.FunctionalTests/EFCore.AspNet.InMemory.FunctionalTests.csproj @@ -1,7 +1,7 @@ - net6.0 + net7.0 Microsoft.EntityFrameworkCore.AspNet.InMemory.FunctionalTests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.AspNet.Specification.Tests/EFCore.AspNet.Specification.Tests.csproj b/test/EFCore.AspNet.Specification.Tests/EFCore.AspNet.Specification.Tests.csproj index def779ed667..5b1774fafcc 100644 --- a/test/EFCore.AspNet.Specification.Tests/EFCore.AspNet.Specification.Tests.csproj +++ b/test/EFCore.AspNet.Specification.Tests/EFCore.AspNet.Specification.Tests.csproj @@ -2,7 +2,7 @@ Shared ASP.NET test suite for Entity Framework Core database providers. - net6.0 + net7.0 Microsoft.EntityFrameworkCore.AspNet.Specification.Tests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.AspNet.SqlServer.FunctionalTests/EFCore.AspNet.SqlServer.FunctionalTests.csproj b/test/EFCore.AspNet.SqlServer.FunctionalTests/EFCore.AspNet.SqlServer.FunctionalTests.csproj index b301d86d8d0..e2da545b8dd 100644 --- a/test/EFCore.AspNet.SqlServer.FunctionalTests/EFCore.AspNet.SqlServer.FunctionalTests.csproj +++ b/test/EFCore.AspNet.SqlServer.FunctionalTests/EFCore.AspNet.SqlServer.FunctionalTests.csproj @@ -1,7 +1,7 @@ - net6.0 + net7.0 Microsoft.EntityFrameworkCore.AspNet.SqlServer.FunctionalTests Microsoft.EntityFrameworkCore True diff --git a/test/EFCore.AspNet.Sqlite.FunctionalTests/EFCore.AspNet.Sqlite.FunctionalTests.csproj b/test/EFCore.AspNet.Sqlite.FunctionalTests/EFCore.AspNet.Sqlite.FunctionalTests.csproj index 07761013b1c..64a835ff1be 100644 --- a/test/EFCore.AspNet.Sqlite.FunctionalTests/EFCore.AspNet.Sqlite.FunctionalTests.csproj +++ b/test/EFCore.AspNet.Sqlite.FunctionalTests/EFCore.AspNet.Sqlite.FunctionalTests.csproj @@ -1,7 +1,7 @@ - net6.0 + net7.0 Microsoft.EntityFrameworkCore.AspNet.Sqlite.FunctionalTests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.Cosmos.FunctionalTests/EFCore.Cosmos.FunctionalTests.csproj b/test/EFCore.Cosmos.FunctionalTests/EFCore.Cosmos.FunctionalTests.csproj index 6f149b3a68f..eb810463022 100644 --- a/test/EFCore.Cosmos.FunctionalTests/EFCore.Cosmos.FunctionalTests.csproj +++ b/test/EFCore.Cosmos.FunctionalTests/EFCore.Cosmos.FunctionalTests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.EntityFrameworkCore.Cosmos.FunctionalTests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.Cosmos.Tests/EFCore.Cosmos.Tests.csproj b/test/EFCore.Cosmos.Tests/EFCore.Cosmos.Tests.csproj index 2272660190a..31ef559f63c 100644 --- a/test/EFCore.Cosmos.Tests/EFCore.Cosmos.Tests.csproj +++ b/test/EFCore.Cosmos.Tests/EFCore.Cosmos.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.EntityFrameworkCore.Cosmos.Tests Microsoft.EntityFrameworkCore.Cosmos true diff --git a/test/EFCore.CrossStore.FunctionalTests/EFCore.CrossStore.FunctionalTests.csproj b/test/EFCore.CrossStore.FunctionalTests/EFCore.CrossStore.FunctionalTests.csproj index 3d0b2a85525..8f93c9f64bd 100644 --- a/test/EFCore.CrossStore.FunctionalTests/EFCore.CrossStore.FunctionalTests.csproj +++ b/test/EFCore.CrossStore.FunctionalTests/EFCore.CrossStore.FunctionalTests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.EntityFrameworkCore.CrossStore.FunctionalTests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.Design.Tests/EFCore.Design.Tests.csproj b/test/EFCore.Design.Tests/EFCore.Design.Tests.csproj index 3d377074941..0456e084046 100644 --- a/test/EFCore.Design.Tests/EFCore.Design.Tests.csproj +++ b/test/EFCore.Design.Tests/EFCore.Design.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 true Microsoft.EntityFrameworkCore.Design.Tests Microsoft.EntityFrameworkCore diff --git a/test/EFCore.InMemory.FunctionalTests/EFCore.InMemory.FunctionalTests.csproj b/test/EFCore.InMemory.FunctionalTests/EFCore.InMemory.FunctionalTests.csproj index e4cdd0f033b..e78f51f16f1 100644 --- a/test/EFCore.InMemory.FunctionalTests/EFCore.InMemory.FunctionalTests.csproj +++ b/test/EFCore.InMemory.FunctionalTests/EFCore.InMemory.FunctionalTests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.EntityFrameworkCore.InMemory.FunctionalTests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.InMemory.Tests/EFCore.InMemory.Tests.csproj b/test/EFCore.InMemory.Tests/EFCore.InMemory.Tests.csproj index c6f456b5a72..1b1408fe5a0 100644 --- a/test/EFCore.InMemory.Tests/EFCore.InMemory.Tests.csproj +++ b/test/EFCore.InMemory.Tests/EFCore.InMemory.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.EntityFrameworkCore.InMemory.Tests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.OData.FunctionalTests/EFCore.OData.FunctionalTests.csproj b/test/EFCore.OData.FunctionalTests/EFCore.OData.FunctionalTests.csproj index b98740ade3d..6a258b52ed5 100644 --- a/test/EFCore.OData.FunctionalTests/EFCore.OData.FunctionalTests.csproj +++ b/test/EFCore.OData.FunctionalTests/EFCore.OData.FunctionalTests.csproj @@ -1,7 +1,7 @@ - net6.0 + net7.0 Microsoft.EntityFrameworkCore.OData.FunctionalTests Microsoft.EntityFrameworkCore True diff --git a/test/EFCore.Proxies.Tests/EFCore.Proxies.Tests.csproj b/test/EFCore.Proxies.Tests/EFCore.Proxies.Tests.csproj index 1834ef65960..51cdbc2b73b 100644 --- a/test/EFCore.Proxies.Tests/EFCore.Proxies.Tests.csproj +++ b/test/EFCore.Proxies.Tests/EFCore.Proxies.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.EntityFrameworkCore.Proxies.Tests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.Relational.Specification.Tests/EFCore.Relational.Specification.Tests.csproj b/test/EFCore.Relational.Specification.Tests/EFCore.Relational.Specification.Tests.csproj index 7482cd6aeee..b45763d16c6 100644 --- a/test/EFCore.Relational.Specification.Tests/EFCore.Relational.Specification.Tests.csproj +++ b/test/EFCore.Relational.Specification.Tests/EFCore.Relational.Specification.Tests.csproj @@ -2,7 +2,7 @@ Shared test suite for Entity Framework Core relational database providers. - net6.0 + net7.0 Microsoft.EntityFrameworkCore.Relational.Specification.Tests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.Relational.Tests/EFCore.Relational.Tests.csproj b/test/EFCore.Relational.Tests/EFCore.Relational.Tests.csproj index e95070b881d..cd7dacc9d37 100644 --- a/test/EFCore.Relational.Tests/EFCore.Relational.Tests.csproj +++ b/test/EFCore.Relational.Tests/EFCore.Relational.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.EntityFrameworkCore.Relational.Tests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj b/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj index ebc8f8efb78..b16da6d2127 100644 --- a/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj +++ b/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj @@ -2,7 +2,7 @@ Shared test suite for Entity Framework Core database providers. - net6.0 + net7.0 Microsoft.EntityFrameworkCore.Specification.Tests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.SqlServer.FunctionalTests/EFCore.SqlServer.FunctionalTests.csproj b/test/EFCore.SqlServer.FunctionalTests/EFCore.SqlServer.FunctionalTests.csproj index 15e5541da86..5b652cc3a80 100644 --- a/test/EFCore.SqlServer.FunctionalTests/EFCore.SqlServer.FunctionalTests.csproj +++ b/test/EFCore.SqlServer.FunctionalTests/EFCore.SqlServer.FunctionalTests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.SqlServer.Tests/EFCore.SqlServer.Tests.csproj b/test/EFCore.SqlServer.Tests/EFCore.SqlServer.Tests.csproj index 15acd5677a2..4f9362333ab 100644 --- a/test/EFCore.SqlServer.Tests/EFCore.SqlServer.Tests.csproj +++ b/test/EFCore.SqlServer.Tests/EFCore.SqlServer.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.EntityFrameworkCore.SqlServer.Tests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.Sqlite.FunctionalTests/EFCore.Sqlite.FunctionalTests.csproj b/test/EFCore.Sqlite.FunctionalTests/EFCore.Sqlite.FunctionalTests.csproj index 06b81d36002..b53862bf4ad 100644 --- a/test/EFCore.Sqlite.FunctionalTests/EFCore.Sqlite.FunctionalTests.csproj +++ b/test/EFCore.Sqlite.FunctionalTests/EFCore.Sqlite.FunctionalTests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.EntityFrameworkCore.Sqlite.FunctionalTests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.Sqlite.Tests/EFCore.Sqlite.Tests.csproj b/test/EFCore.Sqlite.Tests/EFCore.Sqlite.Tests.csproj index b7f4f4b7f3e..5bbee64320f 100644 --- a/test/EFCore.Sqlite.Tests/EFCore.Sqlite.Tests.csproj +++ b/test/EFCore.Sqlite.Tests/EFCore.Sqlite.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.EntityFrameworkCore.Sqlite.Tests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.Tests/EFCore.Tests.csproj b/test/EFCore.Tests/EFCore.Tests.csproj index 06ea852112a..46fcda86760 100644 --- a/test/EFCore.Tests/EFCore.Tests.csproj +++ b/test/EFCore.Tests/EFCore.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.EntityFrameworkCore.Tests Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.Trimming.Tests/EFCore.Trimming.Tests.csproj b/test/EFCore.Trimming.Tests/EFCore.Trimming.Tests.csproj index 77de174db4a..33e2f616fb4 100644 --- a/test/EFCore.Trimming.Tests/EFCore.Trimming.Tests.csproj +++ b/test/EFCore.Trimming.Tests/EFCore.Trimming.Tests.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net7.0 true false diff --git a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.Tests.csproj b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.Tests.csproj index 1b45eb19a40..38907efa8bc 100644 --- a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.Tests.csproj +++ b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.Tests.csproj @@ -1,7 +1,7 @@  - net6.0;net461 + net7.0;net461 $(DefineConstants);E_SQLITE3 enable diff --git a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.e_sqlcipher.Tests.csproj b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.e_sqlcipher.Tests.csproj index 412b23e531b..0e906fb1c30 100644 --- a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.e_sqlcipher.Tests.csproj +++ b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.e_sqlcipher.Tests.csproj @@ -1,7 +1,7 @@ - net6.0;net461 + net7.0;net461 $(DefineConstants);E_SQLCIPHER enable diff --git a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.sqlite3.Tests.csproj b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.sqlite3.Tests.csproj index 3d9358e1e08..b914e95a36c 100644 --- a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.sqlite3.Tests.csproj +++ b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.sqlite3.Tests.csproj @@ -1,7 +1,7 @@ - net6.0;net461 + net7.0;net461 $(DefineConstants);SQLITE3 enable diff --git a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.winsqlite3.Tests.csproj b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.winsqlite3.Tests.csproj index d85f56619c0..980eb5049c5 100644 --- a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.winsqlite3.Tests.csproj +++ b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.winsqlite3.Tests.csproj @@ -1,7 +1,7 @@ - net6.0;net461 + net7.0;net461 $(DefineConstants);WINSQLITE3 enable diff --git a/test/dotnet-ef.Tests/dotnet-ef.Tests.csproj b/test/dotnet-ef.Tests/dotnet-ef.Tests.csproj index acd3db4fa30..5df352ede98 100644 --- a/test/dotnet-ef.Tests/dotnet-ef.Tests.csproj +++ b/test/dotnet-ef.Tests/dotnet-ef.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.EntityFrameworkCore.Tools true diff --git a/test/ef.Tests/ef.Tests.csproj b/test/ef.Tests/ef.Tests.csproj index 2f9bdada152..98acdff6030 100644 --- a/test/ef.Tests/ef.Tests.csproj +++ b/test/ef.Tests/ef.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 Microsoft.EntityFrameworkCore.Tools true From e112653b22aa50d94b078221b87ba65b7466328a Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 24 Feb 2022 13:30:36 +0100 Subject: [PATCH 048/143] Execute trimming tests from the right path --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8bb9a89ecc2..5acdd6d8b81 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -104,7 +104,7 @@ stages: name: Build - script: | .dotnet\dotnet publish --configuration $(_BuildConfig) --runtime win-x64 --self-contained test\EFCore.Trimming.Tests - artifacts\bin\EFCore.Trimming.Tests\$(_BuildConfig)\$(_BuildTargetFramework)\win-x64\publish\EFCore.Trimming.Tests.exe + artifacts\bin\EFCore.Trimming.Tests\$(_BuildConfig)\net7.0\win-x64\publish\EFCore.Trimming.Tests.exe displayName: Test trimming - task: PublishBuildArtifacts@1 displayName: Upload TestResults From 5df9bcf1d71633a0396178c028ee4fc0032a5a96 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 23 Mar 2022 12:48:10 -0700 Subject: [PATCH 049/143] Ignore a failing Cosmos test to unblock builds (#27689) --- .../Query/NorthwindAggregateOperatorsQueryCosmosTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs index 5911a2527eb..ef0cc724245 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs @@ -904,6 +904,7 @@ FROM root c WHERE (c[""Discriminator""] = ""Customer"")"); } + [ConditionalTheory(Skip = "Fails on CI #27688")] public override async Task Distinct_Scalar(bool async) { await base.Distinct_Scalar(async); From 75bfaa7f1d969f9a79860fe7832b0d2000a5fc25 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 23 Mar 2022 13:12:09 -0700 Subject: [PATCH 050/143] Add TPC support for migrations Part of #3170 --- .../CSharpMigrationOperationGenerator.cs | 2 + .../Design/CSharpSnapshotGenerator.cs | 32 +- .../Design/AnnotationCodeGenerator.cs | 34 + .../Diagnostics/RelationalEventId.cs | 6 +- .../Diagnostics/RelationalLoggerExtensions.cs | 6 +- .../RelationalLoggingDefinitions.cs | 2 +- .../RelationalQueryableExtensions.cs | 2 +- .../RelationalModelValidator.cs | 14 +- .../Properties/RelationalStrings.Designer.cs | 44 +- .../Properties/RelationalStrings.resx | 20 +- .../Query/SqlExpressions/SelectExpression.cs | 2 +- .../SqlServerDbContextOptionsBuilder.cs | 2 +- .../SqlServerRetryingExecutionStrategy.cs | 12 +- .../Design/CSharpMigrationsGeneratorTest.cs | 10 +- .../Migrations/ModelSnapshotSqlServerTest.cs | 57 +- .../Query/TPTInheritanceQueryTestBase.cs | 4 +- .../RelationalModelValidatorTest.cs | 22 +- .../Metadata/RelationalModelTest.cs | 44 +- .../Internal/MigrationsModelDifferTest.cs | 1344 +++++++++++++++++ 19 files changed, 1557 insertions(+), 102 deletions(-) diff --git a/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs index b8aa23b7459..f7f702e97c0 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs @@ -2135,6 +2135,7 @@ protected virtual void Annotations( foreach (var annotation in annotations) { // TODO: Give providers an opportunity to render these as provider-specific extension methods + // Issue #6546 builder .AppendLine() .Append(".Annotation(") @@ -2157,6 +2158,7 @@ protected virtual void OldAnnotations( foreach (var annotation in annotations) { // TODO: Give providers an opportunity to render these as provider-specific extension methods + // Issue #6546 builder .AppendLine() .Append(".OldAnnotation(") diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index a7163f24519..b9dcc05a7a7 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -675,19 +675,32 @@ protected virtual void GenerateEntityTypeAnnotations( IEntityType entityType, IndentedStringBuilder stringBuilder) { - var annotationList = entityType.GetAnnotations().ToList(); + IAnnotation? discriminatorPropertyAnnotation = null; + IAnnotation? discriminatorValueAnnotation = null; + IAnnotation? discriminatorMappingCompleteAnnotation = null; - var discriminatorPropertyAnnotation = annotationList.FirstOrDefault(a => a.Name == CoreAnnotationNames.DiscriminatorProperty); - var discriminatorMappingCompleteAnnotation = - annotationList.FirstOrDefault(a => a.Name == CoreAnnotationNames.DiscriminatorMappingComplete); - var discriminatorValueAnnotation = annotationList.FirstOrDefault(a => a.Name == CoreAnnotationNames.DiscriminatorValue); + foreach (var annotation in entityType.GetAnnotations()) + { + switch (annotation.Name) + { + case CoreAnnotationNames.DiscriminatorProperty: + discriminatorPropertyAnnotation = annotation; + break; + case CoreAnnotationNames.DiscriminatorValue: + discriminatorValueAnnotation = annotation; + break; + case CoreAnnotationNames.DiscriminatorMappingComplete: + discriminatorMappingCompleteAnnotation = annotation; + break; + } + } var annotations = Dependencies.AnnotationCodeGenerator .FilterIgnoredAnnotations(entityType.GetAnnotations()) .ToDictionary(a => a.Name, a => a); var tableNameAnnotation = annotations.Find(RelationalAnnotationNames.TableName); - if (tableNameAnnotation?.Value != null + if (tableNameAnnotation != null || entityType.BaseType == null) { var tableName = (string?)tableNameAnnotation?.Value ?? entityType.GetTableName(); @@ -773,7 +786,7 @@ protected virtual void GenerateEntityTypeAnnotations( annotations.Remove(RelationalAnnotationNames.Schema); var viewNameAnnotation = annotations.Find(RelationalAnnotationNames.ViewName); - if (viewNameAnnotation?.Value != null + if (viewNameAnnotation != null || entityType.BaseType == null) { var viewName = (string?)viewNameAnnotation?.Value ?? entityType.GetViewName(); @@ -796,7 +809,6 @@ protected virtual void GenerateEntityTypeAnnotations( stringBuilder .Append(", ") .Append(Code.Literal((string)viewSchemaAnnotation.Value)); - annotations.Remove(viewSchemaAnnotation.Name); } stringBuilder.AppendLine(");"); @@ -807,7 +819,7 @@ protected virtual void GenerateEntityTypeAnnotations( annotations.Remove(RelationalAnnotationNames.ViewDefinitionSql); var functionNameAnnotation = annotations.Find(RelationalAnnotationNames.FunctionName); - if (functionNameAnnotation?.Value != null + if (functionNameAnnotation != null || entityType.BaseType == null) { var functionName = (string?)functionNameAnnotation?.Value ?? entityType.GetFunctionName(); @@ -828,7 +840,7 @@ protected virtual void GenerateEntityTypeAnnotations( } var sqlQueryAnnotation = annotations.Find(RelationalAnnotationNames.SqlQuery); - if (sqlQueryAnnotation?.Value != null + if (sqlQueryAnnotation != null || entityType.BaseType == null) { var sqlQuery = (string?)sqlQueryAnnotation?.Value ?? entityType.GetSqlQuery(); diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index 7b23e8c26d5..1094fa08b02 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -48,6 +48,18 @@ private static readonly MethodInfo EntityTypeHasCommentMethodInfo = typeof(RelationalEntityTypeBuilderExtensions).GetRuntimeMethod( nameof(RelationalEntityTypeBuilderExtensions.HasComment), new[] { typeof(EntityTypeBuilder), typeof(string) })!; + private static readonly MethodInfo EntityTypeUseTpcMappingStrategyMethodInfo + = typeof(RelationalEntityTypeBuilderExtensions).GetRuntimeMethod( + nameof(RelationalEntityTypeBuilderExtensions.UseTpcMappingStrategy), new[] { typeof(EntityTypeBuilder) })!; + + private static readonly MethodInfo EntityTypeUseTphMappingStrategyMethodInfo + = typeof(RelationalEntityTypeBuilderExtensions).GetRuntimeMethod( + nameof(RelationalEntityTypeBuilderExtensions.UseTphMappingStrategy), new[] { typeof(EntityTypeBuilder) })!; + + private static readonly MethodInfo EntityTypeUseTptMappingStrategyMethodInfo + = typeof(RelationalEntityTypeBuilderExtensions).GetRuntimeMethod( + nameof(RelationalEntityTypeBuilderExtensions.UseTptMappingStrategy), new[] { typeof(EntityTypeBuilder) })!; + private static readonly MethodInfo PropertyHasColumnNameMethodInfo = typeof(RelationalPropertyBuilderExtensions).GetRuntimeMethod( nameof(RelationalPropertyBuilderExtensions.HasColumnName), new[] { typeof(PropertyBuilder), typeof(string) })!; @@ -204,6 +216,28 @@ public virtual IReadOnlyList GenerateFluentApiCalls( annotations, RelationalAnnotationNames.Comment, EntityTypeHasCommentMethodInfo, methodCallCodeFragments); + if (annotations.TryGetValue(RelationalAnnotationNames.MappingStrategy, out var mappingStrategyAnnotation) + && mappingStrategyAnnotation.Value is string mappingStrategy) + { + var strategyCall = mappingStrategy switch + { + RelationalAnnotationNames.TpcMappingStrategy => EntityTypeUseTpcMappingStrategyMethodInfo, + RelationalAnnotationNames.TptMappingStrategy => EntityTypeUseTptMappingStrategyMethodInfo, + RelationalAnnotationNames.TphMappingStrategy => EntityTypeUseTphMappingStrategyMethodInfo, + _ => null + }; + + if (strategyCall != null) + { + if (entityType.BaseType == null) + { + methodCallCodeFragments.Add(new MethodCallCodeFragment(strategyCall)); + } + + annotations.Remove(mappingStrategyAnnotation.Name); + } + } + methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(entityType, annotations, GenerateFluentApi)); return methodCallCodeFragments; diff --git a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs index 68c19dcd735..a25572553c6 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs @@ -89,7 +89,7 @@ private enum Id ForeignKeyPropertiesMappedToUnrelatedTables, OptionalDependentWithoutIdentifyingPropertyWarning, DuplicateColumnOrders, - ForeignKeyTPCPrincipalWarning, + ForeignKeyTpcPrincipalWarning, TpcStoreGeneratedIdentityWarning, // Update events @@ -752,8 +752,8 @@ private static EventId MakeValidationId(Id id) /// This event uses the payload when used with a . /// /// - public static readonly EventId ForeignKeyTPCPrincipalWarning = - MakeValidationId(Id.ForeignKeyTPCPrincipalWarning); + public static readonly EventId ForeignKeyTpcPrincipalWarning = + MakeValidationId(Id.ForeignKeyTpcPrincipalWarning); /// /// The PK is using store-generated values in TPC. diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs index 15dc04155a9..82492b22aa6 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs @@ -2832,15 +2832,15 @@ private static string ForeignKeyPropertiesMappedToUnrelatedTables(EventDefinitio } /// - /// Logs the event. + /// Logs the event. /// /// The diagnostics logger to use. /// The foreign key. - public static void ForeignKeyTPCPrincipalWarning( + public static void ForeignKeyTpcPrincipalWarning( this IDiagnosticsLogger diagnostics, IForeignKey foreignKey) { - var definition = RelationalResources.LogForeignKeyTPCPrincipal(diagnostics); + var definition = RelationalResources.LogForeignKeyTpcPrincipal(diagnostics); if (diagnostics.ShouldLog(definition)) { diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs index b2e97e7afb1..a1a9a69a164 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs @@ -491,7 +491,7 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public EventDefinitionBase? LogForeignKeyTPCPrincipal; + public EventDefinitionBase? LogForeignKeyTpcPrincipal; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs b/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs index 03670e2e7de..152562d7c78 100644 --- a/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs @@ -152,7 +152,7 @@ private static FromSqlQueryRootExpression GenerateFromSqlQueryRoot( if ((entityType.BaseType != null || entityType.GetDirectlyDerivedTypes().Any()) && entityType.FindDiscriminatorProperty() == null) { - throw new InvalidOperationException(RelationalStrings.MethodOnNonTPHRootNotSupported(memberName, entityType.DisplayName())); + throw new InvalidOperationException(RelationalStrings.MethodOnNonTphRootNotSupported(memberName, entityType.DisplayName())); } return new FromSqlQueryRootExpression( diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index b1066abdcfe..848021babc3 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -134,7 +134,7 @@ protected virtual void ValidateDbFunctions( && entityType.FindDiscriminatorProperty() == null) { throw new InvalidOperationException( - RelationalStrings.TableValuedFunctionNonTPH(dbFunction.ModelName, entityType.DisplayName())); + RelationalStrings.TableValuedFunctionNonTph(dbFunction.ModelName, entityType.DisplayName())); } } @@ -1056,7 +1056,7 @@ protected virtual void ValidateSharedForeignKeysCompatibility( { if (foreignKey.PrincipalEntityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy) { - logger.ForeignKeyTPCPrincipalWarning(foreignKey); + logger.ForeignKeyTpcPrincipalWarning(foreignKey); } var derivedTables = foreignKey.DeclaringEntityType.GetDerivedTypes() @@ -1309,7 +1309,7 @@ protected override void ValidateInheritanceMapping( && storeObject != null) { throw new InvalidOperationException( - RelationalStrings.AbstractTPC(entityType.DisplayName(), storeObject)); + RelationalStrings.AbstractTpc(entityType.DisplayName(), storeObject)); } } @@ -1403,9 +1403,9 @@ private static void ValidateNonTPHMapping(IEntityType rootEntityType, bool forTa { throw new InvalidOperationException( forTables - ? RelationalStrings.NonTPHTableClash( + ? RelationalStrings.NonTphTableClash( entityType.DisplayName(), otherType.DisplayName(), entityType.GetSchemaQualifiedTableName()) - : RelationalStrings.NonTPHViewClash( + : RelationalStrings.NonTphViewClash( entityType.DisplayName(), otherType.DisplayName(), entityType.GetSchemaQualifiedViewName())); } @@ -1439,10 +1439,10 @@ private static void ValidateTPHMapping(IEntityType rootEntityType, bool forTable { throw new InvalidOperationException( forTables - ? RelationalStrings.TPHTableMismatch( + ? RelationalStrings.TphTableMismatch( entityType.DisplayName(), entityType.GetSchemaQualifiedTableName(), firstType.DisplayName(), firstType.GetSchemaQualifiedTableName()) - : RelationalStrings.TPHViewMismatch( + : RelationalStrings.TphViewMismatch( entityType.DisplayName(), entityType.GetSchemaQualifiedViewName(), firstType.DisplayName(), firstType.GetSchemaQualifiedViewName())); } diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index b3727c74382..dfc6f727e99 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -29,9 +29,9 @@ private static readonly ResourceManager _resourceManager /// /// The corresponding CLR type for entity type '{entityType}' cannot be instantiated, but the entity type was mapped to '{storeObject}' using the 'TPC' mapping strategy. Only instantiable types should be mapped. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. /// - public static string AbstractTPC(object? entityType, object? storeObject) + public static string AbstractTpc(object? entityType, object? storeObject) => string.Format( - GetString("AbstractTPC", nameof(entityType), nameof(storeObject)), + GetString("AbstractTpc", nameof(entityType), nameof(storeObject)), entityType, storeObject); /// @@ -834,9 +834,9 @@ public static string MappedFunctionNotFound(object? entityType, object? function /// /// Using '{methodName}' on DbSet of '{entityType}' is not supported since '{entityType}' is part of hierarchy and does not contain a discriminator property. /// - public static string MethodOnNonTPHRootNotSupported(object? methodName, object? entityType) + public static string MethodOnNonTphRootNotSupported(object? methodName, object? entityType) => string.Format( - GetString("MethodOnNonTPHRootNotSupported", nameof(methodName), nameof(entityType)), + GetString("MethodOnNonTphRootNotSupported", nameof(methodName), nameof(entityType)), methodName, entityType); /// @@ -982,17 +982,17 @@ public static string NonTphMappingStrategy(object? mappingStrategy, object? enti /// /// Both '{entityType}' and '{otherEntityType}' are mapped to the table '{table}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. /// - public static string NonTPHTableClash(object? entityType, object? otherEntityType, object? table) + public static string NonTphTableClash(object? entityType, object? otherEntityType, object? table) => string.Format( - GetString("NonTPHTableClash", nameof(entityType), nameof(otherEntityType), nameof(table)), + GetString("NonTphTableClash", nameof(entityType), nameof(otherEntityType), nameof(table)), entityType, otherEntityType, table); /// /// Both '{entityType}' and '{otherEntityType}' are mapped to the view '{view}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different views. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. /// - public static string NonTPHViewClash(object? entityType, object? otherEntityType, object? view) + public static string NonTphViewClash(object? entityType, object? otherEntityType, object? view) => string.Format( - GetString("NonTPHViewClash", nameof(entityType), nameof(otherEntityType), nameof(view)), + GetString("NonTphViewClash", nameof(entityType), nameof(otherEntityType), nameof(view)), entityType, otherEntityType, view); /// @@ -1070,9 +1070,9 @@ public static string RelationalNotInUse /// /// Cannot create a 'SelectExpression' with a custom 'TableExpressionBase' since the result type '{entityType}' is part of a hierarchy and does not contain a discriminator property. /// - public static string SelectExpressionNonTPHWithCustomTable(object? entityType) + public static string SelectExpressionNonTphWithCustomTable(object? entityType) => string.Format( - GetString("SelectExpressionNonTPHWithCustomTable", nameof(entityType)), + GetString("SelectExpressionNonTphWithCustomTable", nameof(entityType)), entityType); /// @@ -1120,9 +1120,9 @@ public static string TableOverrideMismatch(object? propertySpecification, object /// /// The element type of the result of '{dbFunction}' is mapped to '{entityType}'. This is not supported since '{entityType}' is part of hierarchy and does not contain a discriminator property. /// - public static string TableValuedFunctionNonTPH(object? dbFunction, object? entityType) + public static string TableValuedFunctionNonTph(object? dbFunction, object? entityType) => string.Format( - GetString("TableValuedFunctionNonTPH", nameof(dbFunction), nameof(entityType)), + GetString("TableValuedFunctionNonTph", nameof(dbFunction), nameof(entityType)), dbFunction, entityType); /// @@ -1152,17 +1152,17 @@ public static string TooFewReaderFields(object? expected, object? actual) /// /// '{entityType}' is mapped to the table '{table}' while '{otherEntityType}' is mapped to the table '{otherTable}'. Map all the entity types in the hierarchy to the same table, or remove the discriminator and map them all to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. /// - public static string TPHTableMismatch(object? entityType, object? table, object? otherEntityType, object? otherTable) + public static string TphTableMismatch(object? entityType, object? table, object? otherEntityType, object? otherTable) => string.Format( - GetString("TPHTableMismatch", nameof(entityType), nameof(table), nameof(otherEntityType), nameof(otherTable)), + GetString("TphTableMismatch", nameof(entityType), nameof(table), nameof(otherEntityType), nameof(otherTable)), entityType, table, otherEntityType, otherTable); /// /// '{entityType}' is mapped to the view '{view}' while '{otherEntityType}' is mapped to the view '{otherView}'. Map all the entity types in the hierarchy to the same view, or remove the discriminator and map them all to different views. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. /// - public static string TPHViewMismatch(object? entityType, object? view, object? otherEntityType, object? otherView) + public static string TphViewMismatch(object? entityType, object? view, object? otherEntityType, object? otherView) => string.Format( - GetString("TPHViewMismatch", nameof(entityType), nameof(view), nameof(otherEntityType), nameof(otherView)), + GetString("TphViewMismatch", nameof(entityType), nameof(view), nameof(otherEntityType), nameof(otherView)), entityType, view, otherEntityType, otherView); /// @@ -2123,20 +2123,20 @@ public static FallbackEventDefinition LogForeignKeyPropertiesMappedToUnrelatedTa /// /// The foreign key {foreignKeyProperties} on the entity type '{entityType}' targeting '{principalEntityType}' cannot be represented in the database. '{principalEntityType}' is mapped using the table per concrete type meaning that the derived entities will not be present in {'principalTable'}. If this foreign key on '{entityType}' will never reference entities derived from '{principalEntityType}' then the foreign key constraint name can be specified explicitly to force it to be created. /// - public static FallbackEventDefinition LogForeignKeyTPCPrincipal(IDiagnosticsLogger logger) + public static FallbackEventDefinition LogForeignKeyTpcPrincipal(IDiagnosticsLogger logger) { - var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogForeignKeyTPCPrincipal; + var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogForeignKeyTpcPrincipal; if (definition == null) { definition = NonCapturingLazyInitializer.EnsureInitialized( - ref ((RelationalLoggingDefinitions)logger.Definitions).LogForeignKeyTPCPrincipal, + ref ((RelationalLoggingDefinitions)logger.Definitions).LogForeignKeyTpcPrincipal, logger, static logger => new FallbackEventDefinition( logger.Options, - RelationalEventId.ForeignKeyTPCPrincipalWarning, + RelationalEventId.ForeignKeyTpcPrincipalWarning, LogLevel.Warning, - "RelationalEventId.ForeignKeyTPCPrincipalWarning", - _resourceManager.GetString("LogForeignKeyTPCPrincipal")!)); + "RelationalEventId.ForeignKeyTpcPrincipalWarning", + _resourceManager.GetString("LogForeignKeyTpcPrincipal")!)); } return (FallbackEventDefinition)definition; diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 6e09693e958..800ae376254 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -117,7 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + The corresponding CLR type for entity type '{entityType}' cannot be instantiated, but the entity type was mapped to '{storeObject}' using the 'TPC' mapping strategy. Only instantiable types should be mapped. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. @@ -544,9 +544,9 @@ The foreign key {foreignKeyProperties} on the entity type '{entityType}' targeting '{principalEntityType}' cannot be represented in the database. Either the properties {foreignKeyProperties} aren't mapped to table '{table}', or the principal properties {principalProperties} aren't mapped to table '{principalTable}'. All foreign key properties must map to the table to which the dependent type is mapped, and all principal properties must map to a single table to which the principal type is mapped. Error RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables string string string string string string string - + The foreign key {foreignKeyProperties} on the entity type '{entityType}' targeting '{principalEntityType}' cannot be represented in the database. '{principalEntityType}' is mapped using the table per concrete type meaning that the derived entities will not be present in {'principalTable'}. If this foreign key on '{entityType}' will never reference entities derived from '{principalEntityType}' then the foreign key constraint name can be specified explicitly to force it to be created. - Warning RelationalEventId.ForeignKeyTPCPrincipalWarning string string string string string string string + Warning RelationalEventId.ForeignKeyTpcPrincipalWarning string string string string string string string Generating down script for migration '{migration}'. @@ -671,7 +671,7 @@ The entity type '{entityType}' is mapped to the DbFunction named '{functionName}', but no DbFunction with that name was found in the model. Ensure that the entity type mapping is configured using the model name of a function in the model. - + Using '{methodName}' on DbSet of '{entityType}' is not supported since '{entityType}' is part of hierarchy and does not contain a discriminator property. @@ -734,10 +734,10 @@ The mapping strategy '{mappingStrategy}' specified on '{entityType}' is not supported for entity types with a discriminator. - + Both '{entityType}' and '{otherEntityType}' are mapped to the table '{table}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. - + Both '{entityType}' and '{otherEntityType}' are mapped to the view '{view}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different views. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. @@ -770,7 +770,7 @@ Relational-specific methods can only be used when the context is using a relational database provider. - + Cannot create a 'SelectExpression' with a custom 'TableExpressionBase' since the result type '{entityType}' is part of a hierarchy and does not contain a discriminator property. @@ -791,7 +791,7 @@ The property '{propertySpecification}' has specific configuration for the table '{table}', but isn't mapped to a column on that table. Remove the specific configuration, or map an entity type that contains this property to '{table}'. - + The element type of the result of '{dbFunction}' is mapped to '{entityType}'. This is not supported since '{entityType}' is part of hierarchy and does not contain a discriminator property. @@ -803,10 +803,10 @@ The underlying reader doesn't have as many fields as expected. Expected: {expected}, actual: {actual}. - + '{entityType}' is mapped to the table '{table}' while '{otherEntityType}' is mapped to the table '{otherTable}'. Map all the entity types in the hierarchy to the same table, or remove the discriminator and map them all to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. - + '{entityType}' is mapped to the view '{view}' while '{otherEntityType}' is mapped to the view '{otherView}'. Map all the entity types in the hierarchy to the same view, or remove the discriminator and map them all to different views. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 531b5ea81bd..bbfc2c85534 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -216,7 +216,7 @@ internal SelectExpression(IEntityType entityType, TableExpressionBase tableExpre if ((entityType.BaseType != null || entityType.GetDirectlyDerivedTypes().Any()) && entityType.FindDiscriminatorProperty() == null) { - throw new InvalidOperationException(RelationalStrings.SelectExpressionNonTPHWithCustomTable(entityType.DisplayName())); + throw new InvalidOperationException(RelationalStrings.SelectExpressionNonTphWithCustomTable(entityType.DisplayName())); } var table = tableExpressionBase switch diff --git a/src/EFCore.SqlServer/Infrastructure/SqlServerDbContextOptionsBuilder.cs b/src/EFCore.SqlServer/Infrastructure/SqlServerDbContextOptionsBuilder.cs index 521960728a1..25c9312e95a 100644 --- a/src/EFCore.SqlServer/Infrastructure/SqlServerDbContextOptionsBuilder.cs +++ b/src/EFCore.SqlServer/Infrastructure/SqlServerDbContextOptionsBuilder.cs @@ -101,6 +101,6 @@ public virtual SqlServerDbContextOptionsBuilder EnableRetryOnFailure(ICollection public virtual SqlServerDbContextOptionsBuilder EnableRetryOnFailure( int maxRetryCount, TimeSpan maxRetryDelay, - ICollection? errorNumbersToAdd) + IEnumerable? errorNumbersToAdd) => ExecutionStrategy(c => new SqlServerRetryingExecutionStrategy(c, maxRetryCount, maxRetryDelay, errorNumbersToAdd)); } diff --git a/src/EFCore.SqlServer/SqlServerRetryingExecutionStrategy.cs b/src/EFCore.SqlServer/SqlServerRetryingExecutionStrategy.cs index cca28b4a2eb..b621c7be216 100644 --- a/src/EFCore.SqlServer/SqlServerRetryingExecutionStrategy.cs +++ b/src/EFCore.SqlServer/SqlServerRetryingExecutionStrategy.cs @@ -28,7 +28,7 @@ namespace Microsoft.EntityFrameworkCore; /// public class SqlServerRetryingExecutionStrategy : ExecutionStrategy { - private readonly ICollection? _additionalErrorNumbers; + private readonly HashSet? _additionalErrorNumbers; /// /// Creates a new instance of . @@ -96,7 +96,7 @@ public SqlServerRetryingExecutionStrategy( /// Additional SQL error numbers that should be considered transient. public SqlServerRetryingExecutionStrategy( ExecutionStrategyDependencies dependencies, - ICollection errorNumbersToAdd) + IEnumerable errorNumbersToAdd) : this(dependencies, DefaultMaxRetryCount, DefaultMaxDelay, errorNumbersToAdd) { } @@ -112,13 +112,13 @@ public SqlServerRetryingExecutionStrategy( DbContext context, int maxRetryCount, TimeSpan maxRetryDelay, - ICollection? errorNumbersToAdd) + IEnumerable? errorNumbersToAdd) : base( context, maxRetryCount, maxRetryDelay) { - _additionalErrorNumbers = errorNumbersToAdd; + _additionalErrorNumbers = errorNumbersToAdd?.ToHashSet(); } /// @@ -132,10 +132,10 @@ public SqlServerRetryingExecutionStrategy( ExecutionStrategyDependencies dependencies, int maxRetryCount, TimeSpan maxRetryDelay, - ICollection? errorNumbersToAdd) + IEnumerable? errorNumbersToAdd) : base(dependencies, maxRetryCount, maxRetryDelay) { - _additionalErrorNumbers = errorNumbersToAdd; + _additionalErrorNumbers = errorNumbersToAdd?.ToHashSet(); } /// diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index fe4fd6f5b06..b52226369ec 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -74,7 +74,6 @@ public void Test_new_annotations_handled_for_entity_types() RelationalAnnotationNames.IsFixedLength, RelationalAnnotationNames.Collation, RelationalAnnotationNames.IsStored, - RelationalAnnotationNames.MappingStrategy, // Will be handled in the next PR RelationalAnnotationNames.TpcMappingStrategy, RelationalAnnotationNames.TphMappingStrategy, RelationalAnnotationNames.TptMappingStrategy, @@ -98,6 +97,15 @@ public void Test_new_annotations_handled_for_entity_types() + nameof(RelationalEntityTypeBuilderExtensions.ToTable) + @"(""WithAnnotations"", ""MySchema"")") }, + { + RelationalAnnotationNames.MappingStrategy, + (RelationalAnnotationNames.TphMappingStrategy, + _toTable + + ";" + + _nl + + _nl + + "entityTypeBuilder.UseTphMappingStrategy()") + }, { CoreAnnotationNames.DiscriminatorProperty, ("Id", _toTable diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 9489f117095..1393c44b570 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -605,6 +605,57 @@ public void Views_with_schemas_are_stored_in_the_model_snapshot() Assert.Equal("ViewSchema", o.GetEntityTypes().Single().GetViewSchema()); }); + [ConditionalFact] + public virtual void Entities_are_stored_in_model_snapshot_for_TPC() + => Test( + builder => + { + builder.Entity() + .ToTable("DerivedEntity", "foo") + .ToView("DerivedView", "foo"); + builder.Entity().UseTpcMappingStrategy(); + }, + AddBoilerPlate( + GetHeading() + + @" + modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity"", b => + { + b.Property(""Id"") + .ValueGeneratedOnAdd() + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + + b.Property(""Discriminator"") + .HasColumnType(""nvarchar(max)""); + + b.HasKey(""Id""); + + b.ToTable(""BaseEntity""); + + b.UseTpcMappingStrategy(); + }); + + modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity"", b => + { + b.HasBaseType(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity""); + + b.Property(""Name"") + .HasColumnType(""nvarchar(max)""); + + b.ToTable(""DerivedEntity"", ""foo""); + + b.ToView(""DerivedView"", ""foo""); + });"), + o => + { + Assert.Equal(4, o.GetAnnotations().Count()); + + var derived = o.FindEntityType("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity"); + Assert.Equal("DerivedEntity", derived.GetTableName()); + Assert.Equal("DerivedView", derived.GetViewName()); + }); + [ConditionalFact] public void Unmapped_entity_types_are_stored_in_the_model_snapshot() => Test( @@ -6215,6 +6266,9 @@ protected void Test(IModel model, string expectedCode, Action as var generator = CreateMigrationsGenerator(); var code = generator.GenerateSnapshot("RootNamespace", typeof(DbContext), "Snapshot", model); + var modelFromSnapshot = BuildModelFromSnapshotSource(code); + assert(modelFromSnapshot, model); + try { Assert.Equal(expectedCode, code, ignoreLineEndingDifferences: true); @@ -6224,9 +6278,6 @@ protected void Test(IModel model, string expectedCode, Action as throw new Exception(e.Message + Environment.NewLine + Environment.NewLine + "-- Actual code:" + Environment.NewLine + code); } - var modelFromSnapshot = BuildModelFromSnapshotSource(code); - assert(modelFromSnapshot, model); - var targetOptionsBuilder = TestHelpers .AddProviderOptions(new DbContextOptionsBuilder()) .UseModel(model) diff --git a/test/EFCore.Relational.Specification.Tests/Query/TPTInheritanceQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/TPTInheritanceQueryTestBase.cs index 7e889da5a0b..2ca542112b2 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/TPTInheritanceQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/TPTInheritanceQueryTestBase.cs @@ -42,12 +42,12 @@ public virtual void Using_from_sql_throws() var message = Assert.Throws(() => context.Set().FromSqlRaw("Select * from Birds")).Message; - Assert.Equal(RelationalStrings.MethodOnNonTPHRootNotSupported("FromSqlRaw", typeof(Bird).Name), message); + Assert.Equal(RelationalStrings.MethodOnNonTphRootNotSupported("FromSqlRaw", typeof(Bird).Name), message); message = Assert.Throws(() => context.Set().FromSqlInterpolated($"Select * from Birds")) .Message; - Assert.Equal(RelationalStrings.MethodOnNonTPHRootNotSupported("FromSqlInterpolated", typeof(Bird).Name), message); + Assert.Equal(RelationalStrings.MethodOnNonTphRootNotSupported("FromSqlInterpolated", typeof(Bird).Name), message); } protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index 1666ad475ca..3e2472cb2f4 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -1589,7 +1589,7 @@ public virtual void Detects_clashing_entity_types_in_view_TPT() modelBuilder.Entity().ToTable("Dog").ToView("Cat"); VerifyError( - RelationalStrings.NonTPHViewClash(nameof(Dog), nameof(Cat), "Cat"), + RelationalStrings.NonTphViewClash(nameof(Dog), nameof(Cat), "Cat"), modelBuilder); } @@ -1601,7 +1601,7 @@ public virtual void Detects_table_and_view_TPT_mismatch() modelBuilder.Entity().ToTable("Animal").ToView("Cat"); VerifyError( - RelationalStrings.NonTPHTableClash(nameof(Cat), nameof(Animal), "Animal"), + RelationalStrings.NonTphTableClash(nameof(Cat), nameof(Animal), "Animal"), modelBuilder); } @@ -1613,7 +1613,7 @@ public virtual void Detects_TPT_with_discriminator() modelBuilder.Entity().ToTable("Cat"); VerifyError( - RelationalStrings.TPHTableMismatch(nameof(Cat), nameof(Cat), nameof(Animal), nameof(Animal)), + RelationalStrings.TphTableMismatch(nameof(Cat), nameof(Cat), nameof(Animal), nameof(Animal)), modelBuilder); } @@ -1625,7 +1625,7 @@ public virtual void Detects_view_TPT_with_discriminator() modelBuilder.Entity().ToView("Cat"); VerifyError( - RelationalStrings.TPHViewMismatch(nameof(Cat), nameof(Cat), nameof(Animal), nameof(Animal)), + RelationalStrings.TphViewMismatch(nameof(Cat), nameof(Cat), nameof(Animal), nameof(Animal)), modelBuilder); } @@ -1799,7 +1799,7 @@ public virtual void Detects_ToTable_for_abstract_class_TPC() modelBuilder.Entity>(); VerifyError( - RelationalStrings.AbstractTPC(nameof(Abstract), "dbo.Abstract"), + RelationalStrings.AbstractTpc(nameof(Abstract), "dbo.Abstract"), modelBuilder); } @@ -1812,7 +1812,7 @@ public virtual void Detects_ToView_for_abstract_class_TPC() modelBuilder.Entity>(); VerifyError( - RelationalStrings.AbstractTPC(nameof(Abstract), "Abstract"), + RelationalStrings.AbstractTpc(nameof(Abstract), "Abstract"), modelBuilder); } @@ -1825,7 +1825,7 @@ public virtual void Detects_ToFunction_for_abstract_class_TPC() modelBuilder.Entity>(); VerifyError( - RelationalStrings.AbstractTPC(nameof(Abstract), "Abstract"), + RelationalStrings.AbstractTpc(nameof(Abstract), "Abstract"), modelBuilder); } @@ -1838,7 +1838,7 @@ public virtual void Detects_clashing_entity_types_in_views_TPC() modelBuilder.Entity().ToTable("Dog").ToView("Cat"); VerifyError( - RelationalStrings.NonTPHViewClash(nameof(Dog), nameof(Cat), "Cat"), + RelationalStrings.NonTphViewClash(nameof(Dog), nameof(Cat), "Cat"), modelBuilder); } @@ -1850,7 +1850,7 @@ public virtual void Detects_table_and_view_TPC_mismatch() modelBuilder.Entity().ToTable("Animal").ToView("Cat"); VerifyError( - RelationalStrings.NonTPHTableClash(nameof(Cat), nameof(Animal), "Animal"), + RelationalStrings.NonTphTableClash(nameof(Cat), nameof(Animal), "Animal"), modelBuilder); } @@ -1975,7 +1975,7 @@ public virtual void Detects_unmapped_foreign_keys_in_TPC() .HasPrincipalKey(a => a.Name); var definition = - RelationalResources.LogForeignKeyTPCPrincipal(new TestLogger()); + RelationalResources.LogForeignKeyTpcPrincipal(new TestLogger()); VerifyWarning( definition.GenerateMessage( l => l.Log( @@ -2411,7 +2411,7 @@ public virtual void Non_TPH_as_a_result_of_DbFunction_throws() modelBuilder.HasDbFunction(TestMethods.MethodFMi); VerifyError( - RelationalStrings.TableValuedFunctionNonTPH( + RelationalStrings.TableValuedFunctionNonTph( TestMethods.MethodFMi.DeclaringType.FullName + "." + TestMethods.MethodFMi.Name + "()", "C"), modelBuilder); } diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index c657323d7ac..ae8222c0982 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -32,11 +32,12 @@ public void Can_use_relational_model_with_tables(bool useExplicitMapping, Mappin var model = CreateTestModel(mapToTables: useExplicitMapping, mapping: mapping); Assert.Equal(11, model.Model.GetEntityTypes().Count()); - Assert.Equal(mapping == Mapping.TPC - ? 5 - : mapping == Mapping.TPH - ? 3 - : 6, model.Tables.Count()); + Assert.Equal(mapping switch + { + Mapping.TPC => 5, + Mapping.TPH => 3, + _ => 6 + }, model.Tables.Count()); Assert.Empty(model.Views); Assert.True(model.Model.GetEntityTypes().All(et => !et.GetViewMappings().Any())); @@ -53,11 +54,12 @@ public void Can_use_relational_model_with_views(Mapping mapping) var model = CreateTestModel(mapToTables: false, mapToViews: true, mapping); Assert.Equal(11, model.Model.GetEntityTypes().Count()); - Assert.Equal(mapping == Mapping.TPC - ? 5 - : mapping == Mapping.TPH - ? 3 - : 6, model.Views.Count()); + Assert.Equal(mapping switch + { + Mapping.TPC => 5, + Mapping.TPH => 3, + _ => 6 + }, model.Views.Count()); Assert.Empty(model.Tables); Assert.True(model.Model.GetEntityTypes().All(et => !et.GetTableMappings().Any())); @@ -74,16 +76,18 @@ public void Can_use_relational_model_with_views_and_tables(Mapping mapping) var model = CreateTestModel(mapToTables: true, mapToViews: true, mapping); Assert.Equal(11, model.Model.GetEntityTypes().Count()); - Assert.Equal(mapping == Mapping.TPC - ? 5 - : mapping == Mapping.TPH - ? 3 - : 6, model.Tables.Count()); - Assert.Equal(mapping == Mapping.TPC - ? 5 - : mapping == Mapping.TPH - ? 3 - : 6, model.Views.Count()); + Assert.Equal(mapping switch + { + Mapping.TPC => 5, + Mapping.TPH => 3, + _ => 6 + }, model.Tables.Count()); + Assert.Equal(mapping switch + { + Mapping.TPC => 5, + Mapping.TPH => 3, + _ => 6 + }, model.Views.Count()); AssertDefaultMappings(model, mapping); AssertTables(model, mapping); diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index 41aa9537405..c1cdf2a68e3 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -2113,6 +2113,52 @@ public void Add_table_sharing_to_TPT() Assert.Equal("OrderDate", operation.Name); }); + [ConditionalFact] + public void Add_table_sharing_to_TPC() + => Execute( + common => + { + common.Entity( + "Order", + x => + { + x.UseTpcMappingStrategy(); + x.ToTable("Order"); + x.Property("Id"); + }); + common.Entity( + "DetailedOrder", + x => + { + x.ToTable("DetailedOrder"); + x.HasBaseType("Order"); + x.Property("Description").HasColumnName("Description"); + }); + }, + _ => { }, + target => + { + target.Entity( + "OrderDetails", + x => + { + x.ToTable("DetailedOrder"); + x.Property("Id"); + x.Property("Description").HasColumnName("Description"); + x.Property("OrderDate"); + x.HasOne("DetailedOrder", null).WithOne().HasForeignKey("OrderDetails", "Id"); + }); + }, + operations => + { + Assert.Equal(1, operations.Count); + + var operation = Assert.IsType(operations[0]); + Assert.Null(operation.Schema); + Assert.Equal("DetailedOrder", operation.Table); + Assert.Equal("OrderDate", operation.Name); + }); + [ConditionalFact] public void Rename_column_in_TPT_with_table_sharing_and_seed_data() => Execute( @@ -6794,6 +6840,1304 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data_readonly_discriminator() Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); })); + // Seeding not supported yet + //[ConditionalFact] + public void Change_TPH_to_TPC_with_FKs_and_seed_data() + => Execute( + modelBuilder => + { + modelBuilder.Entity( + "Animal", x => + { + x.Property("Id"); + x.Property("MouseId"); + + x.HasOne("Mouse").WithMany().HasForeignKey("MouseId"); + }); + modelBuilder.Entity( + "Cat", x => + { + x.HasBaseType("Animal"); + x.Property("PreyId").HasColumnName("PreyId"); + + x.HasOne("Animal").WithMany().HasForeignKey("PreyId"); + x.HasData( + new { Id = 11, MouseId = 31 }); + }); + modelBuilder.Entity( + "Dog", x => + { + x.HasBaseType("Animal"); + x.Property("PreyId").HasColumnName("PreyId"); + + x.HasOne("Animal").WithMany().HasForeignKey("PreyId"); + x.HasData( + new { Id = 21, PreyId = 31 }); + }); + modelBuilder.Entity( + "Mouse", x => + { + x.HasBaseType("Animal"); + + x.HasData( + new { Id = 31 }); + }); + }, + source => + { + source.Entity( + "Animal", x => + { + x.Property("Discriminator").Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save); + }); + source.Entity( + "Cat", x => + { + x.HasData( + new { Id = 12, MouseId = 32 }, + new { Id = 13 }); + }); + source.Entity( + "Dog", x => + { + x.HasData( + new { Id = 22, PreyId = 32 }); + }); + source.Entity( + "Mouse", x => + { + x.HasData( + new { Id = 32 }); + }); + source.Entity( + "UnrelatedDog", x => + { + x.ToTable("Dogs"); + x.Property("Id"); + x.Property("PreyId"); + }); + }, + target => + { + target.Entity( + "Animal", x => + { + x.UseTpcMappingStrategy(); + }); + target.Entity( + "Cat", x => + { + x.ToTable("Cats"); + x.HasData( + new { Id = 12 }, + new { Id = 13, MouseId = 32 }); + }); + target.Entity( + "Dog", x => + { + x.ToTable("Dogs"); + x.HasOne("Animal", null).WithOne().HasForeignKey("Dog", "Id") + .HasConstraintName("FK_Dogs_Animal"); + x.HasData( + new { Id = 22, PreyId = 33 }, + new { Id = 23 }); + }); + target.Entity( + "Mouse", x => + { + x.ToTable("Mice"); + x.HasData( + new { Id = 33 }); + }); + }, + upOps => Assert.Collection( + upOps, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Animal_MouseId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Animal_PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Animal_PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Discriminator", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("MouseId", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(typeof(int), operation.ClrType); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Name); + Assert.Collection( + operation.Columns, + c => + { + Assert.Equal("Id", c.Name); + Assert.Equal("default_int_mapping", c.ColumnType); + Assert.Equal("Mice", c.Table); + Assert.False(c.IsNullable); + Assert.False(c.IsRowVersion); + Assert.Null(c.IsUnicode); + Assert.Null(c.IsFixedLength); + Assert.Null(c.MaxLength); + Assert.Null(c.Precision); + Assert.Null(c.Scale); + Assert.Null(c.DefaultValue); + Assert.Null(c.DefaultValueSql); + Assert.Null(c.ComputedColumnSql); + Assert.Null(c.IsStored); + Assert.Null(c.Comment); + Assert.Null(c.Collation); + }, + c => + { + Assert.Equal("MouseId", c.Name); + Assert.Equal("default_int_mapping", c.ColumnType); + Assert.Equal("Mice", c.Table); + Assert.True(c.IsNullable); + }); + + var pk = operation.PrimaryKey; + Assert.Equal("PK_Mice", pk.Name); + Assert.Equal("Mice", pk.Table); + Assert.Equal(new[] { "Id" }, pk.Columns); + + var fk = operation.ForeignKeys.Single(); + Assert.Equal("FK_Mice_Mice_MouseId", fk.Name); + Assert.Equal("Mice", fk.Table); + Assert.Equal("Mice", fk.PrincipalTable); + Assert.Equal(new[] { "MouseId" }, fk.Columns); + Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, fk.OnDelete); + + Assert.Empty(operation.UniqueConstraints); + Assert.Null(operation.Comment); + Assert.Empty(operation.CheckConstraints); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Cats", operation.Name); + Assert.Collection( + operation.Columns, + c => + { + Assert.Equal("Id", c.Name); + Assert.Equal("default_int_mapping", c.ColumnType); + Assert.Equal("Cats", c.Table); + Assert.False(c.IsNullable); + }, + c => + { + Assert.Equal("MouseId", c.Name); + Assert.Equal("default_int_mapping", c.ColumnType); + Assert.Equal("Cats", c.Table); + Assert.True(c.IsNullable); + }, + c => + { + Assert.Equal("PreyId", c.Name); + Assert.Equal("default_int_mapping", c.ColumnType); + Assert.Equal("Cats", c.Table); + Assert.True(c.IsNullable); + }); + + var pk = operation.PrimaryKey; + Assert.Equal("PK_Cats", pk.Name); + Assert.Equal("Cats", pk.Table); + Assert.Equal(new[] { "Id" }, pk.Columns); + + Assert.Collection( + operation.ForeignKeys, + fk => + { + Assert.Equal("FK_Cats_Mice_MouseId", fk.Name); + Assert.Equal("Cats", fk.Table); + Assert.Equal("Mice", fk.PrincipalTable); + Assert.Equal(new[] { "MouseId" }, fk.Columns); + Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, fk.OnDelete); + }); + + Assert.Empty(operation.UniqueConstraints); + Assert.Null(operation.Comment); + Assert.Empty(operation.CheckConstraints); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Cats", operation.Table); + Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(12, v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Cats", operation.Table); + Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(13, v), + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(23, v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(33, v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(21, v), + v => Assert.Equal(31, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(22, v), + v => Assert.Equal(33, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(23, v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Table); + Assert.Equal(new[] { "Id" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(33, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Dogs_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Cats_PreyId", operation.Name); + Assert.Equal("Cats", operation.Table); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Mice_MouseId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal("Mice", operation.PrincipalTable); + Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "Id" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, operation.OnDelete); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + }), + downOps => Assert.Collection( + downOps, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Mice_MouseId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Cats", operation.Name); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Name); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Dogs_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(21, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(22, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(23, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(23, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(33, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Discriminator", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(typeof(string), operation.ClrType); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Discriminator", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(typeof(string), operation.ClrType); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(typeof(int), operation.ClrType); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(11, v)); + + Assert.Equal(new[] { "Discriminator" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Cat", v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(13, v)); + + Assert.Equal(new[] { "Discriminator", "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Cat", v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(21, v)); + + Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Dog", v), + v => Assert.Equal(31, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(31, v)); + + Assert.Equal(new[] { "Discriminator" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Mouse", v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id", "Discriminator", "MouseId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(32, v), + v => Assert.Equal("Mouse", v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(12, v)); + + Assert.Equal(new[] { "Discriminator", "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Cat", v), + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(22, v)); + + Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Dog", v), + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Animal_PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Animal_MouseId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Animal_PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + })); + + // Seeding not supported yet + //[ConditionalFact] + public void Change_TPT_to_TPC_with_FKs_and_seed_data() + => Execute( + modelBuilder => + { + modelBuilder.Entity( + "Animal", x => + { + x.Property("Id"); + x.Property("MouseId"); + + x.HasOne("Mouse").WithMany().HasForeignKey("MouseId"); + }); + modelBuilder.Entity( + "Cat", x => + { + x.HasBaseType("Animal"); + x.Property("PreyId").HasColumnName("PreyId"); + + x.HasOne("Animal").WithMany().HasForeignKey("PreyId"); + x.HasData( + new { Id = 11, MouseId = 31 }); + }); + modelBuilder.Entity( + "Dog", x => + { + x.HasBaseType("Animal"); + x.Property("PreyId").HasColumnName("PreyId"); + + x.HasOne("Animal").WithMany().HasForeignKey("PreyId"); + x.HasData( + new { Id = 21, PreyId = 31 }); + }); + modelBuilder.Entity( + "Mouse", x => + { + x.HasBaseType("Animal"); + + x.HasData( + new { Id = 31 }); + }); + }, + source => + { + source.Entity( + "Animal", x => + { + x.UseTptMappingStrategy(); + }); + source.Entity( + "Cat", x => + { + x.ToTable("Cats"); + x.HasData( + new { Id = 12, MouseId = 32 }, + new { Id = 13 }); + }); + source.Entity( + "Dog", x => + { + x.ToTable("Dogs"); + x.HasData( + new { Id = 22, PreyId = 32 }); + }); + source.Entity( + "Mouse", x => + { + x.ToTable("Mice"); + x.HasData( + new { Id = 32 }); + }); + source.Entity( + "UnrelatedDog", x => + { + x.ToTable("Dogs"); + x.Property("Id"); + x.Property("PreyId"); + }); + }, + target => + { + target.Entity( + "Animal", x => + { + x.UseTpcMappingStrategy(); + }); + target.Entity( + "Cat", x => + { + x.ToTable("Cats"); + x.HasData( + new { Id = 12 }, + new { Id = 13, MouseId = 32 }); + }); + target.Entity( + "Dog", x => + { + x.ToTable("Dogs"); + x.HasOne("Animal", null).WithOne().HasForeignKey("Dog", "Id") + .HasConstraintName("FK_Dogs_Animal"); + x.HasData( + new { Id = 22, PreyId = 33 }, + new { Id = 23 }); + }); + target.Entity( + "Mouse", x => + { + x.ToTable("Mice"); + x.HasData( + new { Id = 33 }); + }); + }, + upOps => Assert.Collection( + upOps, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Animal_MouseId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Animal_PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Animal_PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Cats", operation.Name); + Assert.Collection( + operation.Columns, + c => + { + Assert.Equal("Id", c.Name); + Assert.Equal("default_int_mapping", c.ColumnType); + Assert.Equal("Cats", c.Table); + Assert.False(c.IsNullable); + Assert.False(c.IsRowVersion); + Assert.Null(c.IsUnicode); + Assert.Null(c.IsFixedLength); + Assert.Null(c.MaxLength); + Assert.Null(c.Precision); + Assert.Null(c.Scale); + Assert.Null(c.DefaultValue); + Assert.Null(c.DefaultValueSql); + Assert.Null(c.ComputedColumnSql); + Assert.Null(c.IsStored); + Assert.Null(c.Comment); + Assert.Null(c.Collation); + }, + c => + { + Assert.Equal("PreyId", c.Name); + Assert.Equal("default_int_mapping", c.ColumnType); + Assert.Equal("Cats", c.Table); + Assert.True(c.IsNullable); + Assert.False(c.IsRowVersion); + Assert.Null(c.IsUnicode); + Assert.Null(c.IsFixedLength); + Assert.Null(c.MaxLength); + Assert.Null(c.Precision); + Assert.Null(c.Scale); + Assert.Null(c.DefaultValue); + Assert.Null(c.DefaultValueSql); + Assert.Null(c.ComputedColumnSql); + Assert.Null(c.IsStored); + Assert.Null(c.Comment); + Assert.Null(c.Collation); + }); + + var pk = operation.PrimaryKey; + Assert.Equal("PK_Cats", pk.Name); + Assert.Equal("Cats", pk.Table); + Assert.Equal(new[] { "Id" }, pk.Columns); + + Assert.Collection( + operation.ForeignKeys, + fk => + { + Assert.Equal("FK_Cats_Animal_Id", fk.Name); + Assert.Equal("Cats", fk.Table); + Assert.Equal("Animal", fk.PrincipalTable); + Assert.Equal(new[] { "Id" }, fk.Columns); + Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, fk.OnDelete); + }, + fk => + { + Assert.Equal("FK_Cats_Animal_PreyId", fk.Name); + Assert.Equal("Cats", fk.Table); + Assert.Equal("Animal", fk.PrincipalTable); + Assert.Equal(new[] { "PreyId" }, fk.Columns); + Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, fk.OnDelete); + }); + + Assert.Empty(operation.UniqueConstraints); + Assert.Null(operation.Comment); + Assert.Empty(operation.CheckConstraints); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Name); + Assert.Collection( + operation.Columns, + c => + { + Assert.Equal("Id", c.Name); + Assert.Equal("default_int_mapping", c.ColumnType); + Assert.Equal("Mice", c.Table); + Assert.False(c.IsNullable); + Assert.False(c.IsRowVersion); + Assert.Null(c.IsUnicode); + Assert.Null(c.IsFixedLength); + Assert.Null(c.MaxLength); + Assert.Null(c.Precision); + Assert.Null(c.Scale); + Assert.Null(c.DefaultValue); + Assert.Null(c.DefaultValueSql); + Assert.Null(c.ComputedColumnSql); + Assert.Null(c.IsStored); + Assert.Null(c.Comment); + Assert.Null(c.Collation); + }); + + var pk = operation.PrimaryKey; + Assert.Equal("PK_Mice", pk.Name); + Assert.Equal("Mice", pk.Table); + Assert.Equal(new[] { "Id" }, pk.Columns); + + var fk = operation.ForeignKeys.Single(); + Assert.Equal("FK_Mice_Animal_Id", fk.Name); + Assert.Equal("Mice", fk.Table); + Assert.Equal("Animal", fk.PrincipalTable); + Assert.Equal(new[] { "Id" }, fk.Columns); + Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, fk.OnDelete); + + Assert.Empty(operation.UniqueConstraints); + Assert.Null(operation.Comment); + Assert.Empty(operation.CheckConstraints); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Discriminator", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(typeof(string), operation.ClrType); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(12, v)); + + Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(13, v)); + + Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(23, v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(33, v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(21, v), + v => Assert.Equal(31, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(22, v), + v => Assert.Equal(33, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(23, v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Table); + Assert.Equal(new[] { "Id" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(33, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Dogs_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Cats_PreyId", operation.Name); + Assert.Equal("Cats", operation.Table); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Mice_MouseId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal("Mice", operation.PrincipalTable); + Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "Id" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, operation.OnDelete); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + }), + downOps => Assert.Collection( + downOps, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Mice_MouseId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Cats", operation.Name); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Name); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Dogs_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(21, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(22, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(23, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(23, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(33, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Discriminator", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(typeof(string), operation.ClrType); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Discriminator", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(typeof(string), operation.ClrType); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(typeof(int), operation.ClrType); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(11, v)); + + //Assert.Equal(new[] { "Discriminator" }, operation.Columns); + //Assert.Null(operation.ColumnTypes); + //AssertMultidimensionalArray( + // operation.Values, + // v => Assert.Equal("Cat", v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(13, v)); + + Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(21, v)); + + Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Dog", v), + v => Assert.Equal(31, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(31, v)); + + Assert.Equal(new[] { "Discriminator" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Mouse", v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id", "Discriminator", "MouseId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(32, v), + v => Assert.Equal("Mouse", v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(12, v)); + + Assert.Equal(new[] { "Discriminator", "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Cat", v), + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(22, v)); + + Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Dog", v), + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Animal_PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Animal_MouseId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Animal_PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + })); [ConditionalFact] public void Add_foreign_key_on_base_type() => Execute( From 12bcdbdffbfbbda9226ddeea54adba7605601c37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Mar 2022 13:59:41 -0700 Subject: [PATCH 051/143] Bump Microsoft.Azure.Cosmos from 3.26.0 to 3.26.1 (#27680) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/EFCore.Cosmos/EFCore.Cosmos.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EFCore.Cosmos/EFCore.Cosmos.csproj b/src/EFCore.Cosmos/EFCore.Cosmos.csproj index 368f4b33652..acf939e7ffc 100644 --- a/src/EFCore.Cosmos/EFCore.Cosmos.csproj +++ b/src/EFCore.Cosmos/EFCore.Cosmos.csproj @@ -45,7 +45,7 @@ - + From 97b9376811e57eaf9f56d8e88ccae88cc64e8dc2 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 26 Mar 2022 21:11:18 +0300 Subject: [PATCH 052/143] Reimplement MaxBatchSize as a pre-check (#27696) For better perf on SQLite (#27681) Also moves the handling of MaxBatchSize to ReaderModificationCommandBatch and does some cleanup. --- .../Update/ModificationCommandBatch.cs | 4 +-- .../Update/ReaderModificationCommandBatch.cs | 30 ++++++++++++++----- .../SingularModificationCommandBatch.cs | 9 ++---- .../SqlServerModificationCommandBatch.cs | 24 +++++++-------- ...qlServerModificationCommandBatchFactory.cs | 21 ++++++++----- .../TestModificationCommandBatch.cs | 9 ++---- .../ReaderModificationCommandBatchTest.cs | 3 ++ .../SqlServerModificationCommandBatchTest.cs | 4 +-- 8 files changed, 59 insertions(+), 45 deletions(-) diff --git a/src/EFCore.Relational/Update/ModificationCommandBatch.cs b/src/EFCore.Relational/Update/ModificationCommandBatch.cs index 3d97110eb52..b7b01752428 100644 --- a/src/EFCore.Relational/Update/ModificationCommandBatch.cs +++ b/src/EFCore.Relational/Update/ModificationCommandBatch.cs @@ -56,7 +56,5 @@ public abstract class ModificationCommandBatch /// A to observe while waiting for the task to complete. /// A task that represents the asynchronous save operation. /// If the is canceled. - public abstract Task ExecuteAsync( - IRelationalConnection connection, - CancellationToken cancellationToken = default); + public abstract Task ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken = default); } diff --git a/src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs b/src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs index 641d886d0ee..f49c52ccf9d 100644 --- a/src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs +++ b/src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs @@ -23,9 +23,9 @@ public abstract class ReaderModificationCommandBatch : ModificationCommandBatch { private readonly List _modificationCommands = new(); private readonly int _batchHeaderLength; - private readonly List _pendingParameterNames = new(); private bool _requiresTransaction = true; private int _sqlBuilderPosition, _commandResultSetCount, _resultsPositionalMappingEnabledLength; + private int _pendingParameters; /// /// Creates a new instance. @@ -57,6 +57,12 @@ protected ReaderModificationCommandBatch(ModificationCommandBatchFactoryDependen /// protected virtual IRelationalCommandBuilder RelationalCommandBuilder { get; } + /// + /// The maximum number of instances that can be added to a single batch. + /// + protected virtual int MaxBatchSize + => 1000; + /// /// Gets the command text builder for the commands in the batch. /// @@ -98,9 +104,14 @@ public override bool TryAddCommand(IReadOnlyModificationCommand modificationComm throw new InvalidOperationException(RelationalStrings.ModificationCommandBatchAlreadyComplete); } + if (_modificationCommands.Count >= MaxBatchSize) + { + return false; + } + _sqlBuilderPosition = SqlBuilder.Length; _commandResultSetCount = CommandResultSet.Count; - _pendingParameterNames.Clear(); + _pendingParameters = 0; _resultsPositionalMappingEnabledLength = ResultsPositionalMappingEnabled?.Length ?? 0; AddCommand(modificationCommand); @@ -144,11 +155,13 @@ protected virtual void RollbackLastCommand() ResultsPositionalMappingEnabled.Length = _resultsPositionalMappingEnabledLength; } - foreach (var pendingParameterName in _pendingParameterNames) + for (var i = 0; i < _pendingParameters; i++) { - ParameterValues.Remove(pendingParameterName); + var parameterIndex = RelationalCommandBuilder.Parameters.Count - 1; + var parameter = RelationalCommandBuilder.Parameters[parameterIndex]; - RelationalCommandBuilder.RemoveParameterAt(RelationalCommandBuilder.Parameters.Count - 1); + RelationalCommandBuilder.RemoveParameterAt(parameterIndex); + ParameterValues.Remove(parameter.InvariantName); } } @@ -173,7 +186,8 @@ protected virtual void SetRequiresTransaction(bool requiresTransaction) /// Checks whether the command text is valid. /// /// if the command text is valid; otherwise. - protected abstract bool IsValid(); + protected virtual bool IsValid() + => true; /// /// Adds Updates the command text for the command at the given position in the list. @@ -265,7 +279,7 @@ protected virtual void AddParameter(IColumnModification columnModification) ParameterValues.Add(columnModification.ParameterName, columnModification.Value); - _pendingParameterNames.Add(columnModification.ParameterName); + _pendingParameters++; } if (columnModification.UseOriginalValueParameter) @@ -278,7 +292,7 @@ protected virtual void AddParameter(IColumnModification columnModification) ParameterValues.Add(columnModification.OriginalParameterName, columnModification.OriginalValue); - _pendingParameterNames.Add(columnModification.OriginalParameterName); + _pendingParameters++; } } diff --git a/src/EFCore.Relational/Update/SingularModificationCommandBatch.cs b/src/EFCore.Relational/Update/SingularModificationCommandBatch.cs index e9763f2ab94..e63dd91c95f 100644 --- a/src/EFCore.Relational/Update/SingularModificationCommandBatch.cs +++ b/src/EFCore.Relational/Update/SingularModificationCommandBatch.cs @@ -28,11 +28,8 @@ public SingularModificationCommandBatch(ModificationCommandBatchFactoryDependenc } /// - /// Returns only when the batch contains a single command. + /// The maximum number of instances that can be added to a single batch; always returns 1. /// - /// - /// when the batch contains a single command, otherwise. - /// - protected override bool IsValid() - => ModificationCommands.Count == 1; + protected override int MaxBatchSize + => 1; } diff --git a/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs b/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs index 336e318c894..c6d8a9085e4 100644 --- a/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs +++ b/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatch.cs @@ -18,7 +18,6 @@ public class SqlServerModificationCommandBatch : AffectedCountModificationComman private const int DefaultNetworkPacketSizeBytes = 4096; private const int MaxScriptLength = 65536 * DefaultNetworkPacketSizeBytes / 2; private const int MaxParameterCount = 2100; - private const int MaxRowCount = 1000; private readonly int _maxBatchSize; private readonly List _pendingBulkInsertCommands = new(); @@ -30,16 +29,9 @@ public class SqlServerModificationCommandBatch : AffectedCountModificationComman /// public SqlServerModificationCommandBatch( ModificationCommandBatchFactoryDependencies dependencies, - int? maxBatchSize) + int maxBatchSize) : base(dependencies) - { - if (maxBatchSize is <= 0) - { - throw new ArgumentOutOfRangeException(nameof(maxBatchSize), RelationalStrings.InvalidMaxBatchSize(maxBatchSize.Value)); - } - - _maxBatchSize = Math.Min(maxBatchSize ?? 42, MaxRowCount); - } + => _maxBatchSize = maxBatchSize; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -50,6 +42,15 @@ public SqlServerModificationCommandBatch( protected new virtual ISqlServerUpdateSqlGenerator UpdateSqlGenerator => (ISqlServerUpdateSqlGenerator)base.UpdateSqlGenerator; + /// + /// The maximum number of instances that can be added to a single batch. + /// + /// + /// For SQL Server, this is 42 by default, and cannot exceed 1000. + /// + protected override int MaxBatchSize + => _maxBatchSize; + /// /// 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 @@ -74,8 +75,7 @@ protected override void RollbackLastCommand() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override bool IsValid() - => ModificationCommands.Count <= _maxBatchSize - && SqlBuilder.Length < MaxScriptLength + => SqlBuilder.Length < MaxScriptLength // A single implicit parameter for the command text itself && ParameterValues.Count + 1 < MaxParameterCount; diff --git a/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatchFactory.cs b/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatchFactory.cs index bbb1aad0212..bf2f09f8210 100644 --- a/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatchFactory.cs +++ b/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommandBatchFactory.cs @@ -13,7 +13,9 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Update.Internal; /// public class SqlServerModificationCommandBatchFactory : IModificationCommandBatchFactory { - private readonly IDbContextOptions _options; + private const int DefaultMaxBatchSize = 42; + private const int MaxMaxBatchSize = 1000; + private readonly int _maxBatchSize; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -26,7 +28,16 @@ public SqlServerModificationCommandBatchFactory( IDbContextOptions options) { Dependencies = dependencies; - _options = options; + + _maxBatchSize = Math.Min( + options.Extensions.OfType().FirstOrDefault()?.MaxBatchSize ?? DefaultMaxBatchSize, + MaxMaxBatchSize); + + if (_maxBatchSize <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(RelationalOptionsExtension.MaxBatchSize), RelationalStrings.InvalidMaxBatchSize(_maxBatchSize)); + } } /// @@ -41,9 +52,5 @@ public SqlServerModificationCommandBatchFactory( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual ModificationCommandBatch Create() - { - var optionsExtension = _options.Extensions.OfType().FirstOrDefault(); - - return new SqlServerModificationCommandBatch(Dependencies, optionsExtension?.MaxBatchSize); - } + => new SqlServerModificationCommandBatch(Dependencies, _maxBatchSize); } diff --git a/test/EFCore.Relational.Tests/TestUtilities/TestModificationCommandBatch.cs b/test/EFCore.Relational.Tests/TestUtilities/TestModificationCommandBatch.cs index 9d50183e4f0..e27a95b4a16 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/TestModificationCommandBatch.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/TestModificationCommandBatch.cs @@ -5,16 +5,11 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities; public class TestModificationCommandBatch : SingularModificationCommandBatch { - private readonly int _maxBatchSize; - public TestModificationCommandBatch( ModificationCommandBatchFactoryDependencies dependencies, int? maxBatchSize) : base(dependencies) - { - _maxBatchSize = maxBatchSize ?? 1; - } + => MaxBatchSize = maxBatchSize ?? 1; - protected override bool IsValid() - => ModificationCommands.Count <= _maxBatchSize; + protected override int MaxBatchSize { get; } } diff --git a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs index 8bcd49c6c92..2551ec6d605 100644 --- a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs +++ b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs @@ -125,6 +125,9 @@ public void AddCommand_does_not_add_command_batch_is_invalid() ", batch.CommandText, ignoreLineEndingDifferences: true); + + Assert.Equal(1, batch.StoreCommand.RelationalCommand.Parameters.Count); + Assert.Equal(1, batch.StoreCommand.ParameterValues.Count); } [ConditionalFact] diff --git a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs index 606372df527..3d6fca78ffa 100644 --- a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs +++ b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs @@ -61,7 +61,7 @@ private class FakeDbContext : DbContext { } - private static TestSqlServerModificationCommandBatch CreateBatch(int? maxBatchSize = null) + private static TestSqlServerModificationCommandBatch CreateBatch(int maxBatchSize = 42) { var typeMapper = CreateTypeMappingSource(); @@ -100,7 +100,7 @@ private static IModificationCommand CreateModificationCommand( private class TestSqlServerModificationCommandBatch : SqlServerModificationCommandBatch { - public TestSqlServerModificationCommandBatch(ModificationCommandBatchFactoryDependencies dependencies, int? maxBatchSize) + public TestSqlServerModificationCommandBatch(ModificationCommandBatchFactoryDependencies dependencies, int maxBatchSize) : base(dependencies, maxBatchSize) { } From 06b2b1f75fbfd16501d2b6bfec6d0374cf203b03 Mon Sep 17 00:00:00 2001 From: AraHaan Date: Mon, 28 Mar 2022 05:56:51 -0400 Subject: [PATCH 053/143] Update tool to roll forward to latest major version of runtime installed. (#27671) --- src/ef/ef.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ef/ef.csproj b/src/ef/ef.csproj index 77a7d8d87fe..a75e3579617 100644 --- a/src/ef/ef.csproj +++ b/src/ef/ef.csproj @@ -8,6 +8,7 @@ Microsoft.EntityFrameworkCore.Tools False $(MSBuildThisFileDirectory)..\..\rulesets\EFCore.noxmldocs.ruleset + Major From b82e6ecd2ba1647c9d141f21fba2225ccf27235a Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 13:04:31 +0000 Subject: [PATCH 054/143] Update dependencies from https://github.com/dotnet/runtime build 20220328.1 (#27718) [main] Update dependencies from dotnet/runtime --- eng/Versions.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index ab60226b68f..85fa9f1330a 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -22,9 +22,9 @@ 7.0.0-preview.2.22152.2 7.0.0-preview.2.22152.2 7.0.0-preview.2.22152.2 - 7.0.0-preview.3.22171.1 + 7.0.0-preview.4.22178.1 7.0.0-preview.2.22152.2 - 7.0.0-preview.3.22163.2 + 7.0.0-preview.4.22178.1 7.0.0-preview.2.22152.2 From b7fefcae719b05c51d149f458a1fbca009a3cbf8 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 13:10:12 +0000 Subject: [PATCH 055/143] Update dependencies from https://github.com/dotnet/arcade build 20220321.2 (#27719) [main] Update dependencies from dotnet/arcade --- eng/Version.Details.xml | 8 ++++---- global.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 41af0e6f7a3..91f42e7d734 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -48,13 +48,13 @@ - + https://github.com/dotnet/arcade - bafd55901b50d6fc3507c8ed96a7777fcca1796f + c8a95297e2622251c125aa5c0ef7c822275a792d - + https://github.com/dotnet/arcade - bafd55901b50d6fc3507c8ed96a7777fcca1796f + c8a95297e2622251c125aa5c0ef7c822275a792d diff --git a/global.json b/global.json index b945df16521..f5484ab38e1 100644 --- a/global.json +++ b/global.json @@ -13,7 +13,7 @@ "rollForward": "latestMajor" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22168.2", - "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22168.2" + "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22171.2", + "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22171.2" } } From 82be579be1c75a61aaa1e65c38641c8396369c61 Mon Sep 17 00:00:00 2001 From: DotNet-Bot Date: Wed, 30 Mar 2022 09:37:49 +0000 Subject: [PATCH 056/143] Update dependencies from https://dev.azure.com/dnceng/internal/_git/dotnet-extensions build 20220330.2 Microsoft.JSInterop , Microsoft.Extensions.ValueStopwatch.Sources , Microsoft.Extensions.FileProviders.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks , Microsoft.Extensions.DiagnosticAdapter , Microsoft.Extensions.DependencyInjection.Abstractions , Microsoft.Extensions.DependencyInjection , Microsoft.Extensions.Configuration.Xml , Microsoft.Extensions.Configuration.UserSecrets , Microsoft.Extensions.Configuration.KeyPerFile , Microsoft.Extensions.Configuration.Json , Microsoft.Extensions.Configuration.Ini , Microsoft.Extensions.Configuration.FileExtensions , Microsoft.Extensions.Configuration.EnvironmentVariables , Microsoft.Extensions.Configuration.CommandLine , Microsoft.Extensions.Configuration.Binder , Internal.AspNetCore.Analyzers , Microsoft.AspNetCore.Analyzer.Testing , Microsoft.AspNetCore.BenchmarkRunner.Sources , Microsoft.AspNetCore.Testing , Microsoft.Extensions.ActivatorUtilities.Sources , Microsoft.Extensions.FileProviders.Composite , Microsoft.Extensions.Caching.Abstractions , Microsoft.Extensions.Caching.SqlServer , Microsoft.Extensions.Caching.StackExchangeRedis , Microsoft.Extensions.CommandLineUtils.Sources , Microsoft.Extensions.Configuration , Microsoft.Extensions.Configuration.Abstractions , Microsoft.Extensions.Configuration.AzureKeyVault , Microsoft.Extensions.Caching.Memory , Microsoft.Extensions.WebEncoders , Microsoft.Extensions.FileProviders.Embedded , Microsoft.Extensions.FileSystemGlobbing , Microsoft.Extensions.TypeNameHelper.Sources , Microsoft.Extensions.Primitives , Microsoft.Extensions.ParameterDefaultValue.Sources , Microsoft.Extensions.Options.DataAnnotations , Microsoft.Extensions.Options.ConfigurationExtensions , Microsoft.Extensions.Options , Microsoft.Extensions.ObjectPool , Microsoft.Extensions.Logging.TraceSource , Microsoft.Extensions.Logging.Testing , Microsoft.Extensions.Logging.EventSource , Microsoft.Extensions.Logging.EventLog , Microsoft.Extensions.FileProviders.Physical , Microsoft.Extensions.Logging.Console , Microsoft.Extensions.Logging.Configuration , Microsoft.Extensions.Logging.Debug , Microsoft.Extensions.HashCodeCombiner.Sources , Microsoft.Extensions.HostFactoryResolver.Sources , Microsoft.Extensions.Hosting , Microsoft.Extensions.Hosting.Abstractions , Microsoft.Extensions.Logging.AzureAppServices , Microsoft.Extensions.Http , Microsoft.Extensions.Localization , Microsoft.Extensions.Localization.Abstractions , Microsoft.Extensions.Logging , Microsoft.Extensions.Logging.Abstractions From Version 3.1.24 -> To Version 3.1.24 Dependency coherency updates Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Internal,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64 From Version 3.1.24 -> To Version 3.1.24 (parent: Microsoft.Extensions.Logging --- NuGet.config | 7 +- eng/Version.Details.xml | 148 ++++++++++++++++++++-------------------- eng/Versions.props | 4 +- 3 files changed, 80 insertions(+), 79 deletions(-) diff --git a/NuGet.config b/NuGet.config index 8a2804dfd46..0dff8158a3d 100644 --- a/NuGet.config +++ b/NuGet.config @@ -6,10 +6,10 @@ - + - + @@ -24,12 +24,13 @@ - + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index a59f86e1ba5..bd230a5d362 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,21 +1,21 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-corefx @@ -33,85 +33,85 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup 3acd9b0cd16596bad450c82be08780875a73c05c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc @@ -124,151 +124,151 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7580d79da13bf89d8cc37bd84b9865bc41a78ed0 + 8e0af0dbdd80016da26eab546c3f10e943cea1bc https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 593c84e5585926c074a74992ccb1f2fb24709775 + 3b3838608327c1d49fcf77fc47487e40e4dc3000 - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 593c84e5585926c074a74992ccb1f2fb24709775 + 3b3838608327c1d49fcf77fc47487e40e4dc3000 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx diff --git a/eng/Versions.props b/eng/Versions.props index 17a5d4b3722..3afc298e67c 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -37,7 +37,7 @@ 3.1.24 3.1.24 3.1.24 - 3.1.24-servicing.22164.6 + 3.1.24-servicing.22180.2 3.1.24 @@ -56,7 +56,7 @@ 3.1.6 3.1.0 3.1.24 - 3.1.24-servicing.22164.10 + 3.1.24-servicing.22179.7 2.1.0 From b307ba57cddc25bfb05a8e5a1789293a559fc71e Mon Sep 17 00:00:00 2001 From: AraHaan Date: Mon, 4 Apr 2022 06:39:26 -0400 Subject: [PATCH 057/143] Remove needless package references in EFCore. (#27720) --- eng/Version.Details.xml | 8 -------- eng/Versions.props | 2 -- src/EFCore/EFCore.csproj | 2 -- 3 files changed, 12 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 91f42e7d734..3f707ad1b99 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -38,14 +38,6 @@ https://github.com/dotnet/runtime e24f66dff0770eee344038da8c12476d8c450c41 - - https://github.com/dotnet/runtime - e24f66dff0770eee344038da8c12476d8c450c41 - - - https://github.com/dotnet/runtime - e24f66dff0770eee344038da8c12476d8c450c41 - diff --git a/eng/Versions.props b/eng/Versions.props index 85fa9f1330a..62f312fa0ce 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -15,8 +15,6 @@ False - 7.0.0-preview.2.22152.2 - 7.0.0-preview.2.22152.2 7.0.0-preview.2.22152.2 7.0.0-preview.2.22152.2 7.0.0-preview.2.22152.2 diff --git a/src/EFCore/EFCore.csproj b/src/EFCore/EFCore.csproj index 84ae4596cd8..825acc8fb8c 100644 --- a/src/EFCore/EFCore.csproj +++ b/src/EFCore/EFCore.csproj @@ -45,9 +45,7 @@ Microsoft.EntityFrameworkCore.DbSet - - From d989afae4a57e8087ad30d6dc7cd0bf977e2ff5b Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 13:05:00 +0000 Subject: [PATCH 058/143] Update dependencies from https://github.com/dotnet/arcade build 20220331.2 (#27749) [main] Update dependencies from dotnet/arcade --- eng/Version.Details.xml | 8 ++++---- global.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 3f707ad1b99..fe5cb25f6cd 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -40,13 +40,13 @@ - + https://github.com/dotnet/arcade - c8a95297e2622251c125aa5c0ef7c822275a792d + e0b311bcd81fc9e27bcf7715dcda62fa38dfa49a - + https://github.com/dotnet/arcade - c8a95297e2622251c125aa5c0ef7c822275a792d + e0b311bcd81fc9e27bcf7715dcda62fa38dfa49a diff --git a/global.json b/global.json index f5484ab38e1..679c061a34a 100644 --- a/global.json +++ b/global.json @@ -13,7 +13,7 @@ "rollForward": "latestMajor" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22171.2", - "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22171.2" + "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22181.2", + "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22181.2" } } From 250021715c1c0d7c96b002267bb0873c49d2ea6e Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 13:09:22 +0000 Subject: [PATCH 059/143] Update dependencies from https://github.com/dotnet/runtime build 20220401.3 (#27748) [main] Update dependencies from dotnet/runtime --- eng/Version.Details.xml | 8 ++++---- eng/Versions.props | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index fe5cb25f6cd..2372357fb3f 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -22,17 +22,17 @@ https://github.com/dotnet/runtime e24f66dff0770eee344038da8c12476d8c450c41 - + https://github.com/dotnet/runtime - eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 + c3e44952bb75d054517f2a24943502d3a14a47f5 https://github.com/dotnet/runtime e24f66dff0770eee344038da8c12476d8c450c41 - + https://github.com/dotnet/runtime - eb51b02b158c3ff71a1ec7eac8a211d1d464c1a5 + c3e44952bb75d054517f2a24943502d3a14a47f5 https://github.com/dotnet/runtime diff --git a/eng/Versions.props b/eng/Versions.props index 62f312fa0ce..85ea741a395 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -20,9 +20,9 @@ 7.0.0-preview.2.22152.2 7.0.0-preview.2.22152.2 7.0.0-preview.2.22152.2 - 7.0.0-preview.4.22178.1 + 7.0.0-preview.4.22201.3 7.0.0-preview.2.22152.2 - 7.0.0-preview.4.22178.1 + 7.0.0-preview.4.22201.3 7.0.0-preview.2.22152.2 From 246cc8692915c72d77e393952c1c6797dea92fc9 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Tue, 5 Apr 2022 13:08:46 -0700 Subject: [PATCH 060/143] Add TPC support to update pipeline Switch the update pipeline to use the relational model for command ordering, this is required for a robust implementation of seed data support for TPC Add provider value comparer Part of #3170 --- ...ntityFrameworkRelationalServicesBuilder.cs | 8 +- .../RelationalModelDependencies.cs | 37 +- .../RelationalModelValidator.cs | 40 +- src/EFCore.Relational/Metadata/IColumn.cs | 24 +- src/EFCore.Relational/Metadata/IColumnBase.cs | 7 +- .../Metadata/IForeignKeyConstraint.cs | 11 +- .../Metadata/IFunctionColumn.cs | 2 +- .../Metadata/ISqlQueryColumn.cs | 2 +- src/EFCore.Relational/Metadata/ITable.cs | 5 + src/EFCore.Relational/Metadata/ITableBase.cs | 6 + .../Metadata/IUniqueConstraint.cs | 2 +- src/EFCore.Relational/Metadata/IViewColumn.cs | 2 +- .../Metadata/Internal/Column.cs | 23 +- .../Metadata/Internal/ColumnBase.cs | 52 +- .../Metadata/Internal/ColumnMapping.cs | 9 +- .../Metadata/Internal/ColumnMappingBase.cs | 32 +- .../Internal/ColumnMappingBaseComparer.cs | 27 +- .../Metadata/Internal/ForeignKeyConstraint.cs | 34 +- .../Internal/ForeignKeyConstraintComparer.cs | 2 +- .../Metadata/Internal/FunctionColumn.cs | 6 +- .../Internal/FunctionColumnMapping.cs | 9 +- .../Metadata/Internal/FunctionMapping.cs | 4 +- .../Metadata/Internal/RelationalModel.cs | 318 +++++++----- .../Metadata/Internal/SqlQueryColumn.cs | 6 +- .../Internal/SqlQueryColumnMapping.cs | 9 +- .../Metadata/Internal/SqlQueryMapping.cs | 4 +- .../Metadata/Internal/Table.cs | 23 +- .../Metadata/Internal/TableBase.cs | 4 +- .../Metadata/Internal/TableIndex.cs | 16 + .../Metadata/Internal/TableMapping.cs | 4 +- .../Metadata/Internal/TableMappingBase.cs | 26 +- .../Metadata/Internal/UniqueConstraint.cs | 16 + .../Metadata/Internal/ViewColumn.cs | 6 +- .../Metadata/Internal/ViewColumnMapping.cs | 9 +- .../Metadata/Internal/ViewMapping.cs | 4 +- .../Internal/MigrationsModelDiffer.cs | 6 +- .../Properties/RelationalStrings.Designer.cs | 18 +- .../Properties/RelationalStrings.resx | 6 + .../Internal/TypeMappedRelationalParameter.cs | 6 +- .../Storage/RelationalTypeMapping.cs | 182 ++++--- .../Update/ColumnModificationParameters.cs | 4 - .../Update/IReadOnlyModificationCommand.cs | 5 + ...dexFactorySource.cs => ColumnAccessors.cs} | 34 +- .../Update/Internal/ColumnAccessorsFactory.cs | 117 +++++ .../Update/Internal/CommandBatchPreparer.cs | 488 ++++++++---------- .../CommandBatchPreparerDependencies.cs | 10 - ... => CompositeRowForeignKeyValueFactory.cs} | 46 +- .../Internal/CompositeRowIndexValueFactory.cs | 78 +++ .../Internal/CompositeRowKeyValueFactory.cs | 123 +++++ .../Internal/CompositeRowValueFactory.cs | 215 ++++++++ .../Internal/IRowForeignKeyValueFactory.cs | 45 ++ ...s => IRowForeignKeyValueFactoryFactory.cs} | 9 +- ...ndex.cs => IRowForeignKeyValueFactory`.cs} | 45 +- .../Update/Internal/IRowIndexValueFactory.cs | 29 ++ ...dex.cs => IRowIndexValueFactoryFactory.cs} | 4 +- .../Update/Internal/IRowIndexValueFactory`.cs | 47 ++ .../Update/Internal/IRowKeyValueFactory.cs | 29 ++ .../Internal/IRowKeyValueFactoryFactory.cs | 21 + ...ndexFactory.cs => IRowKeyValueFactory`.cs} | 10 +- .../Internal/RowForeignKeyValueFactory.cs | 122 +++++ .../RowForeignKeyValueFactoryFactory.cs | 61 +++ .../Internal/RowIndexValueFactoryFactory.cs | 35 ++ .../Internal/RowKeyValueFactoryFactory.cs | 35 ++ .../Update/Internal/SharedTableEntryMap.cs | 2 +- .../Internal/SharedTableEntryValueFactory.cs | 2 +- ...eFullyNullableRowForeignKeyValueFactory.cs | 68 +++ ...pleNonNullableRowForeignKeyValueFactory.cs | 68 +++ ...lablePrincipalRowForeignKeyValueFactory.cs | 69 +++ ...SimpleNullableRowForeignKeyValueFactory.cs | 79 +++ .../Internal/SimpleRowIndexValueFactory.cs | 111 ++++ .../Internal/SimpleRowKeyValueFactory.cs | 160 ++++++ .../Update/Internal/ValueIndex.cs | 61 +++ .../Update/ModificationCommand.cs | 100 ++-- .../Update/ModificationCommandParameters.cs | 30 ++ .../Internal/CompositeValueFactory.cs | 111 +--- .../Internal/InternalEntityEntry.cs | 49 +- .../SimplePrincipalKeyValueFactory.cs | 9 +- .../Internal/NullableComparerAdapter.cs | 48 ++ src/EFCore/Metadata/IProperty.cs | 26 +- src/EFCore/Update/UpdateEntryExtensions.cs | 24 +- .../RelationalModelValidatorTest.cs | 19 +- .../Metadata/RelationalModelTest.cs | 12 + .../Update/CommandBatchPreparerTest.cs | 1 - .../CustomConvertersTestBase.cs | 2 +- .../TPTTableSplittingSqlServerTest.cs | 21 +- .../SqlServerModelValidatorTest.cs | 15 - .../SqliteModelValidatorTest.cs | 18 - 87 files changed, 2728 insertions(+), 996 deletions(-) rename src/EFCore.Relational/Update/Internal/{KeyValueIndexFactorySource.cs => ColumnAccessors.cs} (65%) create mode 100644 src/EFCore.Relational/Update/Internal/ColumnAccessorsFactory.cs rename src/EFCore.Relational/Update/Internal/{KeyValueIndexFactory.cs => CompositeRowForeignKeyValueFactory.cs} (64%) create mode 100644 src/EFCore.Relational/Update/Internal/CompositeRowIndexValueFactory.cs create mode 100644 src/EFCore.Relational/Update/Internal/CompositeRowKeyValueFactory.cs create mode 100644 src/EFCore.Relational/Update/Internal/CompositeRowValueFactory.cs create mode 100644 src/EFCore.Relational/Update/Internal/IRowForeignKeyValueFactory.cs rename src/EFCore.Relational/Update/Internal/{IKeyValueIndexFactorySource.cs => IRowForeignKeyValueFactoryFactory.cs} (71%) rename src/EFCore.Relational/Update/Internal/{KeyValueIndex.cs => IRowForeignKeyValueFactory`.cs} (64%) create mode 100644 src/EFCore.Relational/Update/Internal/IRowIndexValueFactory.cs rename src/EFCore.Relational/Update/Internal/{IKeyValueIndex.cs => IRowIndexValueFactoryFactory.cs} (91%) create mode 100644 src/EFCore.Relational/Update/Internal/IRowIndexValueFactory`.cs create mode 100644 src/EFCore.Relational/Update/Internal/IRowKeyValueFactory.cs create mode 100644 src/EFCore.Relational/Update/Internal/IRowKeyValueFactoryFactory.cs rename src/EFCore.Relational/Update/Internal/{IKeyValueIndexFactory.cs => IRowKeyValueFactory`.cs} (85%) create mode 100644 src/EFCore.Relational/Update/Internal/RowForeignKeyValueFactory.cs create mode 100644 src/EFCore.Relational/Update/Internal/RowForeignKeyValueFactoryFactory.cs create mode 100644 src/EFCore.Relational/Update/Internal/RowIndexValueFactoryFactory.cs create mode 100644 src/EFCore.Relational/Update/Internal/RowKeyValueFactoryFactory.cs create mode 100644 src/EFCore.Relational/Update/Internal/SimpleFullyNullableRowForeignKeyValueFactory.cs create mode 100644 src/EFCore.Relational/Update/Internal/SimpleNonNullableRowForeignKeyValueFactory.cs create mode 100644 src/EFCore.Relational/Update/Internal/SimpleNullablePrincipalRowForeignKeyValueFactory.cs create mode 100644 src/EFCore.Relational/Update/Internal/SimpleNullableRowForeignKeyValueFactory.cs create mode 100644 src/EFCore.Relational/Update/Internal/SimpleRowIndexValueFactory.cs create mode 100644 src/EFCore.Relational/Update/Internal/SimpleRowKeyValueFactory.cs create mode 100644 src/EFCore.Relational/Update/Internal/ValueIndex.cs create mode 100644 src/EFCore/Internal/NullableComparerAdapter.cs diff --git a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs index 43708a7b249..620e5fb6f2b 100644 --- a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs +++ b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs @@ -44,7 +44,9 @@ public class EntityFrameworkRelationalServicesBuilder : EntityFrameworkServicesB public static readonly IDictionary RelationalServices = new Dictionary { - { typeof(IKeyValueIndexFactorySource), new ServiceCharacteristics(ServiceLifetime.Singleton) }, + { typeof(IRowKeyValueFactoryFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, + { typeof(IRowForeignKeyValueFactoryFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, + { typeof(IRowIndexValueFactoryFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IParameterNameGeneratorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IComparer), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IMigrationsIdGenerator), new ServiceCharacteristics(ServiceLifetime.Singleton) }, @@ -125,7 +127,9 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(); TryAdd, ModificationCommandComparer>(); TryAdd(); - TryAdd(); + TryAdd(); + TryAdd(); + TryAdd(); TryAdd(); TryAdd(); TryAdd(); diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelDependencies.cs b/src/EFCore.Relational/Infrastructure/RelationalModelDependencies.cs index 725acf89e50..7e63709e4b1 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelDependencies.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelDependencies.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.Update.Internal; + namespace Microsoft.EntityFrameworkCore.Infrastructure; /// @@ -45,7 +47,40 @@ public sealed record RelationalModelDependencies /// the constructor at any point in this process. /// [EntityFrameworkInternal] - public RelationalModelDependencies() + public RelationalModelDependencies( + IRowKeyValueFactoryFactory rowKeyValueFactoryFactory, + IRowForeignKeyValueFactoryFactory foreignKeyRowValueFactorySource, + IRowIndexValueFactoryFactory rowIndexValueFactoryFactory) { + RowKeyValueFactoryFactory = rowKeyValueFactoryFactory; + RowForeignKeyValueFactoryFactory = foreignKeyRowValueFactorySource; + RowIndexValueFactoryFactory = rowIndexValueFactoryFactory; } + + /// + /// 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 IRowKeyValueFactoryFactory RowKeyValueFactoryFactory { get; init; } + + /// + /// 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 IRowForeignKeyValueFactoryFactory RowForeignKeyValueFactoryFactory { get; init; } + + /// + /// 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 IRowIndexValueFactoryFactory RowIndexValueFactoryFactory { get; init; } } diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index 848021babc3..d861d5d0153 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -559,10 +559,10 @@ protected virtual void ValidateSharedViewCompatibility( mappedTypes.Add(entityType); } - foreach (var (table, mappedTypes) in views) + foreach (var (view, mappedTypes) in views) { - ValidateSharedViewCompatibility(mappedTypes, table.Name, table.Schema, logger); - ValidateSharedColumnsCompatibility(mappedTypes, table, logger); + ValidateSharedViewCompatibility(mappedTypes, view.Name, view.Schema, logger); + ValidateSharedColumnsCompatibility(mappedTypes, view, logger); } } @@ -866,10 +866,12 @@ protected virtual void ValidateCompatible( storeObject.DisplayName())); } + var typeMapping = property.GetRelationalTypeMapping(); + var duplicateTypeMapping = duplicateProperty.GetRelationalTypeMapping(); var currentTypeString = property.GetColumnType(storeObject) - ?? property.GetRelationalTypeMapping().StoreType; + ?? typeMapping.StoreType; var previousTypeString = duplicateProperty.GetColumnType(storeObject) - ?? duplicateProperty.GetRelationalTypeMapping().StoreType; + ?? duplicateTypeMapping.StoreType; if (!string.Equals(currentTypeString, previousTypeString, StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException( @@ -884,6 +886,22 @@ protected virtual void ValidateCompatible( currentTypeString)); } + var currentProviderType = typeMapping.Converter?.ProviderClrType ?? typeMapping.ClrType; + var previousProviderType = duplicateTypeMapping.Converter?.ProviderClrType ?? duplicateTypeMapping.ClrType; + if (currentProviderType != previousProviderType) + { + throw new InvalidOperationException( + RelationalStrings.DuplicateColumnNameProviderTypeMismatch( + duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.Name, + property.DeclaringEntityType.DisplayName(), + property.Name, + columnName, + storeObject.DisplayName(), + previousProviderType.ShortDisplayName(), + currentProviderType.ShortDisplayName())); + } + var currentComputedColumnSql = property.GetComputedColumnSql(storeObject) ?? ""; var previousComputedColumnSql = duplicateProperty.GetComputedColumnSql(storeObject) ?? ""; if (!currentComputedColumnSql.Equals(previousComputedColumnSql, StringComparison.OrdinalIgnoreCase)) @@ -1340,8 +1358,8 @@ protected override void ValidateInheritanceMapping( RelationalStrings.NonTphMappingStrategy(mappingStrategy, entityType.DisplayName())); } - ValidateTPHMapping(entityType, forTables: false); - ValidateTPHMapping(entityType, forTables: true); + ValidateTphMapping(entityType, forTables: false); + ValidateTphMapping(entityType, forTables: true); ValidateDiscriminatorValues(entityType); } else @@ -1362,8 +1380,8 @@ protected override void ValidateInheritanceMapping( RelationalStrings.KeylessMappingStrategy(mappingStrategy ?? RelationalAnnotationNames.TptMappingStrategy, entityType.DisplayName())); } - ValidateNonTPHMapping(entityType, forTables: false); - ValidateNonTPHMapping(entityType, forTables: true); + ValidateNonTphMapping(entityType, forTables: false); + ValidateNonTphMapping(entityType, forTables: true); } } } @@ -1387,7 +1405,7 @@ protected virtual void ValidateMappingStrategy(string? mappingStrategy, IEntityT }; } - private static void ValidateNonTPHMapping(IEntityType rootEntityType, bool forTables) + private static void ValidateNonTphMapping(IEntityType rootEntityType, bool forTables) { var derivedTypes = new Dictionary<(string, string?), IEntityType>(); foreach (var entityType in rootEntityType.GetDerivedTypesInclusive()) @@ -1413,7 +1431,7 @@ private static void ValidateNonTPHMapping(IEntityType rootEntityType, bool forTa } } - private static void ValidateTPHMapping(IEntityType rootEntityType, bool forTables) + private static void ValidateTphMapping(IEntityType rootEntityType, bool forTables) { string? firstName = null; string? firstSchema = null; diff --git a/src/EFCore.Relational/Metadata/IColumn.cs b/src/EFCore.Relational/Metadata/IColumn.cs index 3a933958693..cb173e86cfa 100644 --- a/src/EFCore.Relational/Metadata/IColumn.cs +++ b/src/EFCore.Relational/Metadata/IColumn.cs @@ -21,7 +21,7 @@ public interface IColumn : IColumnBase /// /// Gets the property mappings. /// - new IEnumerable PropertyMappings { get; } + new IReadOnlyList PropertyMappings { get; } /// /// Gets the maximum length of data that is allowed in this column. For example, if the property is a ' @@ -98,8 +98,7 @@ public virtual bool TryGetDefaultValue(out object? defaultValue) continue; } - var converter = property.GetValueConverter() ?? PropertyMappings.First().TypeMapping.Converter; - + var converter = property.GetValueConverter() ?? mapping.TypeMapping.Converter; if (converter != null) { defaultValue = converter.ConvertToProvider(defaultValue); @@ -148,6 +147,25 @@ public virtual string? Collation => PropertyMappings.First().Property .GetCollation(StoreObjectIdentifier.Table(Table.Name, Table.Schema)); + /// + /// Returns the property mapping for the given entity type. + /// + /// An entity type. + /// The property mapping or if not found. + public virtual IColumnMapping? FindColumnMapping(IReadOnlyEntityType entityType) + { + for (var i = 0; i < PropertyMappings.Count; i++) + { + var mapping = PropertyMappings[i]; + if (mapping.Property.DeclaringEntityType.IsAssignableFrom(entityType)) + { + return mapping; + } + } + + return null; + } + /// /// /// Creates a human-readable representation of the given metadata. diff --git a/src/EFCore.Relational/Metadata/IColumnBase.cs b/src/EFCore.Relational/Metadata/IColumnBase.cs index 5489e0bdb06..4588f10abcd 100644 --- a/src/EFCore.Relational/Metadata/IColumnBase.cs +++ b/src/EFCore.Relational/Metadata/IColumnBase.cs @@ -21,6 +21,11 @@ public interface IColumnBase : IAnnotatable /// string StoreType { get; } + /// + /// Gets the provider type. + /// + Type ProviderClrType { get; } + /// /// Gets the value indicating whether the column can contain NULL. /// @@ -34,5 +39,5 @@ public interface IColumnBase : IAnnotatable /// /// Gets the property mappings. /// - IEnumerable PropertyMappings { get; } + IReadOnlyList PropertyMappings { get; } } diff --git a/src/EFCore.Relational/Metadata/IForeignKeyConstraint.cs b/src/EFCore.Relational/Metadata/IForeignKeyConstraint.cs index 18c0b4ea011..b72c01a2f07 100644 --- a/src/EFCore.Relational/Metadata/IForeignKeyConstraint.cs +++ b/src/EFCore.Relational/Metadata/IForeignKeyConstraint.cs @@ -42,7 +42,12 @@ public interface IForeignKeyConstraint : IAnnotatable /// /// Gets the columns that are referenced by the foreign key constraint. /// - IReadOnlyList PrincipalColumns { get; } + IReadOnlyList PrincipalColumns => PrincipalUniqueConstraint.Columns; + + /// + /// Gets the unique constraint on the columns referenced by the foreign key constraint. + /// + IUniqueConstraint PrincipalUniqueConstraint { get; } /// /// Gets the action to be performed when the referenced row is deleted. @@ -78,11 +83,11 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt .Append(' ') .Append(Table.Name) .Append(' ') - .Append(ColumnBase.Format(Columns)) + .Append(ColumnBase.Format(Columns)) .Append(" -> ") .Append(PrincipalTable.Name) .Append(' ') - .Append(ColumnBase.Format(PrincipalColumns)); + .Append(ColumnBase.Format(PrincipalColumns)); if (OnDeleteAction != ReferentialAction.NoAction) { diff --git a/src/EFCore.Relational/Metadata/IFunctionColumn.cs b/src/EFCore.Relational/Metadata/IFunctionColumn.cs index 8cf75f68b96..6ff09a72e94 100644 --- a/src/EFCore.Relational/Metadata/IFunctionColumn.cs +++ b/src/EFCore.Relational/Metadata/IFunctionColumn.cs @@ -21,7 +21,7 @@ public interface IFunctionColumn : IColumnBase /// /// Gets the property mappings. /// - new IEnumerable PropertyMappings { get; } + new IReadOnlyList PropertyMappings { get; } /// /// diff --git a/src/EFCore.Relational/Metadata/ISqlQueryColumn.cs b/src/EFCore.Relational/Metadata/ISqlQueryColumn.cs index 175572e2bb9..485f567dd59 100644 --- a/src/EFCore.Relational/Metadata/ISqlQueryColumn.cs +++ b/src/EFCore.Relational/Metadata/ISqlQueryColumn.cs @@ -22,7 +22,7 @@ public interface ISqlQueryColumn : IColumnBase /// /// Gets the property mappings. /// - new IEnumerable PropertyMappings { get; } + new IReadOnlyList PropertyMappings { get; } /// /// diff --git a/src/EFCore.Relational/Metadata/ITable.cs b/src/EFCore.Relational/Metadata/ITable.cs index cd8a0b342c6..cd2e7c59de0 100644 --- a/src/EFCore.Relational/Metadata/ITable.cs +++ b/src/EFCore.Relational/Metadata/ITable.cs @@ -33,6 +33,11 @@ public interface ITable : ITableBase /// IEnumerable ForeignKeyConstraints { get; } + /// + /// Gets the foreign key constraints referencing this table. + /// + IEnumerable ReferencingForeignKeyConstraints { get; } + /// /// Gets the unique constraints including the primary key for this table. /// diff --git a/src/EFCore.Relational/Metadata/ITableBase.cs b/src/EFCore.Relational/Metadata/ITableBase.cs index c8547fa2ddb..c71763545eb 100644 --- a/src/EFCore.Relational/Metadata/ITableBase.cs +++ b/src/EFCore.Relational/Metadata/ITableBase.cs @@ -21,6 +21,12 @@ public interface ITableBase : IAnnotatable /// string? Schema { get; } + /// + /// Gets the schema-qualified name of the table in the database. + /// + string SchemaQualifiedName + => Schema == null ? Name : Schema + "." + Name; + /// /// Gets the database model. /// diff --git a/src/EFCore.Relational/Metadata/IUniqueConstraint.cs b/src/EFCore.Relational/Metadata/IUniqueConstraint.cs index 7b87ae8ebe7..6df46ae6295 100644 --- a/src/EFCore.Relational/Metadata/IUniqueConstraint.cs +++ b/src/EFCore.Relational/Metadata/IUniqueConstraint.cs @@ -68,7 +68,7 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt builder .Append(Name) .Append(' ') - .Append(ColumnBase.Format(Columns)); + .Append(ColumnBase.Format(Columns)); if (GetIsPrimaryKey()) { diff --git a/src/EFCore.Relational/Metadata/IViewColumn.cs b/src/EFCore.Relational/Metadata/IViewColumn.cs index ebdc515b117..a43bb1e76b8 100644 --- a/src/EFCore.Relational/Metadata/IViewColumn.cs +++ b/src/EFCore.Relational/Metadata/IViewColumn.cs @@ -21,7 +21,7 @@ public interface IViewColumn : IColumnBase /// /// Gets the property mappings. /// - new IEnumerable PropertyMappings { get; } + new IReadOnlyList PropertyMappings { get; } /// /// diff --git a/src/EFCore.Relational/Metadata/Internal/Column.cs b/src/EFCore.Relational/Metadata/Internal/Column.cs index 70ead4124db..ddec4c1e143 100644 --- a/src/EFCore.Relational/Metadata/Internal/Column.cs +++ b/src/EFCore.Relational/Metadata/Internal/Column.cs @@ -1,6 +1,9 @@ // 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.Internal; +using Microsoft.EntityFrameworkCore.Update.Internal; + namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// @@ -9,8 +12,11 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class Column : ColumnBase, IColumn +public class Column : ColumnBase, IColumn { + // Warning: Never access these fields directly as access needs to be thread-safe + private ColumnAccessors? _accessors; + /// /// 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 @@ -31,6 +37,17 @@ public Column(string name, string type, Table table) public new virtual Table Table => (Table)base.Table; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ColumnAccessors Accessors + => NonCapturingLazyInitializer.EnsureInitialized( + ref _accessors, this, static column => + ColumnAccessorsFactory.Create(column)); + /// /// 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 @@ -48,9 +65,9 @@ ITable IColumn.Table } /// - IEnumerable IColumn.PropertyMappings + IReadOnlyList IColumn.PropertyMappings { [DebuggerStepThrough] - get => PropertyMappings.Cast(); + get => PropertyMappings; } } diff --git a/src/EFCore.Relational/Metadata/Internal/ColumnBase.cs b/src/EFCore.Relational/Metadata/Internal/ColumnBase.cs index 13e1b63f3ec..8e59938a61b 100644 --- a/src/EFCore.Relational/Metadata/Internal/ColumnBase.cs +++ b/src/EFCore.Relational/Metadata/Internal/ColumnBase.cs @@ -9,8 +9,11 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class ColumnBase : Annotatable, IColumnBase +public class ColumnBase : Annotatable, IColumnBase + where TColumnMappingBase : class, IColumnMappingBase { + private Type? _providerClrType; + /// /// 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 @@ -71,8 +74,49 @@ public override bool IsReadOnly /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual SortedSet PropertyMappings { get; } - = new(ColumnMappingBaseComparer.Instance); + public virtual Type ProviderClrType + { + get + { + if (_providerClrType != null) + { + return _providerClrType; + } + + var typeMapping = PropertyMappings.First().TypeMapping; + var providerType = typeMapping.Converter?.ProviderClrType ?? typeMapping.ClrType; + + return _providerClrType = providerType; + } + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected virtual List PropertyMappings { get; } + = new(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool AddPropertyMapping(TColumnMappingBase columnMapping) + { + if (PropertyMappings.IndexOf(columnMapping, ColumnMappingBaseComparer.Instance) != -1) + { + return false; + } + + PropertyMappings.Add(columnMapping); + PropertyMappings.Sort(ColumnMappingBaseComparer.Instance); + + return true; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -88,7 +132,7 @@ public static string Format(IEnumerable columns) + "}"; /// - IEnumerable IColumnBase.PropertyMappings + IReadOnlyList IColumnBase.PropertyMappings { [DebuggerStepThrough] get => PropertyMappings; diff --git a/src/EFCore.Relational/Metadata/Internal/ColumnMapping.cs b/src/EFCore.Relational/Metadata/Internal/ColumnMapping.cs index 721750c7b23..b3a38460409 100644 --- a/src/EFCore.Relational/Metadata/Internal/ColumnMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/ColumnMapping.cs @@ -29,8 +29,13 @@ public ColumnMapping( public new virtual ITableMapping TableMapping => (ITableMapping)base.TableMapping; - /// - public override RelationalTypeMapping TypeMapping + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override RelationalTypeMapping GetTypeMapping() => Property.FindRelationalTypeMapping( StoreObjectIdentifier.Table(TableMapping.Table.Name, TableMapping.Table.Schema))!; diff --git a/src/EFCore.Relational/Metadata/Internal/ColumnMappingBase.cs b/src/EFCore.Relational/Metadata/Internal/ColumnMappingBase.cs index 77c71cacd5f..1f241a327f5 100644 --- a/src/EFCore.Relational/Metadata/Internal/ColumnMappingBase.cs +++ b/src/EFCore.Relational/Metadata/Internal/ColumnMappingBase.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.Internal; + namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// @@ -11,6 +13,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// public class ColumnMappingBase : Annotatable, IColumnMappingBase { + private RelationalTypeMapping? _typeMapping; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -19,8 +23,8 @@ public class ColumnMappingBase : Annotatable, IColumnMappingBase /// public ColumnMappingBase( IProperty property, - ColumnBase column, - TableMappingBase tableMapping) + IColumnBase column, + ITableMappingBase tableMapping) { Property = property; Column = column; @@ -30,16 +34,21 @@ public ColumnMappingBase( /// public virtual IProperty Property { get; } + /// + public virtual IColumnBase Column { get; } + + /// + public virtual RelationalTypeMapping TypeMapping => + NonCapturingLazyInitializer.EnsureInitialized( + ref _typeMapping, this, static mapping => mapping.GetTypeMapping()); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual ColumnBase Column { get; } - - /// - public virtual RelationalTypeMapping TypeMapping + protected virtual RelationalTypeMapping GetTypeMapping() => Property.GetRelationalTypeMapping(); /// @@ -48,7 +57,7 @@ public virtual RelationalTypeMapping TypeMapping /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual TableMappingBase TableMapping { get; } + public virtual ITableMappingBase TableMapping { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -57,14 +66,7 @@ public virtual RelationalTypeMapping TypeMapping /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override bool IsReadOnly - => TableMapping.IsReadOnly; - - /// - IColumnBase IColumnMappingBase.Column - { - [DebuggerStepThrough] - get => Column; - } + => ((AnnotatableBase)TableMapping).IsReadOnly; /// ITableMappingBase IColumnMappingBase.TableMapping diff --git a/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs b/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs index 85e0eeacbf3..32bd80aa5e6 100644 --- a/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs +++ b/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs @@ -64,19 +64,7 @@ public int Compare(IColumnMappingBase? x, IColumnMappingBase? y) return result; } - result = EntityTypeFullNameComparer.Instance.Compare(x.TableMapping.EntityType, y.TableMapping.EntityType); - if (result != 0) - { - return result; - } - - result = StringComparer.Ordinal.Compare(x.Column.Table.Name, y.Column.Table.Name); - if (result != 0) - { - return result; - } - - return StringComparer.Ordinal.Compare(x.Column.Table.Schema, y.Column.Table.Schema); + return TableMappingBaseComparer.Instance.Compare(x.TableMapping, y.TableMapping); } /// @@ -86,7 +74,10 @@ public int Compare(IColumnMappingBase? x, IColumnMappingBase? y) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public bool Equals(IColumnMappingBase? x, IColumnMappingBase? y) - => ReferenceEquals(x, y) || x is not null && y is not null && x.Property == y.Property && x.Column == y.Column; + => ReferenceEquals(x, y) + || (x is not null && y is not null + && x.Property == y.Property && x.Column == y.Column + && TableMappingBaseComparer.Instance.Equals(x.TableMapping, y.TableMapping)); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -97,11 +88,9 @@ public bool Equals(IColumnMappingBase? x, IColumnMappingBase? y) public int GetHashCode(IColumnMappingBase obj) { var hashCode = new HashCode(); - hashCode.Add(obj.Property.Name); - hashCode.Add(obj.Column.Name); - hashCode.Add(obj.Property.DeclaringEntityType, EntityTypeFullNameComparer.Instance); - hashCode.Add(obj.Column.Table.Name); - hashCode.Add(obj.Column.Table.Schema); + hashCode.Add(obj.Property); + hashCode.Add(obj.Column); + hashCode.Add(obj.TableMapping, TableMappingBaseComparer.Instance); return hashCode.ToHashCode(); } diff --git a/src/EFCore.Relational/Metadata/Internal/ForeignKeyConstraint.cs b/src/EFCore.Relational/Metadata/Internal/ForeignKeyConstraint.cs index 23ad7ddcf8c..471f466462f 100644 --- a/src/EFCore.Relational/Metadata/Internal/ForeignKeyConstraint.cs +++ b/src/EFCore.Relational/Metadata/Internal/ForeignKeyConstraint.cs @@ -1,6 +1,9 @@ // 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.Internal; +using Microsoft.EntityFrameworkCore.Update.Internal; + namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// @@ -22,14 +25,14 @@ public ForeignKeyConstraint( Table table, Table principalTable, IReadOnlyList columns, - IReadOnlyList principalColumns, + UniqueConstraint principalUniqueConstraint, ReferentialAction onDeleteAction) { Name = name; Table = table; PrincipalTable = principalTable; Columns = columns; - PrincipalColumns = principalColumns; + PrincipalUniqueConstraint = principalUniqueConstraint; OnDeleteAction = onDeleteAction; } @@ -74,7 +77,15 @@ public ForeignKeyConstraint( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IReadOnlyList PrincipalColumns { get; } + public virtual IReadOnlyList PrincipalColumns => PrincipalUniqueConstraint.Columns; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual UniqueConstraint PrincipalUniqueConstraint { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -88,6 +99,19 @@ public override bool IsReadOnly /// public virtual ReferentialAction OnDeleteAction { get; set; } + private IRowForeignKeyValueFactory? _foreignKeyRowValueFactory; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IRowForeignKeyValueFactory GetRowForeignKeyValueFactory() + => NonCapturingLazyInitializer.EnsureInitialized( + ref _foreignKeyRowValueFactory, this, + static constraint => constraint.Table.Model.Model.GetRelationalDependencies().RowForeignKeyValueFactoryFactory.Create(constraint)); + /// /// 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 @@ -116,4 +140,8 @@ IReadOnlyList IForeignKeyConstraint.Columns /// IReadOnlyList IForeignKeyConstraint.PrincipalColumns => PrincipalColumns; + + /// + IUniqueConstraint IForeignKeyConstraint.PrincipalUniqueConstraint + => PrincipalUniqueConstraint; } diff --git a/src/EFCore.Relational/Metadata/Internal/ForeignKeyConstraintComparer.cs b/src/EFCore.Relational/Metadata/Internal/ForeignKeyConstraintComparer.cs index a3263c8795f..1f1a54d6c45 100644 --- a/src/EFCore.Relational/Metadata/Internal/ForeignKeyConstraintComparer.cs +++ b/src/EFCore.Relational/Metadata/Internal/ForeignKeyConstraintComparer.cs @@ -90,8 +90,8 @@ public int GetHashCode(IForeignKeyConstraint obj) hashCode.Add(obj.Name); hashCode.Add(obj.Columns, ColumnListComparer.Instance); hashCode.Add(obj.PrincipalColumns, ColumnListComparer.Instance); - hashCode.Add(obj.Table.Name); hashCode.Add(obj.PrincipalTable.Name); + hashCode.Add(obj.Table.Name); return hashCode.ToHashCode(); } } diff --git a/src/EFCore.Relational/Metadata/Internal/FunctionColumn.cs b/src/EFCore.Relational/Metadata/Internal/FunctionColumn.cs index 89b00ef1630..ff077234962 100644 --- a/src/EFCore.Relational/Metadata/Internal/FunctionColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/FunctionColumn.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class FunctionColumn : ColumnBase, IFunctionColumn +public class FunctionColumn : ColumnBase, IFunctionColumn { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -48,9 +48,9 @@ IStoreFunction IFunctionColumn.Function } /// - IEnumerable IFunctionColumn.PropertyMappings + IReadOnlyList IFunctionColumn.PropertyMappings { [DebuggerStepThrough] - get => PropertyMappings.Cast(); + get => PropertyMappings; } } diff --git a/src/EFCore.Relational/Metadata/Internal/FunctionColumnMapping.cs b/src/EFCore.Relational/Metadata/Internal/FunctionColumnMapping.cs index 04af21264e8..ec3c11cb122 100644 --- a/src/EFCore.Relational/Metadata/Internal/FunctionColumnMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/FunctionColumnMapping.cs @@ -29,8 +29,13 @@ public FunctionColumnMapping( public virtual IFunctionMapping FunctionMapping => (IFunctionMapping)TableMapping; - /// - public override RelationalTypeMapping TypeMapping + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override RelationalTypeMapping GetTypeMapping() => Property.FindRelationalTypeMapping( StoreObjectIdentifier.DbFunction(FunctionMapping.DbFunction.Name))!; diff --git a/src/EFCore.Relational/Metadata/Internal/FunctionMapping.cs b/src/EFCore.Relational/Metadata/Internal/FunctionMapping.cs index 8eb7bd6b9a0..dba228f71fc 100644 --- a/src/EFCore.Relational/Metadata/Internal/FunctionMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/FunctionMapping.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class FunctionMapping : TableMappingBase, IFunctionMapping +public class FunctionMapping : TableMappingBase, IFunctionMapping { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -46,6 +46,6 @@ public override string ToString() IEnumerable IFunctionMapping.ColumnMappings { [DebuggerStepThrough] - get => ColumnMappings.Cast(); + get => ColumnMappings; } } diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 2a2e6c775e5..7b266507996 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -150,7 +150,7 @@ public static IRelationalModel Create( foreach (var table in databaseModel.Tables.Values) { - PopulateRowInternalForeignKeys(table); + PopulateRowInternalForeignKeys(table); PopulateTableConfiguration(table, designTime); if (relationalAnnotationProvider != null) @@ -170,11 +170,6 @@ public static IRelationalModel Create( index.AddAnnotations(relationalAnnotationProvider.For(index, designTime)); } - foreach (var constraint in table.ForeignKeyConstraints.Values) - { - constraint.AddAnnotations(relationalAnnotationProvider.For(constraint, designTime)); - } - if (designTime) { foreach (var checkConstraint in table.CheckConstraints.Values) @@ -187,6 +182,19 @@ public static IRelationalModel Create( { ((AnnotatableBase)trigger).AddAnnotations(relationalAnnotationProvider.For(trigger, designTime)); } + } + } + + foreach (var table in databaseModel.Tables.Values) + { + PopulateForeignKeyConstraints(table); + + if (relationalAnnotationProvider != null) + { + foreach (var constraint in table.ForeignKeyConstraints) + { + constraint.AddAnnotations(relationalAnnotationProvider.For(constraint, designTime)); + } table.AddAnnotations(relationalAnnotationProvider.For(table, designTime)); } @@ -194,7 +202,7 @@ public static IRelationalModel Create( foreach (var view in databaseModel.Views.Values) { - PopulateRowInternalForeignKeys(view); + PopulateRowInternalForeignKeys(view); if (relationalAnnotationProvider != null) { @@ -251,7 +259,7 @@ private static void AddDefaultMappings(RelationalModel databaseModel, IEntityTyp { var mappedType = entityType; Check.DebugAssert(entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.DefaultMappings) == null, "not null"); - var tableMappings = new List(); + var tableMappings = new List>(); entityType.AddRuntimeAnnotation(RelationalAnnotationNames.DefaultMappings, tableMappings); var isTpc = entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy; @@ -265,7 +273,7 @@ private static void AddDefaultMappings(RelationalModel databaseModel, IEntityTyp databaseModel.DefaultTables.Add(mappedTableName, defaultTable); } - var tableMapping = new TableMappingBase(entityType, defaultTable, includesDerivedTypes: !isTpc && mappedType == entityType) + var tableMapping = new TableMappingBase(entityType, defaultTable, includesDerivedTypes: !isTpc && mappedType == entityType) { // Table splitting is not supported for default mapping IsSharedTablePrincipal = true, @@ -282,10 +290,10 @@ private static void AddDefaultMappings(RelationalModel databaseModel, IEntityTyp continue; } - var column = (ColumnBase?)defaultTable.FindColumn(columnName); + var column = (ColumnBase?)defaultTable.FindColumn(columnName); if (column == null) { - column = new ColumnBase(columnName, property.GetColumnType(), defaultTable) + column = new ColumnBase(columnName, property.GetColumnType(), defaultTable) { IsNullable = property.IsColumnNullable() }; @@ -297,8 +305,8 @@ private static void AddDefaultMappings(RelationalModel databaseModel, IEntityTyp } var columnMapping = new ColumnMappingBase(property, column, tableMapping); - tableMapping.ColumnMappings.Add(columnMapping); - column.PropertyMappings.Add(columnMapping); + tableMapping.AddColumnMapping(columnMapping); + column.AddPropertyMapping(columnMapping); if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.DefaultColumnMappings) is not SortedSet columnMappings) @@ -310,7 +318,7 @@ private static void AddDefaultMappings(RelationalModel databaseModel, IEntityTyp columnMappings.Add(columnMapping); } - if (tableMapping.ColumnMappings.Count != 0 + if (((ITableMappingBase)tableMapping).ColumnMappings.Any() || tableMappings.Count == 0) { tableMappings.Add(tableMapping); @@ -394,8 +402,8 @@ private static void AddTables(RelationalModel databaseModel, IEntityType entityT } var columnMapping = new ColumnMapping(property, column, tableMapping); - tableMapping.ColumnMappings.Add(columnMapping); - column.PropertyMappings.Add(columnMapping); + tableMapping.AddColumnMapping(columnMapping); + column.AddPropertyMapping(columnMapping); if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableColumnMappings) is not SortedSet columnMappings) @@ -407,7 +415,7 @@ private static void AddTables(RelationalModel databaseModel, IEntityType entityT columnMappings.Add(columnMapping); } - if (tableMapping.ColumnMappings.Count != 0 + if (((ITableMappingBase)tableMapping).ColumnMappings.Any() || tableMappings.Count == 0) { tableMappings.Add(tableMapping); @@ -491,8 +499,8 @@ private static void AddViews(RelationalModel databaseModel, IEntityType entityTy } var columnMapping = new ViewColumnMapping(property, column, viewMapping); - viewMapping.ColumnMappings.Add(columnMapping); - column.PropertyMappings.Add(columnMapping); + viewMapping.AddColumnMapping(columnMapping); + column.AddPropertyMapping(columnMapping); if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.ViewColumnMappings) is not SortedSet columnMappings) @@ -504,7 +512,7 @@ private static void AddViews(RelationalModel databaseModel, IEntityType entityTy columnMappings.Add(columnMapping); } - if (viewMapping.ColumnMappings.Count != 0 + if (((ITableMappingBase)viewMapping).ColumnMappings.Any() || viewMappings.Count == 0) { viewMappings.Add(viewMapping); @@ -594,8 +602,8 @@ private static void AddSqlQueries(RelationalModel databaseModel, IEntityType ent } var columnMapping = new SqlQueryColumnMapping(property, column, queryMapping); - queryMapping.ColumnMappings.Add(columnMapping); - column.PropertyMappings.Add(columnMapping); + queryMapping.AddColumnMapping(columnMapping); + column.AddPropertyMapping(columnMapping); if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.SqlQueryColumnMappings) is not SortedSet columnMappings) @@ -616,7 +624,7 @@ private static void AddSqlQueries(RelationalModel databaseModel, IEntityType ent entityType.AddRuntimeAnnotation(RelationalAnnotationNames.SqlQueryMappings, queryMappings); } - if (queryMapping.ColumnMappings.Count != 0 + if (((ITableMappingBase)queryMapping).ColumnMappings.Any() || queryMappings.Count == 0) { queryMappings.Add(queryMapping); @@ -661,7 +669,7 @@ private static void AddMappedFunctions(RelationalModel databaseModel, IEntityTyp entityType.AddRuntimeAnnotation(RelationalAnnotationNames.FunctionMappings, functionMappings); } - if (functionMapping.ColumnMappings.Count != 0 + if (((ITableMappingBase)functionMapping).ColumnMappings.Any() || functionMappings.Count == 0) { functionMappings.Add(functionMapping); @@ -744,8 +752,8 @@ private static FunctionMapping CreateFunctionMapping( } var columnMapping = new FunctionColumnMapping(property, column, functionMapping); - functionMapping.ColumnMappings.Add(columnMapping); - column.PropertyMappings.Add(columnMapping); + functionMapping.AddColumnMapping(columnMapping); + column.AddPropertyMapping(columnMapping); if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.FunctionColumnMappings) is not SortedSet columnMappings) @@ -799,117 +807,6 @@ private static void PopulateTableConfiguration(Table table, bool designTime) } var entityType = entityTypeMapping.EntityType; - foreach (var foreignKey in entityType.GetForeignKeys()) - { - var firstPrincipalMapping = true; - foreach (var principalMapping in foreignKey.PrincipalEntityType.GetTableMappings().Reverse()) - { - if (firstPrincipalMapping - && !principalMapping.IncludesDerivedTypes - && foreignKey.PrincipalEntityType.GetDirectlyDerivedTypes().Any(e => e.GetTableMappings().Any())) - { - // Derived principal entity types are mapped to different tables, so the constraint is not enforceable - // TODO: Allow this to be overriden #15854 - break; - } - - firstPrincipalMapping = false; - - var principalTable = (Table)principalMapping.Table; - var name = foreignKey.GetConstraintName( - storeObject, - StoreObjectIdentifier.Table(principalTable.Name, principalTable.Schema)); - if (name == null) - { - continue; - } - - var foreignKeyConstraints = foreignKey.FindRuntimeAnnotationValue(RelationalAnnotationNames.ForeignKeyMappings) - as SortedSet; - if (table.ForeignKeyConstraints.TryGetValue(name, out var constraint)) - { - if (foreignKeyConstraints == null) - { - foreignKeyConstraints = new SortedSet(ForeignKeyConstraintComparer.Instance); - foreignKey.AddRuntimeAnnotation(RelationalAnnotationNames.ForeignKeyMappings, foreignKeyConstraints); - } - - foreignKeyConstraints.Add(constraint); - - constraint.MappedForeignKeys.Add(foreignKey); - break; - } - - var principalColumns = new Column[foreignKey.Properties.Count]; - for (var i = 0; i < principalColumns.Length; i++) - { - if (principalTable.FindColumn(foreignKey.PrincipalKey.Properties[i]) is Column principalColumn) - { - principalColumns[i] = principalColumn; - } - else - { - principalColumns = null; - break; - } - } - - if (principalColumns == null) - { - continue; - } - - var columns = new Column[foreignKey.Properties.Count]; - for (var i = 0; i < columns.Length; i++) - { - if (table.FindColumn(foreignKey.Properties[i]) is Column foreignKeyColumn) - { - columns[i] = foreignKeyColumn; - } - else - { - columns = null; - break; - } - } - - if (columns == null) - { - break; - } - - if (columns.SequenceEqual(principalColumns)) - { - // Principal and dependent properties are mapped to the same columns so the constraint is redundant - break; - } - - if (entityTypeMapping.IncludesDerivedTypes - && foreignKey.DeclaringEntityType != entityType - && entityType.FindPrimaryKey() is IKey primaryKey - && foreignKey.Properties.SequenceEqual(primaryKey.Properties)) - { - // The identifying FK constraint is needed to be created only on the table that corresponds - // to the declaring entity type - break; - } - - constraint = new ForeignKeyConstraint( - name, table, principalTable, columns, principalColumns, ToReferentialAction(foreignKey.DeleteBehavior)); - constraint.MappedForeignKeys.Add(foreignKey); - - if (foreignKeyConstraints == null) - { - foreignKeyConstraints = new SortedSet(ForeignKeyConstraintComparer.Instance); - foreignKey.AddRuntimeAnnotation(RelationalAnnotationNames.ForeignKeyMappings, foreignKeyConstraints); - } - - foreignKeyConstraints.Add(constraint); - table.ForeignKeyConstraints.Add(name, constraint); - break; - } - } - foreach (var key in entityType.GetKeys()) { var name = key.GetName(storeObject); @@ -1041,13 +938,14 @@ private static void PopulateTableConfiguration(Table table, bool designTime) } } - private static void PopulateRowInternalForeignKeys(TableBase table) + private static void PopulateRowInternalForeignKeys(TableBase table) + where TColumnMapping : class, IColumnMappingBase { SortedDictionary>? internalForeignKeyMap = null; SortedDictionary>? referencingInternalForeignKeyMap = null; - TableMappingBase? mainMapping = null; + TableMappingBase? mainMapping = null; var mappedEntityTypes = new HashSet(); - foreach (TableMappingBase entityTypeMapping in ((ITableBase)table).EntityTypeMappings) + foreach (TableMappingBase entityTypeMapping in table.EntityTypeMappings) { var entityType = entityTypeMapping.EntityType; mappedEntityTypes.Add(entityType); @@ -1165,6 +1063,146 @@ private static void PopulateRowInternalForeignKeys(TableBase table) table.OptionalEntityTypes = optionalTypes; } + else + { + table.OptionalEntityTypes = table.EntityTypeMappings.ToDictionary(etm => etm.EntityType, et => false); + } + } + + private static void PopulateForeignKeyConstraints(Table table) + { + var storeObject = StoreObjectIdentifier.Table(table.Name, table.Schema); + foreach (var entityTypeMapping in ((ITable)table).EntityTypeMappings) + { + if (!entityTypeMapping.IncludesDerivedTypes + && entityTypeMapping.EntityType.GetTableMappings().Any(m => m.IncludesDerivedTypes)) + { + continue; + } + + var entityType = entityTypeMapping.EntityType; + foreach (var foreignKey in entityType.GetForeignKeys()) + { + var firstPrincipalMapping = true; + foreach (var principalMapping in foreignKey.PrincipalEntityType.GetTableMappings().Reverse()) + { + if (firstPrincipalMapping + && !principalMapping.IncludesDerivedTypes + && foreignKey.PrincipalEntityType.GetDirectlyDerivedTypes().Any(e => e.GetTableMappings().Any())) + { + // Derived principal entity types are mapped to different tables, so the constraint is not enforceable + // TODO: Allow this to be overriden #15854 + break; + } + + firstPrincipalMapping = false; + + var principalTable = (Table)principalMapping.Table; + var principalStoreObject = StoreObjectIdentifier.Table(principalTable.Name, principalTable.Schema); + var name = foreignKey.GetConstraintName(storeObject, principalStoreObject); + if (name == null) + { + continue; + } + + var foreignKeyConstraints = foreignKey.FindRuntimeAnnotationValue(RelationalAnnotationNames.ForeignKeyMappings) + as SortedSet; + var constraint = table.ForeignKeyConstraints.FirstOrDefault(fk => fk.Name == name); + if (constraint != null) + { + if (foreignKeyConstraints == null) + { + foreignKeyConstraints = new SortedSet(ForeignKeyConstraintComparer.Instance); + foreignKey.AddRuntimeAnnotation(RelationalAnnotationNames.ForeignKeyMappings, foreignKeyConstraints); + } + + foreignKeyConstraints.Add(constraint); + + constraint.MappedForeignKeys.Add(foreignKey); + break; + } + + var principalColumns = new Column[foreignKey.Properties.Count]; + for (var i = 0; i < principalColumns.Length; i++) + { + if (principalTable.FindColumn(foreignKey.PrincipalKey.Properties[i]) is Column principalColumn) + { + principalColumns[i] = principalColumn; + } + else + { + principalColumns = null; + break; + } + } + + if (principalColumns == null) + { + continue; + } + + var columns = new Column[foreignKey.Properties.Count]; + for (var i = 0; i < columns.Length; i++) + { + if (table.FindColumn(foreignKey.Properties[i]) is Column foreignKeyColumn) + { + columns[i] = foreignKeyColumn; + } + else + { + columns = null; + break; + } + } + + if (columns == null) + { + break; + } + + if (columns.SequenceEqual(principalColumns)) + { + // Principal and dependent properties are mapped to the same columns so the constraint is redundant + break; + } + + if (entityTypeMapping.IncludesDerivedTypes + && foreignKey.DeclaringEntityType != entityType + && entityType.FindPrimaryKey() is IKey primaryKey + && foreignKey.Properties.SequenceEqual(primaryKey.Properties)) + { + // The identifying FK constraint is needed to be created only on the table that corresponds + // to the declaring entity type + break; + } + + var principalUniqueConstraintName = foreignKey.PrincipalKey.GetName(principalStoreObject); + if (principalUniqueConstraintName == null) + { + continue; + } + + var principalUniqueConstraint = principalTable.FindUniqueConstraint(principalUniqueConstraintName)!; + + Check.DebugAssert(principalUniqueConstraint != null, "Invalid unique constraint " + principalUniqueConstraintName); + + constraint = new ForeignKeyConstraint( + name, table, principalTable, columns, principalUniqueConstraint, ToReferentialAction(foreignKey.DeleteBehavior)); + constraint.MappedForeignKeys.Add(foreignKey); + + if (foreignKeyConstraints == null) + { + foreignKeyConstraints = new SortedSet(ForeignKeyConstraintComparer.Instance); + foreignKey.AddRuntimeAnnotation(RelationalAnnotationNames.ForeignKeyMappings, foreignKeyConstraints); + } + + foreignKeyConstraints.Add(constraint); + table.ForeignKeyConstraints.Add(constraint); + principalTable.ReferencingForeignKeyConstraints.Add(constraint); + break; + } + } + } } /// diff --git a/src/EFCore.Relational/Metadata/Internal/SqlQueryColumn.cs b/src/EFCore.Relational/Metadata/Internal/SqlQueryColumn.cs index c1b9a529d6c..fcf44b197b4 100644 --- a/src/EFCore.Relational/Metadata/Internal/SqlQueryColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/SqlQueryColumn.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class SqlQueryColumn : ColumnBase, ISqlQueryColumn +public class SqlQueryColumn : ColumnBase, ISqlQueryColumn { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -48,9 +48,9 @@ ISqlQuery ISqlQueryColumn.SqlQuery } /// - IEnumerable ISqlQueryColumn.PropertyMappings + IReadOnlyList ISqlQueryColumn.PropertyMappings { [DebuggerStepThrough] - get => PropertyMappings.Cast(); + get => PropertyMappings; } } diff --git a/src/EFCore.Relational/Metadata/Internal/SqlQueryColumnMapping.cs b/src/EFCore.Relational/Metadata/Internal/SqlQueryColumnMapping.cs index 5ef439a76a5..f94fbf9dd79 100644 --- a/src/EFCore.Relational/Metadata/Internal/SqlQueryColumnMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/SqlQueryColumnMapping.cs @@ -29,8 +29,13 @@ public SqlQueryColumnMapping( public virtual ISqlQueryMapping SqlQueryMapping => (ISqlQueryMapping)TableMapping; - /// - public override RelationalTypeMapping TypeMapping + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override RelationalTypeMapping GetTypeMapping() => Property.FindRelationalTypeMapping( StoreObjectIdentifier.SqlQuery(SqlQueryMapping.SqlQuery.Name))!; diff --git a/src/EFCore.Relational/Metadata/Internal/SqlQueryMapping.cs b/src/EFCore.Relational/Metadata/Internal/SqlQueryMapping.cs index 1ea1c5fd311..0453028c964 100644 --- a/src/EFCore.Relational/Metadata/Internal/SqlQueryMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/SqlQueryMapping.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class SqlQueryMapping : TableMappingBase, ISqlQueryMapping +public class SqlQueryMapping : TableMappingBase, ISqlQueryMapping { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -45,6 +45,6 @@ public override string ToString() IEnumerable ISqlQueryMapping.ColumnMappings { [DebuggerStepThrough] - get => ColumnMappings.Cast(); + get => ColumnMappings; } } diff --git a/src/EFCore.Relational/Metadata/Internal/Table.cs b/src/EFCore.Relational/Metadata/Internal/Table.cs index 7dd967f32a1..7fc96c7b31c 100644 --- a/src/EFCore.Relational/Metadata/Internal/Table.cs +++ b/src/EFCore.Relational/Metadata/Internal/Table.cs @@ -22,7 +22,7 @@ public class Table : TableBase, ITable public Table(string name, string? schema, RelationalModel model) : base(name, schema, model) { - Columns = new SortedDictionary(new ColumnNameComparer(this)); + Columns = new(new ColumnNameComparer(this)); } /// @@ -31,8 +31,17 @@ public Table(string name, string? schema, RelationalModel model) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual SortedDictionary ForeignKeyConstraints { get; } - = new(); + public virtual SortedSet ForeignKeyConstraints { get; } + = new(ForeignKeyConstraintComparer.Instance); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual SortedSet ReferencingForeignKeyConstraints { get; } + = new(ForeignKeyConstraintComparer.Instance); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -168,7 +177,13 @@ IEnumerable ITable.Columns IEnumerable ITable.ForeignKeyConstraints { [DebuggerStepThrough] - get => ForeignKeyConstraints.Values; + get => ForeignKeyConstraints; + } + + IEnumerable ITable.ReferencingForeignKeyConstraints + { + [DebuggerStepThrough] + get => ReferencingForeignKeyConstraints; } /// diff --git a/src/EFCore.Relational/Metadata/Internal/TableBase.cs b/src/EFCore.Relational/Metadata/Internal/TableBase.cs index 5dff0aa590a..6c735468904 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableBase.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableBase.cs @@ -63,7 +63,7 @@ public override bool IsReadOnly /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual SortedSet EntityTypeMappings { get; } + public virtual SortedSet EntityTypeMappings { get; } = new(TableMappingBaseComparer.Instance); /// @@ -72,7 +72,7 @@ public override bool IsReadOnly /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual SortedDictionary Columns { get; protected set; } + public virtual SortedDictionary Columns { get; protected set; } = new(StringComparer.Ordinal); /// diff --git a/src/EFCore.Relational/Metadata/Internal/TableIndex.cs b/src/EFCore.Relational/Metadata/Internal/TableIndex.cs index 318a1c97453..31866e8084d 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableIndex.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableIndex.cs @@ -1,6 +1,9 @@ // 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.Internal; +using Microsoft.EntityFrameworkCore.Update.Internal; + namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// @@ -76,6 +79,19 @@ public virtual IReadOnlyList? IsDescending public virtual string? Filter => MappedIndexes.First().GetFilter(StoreObjectIdentifier.Table(Table.Name, Table.Schema)); + private IRowIndexValueFactory? _rowIndexValueFactory; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IRowIndexValueFactory GetRowIndexValueFactory() + => NonCapturingLazyInitializer.EnsureInitialized( + ref _rowIndexValueFactory, this, + static constraint => constraint.Table.Model.Model.GetRelationalDependencies().RowIndexValueFactoryFactory.Create(constraint)); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.Relational/Metadata/Internal/TableMapping.cs b/src/EFCore.Relational/Metadata/Internal/TableMapping.cs index 50fed714fe8..09c8e2a2ec2 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableMapping.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class TableMapping : TableMappingBase, ITableMapping +public class TableMapping : TableMappingBase, ITableMapping { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -49,6 +49,6 @@ ITableBase ITableMappingBase.Table IEnumerable ITableMapping.ColumnMappings { [DebuggerStepThrough] - get => ColumnMappings.Cast(); + get => ColumnMappings; } } diff --git a/src/EFCore.Relational/Metadata/Internal/TableMappingBase.cs b/src/EFCore.Relational/Metadata/Internal/TableMappingBase.cs index 8737add532e..619a422333d 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableMappingBase.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableMappingBase.cs @@ -9,7 +9,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class TableMappingBase : Annotatable, ITableMappingBase +public class TableMappingBase : Annotatable, ITableMappingBase + where TColumnMapping : class, IColumnMappingBase { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -53,8 +54,27 @@ public override bool IsReadOnly /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual SortedSet ColumnMappings { get; } - = new(ColumnMappingBaseComparer.Instance); + protected virtual List ColumnMappings { get; } + = new(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool AddColumnMapping(TColumnMapping columnMapping) + { + if (ColumnMappings.IndexOf(columnMapping, ColumnMappingBaseComparer.Instance) != -1) + { + return false; + } + + ColumnMappings.Add(columnMapping); + ColumnMappings.Sort(ColumnMappingBaseComparer.Instance); + + return true; + } /// public virtual bool IncludesDerivedTypes { get; } diff --git a/src/EFCore.Relational/Metadata/Internal/UniqueConstraint.cs b/src/EFCore.Relational/Metadata/Internal/UniqueConstraint.cs index 547bbd08a72..eec6a5c4422 100644 --- a/src/EFCore.Relational/Metadata/Internal/UniqueConstraint.cs +++ b/src/EFCore.Relational/Metadata/Internal/UniqueConstraint.cs @@ -1,6 +1,9 @@ // 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.Internal; +using Microsoft.EntityFrameworkCore.Update.Internal; + namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// @@ -63,6 +66,19 @@ public UniqueConstraint( public override bool IsReadOnly => Table.Model.IsReadOnly; + private IRowKeyValueFactory? _rowKeyValueFactory; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IRowKeyValueFactory GetRowKeyValueFactory() + => NonCapturingLazyInitializer.EnsureInitialized( + ref _rowKeyValueFactory, this, + static constraint => constraint.Table.Model.Model.GetRelationalDependencies().RowKeyValueFactoryFactory.Create(constraint)); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.Relational/Metadata/Internal/ViewColumn.cs b/src/EFCore.Relational/Metadata/Internal/ViewColumn.cs index bb00d23e4d9..635fd1185bb 100644 --- a/src/EFCore.Relational/Metadata/Internal/ViewColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/ViewColumn.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class ViewColumn : ColumnBase, IViewColumn +public class ViewColumn : ColumnBase, IViewColumn { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -48,9 +48,9 @@ IView IViewColumn.View } /// - IEnumerable IViewColumn.PropertyMappings + IReadOnlyList IViewColumn.PropertyMappings { [DebuggerStepThrough] - get => PropertyMappings.Cast(); + get => PropertyMappings; } } diff --git a/src/EFCore.Relational/Metadata/Internal/ViewColumnMapping.cs b/src/EFCore.Relational/Metadata/Internal/ViewColumnMapping.cs index 5d15a0ccfd2..5d2d2f5082a 100644 --- a/src/EFCore.Relational/Metadata/Internal/ViewColumnMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/ViewColumnMapping.cs @@ -29,8 +29,13 @@ public ViewColumnMapping( public virtual IViewMapping ViewMapping => (IViewMapping)TableMapping; - /// - public override RelationalTypeMapping TypeMapping + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override RelationalTypeMapping GetTypeMapping() => Property.FindRelationalTypeMapping( StoreObjectIdentifier.View(ViewMapping.View.Name, ViewMapping.View.Schema))!; diff --git a/src/EFCore.Relational/Metadata/Internal/ViewMapping.cs b/src/EFCore.Relational/Metadata/Internal/ViewMapping.cs index 4197e3bdbb3..0d92331d077 100644 --- a/src/EFCore.Relational/Metadata/Internal/ViewMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/ViewMapping.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class ViewMapping : TableMappingBase, IViewMapping +public class ViewMapping : TableMappingBase, IViewMapping { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -42,6 +42,6 @@ public override string ToString() IEnumerable IViewMapping.ColumnMappings { [DebuggerStepThrough] - get => ColumnMappings.Cast(); + get => ColumnMappings; } } diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index 7e9ebc3ba4e..d61d58aa2ba 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -1122,7 +1122,7 @@ private void Initialize( { throw new InvalidOperationException( RelationalStrings.DefaultValueUnspecified( - (column.Table.Name, column.Table.Schema).FormatTable(), + column.Table.SchemaQualifiedName, column.Name)); } @@ -1130,7 +1130,7 @@ private void Initialize( { throw new InvalidOperationException( RelationalStrings.DefaultValueSqlUnspecified( - (column.Table.Name, column.Table.Schema).FormatTable(), + column.Table.SchemaQualifiedName, column.Name)); } @@ -1139,7 +1139,7 @@ private void Initialize( throw new InvalidOperationException( RelationalStrings.ComputedColumnSqlUnspecified( column.Name, - (column.Table.Name, column.Table.Schema).FormatTable())); + column.Table.SchemaQualifiedName)); } var property = column.PropertyMappings.First().Property; diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index dfc6f727e99..0ca37357f9e 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -429,6 +429,14 @@ public static string DuplicateColumnNamePrecisionMismatch(object? entityType1, o GetString("DuplicateColumnNamePrecisionMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table), nameof(precision1), nameof(precision2)), entityType1, property1, entityType2, property2, columnName, table, precision1, precision2); + /// + /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured to use differing provider types ('{type1}' and '{type2}'). + /// + public static string DuplicateColumnNameProviderTypeMismatch(object? entityType1, object? property1, object? entityType2, object? property2, object? columnName, object? table, object? type1, object? type2) + => string.Format( + GetString("DuplicateColumnNameProviderTypeMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table), nameof(type1), nameof(type2)), + entityType1, property1, entityType2, property2, columnName, table, type1, type2); + /// /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different scales ('{scale1}' and '{scale2}'). /// @@ -1001,6 +1009,14 @@ public static string NonTphViewClash(object? entityType, object? otherEntityType public static string NoProviderConfigured => GetString("NoProviderConfigured"); + /// + /// Unable to modify a row in table '{table}' because its key column '{keyColumn}' is null. + /// + public static string NullKeyValue(object? table, object? keyColumn) + => string.Format( + GetString("NullKeyValue", nameof(table), nameof(keyColumn)), + table, keyColumn); + /// /// Expression '{sqlExpression}' in the SQL tree does not have a type mapping assigned. /// @@ -1192,7 +1208,7 @@ public static string TriggerOnUnmappedEntityType(object? trigger, object? entity trigger, entityType); /// - /// Trigger '{trigger}' with table '{triggerTable}' is defined on entity type '{entityType}', which is mapped to table '{entityTable}'. See https://aka.ms/efcore-docs-triggers for more information on triggers. + /// Trigger '{trigger}' for table '{triggerTable}' is defined on entity type '{entityType}', which is mapped to table '{entityTable}'. See https://aka.ms/efcore-docs-triggers for more information on triggers. /// public static string TriggerWithMismatchedTable(object? trigger, object? triggerTable, object? entityType, object? entityTable) => string.Format( diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 800ae376254..f593fd008f0 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -274,6 +274,9 @@ '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different precisions ('{precision1}' and '{precision2}'). + + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured to use differing provider types ('{type1}' and '{type2}'). + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different scales ('{scale1}' and '{scale2}'). @@ -743,6 +746,9 @@ No relational database providers are configured. Configure a database provider using 'OnConfiguring' or by creating an ImmutableDbContextOptions with a configured database provider and passing it to the context. + + Unable to modify a row in table '{table}' because its key column '{keyColumn}' is null. + Expression '{sqlExpression}' in the SQL tree does not have a type mapping assigned. diff --git a/src/EFCore.Relational/Storage/Internal/TypeMappedRelationalParameter.cs b/src/EFCore.Relational/Storage/Internal/TypeMappedRelationalParameter.cs index 93f7223cc0f..2bef9a633be 100644 --- a/src/EFCore.Relational/Storage/Internal/TypeMappedRelationalParameter.cs +++ b/src/EFCore.Relational/Storage/Internal/TypeMappedRelationalParameter.cs @@ -50,8 +50,6 @@ public TypeMappedRelationalParameter( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override void AddDbParameter(DbCommand command, object? value) - => command.Parameters - .Add( - RelationalTypeMapping - .CreateParameter(command, Name, value, IsNullable)); + => command.Parameters.Add( + RelationalTypeMapping.CreateParameter(command, Name, value, IsNullable)); } diff --git a/src/EFCore.Relational/Storage/RelationalTypeMapping.cs b/src/EFCore.Relational/Storage/RelationalTypeMapping.cs index fff9daad971..00c7f278e84 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMapping.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMapping.cs @@ -3,6 +3,7 @@ using System.Data; using System.Globalization; +using Microsoft.EntityFrameworkCore.Internal; namespace Microsoft.EntityFrameworkCore.Storage; @@ -260,6 +261,8 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p => this; } + private ValueComparer? _providerValueComparer; + /// /// Initializes a new instance of the class. /// @@ -274,55 +277,17 @@ protected RelationalTypeMapping(RelationalTypeMappingParameters parameters) StoreTypeNameBase = storeTypeNameBase; StoreType = ProcessStoreType(parameters, storeType, storeTypeNameBase); - } - /// - /// Processes the store type name to add appropriate postfix/prefix text as needed. - /// - /// The parameters for this mapping. - /// The specified store type name. - /// The calculated based name - /// The store type name to use. - protected virtual string ProcessStoreType( - RelationalTypeMappingParameters parameters, - string storeType, - string storeTypeNameBase) - { - var size = parameters.Size; - - if (size != null - && parameters.StoreTypePostfix == StoreTypePostfix.Size) - { - storeType = storeTypeNameBase + "(" + size + ")"; - } - else if (parameters.StoreTypePostfix == StoreTypePostfix.PrecisionAndScale - || parameters.StoreTypePostfix == StoreTypePostfix.Precision) + static string GetBaseName(string storeType) { - var precision = parameters.Precision; - if (precision != null) + var openParen = storeType.IndexOf("(", StringComparison.Ordinal); + if (openParen >= 0) { - var scale = parameters.Scale; - storeType = storeTypeNameBase - + "(" - + (scale == null || parameters.StoreTypePostfix == StoreTypePostfix.Precision - ? precision.ToString() - : precision + "," + scale) - + ")"; + storeType = storeType[..openParen]; } - } - return storeType; - } - - private static string GetBaseName(string storeType) - { - var openParen = storeType.IndexOf("(", StringComparison.Ordinal); - if (openParen >= 0) - { - storeType = storeType[..openParen]; + return storeType; } - - return storeType; } /// @@ -357,48 +322,6 @@ protected RelationalTypeMapping( /// protected new virtual RelationalTypeMappingParameters Parameters { get; } - /// - /// Creates a copy of this mapping. - /// - /// The parameters for this mapping. - /// The newly created mapping. - protected abstract RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters); - - /// - /// Creates a copy of this mapping. - /// - /// The name of the database type. - /// The size of data the property is configured to store, or null if no size is configured. - /// The newly created mapping. - public virtual RelationalTypeMapping Clone(string storeType, int? size) - => Clone(Parameters.WithStoreTypeAndSize(storeType, size)); - - /// - /// Creates a copy of this mapping. - /// - /// The precision of data the property is configured to store, or null if no size is configured. - /// The scale of data the property is configured to store, or null if no size is configured. - /// The newly created mapping. - public virtual RelationalTypeMapping Clone(int? precision, int? scale) - => Clone(Parameters.WithPrecisionAndScale(precision, scale)); - - /// - /// Returns a new copy of this type mapping with the given - /// added. - /// - /// The converter to use. - /// A new type mapping - public override CoreTypeMapping Clone(ValueConverter? converter) - => Clone(Parameters.WithComposedConverter(converter)); - - /// - /// Clones the type mapping to update facets from the mapping info, if needed. - /// - /// The mapping info containing the facets to use. - /// The cloned mapping, or the original mapping if no clone was needed. - public virtual RelationalTypeMapping Clone(in RelationalTypeMappingInfo mappingInfo) - => Clone(Parameters.WithTypeMappingInfo(mappingInfo)); - /// /// Gets the name of the database type. /// @@ -457,6 +380,95 @@ public virtual bool IsFixedLength protected virtual string SqlLiteralFormatString => "{0}"; + /// + /// A for the provider CLR type values. + /// + public virtual ValueComparer ProviderValueComparer + => NonCapturingLazyInitializer.EnsureInitialized( + ref _providerValueComparer, + this, + static c => ValueComparer.CreateDefault(c.Converter?.ProviderClrType ?? c.ClrType, favorStructuralComparisons: true)); + + /// + /// Creates a copy of this mapping. + /// + /// The parameters for this mapping. + /// The newly created mapping. + protected abstract RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters); + + /// + /// Creates a copy of this mapping. + /// + /// The name of the database type. + /// The size of data the property is configured to store, or null if no size is configured. + /// The newly created mapping. + public virtual RelationalTypeMapping Clone(string storeType, int? size) + => Clone(Parameters.WithStoreTypeAndSize(storeType, size)); + + /// + /// Creates a copy of this mapping. + /// + /// The precision of data the property is configured to store, or null if no size is configured. + /// The scale of data the property is configured to store, or null if no size is configured. + /// The newly created mapping. + public virtual RelationalTypeMapping Clone(int? precision, int? scale) + => Clone(Parameters.WithPrecisionAndScale(precision, scale)); + + /// + /// Returns a new copy of this type mapping with the given + /// added. + /// + /// The converter to use. + /// A new type mapping + public override CoreTypeMapping Clone(ValueConverter? converter) + => Clone(Parameters.WithComposedConverter(converter)); + + /// + /// Clones the type mapping to update facets from the mapping info, if needed. + /// + /// The mapping info containing the facets to use. + /// The cloned mapping, or the original mapping if no clone was needed. + public virtual RelationalTypeMapping Clone(in RelationalTypeMappingInfo mappingInfo) + => Clone(Parameters.WithTypeMappingInfo(mappingInfo)); + + /// + /// Processes the store type name to add appropriate postfix/prefix text as needed. + /// + /// The parameters for this mapping. + /// The specified store type name. + /// The calculated based name + /// The store type name to use. + protected virtual string ProcessStoreType( + RelationalTypeMappingParameters parameters, + string storeType, + string storeTypeNameBase) + { + var size = parameters.Size; + + if (size != null + && parameters.StoreTypePostfix == StoreTypePostfix.Size) + { + storeType = storeTypeNameBase + "(" + size + ")"; + } + else if (parameters.StoreTypePostfix == StoreTypePostfix.PrecisionAndScale + || parameters.StoreTypePostfix == StoreTypePostfix.Precision) + { + var precision = parameters.Precision; + if (precision != null) + { + var scale = parameters.Scale; + storeType = storeTypeNameBase + + "(" + + (scale == null || parameters.StoreTypePostfix == StoreTypePostfix.Precision + ? precision.ToString() + : precision + "," + scale) + + ")"; + } + } + + return storeType; + } + /// /// Creates a with the appropriate type information configured. /// diff --git a/src/EFCore.Relational/Update/ColumnModificationParameters.cs b/src/EFCore.Relational/Update/ColumnModificationParameters.cs index 7e063db2625..61794cc2194 100644 --- a/src/EFCore.Relational/Update/ColumnModificationParameters.cs +++ b/src/EFCore.Relational/Update/ColumnModificationParameters.cs @@ -131,8 +131,6 @@ public ColumnModificationParameters( GenerateParameterName = null; Entry = null; - - //IsConcurrencyToken = false; } /// @@ -175,7 +173,5 @@ public ColumnModificationParameters( GenerateParameterName = generateParameterName; Entry = entry; - - //IsConcurrencyToken = false; } } diff --git a/src/EFCore.Relational/Update/IReadOnlyModificationCommand.cs b/src/EFCore.Relational/Update/IReadOnlyModificationCommand.cs index ad125cfb000..43586af2dd8 100644 --- a/src/EFCore.Relational/Update/IReadOnlyModificationCommand.cs +++ b/src/EFCore.Relational/Update/IReadOnlyModificationCommand.cs @@ -17,6 +17,11 @@ namespace Microsoft.EntityFrameworkCore.Update; /// public interface IReadOnlyModificationCommand { + /// + /// The table containing the data to be modified. + /// + public ITable? Table { get; } + /// /// The name of the table containing the data to be modified. /// diff --git a/src/EFCore.Relational/Update/Internal/KeyValueIndexFactorySource.cs b/src/EFCore.Relational/Update/Internal/ColumnAccessors.cs similarity index 65% rename from src/EFCore.Relational/Update/Internal/KeyValueIndexFactorySource.cs rename to src/EFCore.Relational/Update/Internal/ColumnAccessors.cs index 4a554a30d1c..31f089d0859 100644 --- a/src/EFCore.Relational/Update/Internal/KeyValueIndexFactorySource.cs +++ b/src/EFCore.Relational/Update/Internal/ColumnAccessors.cs @@ -1,9 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Concurrent; -using JetBrains.Annotations; - namespace Microsoft.EntityFrameworkCore.Update.Internal; /// @@ -12,18 +9,22 @@ namespace Microsoft.EntityFrameworkCore.Update.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class KeyValueIndexFactorySource : IKeyValueIndexFactorySource +// Sealed for perf +public sealed class ColumnAccessors { - private readonly ConcurrentDictionary _factories = new(); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IKeyValueIndexFactory GetKeyValueIndexFactory(IKey key) - => _factories.GetOrAdd(key, Create); + public ColumnAccessors( + Delegate currentValueGetter, + Delegate originalValueGetter) + { + CurrentValueGetter = currentValueGetter; + OriginalValueGetter = originalValueGetter; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -31,14 +32,13 @@ public virtual IKeyValueIndexFactory GetKeyValueIndexFactory(IKey key) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IKeyValueIndexFactory Create(IKey key) - => (IKeyValueIndexFactory)typeof(KeyValueIndexFactorySource).GetTypeInfo() - .GetDeclaredMethod(nameof(CreateFactory))! - .MakeGenericMethod(key.GetKeyType()) - .Invoke(null, new object[] { key })!; + public Delegate CurrentValueGetter { get; } - [UsedImplicitly] - private static IKeyValueIndexFactory CreateFactory(IKey key) - where TKey : notnull - => new KeyValueIndexFactory(key.GetPrincipalKeyValueFactory()); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public Delegate OriginalValueGetter { get; } } diff --git a/src/EFCore.Relational/Update/Internal/ColumnAccessorsFactory.cs b/src/EFCore.Relational/Update/Internal/ColumnAccessorsFactory.cs new file mode 100644 index 00000000000..6cf9b492358 --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/ColumnAccessorsFactory.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public static class ColumnAccessorsFactory +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static ColumnAccessors Create(IColumn column) + => (ColumnAccessors)GenericCreate + .MakeGenericMethod(column.ProviderClrType) + .Invoke(null, new object[] { column })!; + + private static readonly MethodInfo GenericCreate + = typeof(ColumnAccessorsFactory).GetTypeInfo().GetDeclaredMethod(nameof(CreateGeneric))!; + + [UsedImplicitly] + private static ColumnAccessors CreateGeneric(IColumn column) + => new( + CreateCurrentValueGetter(column), + CreateOriginalValueGetter(column)); + + private static Func CreateCurrentValueGetter(IColumn column) + => c => + { + if (c.Entries.Count > 0) + { + var value = default(TColumn)!; + var valueFound = false; + for (var i = 0; i < c.Entries.Count; i++) + { + var entry = c.Entries[i]; + var property = column.FindColumnMapping(entry.EntityType)?.Property; + if (property == null) + { + continue; + } + + var providerValue = entry.GetCurrentProviderValue(property); + if (providerValue == null + && !typeof(TColumn).IsNullableType()) + { + return (value!, valueFound); + } + + value = (TColumn)providerValue!; + valueFound = true; + if (entry.EntityState == EntityState.Added + || entry.IsModified(property)) + { + return (value, valueFound); + } + } + + return (value, valueFound); + } + + var modification = c.ColumnModifications.FirstOrDefault(m => m.ColumnName == column.Name); + return modification == null + ? (default(TColumn)!, false) + : ((TColumn)modification.Value!, true); + }; + + private static Func CreateOriginalValueGetter(IColumn column) + => c => + { + if (c.Entries.Count > 0) + { + var value = default(TColumn)!; + var valueFound = false; + for (var i = 0; i < c.Entries.Count; i++) + { + var entry = c.Entries[i]; + var property = column.FindColumnMapping(entry.EntityType)?.Property; + if (property == null) + { + continue; + } + + var providerValue = entry.GetOriginalProviderValue(property); + if (providerValue == null + && !typeof(TColumn).IsNullableType()) + { + return (value!, valueFound); + } + + value = (TColumn)providerValue!; + valueFound = true; + if (entry.EntityState == EntityState.Unchanged + || (entry.EntityState == EntityState.Modified && !entry.IsModified(property))) + { + return (value, valueFound); + } + } + + return (value, valueFound); + } + + var modification = c.ColumnModifications.FirstOrDefault(m => m.ColumnName == column.Name); + return modification == null + ? (default(TColumn)!, false) + : ((TColumn)modification.OriginalValue!, true); + }; +} diff --git a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs index 28e73094e27..3e58ba607ac 100644 --- a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs +++ b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Text; +using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Update.Internal; @@ -191,16 +192,16 @@ protected virtual IEnumerable CreateModificationCo command = sharedCommandsMap.GetOrAddValue( entry, - (n, s, comparer) => Dependencies.ModificationCommandFactory.CreateModificationCommand( + (t, comparer) => Dependencies.ModificationCommandFactory.CreateModificationCommand( new ModificationCommandParameters( - n, s, _sensitiveLoggingEnabled, comparer, generateParameterName, Dependencies.UpdateLogger))); + t, _sensitiveLoggingEnabled, comparer, generateParameterName, Dependencies.UpdateLogger))); isMainEntry = sharedCommandsMap.IsMainEntry(entry); } else { command = Dependencies.ModificationCommandFactory.CreateModificationCommand( new ModificationCommandParameters( - table.Name, table.Schema, _sensitiveLoggingEnabled, comparer: null, generateParameterName, + table, _sensitiveLoggingEnabled, comparer: null, generateParameterName, Dependencies.UpdateLogger)); } @@ -279,15 +280,13 @@ protected virtual IReadOnlyList> TopologicalS _modificationCommandGraph.Clear(); _modificationCommandGraph.AddVertices(commands); - // The predecessors map allows to populate the graph in linear time - var predecessorsMap = CreateKeyValuePredecessorMap(_modificationCommandGraph); - AddForeignKeyEdges(_modificationCommandGraph, predecessorsMap); + AddForeignKeyEdges(_modificationCommandGraph); AddUniqueValueEdges(_modificationCommandGraph); AddSameTableEdges(_modificationCommandGraph); - return _modificationCommandGraph.BatchingTopologicalSort(static (_, _, edges) => edges.All(e => e is IEntityType), FormatCycle); + return _modificationCommandGraph.BatchingTopologicalSort(static (_, _, edges) => edges.All(e => e is ITable), FormatCycle); } private string FormatCycle( @@ -301,12 +300,18 @@ private string FormatCycle( switch (annotatables.First()) { - case IForeignKey foreignKey: + case IForeignKeyConstraint foreignKey: Format(foreignKey, command1, command2, builder); break; - case IIndex index: + case IUniqueConstraint key: + Format(key, command1, command2, builder); + break; + case ITableIndex index: Format(index, command1, command2, builder); break; + default: + builder.AppendLine(" <-"); + break; } if (i == data.Count - 1) @@ -359,12 +364,12 @@ private void Format(IReadOnlyModificationCommand command, StringBuilder builder) } private void Format( - IForeignKey foreignKey, + IForeignKeyConstraint foreignKey, IReadOnlyModificationCommand source, IReadOnlyModificationCommand target, StringBuilder builder) { - var reverseDependency = !source.Entries.Any(e => foreignKey.DeclaringEntityType.IsAssignableFrom(e.EntityType)); + var reverseDependency = source.Table != foreignKey.Table; if (reverseDependency) { builder.AppendLine(" <-"); @@ -374,54 +379,38 @@ private void Format( builder.Append(' '); } - if (foreignKey.DependentToPrincipal != null - || foreignKey.PrincipalToDependent != null) - { - if (!reverseDependency - && foreignKey.DependentToPrincipal != null) - { - builder.Append(foreignKey.DependentToPrincipal.Name); - builder.Append(' '); - } + builder.Append("ForeignKey { "); - if (foreignKey.PrincipalToDependent != null) - { - builder.Append(foreignKey.PrincipalToDependent.Name); - builder.Append(' '); - } + var rowForeignKeyValueFactory = ((ForeignKeyConstraint)foreignKey).GetRowForeignKeyValueFactory(); + var dependentCommand = reverseDependency ? target : source; + var values = rowForeignKeyValueFactory.CreateDependentKeyValue(dependentCommand, fromOriginalValues: !reverseDependency)!; + FormatValues(values, foreignKey.Columns, dependentCommand, builder); - if (reverseDependency - && foreignKey.DependentToPrincipal != null) - { - builder.Append(foreignKey.DependentToPrincipal.Name); - builder.Append(' '); - } + builder.Append(" } "); + + if (!reverseDependency) + { + builder.AppendLine("<-"); + } + } + + private void Format(IUniqueConstraint key, IReadOnlyModificationCommand source, IReadOnlyModificationCommand target, StringBuilder builder) + { + var reverseDependency = source.EntityState != EntityState.Deleted; + if (reverseDependency) + { + builder.AppendLine(" <-"); } else { - builder.Append("ForeignKey "); + builder.Append(' '); } + builder.Append("Key { "); + var rowForeignKeyValueFactory = ((UniqueConstraint)key).GetRowKeyValueFactory(); var dependentCommand = reverseDependency ? target : source; - var dependentEntry = dependentCommand.Entries.First(e => foreignKey.DeclaringEntityType.IsAssignableFrom(e.EntityType)); - builder.Append("{ "); - for (var i = 0; i < foreignKey.Properties.Count; i++) - { - var property = foreignKey.Properties[i]; - builder.Append('\''); - builder.Append(property.Name); - builder.Append('\''); - if (_sensitiveLoggingEnabled) - { - builder.Append(": "); - builder.Append(dependentEntry.GetCurrentValue(property)); - } - - if (i != foreignKey.Properties.Count - 1) - { - builder.Append(", "); - } - } + var values = rowForeignKeyValueFactory.CreateKeyValue(dependentCommand, fromOriginalValues: !reverseDependency)!; + FormatValues(values, key.Columns, dependentCommand, builder); builder.Append(" } "); @@ -431,7 +420,7 @@ private void Format( } } - private void Format(IIndex index, IReadOnlyModificationCommand source, IReadOnlyModificationCommand target, StringBuilder builder) + private void Format(ITableIndex index, IReadOnlyModificationCommand source, IReadOnlyModificationCommand target, StringBuilder builder) { var reverseDependency = source.EntityState != EntityState.Deleted; if (reverseDependency) @@ -443,212 +432,184 @@ private void Format(IIndex index, IReadOnlyModificationCommand source, IReadOnly builder.Append(' '); } - builder.Append("Index "); + builder.Append("Index { "); + var rowForeignKeyValueFactory = ((TableIndex)index).GetRowIndexValueFactory(); var dependentCommand = reverseDependency ? target : source; - var dependentEntry = dependentCommand.Entries.First(e => index.DeclaringEntityType.IsAssignableFrom(e.EntityType)); - builder.Append("{ "); - for (var i = 0; i < index.Properties.Count; i++) + var values = rowForeignKeyValueFactory.CreateValue(dependentCommand, fromOriginalValues: !reverseDependency)!; + FormatValues(values, index.Columns, dependentCommand, builder); + + builder.Append(" } "); + + if (!reverseDependency) + { + builder.AppendLine("<-"); + } + } + + private void FormatValues(object[] values, IReadOnlyList columns, IReadOnlyModificationCommand dependentCommand, StringBuilder builder) + { + for (var i = 0; i < columns.Count; i++) { - var property = index.Properties[i]; + var column = columns[i]; builder.Append('\''); - builder.Append(property.Name); + builder.Append(column.Name); builder.Append('\''); if (_sensitiveLoggingEnabled) { builder.Append(": "); - builder.Append(dependentEntry.GetCurrentValue(property)); + builder.Append(values[i]); } - if (i != index.Properties.Count - 1) + if (i != columns.Count - 1) { builder.Append(", "); } } - - builder.Append(" } "); - - if (!reverseDependency) - { - builder.AppendLine("<-"); - } } - // Builds a map from foreign key values to list of modification commands, with an entry for every command - // that may need to precede some other command involving that foreign key value. - private Dictionary> CreateKeyValuePredecessorMap( + private void AddForeignKeyEdges( Multigraph commandGraph) { - var predecessorsMap = new Dictionary>(); + var predecessorsMap = new Dictionary>(); + var originalPredecessorsMap = new Dictionary>(); foreach (var command in commandGraph.Vertices) { - if (command.EntityState == EntityState.Modified - || command.EntityState == EntityState.Added) + if (command.EntityState is EntityState.Modified or EntityState.Added) { - // ReSharper disable once ForCanBeConvertedToForeach - for (var i = 0; i < command.Entries.Count; i++) + foreach (var foreignKey in command.Table!.ReferencingForeignKeyConstraints) { - var entry = command.Entries[i]; - foreach (var foreignKey in entry.EntityType.GetReferencingForeignKeys()) + if (!IsModified(foreignKey.PrincipalUniqueConstraint.Columns, command)) { - if (!IsMapped(foreignKey, command, principal: true) - || !IsModified(foreignKey.PrincipalKey.Properties, entry)) - { - continue; - } + continue; + } - var principalKeyValue = Dependencies.KeyValueIndexFactorySource - .GetKeyValueIndexFactory(foreignKey.PrincipalKey) - .CreatePrincipalKeyValue(entry, foreignKey); + var principalKeyValue = ((ForeignKeyConstraint)foreignKey).GetRowForeignKeyValueFactory() + .CreatePrincipalValueIndex(command); + Check.DebugAssert(principalKeyValue != null, "null principalKeyValue"); - if (principalKeyValue != null) - { - if (!predecessorsMap.TryGetValue(principalKeyValue, out var predecessorCommands)) - { - predecessorCommands = new List(); - predecessorsMap.Add(principalKeyValue, predecessorCommands); - } - - predecessorCommands.Add(command); - } + if (!predecessorsMap.TryGetValue(principalKeyValue, out var predecessorCommands)) + { + predecessorCommands = new List(); + predecessorsMap.Add(principalKeyValue, predecessorCommands); } + + predecessorCommands.Add(command); } } - if (command.EntityState == EntityState.Modified - || command.EntityState == EntityState.Deleted) + if (command.EntityState is EntityState.Modified or EntityState.Deleted) { - foreach (var entry in command.Entries) + foreach (var foreignKey in command.Table!.ForeignKeyConstraints) { - foreach (var foreignKey in entry.EntityType.GetForeignKeys()) + if (!IsModified(foreignKey.Columns, command)) { - if (!IsMapped(foreignKey, command, principal: false) - || !IsModified(foreignKey.Properties, entry)) - { - continue; - } - - var dependentKeyValue = Dependencies.KeyValueIndexFactorySource - .GetKeyValueIndexFactory(foreignKey.PrincipalKey) - .CreateDependentKeyValueFromOriginalValues(entry, foreignKey); + continue; + } - if (dependentKeyValue != null) + var dependentKeyValue = ((ForeignKeyConstraint)foreignKey).GetRowForeignKeyValueFactory() + .CreateDependentValueIndex(command, fromOriginalValues: true); + if (dependentKeyValue != null) + { + if (!originalPredecessorsMap.TryGetValue(dependentKeyValue, out var predecessorCommands)) { - if (!predecessorsMap.TryGetValue(dependentKeyValue, out var predecessorCommands)) - { - predecessorCommands = new List(); - predecessorsMap.Add(dependentKeyValue, predecessorCommands); - } - - predecessorCommands.Add(command); + predecessorCommands = new(); + originalPredecessorsMap.Add(dependentKeyValue, predecessorCommands); } + + predecessorCommands.Add(command); } } } } - return predecessorsMap; - } - - private void AddForeignKeyEdges( - Multigraph commandGraph, - Dictionary> predecessorsMap) - { foreach (var command in commandGraph.Vertices) { - switch (command.EntityState) + if (command.EntityState is EntityState.Modified or EntityState.Added) { - case EntityState.Modified: - case EntityState.Added: - // ReSharper disable once ForCanBeConvertedToForeach - for (var entryIndex = 0; entryIndex < command.Entries.Count; entryIndex++) + foreach (var foreignKey in command.Table!.ForeignKeyConstraints) + { + if (!IsModified(foreignKey.Columns, command)) { - var entry = command.Entries[entryIndex]; - foreach (var foreignKey in entry.EntityType.GetForeignKeys()) - { - if (!IsMapped(foreignKey, command, principal: false) - || !IsModified(foreignKey.Properties, entry)) - { - continue; - } - - var dependentKeyValue = Dependencies.KeyValueIndexFactorySource - .GetKeyValueIndexFactory(foreignKey.PrincipalKey) - .CreateDependentKeyValue(entry, foreignKey); - if (dependentKeyValue == null) - { - continue; - } - - AddMatchingPredecessorEdge( - predecessorsMap, dependentKeyValue, commandGraph, command, foreignKey); - } + continue; } - break; - case EntityState.Deleted: - // ReSharper disable once ForCanBeConvertedToForeach - for (var entryIndex = 0; entryIndex < command.Entries.Count; entryIndex++) + var dependentKeyValue = ((ForeignKeyConstraint)foreignKey).GetRowForeignKeyValueFactory() + .CreateDependentValueIndex(command); + if (dependentKeyValue != null) { - var entry = command.Entries[entryIndex]; - foreach (var foreignKey in entry.EntityType.GetReferencingForeignKeys()) - { - if (!IsMapped(foreignKey, command, principal: true)) - { - continue; - } - - var principalKeyValue = Dependencies.KeyValueIndexFactorySource - .GetKeyValueIndexFactory(foreignKey.PrincipalKey) - .CreatePrincipalKeyValueFromOriginalValues(entry, foreignKey); - if (principalKeyValue != null) - { - AddMatchingPredecessorEdge( - predecessorsMap, principalKeyValue, commandGraph, command, foreignKey); - } - } + AddMatchingPredecessorEdge( + predecessorsMap, dependentKeyValue, commandGraph, command, foreignKey); } - - break; - } - } - } - - private static bool IsMapped(IForeignKey foreignKey, IReadOnlyModificationCommand command, bool principal) - { - foreach (var constraint in foreignKey.GetMappedConstraints()) - { - if (principal) - { - if (constraint.PrincipalTable.Name == command.TableName - && constraint.PrincipalTable.Schema == command.Schema) - { - return true; } } - else + + if (command.EntityState is EntityState.Modified or EntityState.Deleted) { - if (constraint.Table.Name == command.TableName - && constraint.Table.Schema == command.Schema) + foreach (var foreignKey in command.Table!.ReferencingForeignKeyConstraints) { - return true; + if (!IsModified(foreignKey.PrincipalUniqueConstraint.Columns, command)) + { + continue; + } + + var principalKeyValue = ((ForeignKeyConstraint)foreignKey).GetRowForeignKeyValueFactory() + .CreatePrincipalValueIndex(command, fromOriginalValues: true); + Check.DebugAssert(principalKeyValue != null, "null principalKeyValue"); + AddMatchingPredecessorEdge( + originalPredecessorsMap, principalKeyValue, commandGraph, command, foreignKey); } } } - - return false; } - private static bool IsModified(IReadOnlyList properties, IUpdateEntry entry) + private static bool IsModified(IReadOnlyList columns, IReadOnlyModificationCommand command) { - if (entry.EntityState != EntityState.Modified) + if (command.EntityState != EntityState.Modified) { return true; } - foreach (var property in properties) + for (var columnIndex = 0; columnIndex < columns.Count; columnIndex++) { - if (entry.IsModified(property)) + var column = columns[columnIndex]; + object? originalValue = null; + object? currentValue = null; + RelationalTypeMapping? typeMapping = null; + for (var entryIndex = 0; entryIndex < command.Entries.Count; entryIndex++) + { + var entry = command.Entries[entryIndex]; + var columnMapping = column.FindColumnMapping(entry.EntityType); + var property = columnMapping?.Property; + if (property != null + && ((property.GetAfterSaveBehavior() == PropertySaveBehavior.Save) + || (!property.IsPrimaryKey() && entry.EntityState != EntityState.Modified))) + { + switch (entry.EntityState) + { + case EntityState.Added: + currentValue = entry.GetCurrentProviderValue(property); + break; + case EntityState.Deleted: + case EntityState.Unchanged: + originalValue ??= entry.GetOriginalProviderValue(property); + break; + case EntityState.Modified: + if (entry.IsModified(property)) + { + return true; + } + + originalValue ??= entry.GetOriginalProviderValue(property); + break; + } + + typeMapping = columnMapping!.TypeMapping; + } + } + + if (typeMapping != null + && !typeMapping.ProviderValueComparer.Equals(originalValue, currentValue)) { return true; } @@ -679,72 +640,55 @@ private static void AddMatchingPredecessorEdge( private void AddUniqueValueEdges(Multigraph commandGraph) { - Dictionary>? indexPredecessorsMap = null; - var keyPredecessorsMap = new Dictionary<(IKey, IKeyValueIndex), List>(); + Dictionary>? indexPredecessorsMap = null; + var keyPredecessorsMap = new Dictionary>(); foreach (var command in commandGraph.Vertices) { - if (command.EntityState != EntityState.Modified - && command.EntityState != EntityState.Deleted) + if (command.EntityState is EntityState.Added) { continue; } - for (var entryIndex = 0; entryIndex < command.Entries.Count; entryIndex++) + foreach (var index in command.Table!.Indexes) { - var entry = command.Entries[entryIndex]; - foreach (var index in entry.EntityType.GetIndexes()) - { - if (!index.IsUnique - || !index.GetMappedTableIndexes().Any() - || !IsModified(index.Properties, entry)) - { - continue; - } - - var valueFactory = index.GetNullableValueFactory(); - if (valueFactory.TryCreateFromOriginalValues(entry, out var indexValue)) - { - indexPredecessorsMap ??= new Dictionary>(); - if (!indexPredecessorsMap.TryGetValue(index, out var predecessorCommands)) - { - predecessorCommands = new Dictionary(valueFactory.EqualityComparer); - indexPredecessorsMap.Add(index, predecessorCommands); - } - - if (!predecessorCommands.ContainsKey(indexValue)) - { - predecessorCommands.Add(indexValue, command); - } - } - } - - if (command.EntityState != EntityState.Deleted) + if (!index.IsUnique + || !IsModified(index.Columns, command)) { continue; } - foreach (var key in entry.EntityType.GetKeys()) + var indexValue = ((TableIndex)index).GetRowIndexValueFactory() + .CreateValueIndex(command, fromOriginalValues: true); + if (indexValue != null) { - if (!key.GetMappedConstraints().Any()) + indexPredecessorsMap ??= new(); + if (!indexPredecessorsMap.TryGetValue(indexValue, out var predecessorCommands)) { - continue; + predecessorCommands = new(); + indexPredecessorsMap.Add(indexValue, predecessorCommands); } - var principalKeyValue = Dependencies.KeyValueIndexFactorySource - .GetKeyValueIndexFactory(key) - .CreatePrincipalKeyValue(entry, null); + predecessorCommands.Add(command); + } + } - if (principalKeyValue != null) - { - if (!keyPredecessorsMap.TryGetValue((key, principalKeyValue), out var predecessorCommands)) - { - predecessorCommands = new List(); - keyPredecessorsMap.Add((key, principalKeyValue), predecessorCommands); - } + if (command.EntityState is not EntityState.Deleted) + { + continue; + } - predecessorCommands.Add(command); - } + foreach (var key in command.Table.UniqueConstraints) + { + var keyValue = ((UniqueConstraint)key).GetRowKeyValueFactory() + .CreateValueIndex(command, fromOriginalValues: true); + Check.DebugAssert(keyValue != null, "null keyValue"); + if (!keyPredecessorsMap.TryGetValue((key, keyValue), out var predecessorCommands)) + { + predecessorCommands = new List(); + keyPredecessorsMap.Add((key, keyValue), predecessorCommands); } + + predecessorCommands.Add(command); } } @@ -752,30 +696,25 @@ private void AddUniqueValueEdges(Multigraph(); - if (valueFactory.TryCreateFromCurrentValues(entry, out var indexValue) - && indexPredecessorsMap.TryGetValue(index, out var predecessorCommands) - && predecessorCommands.TryGetValue(indexValue, out var predecessor) - && predecessor != command) - { - commandGraph.AddEdge(predecessor, command, index); - } + var indexValue = ((TableIndex)index).GetRowIndexValueFactory() + .CreateValueIndex(command); + if (indexValue != null) + { + AddMatchingPredecessorEdge( + indexPredecessorsMap, indexValue, commandGraph, command, index); } } } @@ -785,30 +724,19 @@ private void AddUniqueValueEdges(Multigraph modificationCommandComparer, - IKeyValueIndexFactorySource keyValueIndexFactorySource, IModificationCommandFactory modificationCommandFactory, ILoggingOptions loggingOptions, IDiagnosticsLogger updateLogger, @@ -58,7 +57,6 @@ public CommandBatchPreparerDependencies( ModificationCommandBatchFactory = modificationCommandBatchFactory; ParameterNameGeneratorFactory = parameterNameGeneratorFactory; ModificationCommandComparer = modificationCommandComparer; - KeyValueIndexFactorySource = keyValueIndexFactorySource; ModificationCommandFactory = modificationCommandFactory; LoggingOptions = loggingOptions; UpdateLogger = updateLogger; @@ -89,14 +87,6 @@ public CommandBatchPreparerDependencies( /// public IComparer ModificationCommandComparer { get; init; } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public IKeyValueIndexFactorySource KeyValueIndexFactorySource { get; init; } - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.Relational/Update/Internal/KeyValueIndexFactory.cs b/src/EFCore.Relational/Update/Internal/CompositeRowForeignKeyValueFactory.cs similarity index 64% rename from src/EFCore.Relational/Update/Internal/KeyValueIndexFactory.cs rename to src/EFCore.Relational/Update/Internal/CompositeRowForeignKeyValueFactory.cs index c46fa8d4d4e..96d39e55aa1 100644 --- a/src/EFCore.Relational/Update/Internal/KeyValueIndexFactory.cs +++ b/src/EFCore.Relational/Update/Internal/CompositeRowForeignKeyValueFactory.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.Metadata.Internal; + namespace Microsoft.EntityFrameworkCore.Update.Internal; /// @@ -9,9 +11,10 @@ namespace Microsoft.EntityFrameworkCore.Update.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class KeyValueIndexFactory : IKeyValueIndexFactory +public class CompositeRowForeignKeyValueFactory : CompositeRowValueFactory, IRowForeignKeyValueFactory { - private readonly IPrincipalKeyValueFactory _principalKeyValueFactory; + private readonly IForeignKeyConstraint _foreignKey; + private readonly IRowKeyValueFactory _principalKeyValueFactory; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -19,9 +22,11 @@ public class KeyValueIndexFactory : IKeyValueIndexFactory /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public KeyValueIndexFactory(IPrincipalKeyValueFactory principalKeyValueFactory) + public CompositeRowForeignKeyValueFactory(IForeignKeyConstraint foreignKey) + : base(foreignKey.Columns) { - _principalKeyValueFactory = principalKeyValueFactory; + _foreignKey = foreignKey; + _principalKeyValueFactory = (IRowKeyValueFactory)((UniqueConstraint)foreignKey.PrincipalUniqueConstraint).GetRowKeyValueFactory(); } /// @@ -30,12 +35,11 @@ public KeyValueIndexFactory(IPrincipalKeyValueFactory principalKeyValueFac /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IKeyValueIndex CreatePrincipalKeyValue(IUpdateEntry entry, IForeignKey? foreignKey) - => new KeyValueIndex( - foreignKey, - _principalKeyValueFactory.CreateFromCurrentValues(entry), - _principalKeyValueFactory.EqualityComparer, - fromOriginalValues: false); + public virtual object CreatePrincipalValueIndex(IReadOnlyModificationCommand command, bool fromOriginalValues = false) + => new ValueIndex( + _foreignKey, + _principalKeyValueFactory.CreateKeyValue(command, fromOriginalValues), + EqualityComparer); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -43,12 +47,10 @@ public virtual IKeyValueIndex CreatePrincipalKeyValue(IUpdateEntry entry, IForei /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IKeyValueIndex CreatePrincipalKeyValueFromOriginalValues(IUpdateEntry entry, IForeignKey? foreignKey) - => new KeyValueIndex( - foreignKey, - _principalKeyValueFactory.CreateFromOriginalValues(entry), - _principalKeyValueFactory.EqualityComparer, - fromOriginalValues: true); + public virtual object? CreateDependentValueIndex(IReadOnlyModificationCommand command, bool fromOriginalValues = false) + => TryCreateDependentKeyValue(command, fromOriginalValues, out var keyValue) + ? new ValueIndex(_foreignKey, keyValue, EqualityComparer) + : null; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -56,10 +58,8 @@ public virtual IKeyValueIndex CreatePrincipalKeyValueFromOriginalValues(IUpdateE /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IKeyValueIndex? CreateDependentKeyValue(IUpdateEntry entry, IForeignKey foreignKey) - => foreignKey.GetDependentKeyValueFactory()!.TryCreateFromCurrentValues(entry, out var keyValue) - ? new KeyValueIndex(foreignKey, keyValue, _principalKeyValueFactory.EqualityComparer, fromOriginalValues: false) - : null; + public virtual object[] CreatePrincipalKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues = false) + => _principalKeyValueFactory.CreateKeyValue(command, fromOriginalValues)!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -67,8 +67,8 @@ public virtual IKeyValueIndex CreatePrincipalKeyValueFromOriginalValues(IUpdateE /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IKeyValueIndex? CreateDependentKeyValueFromOriginalValues(IUpdateEntry entry, IForeignKey foreignKey) - => foreignKey.GetDependentKeyValueFactory()!.TryCreateFromOriginalValues(entry, out var keyValue) - ? new KeyValueIndex(foreignKey, keyValue, _principalKeyValueFactory.EqualityComparer, fromOriginalValues: true) + public virtual object[]? CreateDependentKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues = false) + => TryCreateDependentKeyValue(command, fromOriginalValues, out var keyValue) + ? (object[])keyValue : null; } diff --git a/src/EFCore.Relational/Update/Internal/CompositeRowIndexValueFactory.cs b/src/EFCore.Relational/Update/Internal/CompositeRowIndexValueFactory.cs new file mode 100644 index 00000000000..e1e5a3a35aa --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/CompositeRowIndexValueFactory.cs @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class CompositeRowIndexValueFactory : CompositeRowValueFactory, IRowIndexValueFactory +{ + private readonly ITableIndex _index; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public CompositeRowIndexValueFactory(ITableIndex index) + : base(index.Columns) + { + _index = index; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool TryCreateIndexValue(object?[] keyValues, [NotNullWhen(true)] out object?[]? key) + => TryCreateDependentKeyValue(keyValues, out key); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool TryCreateIndexValue(IDictionary keyValues, [NotNullWhen(true)] out object?[]? key) + => TryCreateDependentKeyValue(keyValues, out key); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool TryCreateIndexValue(IReadOnlyModificationCommand command, bool fromOriginalValues, [NotNullWhen(true)] out object?[]? key) + => TryCreateDependentKeyValue(command, fromOriginalValues, out key); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual object? CreateValueIndex(IReadOnlyModificationCommand command, bool fromOriginalValues = false) + => TryCreateDependentKeyValue(command, fromOriginalValues, out var keyValue) + ? new ValueIndex(_index, keyValue, EqualityComparer) + : null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual object[]? CreateValue(IReadOnlyModificationCommand command, bool fromOriginalValues = false) + => TryCreateIndexValue(command, fromOriginalValues, out var keyValue) + ? (object[])keyValue + : null; +} diff --git a/src/EFCore.Relational/Update/Internal/CompositeRowKeyValueFactory.cs b/src/EFCore.Relational/Update/Internal/CompositeRowKeyValueFactory.cs new file mode 100644 index 00000000000..212c96b215f --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/CompositeRowKeyValueFactory.cs @@ -0,0 +1,123 @@ +// 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.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class CompositeRowKeyValueFactory : CompositeRowValueFactory, IRowKeyValueFactory +{ + private readonly IUniqueConstraint _constraint; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public CompositeRowKeyValueFactory(IUniqueConstraint key) + : base(key.Columns) + { + _constraint = key; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual object?[] CreateKeyValue(object?[] keyValues) + { + if (keyValues.Any(v => v == null)) + { + throw new InvalidOperationException( + RelationalStrings.NullKeyValue( + _constraint.Table.SchemaQualifiedName, + FindNullColumnInKeyValues(keyValues).Name)); + } + + return keyValues; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual object?[] CreateKeyValue(IDictionary keyValues) + { + if (!TryCreateDependentKeyValue(keyValues, out var key)) + { + throw new InvalidOperationException( + RelationalStrings.NullKeyValue( + _constraint.Table.SchemaQualifiedName, + FindNullColumnInKeyValues(key).Name)); + } + + return key; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual object?[] CreateKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues = false) + { + if (!TryCreateDependentKeyValue(command, fromOriginalValues, out var key)) + { + throw new InvalidOperationException( + RelationalStrings.NullKeyValue( + _constraint.Table.SchemaQualifiedName, + FindNullColumnInKeyValues(key).Name)); + } + + return key; + } + + private IColumn FindNullColumnInKeyValues(object?[]? keyValues) + { + var index = 0; + if (keyValues != null) + { + for (var i = 0; i < keyValues.Length; i++) + { + if (keyValues[i] == null) + { + index = i; + break; + } + } + } + + return _constraint.Columns[index]; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual object CreateValueIndex(IReadOnlyModificationCommand command, bool fromOriginalValues = false) + => new ValueIndex( + _constraint, + CreateKeyValue(command, fromOriginalValues), + EqualityComparer); + + /// + /// 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. + /// + object[] IRowKeyValueFactory.CreateKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues) + => CreateKeyValue(command, fromOriginalValues)!; +} diff --git a/src/EFCore.Relational/Update/Internal/CompositeRowValueFactory.cs b/src/EFCore.Relational/Update/Internal/CompositeRowValueFactory.cs new file mode 100644 index 00000000000..348495ac8ad --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/CompositeRowValueFactory.cs @@ -0,0 +1,215 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public abstract class CompositeRowValueFactory +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public CompositeRowValueFactory(IReadOnlyList columns) + { + Columns = columns; + EqualityComparer = CreateEqualityComparer(columns); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEqualityComparer EqualityComparer { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected virtual IReadOnlyList Columns { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool TryCreateDependentKeyValue(object?[] keyValues, [NotNullWhen(true)] out object?[]? key) + { + key = keyValues; + return keyValues.All(k => k != null); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool TryCreateDependentKeyValue(IDictionary keyValues, [NotNullWhen(true)] out object?[]? key) + { + key = new object[Columns.Count]; + var index = 0; + + foreach (var column in Columns) + { + if (!keyValues.TryGetValue(column.Name, out var value) + || value == null) + { + return false; + } + key[index++] = value; + } + + return true; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool TryCreateDependentKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues, [NotNullWhen(true)] out object?[]? key) + { + key = new object[Columns.Count]; + var index = 0; + + for (var i = 0; i < Columns.Count; i++) + { + var column = Columns[i]; + + if (command.Entries.Count > 0) + { + object? value = null; + var valueFound = false; + foreach (var entry in command.Entries) + { + var property = column.FindColumnMapping(entry.EntityType)?.Property; + if (property == null) + { + continue; + } + + valueFound = true; + value = fromOriginalValues ? entry.GetOriginalProviderValue(property) : entry.GetCurrentProviderValue(property); + if (!fromOriginalValues + && (entry.EntityState == EntityState.Added + || (entry.EntityState == EntityState.Modified && entry.IsModified(property)))) + { + break; + } + + if (fromOriginalValues + && entry.EntityState != EntityState.Added) + { + break; + } + } + + if (!valueFound) + { + return false; + } + key[index++] = value; + } + else + { + var modification = command.ColumnModifications.FirstOrDefault(m => m.ColumnName == column.Name); + if (modification == null) + { + return false; + } + + var value = fromOriginalValues ? modification.OriginalValue : modification.Value; + key[index++] = value; + } + } + + return true; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected static IEqualityComparer CreateEqualityComparer(IReadOnlyList columns) + => new CompositeCustomComparer(columns.Select(c => c.PropertyMappings.First().TypeMapping.ProviderValueComparer).ToList()); + + private sealed class CompositeCustomComparer : IEqualityComparer + { + private readonly Func[] _equals; + private readonly Func[] _hashCodes; + + public CompositeCustomComparer(IList comparers) + { + _equals = comparers.Select(c => (Func)c.Equals).ToArray(); + _hashCodes = comparers.Select(c => (Func)c.GetHashCode).ToArray(); + } + + public bool Equals(object?[]? x, object?[]? y) + { + if (ReferenceEquals(x, y)) + { + return true; + } + + if (x is null) + { + return y is null; + } + + if (y is null) + { + return false; + } + + if (x.Length != y.Length) + { + return false; + } + + for (var i = 0; i < x.Length; i++) + { + if (!_equals[i](x[i], y[i])) + { + return false; + } + } + + return true; + } + + public int GetHashCode(object?[] obj) + { + var hashCode = 0; + + // ReSharper disable once ForCanBeConvertedToForeach + // ReSharper disable once LoopCanBeConvertedToQuery + for (var i = 0; i < obj.Length; i++) + { + var value = obj[i]; + var hash = value == null ? 0 : _hashCodes[i](value); + hashCode = (hashCode * 397) ^ hash; + } + + return hashCode; + } + } +} diff --git a/src/EFCore.Relational/Update/Internal/IRowForeignKeyValueFactory.cs b/src/EFCore.Relational/Update/Internal/IRowForeignKeyValueFactory.cs new file mode 100644 index 00000000000..f0842b1da36 --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/IRowForeignKeyValueFactory.cs @@ -0,0 +1,45 @@ +// 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.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public interface IRowForeignKeyValueFactory +{ + /// + /// 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. + /// + object[] CreatePrincipalKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues = false); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + object[]? CreateDependentKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues = false); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + object CreatePrincipalValueIndex(IReadOnlyModificationCommand command, bool fromOriginalValues = false); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + object? CreateDependentValueIndex(IReadOnlyModificationCommand command, bool fromOriginalValues = false); +} diff --git a/src/EFCore.Relational/Update/Internal/IKeyValueIndexFactorySource.cs b/src/EFCore.Relational/Update/Internal/IRowForeignKeyValueFactoryFactory.cs similarity index 71% rename from src/EFCore.Relational/Update/Internal/IKeyValueIndexFactorySource.cs rename to src/EFCore.Relational/Update/Internal/IRowForeignKeyValueFactoryFactory.cs index aec9e4db31f..f4db0739336 100644 --- a/src/EFCore.Relational/Update/Internal/IKeyValueIndexFactorySource.cs +++ b/src/EFCore.Relational/Update/Internal/IRowForeignKeyValueFactoryFactory.cs @@ -9,12 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Update.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -/// -/// The service lifetime is . This means a single instance -/// is used by many instances. The implementation must be thread-safe. -/// This service cannot depend on services registered as . -/// -public interface IKeyValueIndexFactorySource +public interface IRowForeignKeyValueFactoryFactory { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -22,5 +17,5 @@ public interface IKeyValueIndexFactorySource /// 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. /// - IKeyValueIndexFactory GetKeyValueIndexFactory(IKey key); + IRowForeignKeyValueFactory Create(IForeignKeyConstraint foreignKey); } diff --git a/src/EFCore.Relational/Update/Internal/KeyValueIndex.cs b/src/EFCore.Relational/Update/Internal/IRowForeignKeyValueFactory`.cs similarity index 64% rename from src/EFCore.Relational/Update/Internal/KeyValueIndex.cs rename to src/EFCore.Relational/Update/Internal/IRowForeignKeyValueFactory`.cs index 079208fca14..b53c488df6f 100644 --- a/src/EFCore.Relational/Update/Internal/KeyValueIndex.cs +++ b/src/EFCore.Relational/Update/Internal/IRowForeignKeyValueFactory`.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 System.Diagnostics.CodeAnalysis; + namespace Microsoft.EntityFrameworkCore.Update.Internal; /// @@ -9,30 +11,15 @@ namespace Microsoft.EntityFrameworkCore.Update.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public sealed class KeyValueIndex : IKeyValueIndex +public interface IRowForeignKeyValueFactory : IRowForeignKeyValueFactory { - private readonly IForeignKey? _foreignKey; - private readonly TKey _keyValue; - private readonly IEqualityComparer _keyComparer; - private readonly bool _fromOriginalValues; - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public KeyValueIndex( - IForeignKey? foreignKey, - TKey keyValue, - IEqualityComparer keyComparer, - bool fromOriginalValues) - { - _foreignKey = foreignKey; - _keyValue = keyValue; - _fromOriginalValues = fromOriginalValues; - _keyComparer = keyComparer; - } + bool TryCreateDependentKeyValue(object?[] keyValues, [NotNullWhen(true)] out TKey? key); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -40,13 +27,7 @@ public KeyValueIndex( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public IKeyValueIndex WithOriginalValuesFlag() - => new KeyValueIndex(_foreignKey, _keyValue, _keyComparer, fromOriginalValues: true); - - private bool Equals(KeyValueIndex other) - => other._fromOriginalValues == _fromOriginalValues - && other._foreignKey == _foreignKey - && _keyComparer.Equals(_keyValue, other._keyValue); + bool TryCreateDependentKeyValue(IDictionary keyValues, [NotNullWhen(true)] out TKey? key); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -54,11 +35,7 @@ private bool Equals(KeyValueIndex other) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override bool Equals(object? obj) - => !(obj is null) - && (ReferenceEquals(this, obj) - || obj.GetType() == GetType() - && Equals((KeyValueIndex)obj)); + bool TryCreateDependentKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues, [NotNullWhen(true)] out TKey? key); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -66,13 +43,5 @@ public override bool Equals(object? obj) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override int GetHashCode() - { - var hash = new HashCode(); - hash.Add(typeof(TKey)); - hash.Add(_fromOriginalValues); - hash.Add(_foreignKey); - hash.Add(_keyValue, _keyComparer); - return hash.ToHashCode(); - } + IEqualityComparer EqualityComparer { get; } } diff --git a/src/EFCore.Relational/Update/Internal/IRowIndexValueFactory.cs b/src/EFCore.Relational/Update/Internal/IRowIndexValueFactory.cs new file mode 100644 index 00000000000..af90ff203fa --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/IRowIndexValueFactory.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public interface IRowIndexValueFactory +{ + /// + /// 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. + /// + object[]? CreateValue(IReadOnlyModificationCommand command, bool fromOriginalValues = false); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + object? CreateValueIndex(IReadOnlyModificationCommand command, bool fromOriginalValues = false); +} diff --git a/src/EFCore.Relational/Update/Internal/IKeyValueIndex.cs b/src/EFCore.Relational/Update/Internal/IRowIndexValueFactoryFactory.cs similarity index 91% rename from src/EFCore.Relational/Update/Internal/IKeyValueIndex.cs rename to src/EFCore.Relational/Update/Internal/IRowIndexValueFactoryFactory.cs index 4005109ef2e..9aefff34bf4 100644 --- a/src/EFCore.Relational/Update/Internal/IKeyValueIndex.cs +++ b/src/EFCore.Relational/Update/Internal/IRowIndexValueFactoryFactory.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Update.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public interface IKeyValueIndex +public interface IRowIndexValueFactoryFactory { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -17,5 +17,5 @@ public interface IKeyValueIndex /// 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. /// - IKeyValueIndex WithOriginalValuesFlag(); + IRowIndexValueFactory Create(ITableIndex index); } diff --git a/src/EFCore.Relational/Update/Internal/IRowIndexValueFactory`.cs b/src/EFCore.Relational/Update/Internal/IRowIndexValueFactory`.cs new file mode 100644 index 00000000000..a7033a3d2c0 --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/IRowIndexValueFactory`.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public interface IRowIndexValueFactory : IRowIndexValueFactory +{ + /// + /// 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. + /// + bool TryCreateIndexValue(object?[] keyValues, [NotNullWhen(true)] out TKey? key); + + /// + /// 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. + /// + bool TryCreateIndexValue(IDictionary keyValues, [NotNullWhen(true)] out TKey? key); + + /// + /// 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. + /// + bool TryCreateIndexValue(IReadOnlyModificationCommand command, bool fromOriginalValues, [NotNullWhen(true)] out TKey? key); + + /// + /// 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. + /// + IEqualityComparer EqualityComparer { get; } +} diff --git a/src/EFCore.Relational/Update/Internal/IRowKeyValueFactory.cs b/src/EFCore.Relational/Update/Internal/IRowKeyValueFactory.cs new file mode 100644 index 00000000000..de7106dc77e --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/IRowKeyValueFactory.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public interface IRowKeyValueFactory +{ + /// + /// 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. + /// + object[] CreateKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues = false); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + object CreateValueIndex(IReadOnlyModificationCommand command, bool fromOriginalValues = false); +} diff --git a/src/EFCore.Relational/Update/Internal/IRowKeyValueFactoryFactory.cs b/src/EFCore.Relational/Update/Internal/IRowKeyValueFactoryFactory.cs new file mode 100644 index 00000000000..c3c27cf9c63 --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/IRowKeyValueFactoryFactory.cs @@ -0,0 +1,21 @@ +// 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.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public interface IRowKeyValueFactoryFactory +{ + /// + /// 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. + /// + IRowKeyValueFactory Create(IUniqueConstraint key); +} diff --git a/src/EFCore.Relational/Update/Internal/IKeyValueIndexFactory.cs b/src/EFCore.Relational/Update/Internal/IRowKeyValueFactory`.cs similarity index 85% rename from src/EFCore.Relational/Update/Internal/IKeyValueIndexFactory.cs rename to src/EFCore.Relational/Update/Internal/IRowKeyValueFactory`.cs index cf87907b336..3236136d762 100644 --- a/src/EFCore.Relational/Update/Internal/IKeyValueIndexFactory.cs +++ b/src/EFCore.Relational/Update/Internal/IRowKeyValueFactory`.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Update.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public interface IKeyValueIndexFactory +public interface IRowKeyValueFactory : IRowKeyValueFactory { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -17,7 +17,7 @@ public interface IKeyValueIndexFactory /// 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. /// - IKeyValueIndex CreatePrincipalKeyValue(IUpdateEntry entry, IForeignKey? foreignKey); + TKey CreateKeyValue(object?[] keyValues); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -25,7 +25,7 @@ public interface IKeyValueIndexFactory /// 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. /// - IKeyValueIndex CreatePrincipalKeyValueFromOriginalValues(IUpdateEntry entry, IForeignKey? foreignKey); + TKey CreateKeyValue(IDictionary keyValues); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -33,7 +33,7 @@ public interface IKeyValueIndexFactory /// 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. /// - IKeyValueIndex? CreateDependentKeyValue(IUpdateEntry entry, IForeignKey foreignKey); + new TKey CreateKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues = false); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -41,5 +41,5 @@ public interface IKeyValueIndexFactory /// 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. /// - IKeyValueIndex? CreateDependentKeyValueFromOriginalValues(IUpdateEntry entry, IForeignKey foreignKey); + IEqualityComparer EqualityComparer { get; } } diff --git a/src/EFCore.Relational/Update/Internal/RowForeignKeyValueFactory.cs b/src/EFCore.Relational/Update/Internal/RowForeignKeyValueFactory.cs new file mode 100644 index 00000000000..fdec90c5e4e --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/RowForeignKeyValueFactory.cs @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public abstract class RowForeignKeyValueFactory : IRowForeignKeyValueFactory +{ + private readonly IForeignKeyConstraint _foreignKey; + private readonly IRowKeyValueFactory _principalKeyValueFactory; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public RowForeignKeyValueFactory(IForeignKeyConstraint foreignKey) + { + _foreignKey = foreignKey; + _principalKeyValueFactory = (IRowKeyValueFactory)((UniqueConstraint)foreignKey.PrincipalUniqueConstraint).GetRowKeyValueFactory(); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public abstract IEqualityComparer EqualityComparer { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual object CreatePrincipalValueIndex(IReadOnlyModificationCommand command, bool fromOriginalValues = false) + => new ValueIndex( + _foreignKey, + _principalKeyValueFactory.CreateKeyValue(command, fromOriginalValues), + EqualityComparer); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual object? CreateDependentValueIndex(IReadOnlyModificationCommand command, bool fromOriginalValues = false) + => TryCreateDependentKeyValue(command, fromOriginalValues, out var keyValue) + ? new ValueIndex(_foreignKey, keyValue, EqualityComparer) + : null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public abstract bool TryCreateDependentKeyValue( + object?[] keyValues, [NotNullWhen(true)] out TKey? key); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public abstract bool TryCreateDependentKeyValue( + IDictionary keyPropertyValues, [NotNullWhen(true)] out TKey? key); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public abstract bool TryCreateDependentKeyValue( + IReadOnlyModificationCommand command, bool fromOriginalValues, [NotNullWhen(true)] out TKey? key); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected virtual IEqualityComparer CreateKeyEqualityComparer(IColumn column) +#pragma warning disable EF1001 // Internal EF Core API usage. + => NullableComparerAdapter.Wrap( + column.PropertyMappings.First().TypeMapping.ProviderValueComparer); +#pragma warning restore EF1001 // Internal EF Core API usage. + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual object[] CreatePrincipalKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues = false) + => new object[] { _principalKeyValueFactory.CreateKeyValue(command, fromOriginalValues)! }; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual object[]? CreateDependentKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues = false) + => TryCreateDependentKeyValue(command, fromOriginalValues, out var value) + ? (new object[] { value }) + : null; +} diff --git a/src/EFCore.Relational/Update/Internal/RowForeignKeyValueFactoryFactory.cs b/src/EFCore.Relational/Update/Internal/RowForeignKeyValueFactoryFactory.cs new file mode 100644 index 00000000000..c8d1f24f395 --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/RowForeignKeyValueFactoryFactory.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class RowForeignKeyValueFactoryFactory : IRowForeignKeyValueFactoryFactory +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IRowForeignKeyValueFactory Create(IForeignKeyConstraint foreignKey) + => foreignKey.Columns.Count == 1 + ? (IRowForeignKeyValueFactory)_createMethod + .MakeGenericMethod(foreignKey.Columns.First().ProviderClrType) + .Invoke(null, new object[] { foreignKey })! + : new CompositeRowForeignKeyValueFactory(foreignKey); + + private readonly static MethodInfo _createMethod = typeof(RowForeignKeyValueFactoryFactory).GetTypeInfo() + .GetDeclaredMethod(nameof(CreateSimple))!; + + [UsedImplicitly] + private static IRowForeignKeyValueFactory CreateSimple(IForeignKeyConstraint foreignKey) + where TKey : notnull + { + var dependentColumn = foreignKey.Columns.Single(); + var dependentType = dependentColumn.ProviderClrType; + var principalType = foreignKey.PrincipalColumns.Single().ProviderClrType; + var columnAccessors = ((Column)dependentColumn).Accessors; + + if (dependentType.IsNullableType() + && principalType.IsNullableType()) + { + return new SimpleFullyNullableRowForeignKeyValueFactory(foreignKey, dependentColumn, columnAccessors); + } + + if (dependentType.IsNullableType()) + { + return (IRowForeignKeyValueFactory)Activator.CreateInstance( + typeof(SimpleNullableRowForeignKeyValueFactory<>).MakeGenericType( + typeof(TKey)), foreignKey, dependentColumn, columnAccessors)!; + } + + return principalType.IsNullableType() + ? (IRowForeignKeyValueFactory)Activator.CreateInstance( + typeof(SimpleNullablePrincipalRowForeignKeyValueFactory<,>).MakeGenericType( + typeof(TKey), typeof(TKey).UnwrapNullableType()), foreignKey, dependentColumn, columnAccessors)! + : new SimpleNonNullableRowForeignKeyValueFactory(foreignKey, dependentColumn, columnAccessors); + } +} diff --git a/src/EFCore.Relational/Update/Internal/RowIndexValueFactoryFactory.cs b/src/EFCore.Relational/Update/Internal/RowIndexValueFactoryFactory.cs new file mode 100644 index 00000000000..d907a33e3cd --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/RowIndexValueFactoryFactory.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class RowIndexValueFactoryFactory : IRowIndexValueFactoryFactory +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IRowIndexValueFactory Create(ITableIndex index) + => index.Columns.Count == 1 + ? (IRowIndexValueFactory)_createMethod + .MakeGenericMethod(index.Columns.First().ProviderClrType) + .Invoke(null, new object[] { index })! + : new CompositeRowIndexValueFactory(index); + + private static readonly MethodInfo _createMethod = typeof(RowIndexValueFactoryFactory).GetTypeInfo() + .GetDeclaredMethod(nameof(CreateSimple))!; + + [UsedImplicitly] + private static IRowIndexValueFactory CreateSimple(ITableIndex index) + => new SimpleRowIndexValueFactory(index); +} diff --git a/src/EFCore.Relational/Update/Internal/RowKeyValueFactoryFactory.cs b/src/EFCore.Relational/Update/Internal/RowKeyValueFactoryFactory.cs new file mode 100644 index 00000000000..4bed491fb95 --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/RowKeyValueFactoryFactory.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class RowKeyValueFactoryFactory : IRowKeyValueFactoryFactory +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IRowKeyValueFactory Create(IUniqueConstraint key) + => key.Columns.Count == 1 + ? (IRowKeyValueFactory)_createMethod + .MakeGenericMethod(key.Columns.First().ProviderClrType) + .Invoke(null, new object[] { key })! + : new CompositeRowKeyValueFactory(key); + + private readonly static MethodInfo _createMethod = typeof(RowKeyValueFactoryFactory).GetTypeInfo() + .GetDeclaredMethod(nameof(CreateSimpleFactory))!; + + [UsedImplicitly] + private static IRowKeyValueFactory CreateSimpleFactory(IUniqueConstraint key) + => new SimpleRowKeyValueFactory(key); +} diff --git a/src/EFCore.Relational/Update/Internal/SharedTableEntryMap.cs b/src/EFCore.Relational/Update/Internal/SharedTableEntryMap.cs index 18a9ce31f05..fc723149ac7 100644 --- a/src/EFCore.Relational/Update/Internal/SharedTableEntryMap.cs +++ b/src/EFCore.Relational/Update/Internal/SharedTableEntryMap.cs @@ -54,7 +54,7 @@ public virtual TValue GetOrAddValue(IUpdateEntry entry, SharedTableEntryValueFac return sharedCommand; } - sharedCommand = createElement(_table.Name, _table.Schema, _comparer); + sharedCommand = createElement(_table, _comparer); _entryValueMap.Add(mainEntry, sharedCommand); return sharedCommand; diff --git a/src/EFCore.Relational/Update/Internal/SharedTableEntryValueFactory.cs b/src/EFCore.Relational/Update/Internal/SharedTableEntryValueFactory.cs index 14e5f1720bd..4196f65171d 100644 --- a/src/EFCore.Relational/Update/Internal/SharedTableEntryValueFactory.cs +++ b/src/EFCore.Relational/Update/Internal/SharedTableEntryValueFactory.cs @@ -9,4 +9,4 @@ namespace Microsoft.EntityFrameworkCore.Update.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public delegate TValue SharedTableEntryValueFactory(string tableName, string? schema, IComparer comparer); +public delegate TValue SharedTableEntryValueFactory(ITable table, IComparer comparer); diff --git a/src/EFCore.Relational/Update/Internal/SimpleFullyNullableRowForeignKeyValueFactory.cs b/src/EFCore.Relational/Update/Internal/SimpleFullyNullableRowForeignKeyValueFactory.cs new file mode 100644 index 00000000000..5b17ce45807 --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/SimpleFullyNullableRowForeignKeyValueFactory.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SimpleFullyNullableRowForeignKeyValueFactory : RowForeignKeyValueFactory +{ + private readonly IColumn _column; + private readonly ColumnAccessors _columnAccessors; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public SimpleFullyNullableRowForeignKeyValueFactory( + IForeignKeyConstraint foreignKey, + IColumn column, + ColumnAccessors columnAccessors) + : base(foreignKey) + { + _column = column; + _columnAccessors = columnAccessors; + EqualityComparer = CreateKeyEqualityComparer(column); + } + + /// + public override IEqualityComparer EqualityComparer { get; } + + /// + public override bool TryCreateDependentKeyValue(object?[] keyValues, [NotNullWhen(true)] out TKey? key) + { + key = (TKey?)keyValues[0]; + return key != null; + } + + /// + public override bool TryCreateDependentKeyValue( + IDictionary keyPropertyValues, [NotNullWhen(true)] out TKey? key) + { + if (keyPropertyValues.TryGetValue(_column.Name, out var value)) + { + key = (TKey?)value; + return key != null; + } + + key = default; + return false; + } + + /// + public override bool TryCreateDependentKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues, [NotNullWhen(true)] out TKey? key) + { + (key, var present) = fromOriginalValues + ? ((Func)_columnAccessors.OriginalValueGetter)(command) + : ((Func)_columnAccessors.CurrentValueGetter)(command); + return present && key != null; + } +} diff --git a/src/EFCore.Relational/Update/Internal/SimpleNonNullableRowForeignKeyValueFactory.cs b/src/EFCore.Relational/Update/Internal/SimpleNonNullableRowForeignKeyValueFactory.cs new file mode 100644 index 00000000000..b3a8275fbcc --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/SimpleNonNullableRowForeignKeyValueFactory.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SimpleNonNullableRowForeignKeyValueFactory : RowForeignKeyValueFactory +{ + private readonly IColumn _column; + private readonly ColumnAccessors _columnAccessors; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public SimpleNonNullableRowForeignKeyValueFactory( + IForeignKeyConstraint foreignKey, + IColumn column, + ColumnAccessors columnAccessors) + : base(foreignKey) + { + _column = column; + _columnAccessors = columnAccessors; + EqualityComparer = CreateKeyEqualityComparer(column); + } + + /// + public override IEqualityComparer EqualityComparer { get; } + + /// + public override bool TryCreateDependentKeyValue(object?[] keyValues, [NotNullWhen(true)] out TKey? key) + { + key = (TKey?)keyValues[0]!; + return true; + } + + /// + public override bool TryCreateDependentKeyValue( + IDictionary keyPropertyValues, [NotNullWhen(true)] out TKey? key) + { + if (keyPropertyValues.TryGetValue(_column.Name, out var value)) + { + key = (TKey?)value!; + return true; + } + + key = default; + return false; + } + + /// + public override bool TryCreateDependentKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues, [NotNullWhen(true)] out TKey? key) + { + (key, var present) = fromOriginalValues + ? ((Func)_columnAccessors.OriginalValueGetter)(command) + : ((Func)_columnAccessors.CurrentValueGetter)(command); + return present; + } +} diff --git a/src/EFCore.Relational/Update/Internal/SimpleNullablePrincipalRowForeignKeyValueFactory.cs b/src/EFCore.Relational/Update/Internal/SimpleNullablePrincipalRowForeignKeyValueFactory.cs new file mode 100644 index 00000000000..7a20de00c56 --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/SimpleNullablePrincipalRowForeignKeyValueFactory.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SimpleNullablePrincipalRowForeignKeyValueFactory : RowForeignKeyValueFactory + where TNonNullableKey : struct +{ + private readonly IColumn _column; + private readonly ColumnAccessors _columnAccessors; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public SimpleNullablePrincipalRowForeignKeyValueFactory( + IForeignKeyConstraint foreignKey, + IColumn column, + ColumnAccessors columnAccessors) + : base(foreignKey) + { + _column = column; + _columnAccessors = columnAccessors; + EqualityComparer = CreateKeyEqualityComparer(column); + } + + /// + public override IEqualityComparer EqualityComparer { get; } + + /// + public override bool TryCreateDependentKeyValue(object?[] keyValues, [NotNullWhen(true)] out TKey? key) + { + key = (TKey?)keyValues[0]!; + return true; + } + + /// + public override bool TryCreateDependentKeyValue( + IDictionary keyPropertyValues, [NotNullWhen(true)] out TKey? key) + { + if (keyPropertyValues.TryGetValue(_column.Name, out var value)) + { + key = (TKey?)value!; + return true; + } + + key = default; + return false; + } + + /// + public override bool TryCreateDependentKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues, [NotNullWhen(true)] out TKey? key) + { + (key, var present) = fromOriginalValues + ? ((Func)_columnAccessors.OriginalValueGetter)(command) + : ((Func)_columnAccessors.CurrentValueGetter)(command); + return present; + } +} diff --git a/src/EFCore.Relational/Update/Internal/SimpleNullableRowForeignKeyValueFactory.cs b/src/EFCore.Relational/Update/Internal/SimpleNullableRowForeignKeyValueFactory.cs new file mode 100644 index 00000000000..7ca69bc86a7 --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/SimpleNullableRowForeignKeyValueFactory.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SimpleNullableRowForeignKeyValueFactory : RowForeignKeyValueFactory + where TKey : struct +{ + private readonly IColumn _column; + private readonly ColumnAccessors _columnAccessors; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public SimpleNullableRowForeignKeyValueFactory( + IForeignKeyConstraint foreignKey, + IColumn column, + ColumnAccessors columnAccessors) + : base(foreignKey) + { + _column = column; + _columnAccessors = columnAccessors; + EqualityComparer = CreateKeyEqualityComparer(column); + } + + /// + public override IEqualityComparer EqualityComparer { get; } + + /// + public override bool TryCreateDependentKeyValue(object?[] keyValues, [NotNullWhen(true)] out TKey key) + { + return HandleNullableValue((TKey?)keyValues[0], out key); + } + + /// + public override bool TryCreateDependentKeyValue( + IDictionary keyPropertyValues, [NotNullWhen(true)] out TKey key) + { + if (keyPropertyValues.TryGetValue(_column.Name, out var value)) + { + return HandleNullableValue((TKey?)value, out key); + } + + key = default(TKey); + return false; + } + + /// + public override bool TryCreateDependentKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues, [NotNullWhen(true)] out TKey key) + { + var (keyValue, present) = fromOriginalValues + ? ((Func)_columnAccessors.OriginalValueGetter)(command) + : ((Func)_columnAccessors.CurrentValueGetter)(command); + return HandleNullableValue(present ? keyValue : null, out key); + } + + private static bool HandleNullableValue(TKey? value, out TKey key) + { + if (value.HasValue) + { + key = (TKey)value; + return true; + } + + key = default; + return false; + } +} diff --git a/src/EFCore.Relational/Update/Internal/SimpleRowIndexValueFactory.cs b/src/EFCore.Relational/Update/Internal/SimpleRowIndexValueFactory.cs new file mode 100644 index 00000000000..8e9d8521218 --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/SimpleRowIndexValueFactory.cs @@ -0,0 +1,111 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SimpleRowIndexValueFactory : IRowIndexValueFactory +{ + private readonly IColumn _column; + private readonly ITableIndex _index; + private readonly ColumnAccessors _columnAccessors; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public SimpleRowIndexValueFactory(ITableIndex index) + { + _index = index; + _column = index.Columns.Single(); + _columnAccessors = ((Column)_column).Accessors; +#pragma warning disable EF1001 // Internal EF Core API usage. + EqualityComparer = NullableComparerAdapter.Wrap(_column.PropertyMappings.First().TypeMapping.ProviderValueComparer); +#pragma warning restore EF1001 // Internal EF Core API usage. + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEqualityComparer EqualityComparer { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool TryCreateIndexValue(object?[] keyValues, [NotNullWhen(true)] out TKey? key) + { + key = (TKey?)keyValues[0]; + return key != null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool TryCreateIndexValue(IDictionary keyValues, [NotNullWhen(true)] out TKey? key) + { + if (keyValues.TryGetValue(_column.Name, out var value)) + { + key = (TKey?)value; + return key != null; + } + + key = default; + return false; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool TryCreateIndexValue(IReadOnlyModificationCommand command, bool fromOriginalValues, [NotNullWhen(true)] out TKey? key) + { + (key, var present) = fromOriginalValues + ? ((Func)_columnAccessors.OriginalValueGetter)(command) + : ((Func)_columnAccessors.CurrentValueGetter)(command); + return present && key != null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual object? CreateValueIndex(IReadOnlyModificationCommand command, bool fromOriginalValues = false) + => TryCreateIndexValue(command, fromOriginalValues, out var keyValue) + ? new ValueIndex(_index, keyValue, EqualityComparer) + : null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual object[]? CreateValue(IReadOnlyModificationCommand command, bool fromOriginalValues = false) + => TryCreateIndexValue(command, fromOriginalValues, out var value) + ? (new object[] { value }) + : null; +} diff --git a/src/EFCore.Relational/Update/Internal/SimpleRowKeyValueFactory.cs b/src/EFCore.Relational/Update/Internal/SimpleRowKeyValueFactory.cs new file mode 100644 index 00000000000..1fbcf7d7153 --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/SimpleRowKeyValueFactory.cs @@ -0,0 +1,160 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SimpleRowKeyValueFactory : IRowKeyValueFactory +{ + private readonly IUniqueConstraint _constraint; + private readonly IColumn _column; + private readonly ColumnAccessors _columnAccessors; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public SimpleRowKeyValueFactory(IUniqueConstraint constraint) + { + _constraint = constraint; + _column = constraint.Columns.Single(); + _columnAccessors = ((Column)_column).Accessors; + EqualityComparer = new NoNullsCustomEqualityComparer(_column.PropertyMappings.First().TypeMapping.ProviderValueComparer); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEqualityComparer EqualityComparer { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TKey CreateKeyValue(object?[] keyValues) + { + var value = (TKey?)keyValues[0]; + if (value == null) + { + throw new InvalidOperationException( + RelationalStrings.NullKeyValue( + _constraint.Table.SchemaQualifiedName, + _column.Name)); + } + + return value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TKey CreateKeyValue(IDictionary keyValues) + { + var value = (TKey?)keyValues[_column.Name]; + if (value == null) + { + throw new InvalidOperationException( + RelationalStrings.NullKeyValue( + _constraint.Table.SchemaQualifiedName, + _column.Name)); + } + + return value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TKey CreateKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues = false) + { + var (key, found) = fromOriginalValues + ? ((Func)_columnAccessors.OriginalValueGetter)(command) + : ((Func)_columnAccessors.CurrentValueGetter)(command); + + if (!found) + { + throw new InvalidOperationException( + RelationalStrings.NullKeyValue( + _constraint.Table.SchemaQualifiedName, + _column.Name)); + } + + return key; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual object CreateValueIndex(IReadOnlyModificationCommand command, bool fromOriginalValues = false) + => new ValueIndex( + _constraint, + CreateKeyValue(command, fromOriginalValues), + EqualityComparer); + + object[] IRowKeyValueFactory.CreateKeyValue(IReadOnlyModificationCommand command, bool fromOriginalValues) + => new object[] { CreateKeyValue(command, fromOriginalValues)! }; + + private sealed class NoNullsStructuralEqualityComparer : IEqualityComparer + { + private readonly IEqualityComparer _comparer + = StructuralComparisons.StructuralEqualityComparer; + + public bool Equals(TKey? x, TKey? y) + => _comparer.Equals(x, y); + + public int GetHashCode([DisallowNull] TKey obj) + => _comparer.GetHashCode(obj); + } + + private sealed class NoNullsCustomEqualityComparer : IEqualityComparer + { + private readonly Func _equals; + private readonly Func _hashCode; + + public NoNullsCustomEqualityComparer(ValueComparer comparer) + { + if (comparer.Type != typeof(TKey) + && comparer.Type == typeof(TKey).UnwrapNullableType()) + { +#pragma warning disable EF1001 // Internal EF Core API usage. + comparer = comparer.ToNonNullNullableComparer(); +#pragma warning restore EF1001 // Internal EF Core API usage. + } + + _equals = (Func)comparer.EqualsExpression.Compile(); + _hashCode = (Func)comparer.HashCodeExpression.Compile(); + } + + public bool Equals(TKey? x, TKey? y) + => _equals(x, y); + + public int GetHashCode([DisallowNull] TKey obj) + => _hashCode(obj); + } +} diff --git a/src/EFCore.Relational/Update/Internal/ValueIndex.cs b/src/EFCore.Relational/Update/Internal/ValueIndex.cs new file mode 100644 index 00000000000..47bea8a8d2c --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/ValueIndex.cs @@ -0,0 +1,61 @@ +// 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.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public sealed class ValueIndex +{ + private readonly object _metadata; + private readonly TKey _keyValue; + private readonly IEqualityComparer _keyComparer; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public ValueIndex( + object metadata, + TKey keyValue, + IEqualityComparer keyComparer) + { + _metadata = metadata; + _keyValue = keyValue; + _keyComparer = keyComparer; + } + + private bool Equals(ValueIndex other) + => other._metadata == _metadata + && _keyComparer.Equals(_keyValue, other._keyValue); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override bool Equals(object? obj) + => ReferenceEquals(this, obj) + || (obj is ValueIndex other && Equals(other)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override int GetHashCode() + { + var hash = new HashCode(); + hash.Add(_metadata); + hash.Add(_keyValue, _keyComparer); + return hash.ToHashCode(); + } +} diff --git a/src/EFCore.Relational/Update/ModificationCommand.cs b/src/EFCore.Relational/Update/ModificationCommand.cs index 52adf65a353..280eec7b7b4 100644 --- a/src/EFCore.Relational/Update/ModificationCommand.cs +++ b/src/EFCore.Relational/Update/ModificationCommand.cs @@ -34,38 +34,46 @@ public class ModificationCommand : IModificationCommand /// Creation parameters. public ModificationCommand(in ModificationCommandParameters modificationCommandParameters) { + Table = modificationCommandParameters.Table; TableName = modificationCommandParameters.TableName; Schema = modificationCommandParameters.Schema; _generateParameterName = modificationCommandParameters.GenerateParameterName; _sensitiveLoggingEnabled = modificationCommandParameters.SensitiveLoggingEnabled; _comparer = modificationCommandParameters.Comparer; _logger = modificationCommandParameters.Logger; + EntityState = EntityState.Modified; } - /// - /// The name of the table containing the data to be modified. - /// + /// + public virtual ITable? Table { get; } + + /// public virtual string TableName { get; } - /// - /// The schema containing the table, or to use the default schema. - /// + /// public virtual string? Schema { get; } - /// - /// The s that represent the entities that are mapped to the row - /// to update. - /// + /// public virtual IReadOnlyList Entries => _entries; + /// + public virtual EntityState EntityState { get; private set; } + /// - /// The that indicates whether the row will be - /// inserted (), - /// updated (), - /// or deleted ((). + /// Indicates whether the database will return values for some mapped properties + /// that will then need to be propagated back to the tracked entities. /// - public virtual EntityState EntityState { get; private set; } = EntityState.Modified; + public virtual bool RequiresResultPropagation + { + get + { + // ReSharper disable once AssignmentIsFullyDiscarded + _ = ColumnModifications; + + return _requiresResultPropagation; + } + } /// /// The list of needed to perform the insert, update, or delete. @@ -90,27 +98,7 @@ public virtual void AssertColumnsNotInitialized() } } - /// - /// Indicates whether the database will return values for some mapped properties - /// that will then need to be propagated back to the tracked entities. - /// - public virtual bool RequiresResultPropagation - { - get - { - // ReSharper disable once AssignmentIsFullyDiscarded - _ = ColumnModifications; - - return _requiresResultPropagation; - } - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// + /// public virtual void AddEntry(IUpdateEntry entry, bool mainEntry) { AssertColumnsNotInitialized(); @@ -271,9 +259,7 @@ private List GenerateColumnModifications() foreach (var entry in _entries) { - var nonMainEntry = updating - && (entry.EntityState == EntityState.Deleted - || entry.EntityState == EntityState.Added); + var nonMainEntry = !_mainEntryAdded || entry != _entries[0]; var tableMapping = GetTableMapping(entry.EntityType); if (tableMapping == null) @@ -290,7 +276,7 @@ private List GenerateColumnModifications() foreach (var columnMapping in tableMapping.ColumnMappings) { var property = columnMapping.Property; - var column = (IColumn)columnMapping.Column; + var column = columnMapping.Column; var isKey = property.IsPrimaryKey(); var isCondition = !adding && (isKey || property.IsConcurrencyToken); var readValue = state != EntityState.Deleted && entry.IsStoreGenerated(property); @@ -308,7 +294,7 @@ private List GenerateColumnModifications() else if ((updating && property.GetAfterSaveBehavior() == PropertySaveBehavior.Save) || (!isKey && nonMainEntry)) { - writeValue = columnPropagator?.TryPropagate(property, entry) + writeValue = columnPropagator?.TryPropagate(columnMapping, entry) ?? (entry.EntityState == EntityState.Added || entry.IsModified(property)); } } @@ -383,12 +369,12 @@ private List GenerateColumnModifications() return columnModifications; } - private ITableMappingBase? GetTableMapping(IEntityType entityType) + private ITableMapping? GetTableMapping(IEntityType entityType) { - ITableMappingBase? tableMapping = null; + ITableMapping? tableMapping = null; foreach (var mapping in entityType.GetTableMappings()) { - var table = ((ITableMappingBase)mapping).Table; + var table = mapping.Table; if (table.Name == TableName && table.Schema == Schema) { @@ -402,7 +388,7 @@ private List GenerateColumnModifications() private static void InitializeSharedColumns( IUpdateEntry entry, - ITableMappingBase tableMapping, + ITableMapping tableMapping, bool updating, Dictionary columnMap) { @@ -417,7 +403,7 @@ private static void InitializeSharedColumns( if (updating) { - columnPropagator.RecordValue(columnMapping.Property, entry); + columnPropagator.RecordValue(columnMapping, entry); } } } @@ -460,8 +446,9 @@ private sealed class ColumnValuePropagator public IColumnModification? ColumnModification { get; set; } - public void RecordValue(IProperty property, IUpdateEntry entry) + public void RecordValue(IColumnMapping mapping, IUpdateEntry entry) { + var property = mapping.Property; switch (entry.EntityState) { case EntityState.Modified: @@ -469,17 +456,17 @@ public void RecordValue(IProperty property, IUpdateEntry entry) && entry.IsModified(property)) { _write = true; - _currentValue = entry.GetCurrentValue(property); + _currentValue = entry.GetCurrentProviderValue(property); } break; case EntityState.Added: - _currentValue = entry.GetCurrentValue(property); - _write = !property.GetValueComparer().Equals(_originalValue, _currentValue); + _currentValue = entry.GetCurrentProviderValue(property); + _write = !mapping.TypeMapping.ProviderValueComparer.Equals(_originalValue, _currentValue); break; case EntityState.Deleted: - _originalValue = entry.GetOriginalValue(property); + _originalValue = entry.GetOriginalProviderValue(property); if (!_write && !property.IsPrimaryKey()) { @@ -491,15 +478,20 @@ public void RecordValue(IProperty property, IUpdateEntry entry) } } - public bool TryPropagate(IProperty property, IUpdateEntry entry) + public bool TryPropagate(IColumnMapping mapping, IUpdateEntry entry) { + var property = mapping.Property; if (_write && (entry.EntityState == EntityState.Unchanged || (entry.EntityState == EntityState.Modified && !entry.IsModified(property)) || (entry.EntityState == EntityState.Added - && property.GetValueComparer().Equals(_originalValue, entry.GetCurrentValue(property))))) + && mapping.TypeMapping.ProviderValueComparer.Equals(_originalValue, entry.GetCurrentValue(property))))) { - entry.SetStoreGeneratedValue(property, _currentValue); + if (property.GetAfterSaveBehavior() == PropertySaveBehavior.Save + || entry.EntityState == EntityState.Added) + { + entry.SetStoreGeneratedValue(property, _currentValue); + } return false; } diff --git a/src/EFCore.Relational/Update/ModificationCommandParameters.cs b/src/EFCore.Relational/Update/ModificationCommandParameters.cs index edf3b3c7b84..d8bb902882d 100644 --- a/src/EFCore.Relational/Update/ModificationCommandParameters.cs +++ b/src/EFCore.Relational/Update/ModificationCommandParameters.cs @@ -34,6 +34,7 @@ public ModificationCommandParameters( Func? generateParameterName = null, IDiagnosticsLogger? logger = null) { + Table = null; TableName = tableName; Schema = schemaName; GenerateParameterName = generateParameterName; @@ -42,6 +43,30 @@ public ModificationCommandParameters( Logger = logger; } + /// + /// Creates a new instance. + /// + /// The table containing the data to be modified. + /// Indicates whether potentially sensitive data (e.g. database values) can be logged. + /// An for . + /// A delegate to generate parameter names. + /// An for . + public ModificationCommandParameters( + ITable table, + bool sensitiveLoggingEnabled, + IComparer? comparer = null, + Func? generateParameterName = null, + IDiagnosticsLogger? logger = null) + { + Table = table; + TableName = table.Name; + Schema = table.Schema; + GenerateParameterName = generateParameterName; + SensitiveLoggingEnabled = sensitiveLoggingEnabled; + Comparer = comparer; + Logger = logger; + } + /// /// The name of the table containing the data to be modified. /// @@ -52,6 +77,11 @@ public ModificationCommandParameters( /// public string? Schema { get; init; } + /// + /// The table containing the data to be modified. + /// + public ITable? Table { get; init; } + /// /// A delegate to generate parameter names. /// diff --git a/src/EFCore/ChangeTracking/Internal/CompositeValueFactory.cs b/src/EFCore/ChangeTracking/Internal/CompositeValueFactory.cs index a7e6192af5a..bcf384bcfdb 100644 --- a/src/EFCore/ChangeTracking/Internal/CompositeValueFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/CompositeValueFactory.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections; using System.Diagnostics.CodeAnalysis; namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; @@ -140,15 +139,7 @@ protected virtual bool TryCreateFromEntry( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected static IEqualityComparer CreateEqualityComparer(IReadOnlyList properties) - { - var comparers = properties.Select(p => p.GetKeyValueComparer()).ToList(); - - return comparers.All(c => c != null) - ? new CompositeCustomComparer(comparers) - : properties.Any(p => typeof(IStructuralEquatable).IsAssignableFrom(p.ClrType)) - ? new StructuralCompositeComparer() - : new CompositeComparer(); - } + => new CompositeCustomComparer(properties.Select(p => p.GetKeyValueComparer()).ToList()); private sealed class CompositeCustomComparer : IEqualityComparer { @@ -208,104 +199,4 @@ public int GetHashCode(object[] obj) return hashCode; } } - - private sealed class CompositeComparer : IEqualityComparer - { - public bool Equals(object[]? x, object[]? y) - { - if (ReferenceEquals(x, y)) - { - return true; - } - - if (x is null) - { - return y is null; - } - - if (y is null) - { - return false; - } - - if (x.Length != y.Length) - { - return false; - } - - for (var i = 0; i < x.Length; i++) - { - if (!Equals(x[i], y[i])) - { - return false; - } - } - - return true; - } - - public int GetHashCode(object[] obj) - { - var hash = new HashCode(); - foreach (var value in obj) - { - hash.Add(value); - } - - return hash.ToHashCode(); - } - } - - private sealed class StructuralCompositeComparer : IEqualityComparer - { - private readonly IEqualityComparer _structuralEqualityComparer - = StructuralComparisons.StructuralEqualityComparer; - - public bool Equals(object[]? x, object[]? y) - { - if (ReferenceEquals(x, y)) - { - return true; - } - - if (x is null) - { - return y is null; - } - - if (y is null) - { - return false; - } - - if (x.Length != y.Length) - { - return false; - } - - for (var i = 0; i < x.Length; i++) - { - if (!_structuralEqualityComparer.Equals(x[i], y[i])) - { - return false; - } - } - - return true; - } - - public int GetHashCode(object[] obj) - { - var hashCode = 0; - - // ReSharper disable once ForCanBeConvertedToForeach - // ReSharper disable once LoopCanBeConvertedToQuery - for (var i = 0; i < obj.Length; i++) - { - hashCode = (hashCode * 397) ^ _structuralEqualityComparer.GetHashCode(obj[i]); - } - - return hashCode; - } - } } diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs index 94d13cf4851..444ecec374d 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs @@ -667,22 +667,21 @@ private CurrentValueType GetValueType( return CurrentValueType.Normal; } - equals ??= ValuesEqualFunc(property); var defaultValue = property.ClrType.GetDefaultValue(); var value = ReadPropertyValue(property); - if (!equals(value, defaultValue)) + if (!AreEqual(value, defaultValue, property, equals)) { return CurrentValueType.Normal; } if (_storeGeneratedValues.TryGetValue(tempIndex, out value) - && !equals(value, defaultValue)) + && !AreEqual(value, defaultValue, property, equals)) { return CurrentValueType.StoreGenerated; } if (_temporaryValues.TryGetValue(tempIndex, out value) - && !equals(value, defaultValue)) + && !AreEqual(value, defaultValue, property, equals)) { return CurrentValueType.Temporary; } @@ -1167,19 +1166,17 @@ public object? this[IPropertyBase propertyBase] var defaultValue = propertyClrType.GetDefaultValue(); var property = (IProperty)propertyBase; - var equals = ValuesEqualFunc(property); - if (_storeGeneratedValues.TryGetValue(storeGeneratedIndex, out var generatedValue) - && !equals(generatedValue, defaultValue)) + && !AreEqual(generatedValue, defaultValue, property)) { return generatedValue; } var value = ReadPropertyValue(propertyBase); - if (equals(value, defaultValue)) + if (AreEqual(value, defaultValue, property)) { if (_temporaryValues.TryGetValue(storeGeneratedIndex, out generatedValue) - && !equals(generatedValue, defaultValue)) + && !AreEqual(generatedValue, defaultValue, property)) { return generatedValue; } @@ -1221,22 +1218,21 @@ private void SetProperty( var asProperty = propertyBase as IProperty; int propertyIndex; CurrentValueType currentValueType; - Func equals; - + + var valuesEqual = false; if (asProperty != null) { propertyIndex = asProperty.GetIndex(); - equals = ValuesEqualFunc(asProperty); - currentValueType = GetValueType(asProperty, equals); + valuesEqual = AreEqual(currentValue, value, asProperty); + currentValueType = GetValueType(asProperty); } else { propertyIndex = -1; - equals = ReferenceEquals; + valuesEqual = ReferenceEquals(currentValue, value); currentValueType = CurrentValueType.Normal; } - var valuesEqual = equals(currentValue, value); if (!valuesEqual || (propertyIndex != -1 @@ -1304,7 +1300,7 @@ private void SetProperty( if (valueType == CurrentValueType.StoreGenerated) { var defaultValue = asProperty!.ClrType.GetDefaultValue(); - if (!equals(currentValue, defaultValue)) + if (!AreEqual(currentValue, defaultValue, asProperty!)) { WritePropertyValue(asProperty, defaultValue, isMaterialization); } @@ -1315,13 +1311,13 @@ private void SetProperty( else { var defaultValue = asProperty!.ClrType.GetDefaultValue(); - if (!equals(currentValue, defaultValue)) + if (!AreEqual(currentValue, defaultValue, asProperty!)) { WritePropertyValue(asProperty, defaultValue, isMaterialization); } if (_storeGeneratedValues.TryGetValue(storeGeneratedIndex, out var generatedValue) - && !equals(generatedValue, defaultValue)) + && !AreEqual(generatedValue, defaultValue, asProperty!)) { _storeGeneratedValues.SetValue(asProperty, defaultValue, storeGeneratedIndex); } @@ -1391,8 +1387,13 @@ public void HandleNullForeignKey( } } - private static Func ValuesEqualFunc(IProperty property) - => property.GetValueComparer().Equals; + private static bool AreEqual(object? value, object? otherValue, IProperty property) + => property.GetValueComparer().Equals(value, otherValue); + + private static bool AreEqual(object? value, object? otherValue, IProperty property, Func? equals) + => equals != null + ? equals(value, otherValue) + : AreEqual(value, otherValue, property); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1410,9 +1411,8 @@ public void AcceptChanges() if (storeGeneratedIndex != -1 && _storeGeneratedValues.TryGetValue(storeGeneratedIndex, out var value)) { - var equals = ValuesEqualFunc(property); var defaultValue = property.ClrType.GetDefaultValue(); - if (!equals(value, defaultValue)) + if (!AreEqual(value, defaultValue, property)) { this[property] = value; } @@ -1685,12 +1685,11 @@ public bool HasDefaultValue(IProperty property) } var defaultValue = property.ClrType.GetDefaultValue(); - var equals = ValuesEqualFunc(property); return (!_storeGeneratedValues.TryGetValue(storeGeneratedIndex, out var generatedValue) - || equals(defaultValue, generatedValue)) + || AreEqual(defaultValue, generatedValue, property)) && (!_temporaryValues.TryGetValue(storeGeneratedIndex, out generatedValue) - || equals(defaultValue, generatedValue)); + || AreEqual(defaultValue, generatedValue, property)); } /// diff --git a/src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs b/src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs index 0281204de8e..c890413d708 100644 --- a/src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs @@ -29,14 +29,7 @@ public SimplePrincipalKeyValueFactory(IProperty property) _property = property; _propertyAccessors = _property.GetPropertyAccessors(); - var comparer = property.GetKeyValueComparer(); - - EqualityComparer - = comparer != null - ? new NoNullsCustomEqualityComparer(comparer) - : typeof(IStructuralEquatable).IsAssignableFrom(typeof(TKey)) - ? new NoNullsStructuralEqualityComparer() - : EqualityComparer.Default; + EqualityComparer = new NoNullsCustomEqualityComparer(property.GetKeyValueComparer()); } /// diff --git a/src/EFCore/Internal/NullableComparerAdapter.cs b/src/EFCore/Internal/NullableComparerAdapter.cs new file mode 100644 index 00000000000..aaef157045d --- /dev/null +++ b/src/EFCore/Internal/NullableComparerAdapter.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; + +namespace Microsoft.EntityFrameworkCore.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public sealed class NullableComparerAdapter : IEqualityComparer +{ + private readonly IEqualityComparer _comparer; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public NullableComparerAdapter(IEqualityComparer comparer) + { + _comparer = comparer; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static IEqualityComparer Wrap(IEqualityComparer comparer) + => comparer is IEqualityComparer nullableComparer + ? nullableComparer + : new NullableComparerAdapter(comparer); + + /// + public bool Equals(TNullableKey? x, TNullableKey? y) + => (x is null && y is null) + || (x is not null && y is not null && _comparer.Equals(x, y)); + + /// + public int GetHashCode(TNullableKey obj) + => obj is null ? 0 : _comparer.GetHashCode(obj); +} diff --git a/src/EFCore/Metadata/IProperty.cs b/src/EFCore/Metadata/IProperty.cs index 206b629563a..47aa28b8049 100644 --- a/src/EFCore/Metadata/IProperty.cs +++ b/src/EFCore/Metadata/IProperty.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; +using Microsoft.EntityFrameworkCore.Internal; namespace Microsoft.EntityFrameworkCore.Metadata; @@ -24,30 +25,7 @@ public interface IProperty : IReadOnlyProperty, IPropertyBase /// The property type. /// A new equality comparer. IEqualityComparer CreateKeyEqualityComparer() - { - var comparer = GetKeyValueComparer(); - - return comparer is IEqualityComparer nullableComparer - ? nullableComparer - : new NullableComparer(comparer); - } - - private sealed class NullableComparer : IEqualityComparer - { - private readonly IEqualityComparer _comparer; - - public NullableComparer(IEqualityComparer comparer) - { - _comparer = comparer; - } - - public bool Equals(TNullableKey? x, TNullableKey? y) - => (x == null && y == null) - || (x != null && y != null && _comparer.Equals(x, y)); - - public int GetHashCode(TNullableKey obj) - => obj is null ? 0 : _comparer.GetHashCode(obj); - } + => NullableComparerAdapter.Wrap(GetKeyValueComparer()); /// /// Finds the first principal property that the given property is constrained by diff --git a/src/EFCore/Update/UpdateEntryExtensions.cs b/src/EFCore/Update/UpdateEntryExtensions.cs index d56dc5021f6..233c1ea99bc 100644 --- a/src/EFCore/Update/UpdateEntryExtensions.cs +++ b/src/EFCore/Update/UpdateEntryExtensions.cs @@ -6,7 +6,6 @@ using System.Text; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using System; namespace Microsoft.EntityFrameworkCore.Update; @@ -42,6 +41,29 @@ public static class UpdateEntryExtensions return value; } + /// + /// Gets the original value that was assigned to the property and converts it to the provider-expected value. + /// + /// The entry. + /// The property to get the value for. + /// The value for the property. + public static object? GetOriginalProviderValue(this IUpdateEntry updateEntry, IProperty property) + { + var value = updateEntry.GetOriginalValue(property); + var typeMapping = property.GetTypeMapping(); + value = value?.GetType().IsInteger() == true && typeMapping.ClrType.UnwrapNullableType().IsEnum + ? Enum.ToObject(typeMapping.ClrType.UnwrapNullableType(), value) + : value; + + var converter = typeMapping.Converter; + if (converter != null) + { + value = converter.ConvertToProvider(value); + } + + return value; + } + /// /// /// Creates a human-readable representation of the given . diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index 3e2472cb2f4..9c8698938ae 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -429,7 +429,7 @@ public virtual void Warns_on_not_configured_shared_columns_with_shared_table() } [ConditionalFact] - public virtual void Detects_incompatible_shared_columns_with_shared_table() + public virtual void Detects_incompatible_shared_columns_in_shared_table_with_different_data_types() { var modelBuilder = CreateConventionalModelBuilder(); @@ -445,6 +445,23 @@ public virtual void Detects_incompatible_shared_columns_with_shared_table() modelBuilder); } + [ConditionalFact] + public virtual void Detects_incompatible_shared_columns_in_shared_table_with_different_provider_types() + { + var modelBuilder = CreateConventionalModelBuilder(); + + modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired(); + modelBuilder.Entity().Property(a => a.P0).HasColumnName(nameof(A.P0)).HasColumnType("someInt").HasConversion(); + modelBuilder.Entity().ToTable("Table"); + modelBuilder.Entity().Property(b => b.P0).HasColumnName(nameof(A.P0)).HasColumnType("someInt"); + modelBuilder.Entity().ToTable("Table"); + + VerifyError( + RelationalStrings.DuplicateColumnNameProviderTypeMismatch( + nameof(A), nameof(A.P0), nameof(B), nameof(B.P0), nameof(B.P0), "Table", "long", "int"), + modelBuilder); + } + [ConditionalFact] public virtual void Detects_incompatible_shared_check_constraints_with_shared_table() { diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index ae8222c0982..521377ffd27 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -489,6 +489,7 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) Assert.Equal("default_datetime_mapping", orderDateColumn.StoreType); Assert.False(orderDateColumn.IsNullable); Assert.Same(ordersTable, orderDateColumn.Table); + Assert.Same(orderDateMapping, orderDateColumn.FindColumnMapping(orderType)); var orderPk = orderType.FindPrimaryKey(); var orderPkConstraint = orderPk.GetMappedConstraints().Single(); @@ -536,6 +537,7 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) Assert.Equal("FK_DateDetails", orderDateFkConstraint.Name); Assert.Equal(nameof(Order.OrderDate), orderDateFkConstraint.Columns.Single().Name); Assert.Equal(nameof(DateDetails.Date), orderDateFkConstraint.PrincipalColumns.Single().Name); + Assert.Equal("PK_DateDetails", orderDateFkConstraint.PrincipalUniqueConstraint.Name); Assert.Equal("DateDetails", orderDateFkConstraint.PrincipalTable.Name); var orderCustomerFk = orderType.GetForeignKeys().Single(fk => fk.PrincipalEntityType.ClrType == typeof(Customer)); @@ -718,6 +720,7 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) Assert.Equal(ReferentialAction.Cascade, orderCustomerFkConstraint.OnDeleteAction); Assert.Equal(orderCustomerFk, orderCustomerFkConstraint.MappedForeignKeys.Single()); Assert.Equal(new[] { orderDateFkConstraint, orderCustomerFkConstraint }, ordersTable.ForeignKeyConstraints); + Assert.Empty(ordersTable.ReferencingForeignKeyConstraints); Assert.Equal(orderCustomerFkConstraint.Name, orderCustomerFk.GetConstraintName()); Assert.Equal(orderCustomerFkConstraint.Name, orderCustomerFk.GetConstraintName( @@ -751,6 +754,8 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) Assert.NotNull(anotherSpecialCustomerFkConstraint.MappedForeignKeys.Single()); Assert.Same(specialCustomerTable, anotherSpecialCustomerFkConstraint.PrincipalTable); + Assert.Equal(new[] { orderCustomerFkConstraint, specialCustomerTptFkConstraint }, customerTable.ReferencingForeignKeyConstraints); + var specialCustomerDbIndex = specialCustomerTable.Indexes.Last(); Assert.Equal("IX_SpecialCustomer_RelatedCustomerSpeciality", specialCustomerDbIndex.Name); Assert.NotNull(specialCustomerDbIndex.MappedIndexes.Single()); @@ -811,6 +816,7 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) Assert.Equal(ReferentialAction.Cascade, orderCustomerFkConstraint.OnDeleteAction); Assert.Equal(orderCustomerFk, orderCustomerFkConstraint.MappedForeignKeys.Single()); Assert.Equal(new[] { orderDateFkConstraint, orderCustomerFkConstraint }, ordersTable.ForeignKeyConstraints); + Assert.Empty(ordersTable.ReferencingForeignKeyConstraints); Assert.Equal(orderCustomerFkConstraint.Name, orderCustomerFk.GetConstraintName()); Assert.Equal(orderCustomerFkConstraint.Name, orderCustomerFk.GetConstraintName( @@ -832,6 +838,9 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) Assert.Equal("FK_AbstractBase_AbstractBase_AnotherRelatedCustomerId", anotherSpecialCustomerFkConstraint.Name); Assert.NotNull(anotherSpecialCustomerFkConstraint.MappedForeignKeys.Single()); + Assert.Equal(new[] { anotherSpecialCustomerFkConstraint, specialCustomerFkConstraint, orderCustomerFkConstraint }, + customerTable.ReferencingForeignKeyConstraints); + Assert.Equal("IX_AbstractBase_RelatedCustomerSpeciality", specialCustomerDbIndex.Name); Assert.Equal("IX_AbstractBase_AnotherRelatedCustomerId", anotherSpecialCustomerDbIndex.Name); @@ -847,6 +856,9 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) Assert.False(specialCustomerTypeMapping.IncludesDerivedTypes); Assert.NotSame(customerTable, specialCustomerTable); + Assert.Empty(ordersTable.ReferencingForeignKeyConstraints); + Assert.Empty(customerTable.ReferencingForeignKeyConstraints); + Assert.True(customerTable.EntityTypeMappings.Single().IsSharedTablePrincipal); Assert.Equal(5, customerTable.Columns.Count()); diff --git a/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs b/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs index cf221e4cac1..4cbd36ae702 100644 --- a/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs +++ b/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs @@ -975,7 +975,6 @@ public ICommandBatchPreparer CreateCommandBatchPreparer( modificationCommandBatchFactory, new ParameterNameGeneratorFactory(new ParameterNameGeneratorDependencies()), new ModificationCommandComparer(), - new KeyValueIndexFactorySource(), new ModificationCommandFactory(), loggingOptions, new FakeDiagnosticsLogger(), diff --git a/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs b/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs index 870a0086f1e..cd639c580e5 100644 --- a/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs +++ b/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs @@ -254,7 +254,7 @@ public Fuel(double volume) public double Volume { get; } } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #27738")] public virtual void Can_insert_and_read_back_with_case_insensitive_string_key() { using (var context = CreateContext()) diff --git a/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs index 518ec2d7f57..482777e01e9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs @@ -184,22 +184,17 @@ public override async Task Can_change_dependent_instance_non_derived() { await base.Can_change_dependent_instance_non_derived(); AssertSql( - @"@p1='Trek Pro Fit Madone 6 Series' (Nullable = false) (Size = 450) -@p0='repairman' (Size = 4000) + @"@p0='Trek Pro Fit Madone 6 Series' (Nullable = false) (Size = 450) +@p1='Repair' (Size = 4000) +@p3='Trek Pro Fit Madone 6 Series' (Nullable = false) (Size = 450) +@p2='repairman' (Size = 4000) -SET IMPLICIT_TRANSACTIONS OFF; -SET NOCOUNT ON; -UPDATE [Vehicles] SET [Operator_Name] = @p0 -OUTPUT 1 -WHERE [Name] = @p1;", - // - @"@p2='Trek Pro Fit Madone 6 Series' (Nullable = false) (Size = 450) -@p3='Repair' (Size = 4000) - -SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [LicensedOperators] ([VehicleName], [LicenseType]) -VALUES (@p2, @p3);", +VALUES (@p0, @p1); +UPDATE [Vehicles] SET [Operator_Name] = @p2 +OUTPUT 1 +WHERE [Name] = @p3;", // @"SELECT TOP(2) [v].[Name], [v].[SeatingCapacity], [c].[AttachedVehicleName], CASE WHEN [c].[Name] IS NOT NULL THEN N'CompositeVehicle' diff --git a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs index 4b58e546831..211a2353f82 100644 --- a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs +++ b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs @@ -25,21 +25,6 @@ public override void Detects_duplicate_column_names() modelBuilder); } - public override void Detects_incompatible_shared_columns_with_shared_table() - { - var modelBuilder = CreateConventionalModelBuilder(); - - modelBuilder.Entity().HasOne().WithOne().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired(); - modelBuilder.Entity().Property(a => a.P0).HasColumnName(nameof(A.P0)).HasColumnType("someInt"); - modelBuilder.Entity().ToTable("Table"); - modelBuilder.Entity().Property(a => a.P0).HasColumnName(nameof(A.P0)); - modelBuilder.Entity().ToTable("Table"); - - VerifyError( - RelationalStrings.DuplicateColumnNameDataTypeMismatch( - nameof(A), nameof(A.P0), nameof(B), nameof(B.P0), nameof(B.P0), "Table", "someInt", "int"), modelBuilder); - } - public override void Detects_duplicate_columns_in_derived_types_with_different_types() { var modelBuilder = CreateConventionalModelBuilder(); diff --git a/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs b/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs index 45cc92f9317..cbcd5ffff04 100644 --- a/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs +++ b/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs @@ -50,24 +50,6 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe nameof(Cat), nameof(Cat.Breed), nameof(Dog), nameof(Dog.Breed), nameof(Cat.Breed), nameof(Animal)), modelBuilder); } - public override void Detects_incompatible_shared_columns_with_shared_table() - { - var modelBuilder = CreateConventionalModelBuilder(); - - modelBuilder.Entity().HasOne().WithOne().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired(); - modelBuilder.Entity().Property(a => a.P0).HasColumnName(nameof(A.P0)).HasColumnType("someInt"); - modelBuilder.Entity().ToTable("Table"); - modelBuilder.Entity().Property(a => a.P0).HasColumnName(nameof(A.P0)); - modelBuilder.Entity().ToTable("Table"); - - modelBuilder.Entity().Property(b => b.P0); - modelBuilder.Entity().Property(d => d.P0); - - VerifyError( - RelationalStrings.DuplicateColumnNameDataTypeMismatch( - nameof(A), nameof(A.P0), nameof(B), nameof(B.P0), nameof(B.P0), "Table", "someInt", "INTEGER"), modelBuilder); - } - [ConditionalFact] public void Detects_schemas() { From 52834485c3a0b1ab12e06baa2cd4db50d1437e03 Mon Sep 17 00:00:00 2001 From: vseanreesermsft <78103370+vseanreesermsft@users.noreply.github.com> Date: Tue, 5 Apr 2022 13:26:30 -0700 Subject: [PATCH 061/143] Update branding to 3.1.25 (#27763) --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index 959f0fbc7df..a1c5fd29614 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -1,6 +1,6 @@ - 3.1.24 + 3.1.25 servicing False true From ba390ffc12672ec3e7bfa1be2defdb27acd3ebae Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 21:01:01 +0000 Subject: [PATCH 062/143] Update dependencies from https://github.com/dotnet/arcade build 20220309.6 (#27766) [release/3.1] Update dependencies from dotnet/arcade --- NuGet.config | 5 ----- eng/Version.Details.xml | 4 ++-- eng/common/templates/jobs/jobs.yml | 4 ---- global.json | 2 +- 4 files changed, 3 insertions(+), 12 deletions(-) diff --git a/NuGet.config b/NuGet.config index 9adc0178300..3b304f633a1 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,13 +4,10 @@ - - - @@ -23,10 +20,8 @@ - - diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index a3b03d1d4a6..8806ff817ff 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -321,9 +321,9 @@ - + https://github.com/dotnet/arcade - 184ce2c7f17508264ffa19b307d97e7568da05ab + aebcd10d76469b2e84cffd39d043574bc5357d22 https://github.com/dotnet/roslyn diff --git a/eng/common/templates/jobs/jobs.yml b/eng/common/templates/jobs/jobs.yml index 0bd064ba208..bd9090cb0b5 100644 --- a/eng/common/templates/jobs/jobs.yml +++ b/eng/common/templates/jobs/jobs.yml @@ -17,10 +17,6 @@ parameters: # Optional: Enable publishing using release pipelines enablePublishUsingPipelines: false - # Optional: Disable component governance detection. In general, component governance - # should be on for all jobs. Use only in the event of issues. - disableComponentGovernance: false - graphFileGeneration: # Optional: Enable generating the graph files at the end of the build enabled: false diff --git a/global.json b/global.json index 3648d85f20f..7793fd043a8 100644 --- a/global.json +++ b/global.json @@ -12,6 +12,6 @@ "version": "3.1.417" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.22114.9" + "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.22159.6" } } From 25a147a98fcadfaa75e97ab209327cadd46c0fa9 Mon Sep 17 00:00:00 2001 From: vseanreesermsft <78103370+vseanreesermsft@users.noreply.github.com> Date: Tue, 5 Apr 2022 14:06:36 -0700 Subject: [PATCH 063/143] Update branding to 5.0.17 (#27764) --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index 6a22279ce9a..941a1eb4ef2 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -1,6 +1,6 @@ - 5.0.16 + 5.0.17 servicing False true From 3fa7f23483786758a2e5c21969548d010b32bbb9 Mon Sep 17 00:00:00 2001 From: vseanreesermsft <78103370+vseanreesermsft@users.noreply.github.com> Date: Tue, 5 Apr 2022 15:47:09 -0700 Subject: [PATCH 064/143] Update branding to 6.0.5 (#27765) --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index e86978710ac..ead69aa1ee1 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -1,6 +1,6 @@ - 6.0.4 + 6.0.5 servicing False true From b5ffca6abccb440469ade03a1c545b3531808875 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 15:48:40 -0700 Subject: [PATCH 065/143] Update dependencies from https://github.com/dotnet/arcade build 20220328.5 (#27768) Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 6.0.0-beta.22161.1 -> To Version 6.0.0-beta.22178.5 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 8 ++++---- eng/common/templates/steps/source-build.yml | 4 ++-- global.json | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index aedff6fe7a0..0e919b50495 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -47,13 +47,13 @@ - + https://github.com/dotnet/arcade - 879df783283dfb44c7653493fdf7fd7b07ba6b01 + f8c0d51185208227e582f76ac3c5003db237b689 - + https://github.com/dotnet/arcade - 879df783283dfb44c7653493fdf7fd7b07ba6b01 + f8c0d51185208227e582f76ac3c5003db237b689 diff --git a/eng/common/templates/steps/source-build.yml b/eng/common/templates/steps/source-build.yml index ba40dc82f14..abb1b2bcda4 100644 --- a/eng/common/templates/steps/source-build.yml +++ b/eng/common/templates/steps/source-build.yml @@ -43,8 +43,8 @@ steps: # 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 [ '$(dotnetclimsrc-read-sas-token-base64)' != '$''(dotnetclimsrc-read-sas-token-base64)' ]; then - internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://dotnetclimsrc.blob.core.windows.net/dotnet /p:DotNetRuntimeSourceFeedKey=$(dotnetclimsrc-read-sas-token-base64) --runtimesourcefeed https://dotnetclimsrc.blob.core.windows.net/dotnet --runtimesourcefeedkey $(dotnetclimsrc-read-sas-token-base64)' + 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 diff --git a/global.json b/global.json index 7b96d22581e..21acb34daea 100644 --- a/global.json +++ b/global.json @@ -18,7 +18,7 @@ "rollForward": "latestMajor" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.22161.1", - "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.22161.1" + "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.22178.5", + "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.22178.5" } } From 8b3ccb749211092d687397eda082442a4c6ea43c Mon Sep 17 00:00:00 2001 From: William Godbe Date: Tue, 5 Apr 2022 15:49:17 -0700 Subject: [PATCH 066/143] Update Grpc.AspNetCore dependency (#27726) --- .../EFCore.AspNet.Specification.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/EFCore.AspNet.Specification.Tests/EFCore.AspNet.Specification.Tests.csproj b/test/EFCore.AspNet.Specification.Tests/EFCore.AspNet.Specification.Tests.csproj index c954d2cda60..202539e3b4f 100644 --- a/test/EFCore.AspNet.Specification.Tests/EFCore.AspNet.Specification.Tests.csproj +++ b/test/EFCore.AspNet.Specification.Tests/EFCore.AspNet.Specification.Tests.csproj @@ -19,7 +19,7 @@ - + From 70f92927038b0051ad4c47c81d4d80a5f82edafb Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Tue, 5 Apr 2022 15:50:12 -0700 Subject: [PATCH 067/143] Query: Assign proper type to collection result expression (part 2) (#27664) This improves the fix made in #27134 In earlier PR we converted `IQueryable` to `IEnumerable` becaused we converted enumerable to queryable during preprocessing phase and types aligned in later phase since we create a list (which does implement `IEnumerable`). Though this implementing behavior doesn't work when returning a single result of enumerable during async which wraps collection type inside Task. The better fix is to assign `List` as type since we are eventually creating a list. In case of single result, the single result operator has generic type which will introduce convert node into it automatically which will match types for async tasks. Resolves #27600 --- .../InternalUsageDiagnosticAnalyzer.cs | 2 +- ...ionalProjectionBindingExpressionVisitor.cs | 21 ++++++-- .../Query/NorthwindSelectQueryCosmosTest.cs | 18 +++++++ .../Query/NorthwindSelectQueryTestBase.cs | 42 +++++++++++++++ .../NorthwindSelectQuerySqlServerTest.cs | 53 +++++++++++++++++++ 5 files changed, 131 insertions(+), 5 deletions(-) diff --git a/src/EFCore.Analyzers/InternalUsageDiagnosticAnalyzer.cs b/src/EFCore.Analyzers/InternalUsageDiagnosticAnalyzer.cs index 58929a77b80..6f21d101110 100644 --- a/src/EFCore.Analyzers/InternalUsageDiagnosticAnalyzer.cs +++ b/src/EFCore.Analyzers/InternalUsageDiagnosticAnalyzer.cs @@ -27,7 +27,7 @@ public const string MessageFormat private static readonly int EFLen = "EntityFrameworkCore".Length; private static readonly DiagnosticDescriptor _descriptor - = new( + = new DiagnosticDescriptor( Id, title: DefaultTitle, messageFormat: MessageFormat, diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index b94535ec835..ab68d52d25d 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -198,14 +198,27 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio _clientProjections!.Add(subquery); var type = expression.Type; - if (!(AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue27105", out var enabled) && enabled)) + + if (!(AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue27105", out var enabled27105) && enabled27105)) { - if (type.IsGenericType - && type.GetGenericTypeDefinition() == typeof(IQueryable<>)) + if (!(AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue27600", out var enabled27600) && enabled27600)) + { + if (type.IsGenericType + && type.GetGenericTypeDefinition() == typeof(IQueryable<>)) + { + type = typeof(List<>).MakeGenericType(type.GetSequenceType()); + } + } + else { - type = typeof(IEnumerable<>).MakeGenericType(type.GetSequenceType()); + if (type.IsGenericType + && type.GetGenericTypeDefinition() == typeof(IQueryable<>)) + { + type = typeof(IEnumerable<>).MakeGenericType(type.GetSequenceType()); + } } } + var projectionBindingExpression = new ProjectionBindingExpression( _selectExpression, _clientProjections.Count - 1, type); return subquery.ResultCardinality == ResultCardinality.Enumerable diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs index 1a731c66ce9..1b611b755d0 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs @@ -1332,6 +1332,24 @@ public override Task List_of_list_of_anonymous_type(bool async) return base.List_of_list_of_anonymous_type(async); } + [ConditionalTheory(Skip = "Cross collection join Issue#17246")] + public override Task List_from_result_of_single_result(bool async) + { + return base.List_from_result_of_single_result(async); + } + + [ConditionalTheory(Skip = "Cross collection join Issue#17246")] + public override Task List_from_result_of_single_result_2(bool async) + { + return base.List_from_result_of_single_result_2(async); + } + + [ConditionalTheory(Skip = "Cross collection join Issue#17246")] + public override Task List_from_result_of_single_result_3(bool async) + { + return base.List_from_result_of_single_result_3(async); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs index 1b0be4d80b0..b46661fd38c 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs @@ -2472,5 +2472,47 @@ public virtual Task List_of_list_of_anonymous_type(bool async) elementAsserter: (ee, aa) => AssertCollection(ee, aa, elementSorter: i => (i.OrderID, i.ProductID))); }); } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task List_from_result_of_single_result(bool async) + { + return AssertFirstOrDefault( + async, + ss => ss.Set() + .OrderBy(c => c.CustomerID) + .Select(c => c.Orders.Select(e => e.OrderID)), + asserter: (e, a) => AssertCollection(e, a, elementSorter: e => e, elementAsserter: (ee, aa) => AssertEqual(ee, aa))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task List_from_result_of_single_result_2(bool async) + { + return AssertFirstOrDefault( + async, + ss => ss.Set() + .OrderBy(c => c.CustomerID) + .Select(c => c.Orders.Select(e => new { e.OrderID, e.OrderDate })), + asserter: (e, a) => AssertCollection(e, a, elementSorter: e => e.OrderID, + elementAsserter: (ee, aa) => + { + AssertEqual(ee.OrderID, aa.OrderID); + AssertEqual(ee.OrderDate, aa.OrderDate); + })); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task List_from_result_of_single_result_3(bool async) + { + return AssertFirstOrDefault( + async, + ss => ss.Set() + .OrderBy(c => c.CustomerID) + .Select(c => c.Orders.OrderBy(o => o.OrderDate) + .Select(e => e.OrderDetails.Select(od => od.ProductID)).FirstOrDefault()), + asserter: (e, a) => AssertCollection(e, a, elementSorter: e => e, elementAsserter: (ee, aa) => AssertEqual(ee, aa))); + } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs index 2c690b9064e..b492b5565e2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs @@ -1891,6 +1891,59 @@ WHERE [c].[CustomerID] LIKE N'F%' ORDER BY [c].[CustomerID], [t].[OrderID], [t].[OrderID0]"); } + public override async Task List_from_result_of_single_result(bool async) + { + await base.List_from_result_of_single_result(async); + + AssertSql( + @"SELECT [t].[CustomerID], [o].[OrderID] +FROM ( + SELECT TOP(1) [c].[CustomerID] + FROM [Customers] AS [c] + ORDER BY [c].[CustomerID] +) AS [t] +LEFT JOIN [Orders] AS [o] ON [t].[CustomerID] = [o].[CustomerID] +ORDER BY [t].[CustomerID]"); + } + + public override async Task List_from_result_of_single_result_2(bool async) + { + await base.List_from_result_of_single_result_2(async); + + AssertSql( + @"SELECT [t].[CustomerID], [o].[OrderID], [o].[OrderDate] +FROM ( + SELECT TOP(1) [c].[CustomerID] + FROM [Customers] AS [c] + ORDER BY [c].[CustomerID] +) AS [t] +LEFT JOIN [Orders] AS [o] ON [t].[CustomerID] = [o].[CustomerID] +ORDER BY [t].[CustomerID]"); + } + + public override async Task List_from_result_of_single_result_3(bool async) + { + await base.List_from_result_of_single_result_3(async); + + AssertSql( + @"SELECT [t].[CustomerID], [t0].[OrderID], [o0].[ProductID], [o0].[OrderID], [t0].[c] +FROM ( + SELECT TOP(1) [c].[CustomerID] + FROM [Customers] AS [c] + ORDER BY [c].[CustomerID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[c], [t1].[OrderID], [t1].[CustomerID] + FROM ( + SELECT 1 AS [c], [o].[OrderID], [o].[CustomerID], ROW_NUMBER() OVER(PARTITION BY [o].[CustomerID] ORDER BY [o].[OrderDate]) AS [row] + FROM [Orders] AS [o] + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[CustomerID] = [t0].[CustomerID] +LEFT JOIN [Order Details] AS [o0] ON [t0].[OrderID] = [o0].[OrderID] +ORDER BY [t].[CustomerID], [t0].[OrderID], [o0].[OrderID]"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); From 70461dda0a385d1966f13cb7d483f283c1237773 Mon Sep 17 00:00:00 2001 From: Doug Bunting <6431421+dougbu@users.noreply.github.com> Date: Tue, 5 Apr 2022 20:37:57 -0700 Subject: [PATCH 068/143] [6.0.x] Ignore a failing Cosmos test to unblock builds (#27772) - backport of 5df9bcf1d716 Co-authored-by: Andriy Svyryd --- .../Query/NorthwindAggregateOperatorsQueryCosmosTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs index f96336902a4..ba399797966 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs @@ -782,7 +782,7 @@ FROM root c WHERE (c[""Discriminator""] = ""Customer"")"); } - [ConditionalTheory] + [ConditionalTheory(Skip = "Fails on CI #27688")] public override async Task Distinct_Scalar(bool async) { await base.Distinct_Scalar(async); From 2217ff2c03f10b8fbb1cd5251cf5ef61100501e8 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 6 Apr 2022 21:48:22 +0000 Subject: [PATCH 069/143] Update dependencies from https://github.com/dotnet/arcade build 20220309.5 (#27767) [release/5.0] Update dependencies from dotnet/arcade --- eng/Version.Details.xml | 8 ++++---- global.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index c7ac2cc2afa..7e4862e22a5 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -51,13 +51,13 @@ - + https://github.com/dotnet/arcade - 295d305a5520815cbf4ccb3f209f6ee8ba11b45d + ee744068a4fcccc5b8b56e0025f9c95aa19ff318 - + https://github.com/dotnet/arcade - 295d305a5520815cbf4ccb3f209f6ee8ba11b45d + ee744068a4fcccc5b8b56e0025f9c95aa19ff318 diff --git a/global.json b/global.json index 3c13d83fb55..bdf9083e426 100644 --- a/global.json +++ b/global.json @@ -16,7 +16,7 @@ "rollForward": "latestMajor" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "5.0.0-beta.22123.4", - "Microsoft.DotNet.Helix.Sdk": "5.0.0-beta.22123.4" + "Microsoft.DotNet.Arcade.Sdk": "5.0.0-beta.22159.5", + "Microsoft.DotNet.Helix.Sdk": "5.0.0-beta.22159.5" } } From 63e88891c76d8d803cbb4961ead80d298a7ece46 Mon Sep 17 00:00:00 2001 From: DotNet Bot Date: Wed, 6 Apr 2022 23:26:37 +0000 Subject: [PATCH 070/143] [internal/release/3.1] Update dependencies from dnceng/internal/dotnet-extensions --- NuGet.config | 8 +- eng/Version.Details.xml | 248 ++++++++++++++++++++-------------------- eng/Versions.props | 20 ++-- 3 files changed, 139 insertions(+), 137 deletions(-) diff --git a/NuGet.config b/NuGet.config index 0dff8158a3d..76288f71c0a 100644 --- a/NuGet.config +++ b/NuGet.config @@ -6,10 +6,10 @@ - + - + @@ -24,12 +24,14 @@ - + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index bd230a5d362..f9b23beb842 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,21 +1,21 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx @@ -33,242 +33,242 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup 3acd9b0cd16596bad450c82be08780875a73c05c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 https://github.com/dotnet/extensions 1774c15ac24c65513fa9fc1f4fbb69be9a2a4e25 - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 3acd9b0cd16596bad450c82be08780875a73c05c + d651d1c30aa3ad9368a35abc425ab28270f2b0ca - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 8e0af0dbdd80016da26eab546c3f10e943cea1bc + 5161783136fc968ceda3aba3ea07232b00deaea2 - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 3b3838608327c1d49fcf77fc47487e40e4dc3000 + d651d1c30aa3ad9368a35abc425ab28270f2b0ca - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 3b3838608327c1d49fcf77fc47487e40e4dc3000 + d651d1c30aa3ad9368a35abc425ab28270f2b0ca https://dev.azure.com/dnceng/internal/_git/dotnet-corefx diff --git a/eng/Versions.props b/eng/Versions.props index 7a77f2448ea..0d2c56c5150 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -32,13 +32,13 @@ 1.1.3 - 3.1.24 - 3.1.24 - 3.1.24 - 3.1.24 - 3.1.24 - 3.1.24-servicing.22180.2 - 3.1.24 + 3.1.25 + 3.1.25 + 3.1.25 + 3.1.25 + 3.1.25 + 3.1.25-servicing.22206.2 + 3.1.25 1.1.1 @@ -53,10 +53,10 @@ 3.1.6 - 3.1.6 + 3.1.25 3.1.0 - 3.1.24 - 3.1.24-servicing.22179.7 + 3.1.25 + 3.1.25-servicing.22206.2 2.1.0 From 73db32dd0d7d034717ac3a53c75f0b241d40840d Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Thu, 7 Apr 2022 09:57:57 +0100 Subject: [PATCH 071/143] Support value generation for converted types (#27759) --- .../Internal/CosmosValueGeneratorSelector.cs | 8 +- .../RelationalModelValidator.cs | 2 +- src/EFCore.Relational/Metadata/IColumn.cs | 2 +- .../Internal/MigrationsModelDiffer.cs | 2 +- .../RelationalValueGeneratorSelector.cs | 34 +- ...ISqlServerSequenceValueGeneratorFactory.cs | 3 +- .../SqlServerSequenceValueGeneratorFactory.cs | 9 +- .../SqlServerValueGeneratorSelector.cs | 57 +- .../Internal/SqliteAnnotationProvider.cs | 2 +- .../Internal/PropertyAccessorsFactory.cs | 15 +- src/EFCore/Properties/CoreStrings.Designer.cs | 8 - src/EFCore/Properties/CoreStrings.resx | 3 - .../Internal/ConvertedValueGenerator.cs | 66 + .../TemporaryNumberValueGeneratorFactory.cs | 2 +- src/EFCore/ValueGeneration/ValueGenerator.cs | 10 + .../ValueGeneration/ValueGeneratorSelector.cs | 62 +- .../StoreGeneratedTestBase.cs | 3240 +++++++++++++++-- .../SqlServerValueGenerationScenariosTest.cs | 2 +- .../StoreGeneratedSqlServerTest.cs | 530 ++- .../SqliteValueGenerationScenariosTest.cs | 2 +- ...emporaryNumberValueGeneratorFactoryTest.cs | 1 + 21 files changed, 3682 insertions(+), 378 deletions(-) create mode 100644 src/EFCore/ValueGeneration/Internal/ConvertedValueGenerator.cs diff --git a/src/EFCore.Cosmos/ValueGeneration/Internal/CosmosValueGeneratorSelector.cs b/src/EFCore.Cosmos/ValueGeneration/Internal/CosmosValueGeneratorSelector.cs index ef457ccb739..d11d7fa3943 100644 --- a/src/EFCore.Cosmos/ValueGeneration/Internal/CosmosValueGeneratorSelector.cs +++ b/src/EFCore.Cosmos/ValueGeneration/Internal/CosmosValueGeneratorSelector.cs @@ -28,16 +28,14 @@ public CosmosValueGeneratorSelector(ValueGeneratorSelectorDependencies dependenc /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override ValueGenerator Create(IProperty property, IEntityType entityType) + protected override ValueGenerator? FindForType(IProperty property, IEntityType entityType, Type clrType) { - var type = property.ClrType.UnwrapNullableType().UnwrapEnumType(); - if (property.GetJsonPropertyName() == "" - && type == typeof(int)) + && clrType == typeof(int)) { return new TemporaryNumberValueGeneratorFactory().Create(property, entityType); } - return base.Create(property, entityType); + return base.FindForType(property, entityType, clrType); } } diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index d861d5d0153..a284accdf5a 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -1034,7 +1034,7 @@ protected virtual void ValidateCompatible( in StoreObjectIdentifier storeObject) { var value = property.GetDefaultValue(storeObject); - var converter = property.GetValueConverter() ?? property.FindRelationalTypeMapping(storeObject)?.Converter; + var converter = property.FindRelationalTypeMapping(storeObject)?.Converter; return converter != null ? converter.ConvertToProvider(value) diff --git a/src/EFCore.Relational/Metadata/IColumn.cs b/src/EFCore.Relational/Metadata/IColumn.cs index cb173e86cfa..4620f09db9d 100644 --- a/src/EFCore.Relational/Metadata/IColumn.cs +++ b/src/EFCore.Relational/Metadata/IColumn.cs @@ -98,7 +98,7 @@ public virtual bool TryGetDefaultValue(out object? defaultValue) continue; } - var converter = property.GetValueConverter() ?? mapping.TypeMapping.Converter; + var converter = mapping.TypeMapping.Converter; if (converter != null) { defaultValue = converter.ConvertToProvider(defaultValue); diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index d61d58aa2ba..67a6a39820c 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -2455,7 +2455,7 @@ protected virtual IEnumerable GetSchemas(IRelationalModel model) : type.UnwrapNullableType().GetDefaultValue(); private static ValueConverter? GetValueConverter(IProperty property, RelationalTypeMapping? typeMapping = null) - => property.GetValueConverter() ?? (property.FindRelationalTypeMapping() ?? typeMapping)?.Converter; + => (property.FindRelationalTypeMapping() ?? typeMapping)?.Converter; private static IEntityType GetMainType(ITable table) => table.EntityTypeMappings.First(t => t.IsSharedTablePrincipal).EntityType; diff --git a/src/EFCore.Relational/ValueGeneration/RelationalValueGeneratorSelector.cs b/src/EFCore.Relational/ValueGeneration/RelationalValueGeneratorSelector.cs index 7c914eb305c..a362cc969aa 100644 --- a/src/EFCore.Relational/ValueGeneration/RelationalValueGeneratorSelector.cs +++ b/src/EFCore.Relational/ValueGeneration/RelationalValueGeneratorSelector.cs @@ -40,58 +40,48 @@ public RelationalValueGeneratorSelector(ValueGeneratorSelectorDependencies depen { } - /// - /// Creates a new value generator for the given property. - /// - /// The property to get the value generator for. - /// - /// The entity type that the value generator will be used for. When called on inherited properties on derived entity types, - /// this entity type may be different from the declared entity type on - /// - /// The newly created value generator. - public override ValueGenerator Create(IProperty property, IEntityType entityType) + /// + protected override ValueGenerator? FindForType(IProperty property, IEntityType entityType, Type clrType) { if (property.ValueGenerated != ValueGenerated.Never) { - var propertyType = property.ClrType.UnwrapNullableType().UnwrapEnumType(); - - if (propertyType.IsInteger() - || propertyType == typeof(decimal) - || propertyType == typeof(float) - || propertyType == typeof(double)) + if (clrType.IsInteger() + || clrType == typeof(decimal) + || clrType == typeof(float) + || clrType == typeof(double)) { return _numberFactory.Create(property, entityType); } - if (propertyType == typeof(DateTime)) + if (clrType == typeof(DateTime)) { return new TemporaryDateTimeValueGenerator(); } - if (propertyType == typeof(DateTimeOffset)) + if (clrType == typeof(DateTimeOffset)) { return new TemporaryDateTimeOffsetValueGenerator(); } if (property.GetDefaultValueSql() != null) { - if (propertyType == typeof(Guid)) + if (clrType == typeof(Guid)) { return new TemporaryGuidValueGenerator(); } - if (propertyType == typeof(string)) + if (clrType == typeof(string)) { return new TemporaryStringValueGenerator(); } - if (propertyType == typeof(byte[])) + if (clrType == typeof(byte[])) { return new TemporaryBinaryValueGenerator(); } } } - return base.Create(property, entityType); + return base.FindForType(property, entityType, clrType); } } diff --git a/src/EFCore.SqlServer/ValueGeneration/Internal/ISqlServerSequenceValueGeneratorFactory.cs b/src/EFCore.SqlServer/ValueGeneration/Internal/ISqlServerSequenceValueGeneratorFactory.cs index a2df086413f..f76018dc6fb 100644 --- a/src/EFCore.SqlServer/ValueGeneration/Internal/ISqlServerSequenceValueGeneratorFactory.cs +++ b/src/EFCore.SqlServer/ValueGeneration/Internal/ISqlServerSequenceValueGeneratorFactory.cs @@ -19,8 +19,9 @@ public interface ISqlServerSequenceValueGeneratorFactory /// 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. /// - ValueGenerator Create( + ValueGenerator? TryCreate( IProperty property, + Type clrType, SqlServerSequenceValueGeneratorState generatorState, ISqlServerConnection connection, IRawSqlCommandBuilder rawSqlCommandBuilder, diff --git a/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerSequenceValueGeneratorFactory.cs b/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerSequenceValueGeneratorFactory.cs index 6065b3364dc..cd993a68906 100644 --- a/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerSequenceValueGeneratorFactory.cs +++ b/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerSequenceValueGeneratorFactory.cs @@ -34,15 +34,14 @@ public SqlServerSequenceValueGeneratorFactory( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual ValueGenerator Create( + public virtual ValueGenerator? TryCreate( IProperty property, + Type type, SqlServerSequenceValueGeneratorState generatorState, ISqlServerConnection connection, IRawSqlCommandBuilder rawSqlCommandBuilder, IRelationalCommandDiagnosticsLogger commandLogger) { - var type = property.ClrType.UnwrapNullableType().UnwrapEnumType(); - if (type == typeof(long)) { return new SqlServerSequenceHiLoValueGenerator( @@ -103,8 +102,6 @@ public virtual ValueGenerator Create( rawSqlCommandBuilder, _sqlGenerator, generatorState, connection, commandLogger); } - throw new ArgumentException( - CoreStrings.InvalidValueGeneratorFactoryProperty( - nameof(SqlServerSequenceValueGeneratorFactory), property.Name, property.DeclaringEntityType.DisplayName())); + return null; } } diff --git a/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerValueGeneratorSelector.cs b/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerValueGeneratorSelector.cs index 8c62b13b06f..5a8d83676d6 100644 --- a/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerValueGeneratorSelector.cs +++ b/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerValueGeneratorSelector.cs @@ -54,15 +54,50 @@ public SqlServerValueGeneratorSelector( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override ValueGenerator Select(IProperty property, IEntityType entityType) - => property.GetValueGeneratorFactory() == null - && property.GetValueGenerationStrategy() == SqlServerValueGenerationStrategy.SequenceHiLo - ? _sequenceFactory.Create( - property, - Cache.GetOrAddSequenceState(property, _connection), - _connection, - _rawSqlCommandBuilder, - _commandLogger) - : base.Select(property, entityType); + { + if (property.GetValueGeneratorFactory() != null + || property.GetValueGenerationStrategy() != SqlServerValueGenerationStrategy.SequenceHiLo) + { + return base.Select(property, entityType); + } + + var propertyType = property.ClrType.UnwrapNullableType().UnwrapEnumType(); + + var generator = _sequenceFactory.TryCreate( + property, + propertyType, + Cache.GetOrAddSequenceState(property, _connection), + _connection, + _rawSqlCommandBuilder, + _commandLogger); + + if (generator != null) + { + return generator; + } + + var converter = property.GetTypeMapping().Converter; + if (converter != null + && converter.ProviderClrType != propertyType) + { + generator = _sequenceFactory.TryCreate( + property, + converter.ProviderClrType, + Cache.GetOrAddSequenceState(property, _connection), + _connection, + _rawSqlCommandBuilder, + _commandLogger); + + if (generator != null) + { + return generator.WithConverter(converter); + } + } + + throw new ArgumentException( + CoreStrings.InvalidValueGeneratorFactoryProperty( + nameof(SqlServerSequenceValueGeneratorFactory), property.Name, property.DeclaringEntityType.DisplayName())); + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -70,10 +105,10 @@ public override ValueGenerator Select(IProperty property, IEntityType entityType /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override ValueGenerator Create(IProperty property, IEntityType entityType) + protected override ValueGenerator? FindForType(IProperty property, IEntityType entityType, Type clrType) => property.ClrType.UnwrapNullableType() == typeof(Guid) ? property.ValueGenerated == ValueGenerated.Never || property.GetDefaultValueSql() != null ? new TemporaryGuidValueGenerator() : new SequentialGuidValueGenerator() - : base.Create(property, entityType); + : base.FindForType(property, entityType, clrType); } diff --git a/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs b/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs index 5251d0757e0..bd4b72dd544 100644 --- a/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs +++ b/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs @@ -69,5 +69,5 @@ public override IEnumerable For(IColumn column, bool designTime) } private static bool HasConverter(IProperty property) - => (property.GetValueConverter() ?? property.FindTypeMapping()?.Converter) != null; + => property.FindTypeMapping()?.Converter != null; } diff --git a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs index 24363dd4b3b..94049e13ade 100644 --- a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs +++ b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs @@ -80,11 +80,16 @@ private static Func CreateCurrentValueGetter var storeGeneratedIndex = propertyBase.GetStoreGeneratedIndex(); if (storeGeneratedIndex >= 0) { + var comparer = (propertyBase as IProperty)?.GetValueComparer() + ?? ValueComparer.CreateDefault(propertyBase.ClrType, favorStructuralComparisons: true); + if (useStoreGeneratedValues) { currentValueExpression = Expression.Condition( - Expression.Equal( - currentValueExpression, + comparer.ExtractEqualsBody( + comparer.Type != currentValueExpression.Type + ? Expression.Convert(currentValueExpression, comparer.Type) + : currentValueExpression, Expression.Constant(default(TProperty), typeof(TProperty))), Expression.Call( entryParameter, @@ -94,8 +99,10 @@ private static Func CreateCurrentValueGetter } currentValueExpression = Expression.Condition( - Expression.Equal( - currentValueExpression, + comparer.ExtractEqualsBody( + comparer.Type != currentValueExpression.Type + ? Expression.Convert(currentValueExpression, comparer.Type) + : currentValueExpression, Expression.Constant(default(TProperty), typeof(TProperty))), Expression.Call( entryParameter, diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 3d188fc7b7a..d3a0d7337d5 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -2783,14 +2783,6 @@ public static string ValueCannotBeNull(object? property, object? entityType, obj GetString("ValueCannotBeNull", "0_property", "1_entityType", nameof(propertyType)), property, entityType, propertyType); - /// - /// Value generation is not supported for property '{entityType}.{property}' because it has a '{converter}' converter configured. Configure the property to not use value generation using 'ValueGenerated.Never' or 'DatabaseGeneratedOption.None' and specify explicit values instead. - /// - public static string ValueGenWithConversion(object? entityType, object? property, object? converter) - => string.Format( - GetString("ValueGenWithConversion", nameof(entityType), nameof(property), nameof(converter)), - entityType, property, converter); - /// /// Calling '{visitMethodName}' is not allowed. Visit the expression manually for the relevant part in the visitor. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 9c546496c44..5e6a7f2d2d5 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1472,9 +1472,6 @@ The value for property '{1_entityType}.{0_property}' cannot be set to null because its type is '{propertyType}' which is not a nullable type. - - Value generation is not supported for property '{entityType}.{property}' because it has a '{converter}' converter configured. Configure the property to not use value generation using 'ValueGenerated.Never' or 'DatabaseGeneratedOption.None' and specify explicit values instead. - Calling '{visitMethodName}' is not allowed. Visit the expression manually for the relevant part in the visitor. diff --git a/src/EFCore/ValueGeneration/Internal/ConvertedValueGenerator.cs b/src/EFCore/ValueGeneration/Internal/ConvertedValueGenerator.cs new file mode 100644 index 00000000000..f322be96e97 --- /dev/null +++ b/src/EFCore/ValueGeneration/Internal/ConvertedValueGenerator.cs @@ -0,0 +1,66 @@ +// 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.ValueGeneration.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class ConvertedValueGenerator : ValueGenerator +{ + private readonly ValueGenerator _providerGenerator; + private readonly ValueConverter _converter; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public ConvertedValueGenerator( + ValueGenerator providerGenerator, + ValueConverter converter) + { + _providerGenerator = providerGenerator; + _converter = converter; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override object? NextValue(EntityEntry entry) + => _converter.ConvertFromProvider(_providerGenerator.Next(entry)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override async ValueTask NextAsync(EntityEntry entry, CancellationToken cancellationToken = default) + => _converter.ConvertFromProvider(await _providerGenerator.NextAsync(entry, cancellationToken).ConfigureAwait(false)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override bool GeneratesTemporaryValues + => _providerGenerator.GeneratesTemporaryValues; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override bool GeneratesStableValues + => _providerGenerator.GeneratesStableValues; +} diff --git a/src/EFCore/ValueGeneration/TemporaryNumberValueGeneratorFactory.cs b/src/EFCore/ValueGeneration/TemporaryNumberValueGeneratorFactory.cs index a0f3b10f4e8..a4624fa88f9 100644 --- a/src/EFCore/ValueGeneration/TemporaryNumberValueGeneratorFactory.cs +++ b/src/EFCore/ValueGeneration/TemporaryNumberValueGeneratorFactory.cs @@ -27,7 +27,7 @@ public class TemporaryNumberValueGeneratorFactory : ValueGeneratorFactory /// The newly created value generator. public override ValueGenerator Create(IProperty property, IEntityType entityType) { - var type = property.ClrType.UnwrapNullableType().UnwrapEnumType(); + var type = (property.GetValueConverter()?.ProviderClrType ?? property.GetTypeMapping().ClrType).UnwrapEnumType(); if (type == typeof(int)) { diff --git a/src/EFCore/ValueGeneration/ValueGenerator.cs b/src/EFCore/ValueGeneration/ValueGenerator.cs index 72d01357a26..ac01f1184a7 100644 --- a/src/EFCore/ValueGeneration/ValueGenerator.cs +++ b/src/EFCore/ValueGeneration/ValueGenerator.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.ValueGeneration.Internal; + namespace Microsoft.EntityFrameworkCore.ValueGeneration; /// @@ -91,4 +93,12 @@ public abstract class ValueGenerator /// public virtual bool GeneratesStableValues => false; + + /// + /// Wraps this such that it processes values converted with the given . + /// + /// The value converter. + /// A new value generator that works with the converted values. + public virtual ValueGenerator WithConverter(ValueConverter converter) + => new ConvertedValueGenerator(this, converter); } diff --git a/src/EFCore/ValueGeneration/ValueGeneratorSelector.cs b/src/EFCore/ValueGeneration/ValueGeneratorSelector.cs index 6bd7c950506..9cebb2f3f25 100644 --- a/src/EFCore/ValueGeneration/ValueGeneratorSelector.cs +++ b/src/EFCore/ValueGeneration/ValueGeneratorSelector.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.ValueGeneration.Internal; + namespace Microsoft.EntityFrameworkCore.ValueGeneration; /// @@ -66,26 +68,6 @@ public virtual ValueGenerator Select(IProperty property, IEntityType entityType) { var mapping = property.GetTypeMapping(); factory = mapping.ValueGeneratorFactory; - - if (factory == null) - { - var converter = mapping.Converter; - - if (converter != null) - { - var type = converter.ProviderClrType.UnwrapNullableType(); - if (!type.IsInteger() - && !type.IsEnum - && type != typeof(decimal)) - { - throw new NotSupportedException( - CoreStrings.ValueGenWithConversion( - property.DeclaringEntityType.DisplayName(), - property.Name, - converter.GetType().ShortDisplayName())); - } - } - } } return factory?.Invoke(property, entityType); @@ -103,23 +85,43 @@ public virtual ValueGenerator Select(IProperty property, IEntityType entityType) public virtual ValueGenerator Create(IProperty property, IEntityType entityType) { var propertyType = property.ClrType.UnwrapNullableType().UnwrapEnumType(); - - if (propertyType == typeof(Guid)) + var generator = FindForType(property, entityType, propertyType); + if (generator != null) { - return new GuidValueGenerator(); + return generator; } - if (propertyType == typeof(string)) + var converter = property.GetTypeMapping().Converter; + if (converter != null + && converter.ProviderClrType != propertyType) { - return new StringValueGenerator(); - } - - if (propertyType == typeof(byte[])) - { - return new BinaryValueGenerator(); + generator = FindForType(property, entityType, converter.ProviderClrType); + if (generator != null) + { + return generator.WithConverter(converter); + } } throw new NotSupportedException( CoreStrings.NoValueGenerator(property.Name, property.DeclaringEntityType.DisplayName(), propertyType.ShortDisplayName())); } + + /// + /// Creates a new value generator for the given property and type, where the property may have a . + /// + /// The property to get the value generator for. + /// + /// The entity type that the value generator will be used for. When called on inherited properties on derived entity types, + /// this entity type may be different from the declared entity type on + /// + /// The type, which may be the provider type after conversion, rather than the property type. + /// The newly created value generator. + protected virtual ValueGenerator? FindForType(IProperty property, IEntityType entityType, Type clrType) + => clrType == typeof(Guid) + ? new GuidValueGenerator() + : clrType == typeof(string) + ? new StringValueGenerator() + : clrType == typeof(byte[]) + ? new BinaryValueGenerator() + : null; } diff --git a/test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs b/test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs index fa76f62917f..6e2609537ad 100644 --- a/test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs +++ b/test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs @@ -7,6 +7,8 @@ // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore; +#nullable enable + public abstract class StoreGeneratedTestBase : IClassFixture where TFixture : StoreGeneratedTestBase.StoreGeneratedFixtureBase, new() { @@ -17,25 +19,6 @@ protected StoreGeneratedTestBase(TFixture fixture) protected TFixture Fixture { get; } - [ConditionalFact] - public virtual void Value_generation_throws_for_common_cases() - { - ValueGenerationNegative>(); - ValueGenerationNegative>(); - } - - private void ValueGenerationNegative() - where TEntity : WithConverter, new() - { - using var context = CreateContext(); - Assert.Equal( - CoreStrings.ValueGenWithConversion( - typeof(TEntity).ShortDisplayName(), - nameof(WithConverter.Id), - typeof(TConverter).ShortDisplayName()), - Assert.Throws(() => context.Add(new TEntity())).Message); - } - [ConditionalFact] public virtual void Value_generation_works_for_common_GUID_conversions() { @@ -46,7 +29,7 @@ public virtual void Value_generation_works_for_common_GUID_conversions() private void ValueGenerationPositive() where TEntity : WithConverter, new() { - TKey id; + TKey? id; using (var context = CreateContext()) { @@ -59,7 +42,7 @@ private void ValueGenerationPositive() using (var context = CreateContext()) { - Assert.Equal(id, context.Set().Single(e => e.Id.Equals(id)).Id); + Assert.Equal(id, context.Set().Single(e => e.Id!.Equals(id)).Id); } } @@ -112,7 +95,7 @@ public virtual void Before_save_throw_ignores_value_if_not_set(string propertyNa id = entity.Id; }, - context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id), propertyName))); + context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id)!, propertyName))); } [ConditionalTheory] @@ -143,7 +126,7 @@ public virtual void Before_save_use_always_uses_value_if_set(string propertyName id = entity.Id; }, - context => Assert.Equal("Pink", GetValue(context.Set().Find(id), propertyName))); + context => Assert.Equal("Pink", GetValue(context.Set().Find(id)!, propertyName))); } [ConditionalTheory] @@ -174,7 +157,7 @@ public virtual void Before_save_use_ignores_value_if_not_set(string propertyName id = entity.Id; }, - context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id), propertyName))); + context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id)!, propertyName))); } [ConditionalTheory] @@ -203,7 +186,7 @@ public virtual void Before_save_ignore_ignores_value_if_not_set(string propertyN id = entity.Id; }, - context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id), propertyName))); + context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id)!, propertyName))); } [ConditionalTheory] @@ -232,7 +215,7 @@ public virtual void Before_save_ignore_ignores_value_even_if_set(string property id = entity.Id; }, - context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id), propertyName))); + context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id)!, propertyName))); } [ConditionalTheory] @@ -286,14 +269,14 @@ public virtual void After_save_throw_ignores_value_if_not_modified(string proper }, context => { - var entry = context.Entry(context.Set().Find(id)); + var entry = context.Entry(context.Set().Find(id)!); entry.State = EntityState.Modified; entry.Property(propertyName).CurrentValue = "Daisy"; entry.Property(propertyName).IsModified = false; context.SaveChanges(); }, - context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id), propertyName))); + context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id)!, propertyName))); } [ConditionalTheory] @@ -325,14 +308,14 @@ public virtual void After_save_ignore_ignores_value_if_not_modified(string prope }, context => { - var entry = context.Entry(context.Set().Find(id)); + var entry = context.Entry(context.Set().Find(id)!); entry.State = EntityState.Modified; entry.Property(propertyName).CurrentValue = "Daisy"; entry.Property(propertyName).IsModified = false; context.SaveChanges(); }, - context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id), propertyName))); + context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id)!, propertyName))); } [ConditionalTheory] @@ -364,14 +347,14 @@ public virtual void After_save_ignore_ignores_value_even_if_modified(string prop }, context => { - var entry = context.Entry(context.Set().Find(id)); + var entry = context.Entry(context.Set().Find(id)!); entry.State = EntityState.Modified; entry.Property(propertyName).CurrentValue = "Daisy"; entry.Property(propertyName).IsModified = true; context.SaveChanges(); }, - context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id), propertyName))); + context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id)!, propertyName))); } [ConditionalTheory] @@ -405,14 +388,14 @@ public virtual void After_save_use_ignores_value_if_not_modified(string property }, context => { - var entry = context.Entry(context.Set().Find(id)); + var entry = context.Entry(context.Set().Find(id)!); entry.State = EntityState.Modified; entry.Property(propertyName).CurrentValue = "Daisy"; entry.Property(propertyName).IsModified = false; context.SaveChanges(); }, - context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id), propertyName))); + context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id)!, propertyName))); } [ConditionalTheory] @@ -444,13 +427,13 @@ public virtual void After_save_use_uses_value_if_modified(string propertyName, s }, context => { - var entry = context.Entry(context.Set().Find(id)); + var entry = context.Entry(context.Set().Find(id)!); entry.State = EntityState.Modified; entry.Property(propertyName).CurrentValue = "Daisy"; context.SaveChanges(); }, - context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id), propertyName))); + context => Assert.Equal(expectedValue, GetValue(context.Set().Find(id)!, propertyName))); } private static Anais WithValue(string propertyName, int id = 0) @@ -458,12 +441,12 @@ private static Anais WithValue(string propertyName, int id = 0) private static Anais SetValue(Anais entity, string propertyName) { - entity.GetType().GetTypeInfo().GetDeclaredProperty(propertyName).SetValue(entity, "Pink"); + entity.GetType().GetTypeInfo().GetDeclaredProperty(propertyName)!.SetValue(entity, "Pink"); return entity; } - private static string GetValue(Anais entity, string propertyName) - => (string)entity.GetType().GetTypeInfo().GetDeclaredProperty(propertyName).GetValue(entity); + private static string? GetValue(Anais entity, string propertyName) + => (string?)entity.GetType().GetTypeInfo().GetDeclaredProperty(propertyName)!.GetValue(entity); [ConditionalFact] public virtual void Identity_key_with_read_only_before_save_throws_if_explicit_values_set() @@ -497,7 +480,6 @@ public virtual void Identity_property_on_Added_entity_with_temporary_value_gets_ context => Assert.Equal("Banana Joe", context.Set().Single(e => e.Id == id).Identity)); } -#nullable enable protected class CompositePrincipal { public int Id { get; set; } @@ -512,7 +494,6 @@ protected class CompositeDependent public int Number { get; set; } public CompositePrincipal? Principal { get; set; } } -#nullable disable [ConditionalFact] public virtual void Store_generated_values_are_propagated_with_composite_key_cycles() @@ -539,7 +520,7 @@ protected class NonStoreGenDependent public int? StoreGenPrincipalId { get; set; } public int HasTemp { get; set; } - public StoreGenPrincipal StoreGenPrincipal { get; set; } + public StoreGenPrincipal StoreGenPrincipal { get; set; } = null!; } protected class StoreGenPrincipal @@ -717,7 +698,7 @@ protected class OptionalProduct { public int Id { get; set; } public int? CategoryId { get; set; } - public OptionalCategory Category { get; set; } + public OptionalCategory? Category { get; set; } } protected class OptionalCategory @@ -1457,16 +1438,16 @@ public int Id set => _id = value; } - public string Name { get; set; } + public string? Name { get; set; } - public ICollection MixedMetaphors { get; set; } - public Species Species { get; set; } + public ICollection MixedMetaphors { get; set; } = null!; + public Species? Species { get; set; } } protected class Species { public int Id { get; set; } - public string Name { get; set; } + public string? Name { get; set; } public int? DarwinId { get; set; } public int? MetaphoricId { get; set; } @@ -1475,71 +1456,71 @@ protected class Species protected class Gumball { public int Id { get; set; } - public string NotStoreGenerated { get; set; } + public string? NotStoreGenerated { get; set; } - public string Identity { get; set; } - public string IdentityReadOnlyBeforeSave { get; set; } - public string IdentityReadOnlyAfterSave { get; set; } + public string? Identity { get; set; } + public string? IdentityReadOnlyBeforeSave { get; set; } + public string? IdentityReadOnlyAfterSave { get; set; } - public string AlwaysIdentity { get; set; } - public string AlwaysIdentityReadOnlyBeforeSave { get; set; } - public string AlwaysIdentityReadOnlyAfterSave { get; set; } + public string? AlwaysIdentity { get; set; } + public string? AlwaysIdentityReadOnlyBeforeSave { get; set; } + public string? AlwaysIdentityReadOnlyAfterSave { get; set; } - public string Computed { get; set; } - public string ComputedReadOnlyBeforeSave { get; set; } - public string ComputedReadOnlyAfterSave { get; set; } + public string? Computed { get; set; } + public string? ComputedReadOnlyBeforeSave { get; set; } + public string? ComputedReadOnlyAfterSave { get; set; } - public string AlwaysComputed { get; set; } - public string AlwaysComputedReadOnlyBeforeSave { get; set; } - public string AlwaysComputedReadOnlyAfterSave { get; set; } + public string? AlwaysComputed { get; set; } + public string? AlwaysComputedReadOnlyBeforeSave { get; set; } + public string? AlwaysComputedReadOnlyAfterSave { get; set; } } protected class Anais { public int Id { get; set; } - public string Never { get; set; } - public string NeverUseBeforeUseAfter { get; set; } - public string NeverIgnoreBeforeUseAfter { get; set; } - public string NeverThrowBeforeUseAfter { get; set; } - public string NeverUseBeforeIgnoreAfter { get; set; } - public string NeverIgnoreBeforeIgnoreAfter { get; set; } - public string NeverThrowBeforeIgnoreAfter { get; set; } - public string NeverUseBeforeThrowAfter { get; set; } - public string NeverIgnoreBeforeThrowAfter { get; set; } - public string NeverThrowBeforeThrowAfter { get; set; } - - public string OnAdd { get; set; } - public string OnAddUseBeforeUseAfter { get; set; } - public string OnAddIgnoreBeforeUseAfter { get; set; } - public string OnAddThrowBeforeUseAfter { get; set; } - public string OnAddUseBeforeIgnoreAfter { get; set; } - public string OnAddIgnoreBeforeIgnoreAfter { get; set; } - public string OnAddThrowBeforeIgnoreAfter { get; set; } - public string OnAddUseBeforeThrowAfter { get; set; } - public string OnAddIgnoreBeforeThrowAfter { get; set; } - public string OnAddThrowBeforeThrowAfter { get; set; } - - public string OnAddOrUpdate { get; set; } - public string OnAddOrUpdateUseBeforeUseAfter { get; set; } - public string OnAddOrUpdateIgnoreBeforeUseAfter { get; set; } - public string OnAddOrUpdateThrowBeforeUseAfter { get; set; } - public string OnAddOrUpdateUseBeforeIgnoreAfter { get; set; } - public string OnAddOrUpdateIgnoreBeforeIgnoreAfter { get; set; } - public string OnAddOrUpdateThrowBeforeIgnoreAfter { get; set; } - public string OnAddOrUpdateUseBeforeThrowAfter { get; set; } - public string OnAddOrUpdateIgnoreBeforeThrowAfter { get; set; } - public string OnAddOrUpdateThrowBeforeThrowAfter { get; set; } - - public string OnUpdate { get; set; } - public string OnUpdateUseBeforeUseAfter { get; set; } - public string OnUpdateIgnoreBeforeUseAfter { get; set; } - public string OnUpdateThrowBeforeUseAfter { get; set; } - public string OnUpdateUseBeforeIgnoreAfter { get; set; } - public string OnUpdateIgnoreBeforeIgnoreAfter { get; set; } - public string OnUpdateThrowBeforeIgnoreAfter { get; set; } - public string OnUpdateUseBeforeThrowAfter { get; set; } - public string OnUpdateIgnoreBeforeThrowAfter { get; set; } - public string OnUpdateThrowBeforeThrowAfter { get; set; } + public string? Never { get; set; } + public string? NeverUseBeforeUseAfter { get; set; } + public string? NeverIgnoreBeforeUseAfter { get; set; } + public string? NeverThrowBeforeUseAfter { get; set; } + public string? NeverUseBeforeIgnoreAfter { get; set; } + public string? NeverIgnoreBeforeIgnoreAfter { get; set; } + public string? NeverThrowBeforeIgnoreAfter { get; set; } + public string? NeverUseBeforeThrowAfter { get; set; } + public string? NeverIgnoreBeforeThrowAfter { get; set; } + public string? NeverThrowBeforeThrowAfter { get; set; } + + public string? OnAdd { get; set; } + public string? OnAddUseBeforeUseAfter { get; set; } + public string? OnAddIgnoreBeforeUseAfter { get; set; } + public string? OnAddThrowBeforeUseAfter { get; set; } + public string? OnAddUseBeforeIgnoreAfter { get; set; } + public string? OnAddIgnoreBeforeIgnoreAfter { get; set; } + public string? OnAddThrowBeforeIgnoreAfter { get; set; } + public string? OnAddUseBeforeThrowAfter { get; set; } + public string? OnAddIgnoreBeforeThrowAfter { get; set; } + public string? OnAddThrowBeforeThrowAfter { get; set; } + + public string? OnAddOrUpdate { get; set; } + public string? OnAddOrUpdateUseBeforeUseAfter { get; set; } + public string? OnAddOrUpdateIgnoreBeforeUseAfter { get; set; } + public string? OnAddOrUpdateThrowBeforeUseAfter { get; set; } + public string? OnAddOrUpdateUseBeforeIgnoreAfter { get; set; } + public string? OnAddOrUpdateIgnoreBeforeIgnoreAfter { get; set; } + public string? OnAddOrUpdateThrowBeforeIgnoreAfter { get; set; } + public string? OnAddOrUpdateUseBeforeThrowAfter { get; set; } + public string? OnAddOrUpdateIgnoreBeforeThrowAfter { get; set; } + public string? OnAddOrUpdateThrowBeforeThrowAfter { get; set; } + + public string? OnUpdate { get; set; } + public string? OnUpdateUseBeforeUseAfter { get; set; } + public string? OnUpdateIgnoreBeforeUseAfter { get; set; } + public string? OnUpdateThrowBeforeUseAfter { get; set; } + public string? OnUpdateUseBeforeIgnoreAfter { get; set; } + public string? OnUpdateIgnoreBeforeIgnoreAfter { get; set; } + public string? OnUpdateThrowBeforeIgnoreAfter { get; set; } + public string? OnUpdateUseBeforeThrowAfter { get; set; } + public string? OnUpdateIgnoreBeforeThrowAfter { get; set; } + public string? OnUpdateThrowBeforeThrowAfter { get; set; } } protected class WithBackingFields @@ -1560,7 +1541,7 @@ public int Id public int NullableAsNonNullable { - get => (int)_nullableAsNonNullable; + get => (int)_nullableAsNonNullable!; set => _nullableAsNonNullable = value; } @@ -1618,7 +1599,7 @@ public int NullableBackedIntZeroDefault protected class WithObjectBackingFields { - private object _id; + private object? _id; public int Id { @@ -1626,7 +1607,7 @@ public int Id set => _id = value; } - private object _nullableBackedBoolTrueDefault; + private object? _nullableBackedBoolTrueDefault; public bool NullableBackedBoolTrueDefault { @@ -1634,7 +1615,7 @@ public bool NullableBackedBoolTrueDefault set => _nullableBackedBoolTrueDefault = value; } - private object _nullableBackedIntNonZeroDefault; + private object? _nullableBackedIntNonZeroDefault; public int NullableBackedIntNonZeroDefault { @@ -1642,7 +1623,7 @@ public int NullableBackedIntNonZeroDefault set => _nullableBackedIntNonZeroDefault = value; } - private object _nullableBackedBoolFalseDefault; + private object? _nullableBackedBoolFalseDefault; public bool NullableBackedBoolFalseDefault { @@ -1650,7 +1631,7 @@ public bool NullableBackedBoolFalseDefault set => _nullableBackedBoolFalseDefault = value; } - private object _nullableBackedIntZeroDefault; + private object? _nullableBackedIntZeroDefault; public int NullableBackedIntZeroDefault { @@ -1661,7 +1642,7 @@ public int NullableBackedIntZeroDefault protected class WithConverter { - public TKey Id { get; set; } + public TKey? Id { get; set; } } protected class IntToString : WithConverter @@ -1680,228 +1661,2784 @@ protected class ShortToBytes : WithConverter { } - protected virtual void ExecuteWithStrategyInTransaction( - Action testOperation, - Action nestedTestOperation1 = null, - Action nestedTestOperation2 = null) - => TestHelpers.ExecuteWithStrategyInTransaction( - CreateContext, UseTransaction, - testOperation, nestedTestOperation1, nestedTestOperation2); - - protected virtual void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + protected class WrappedIntClass { + public int Value { get; set; } } - protected DbContext CreateContext() - => Fixture.CreateContext(); - - public abstract class StoreGeneratedFixtureBase : SharedStoreFixtureBase + protected class WrappedIntClassConverter : ValueConverter { - protected override string StoreName { get; } = "StoreGeneratedTest"; + public WrappedIntClassConverter() + : base( + v => v.Value, + v => new WrappedIntClass { Value = v }) + { + } + } - protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + protected class WrappedIntClassComparer : ValueComparer + { + public WrappedIntClassComparer() + : base( + (v1, v2) => (v1 == null && v2 == null) || (v1 != null && v2 != null && v1.Value.Equals(v2.Value)), + v => v != null ? v.Value : 0, + v => v == null ? null : new() { Value = v.Value }) { - modelBuilder.Entity().Property(e => e.Id).HasConversion(); - modelBuilder.Entity().Property(e => e.Id).HasConversion(); - modelBuilder.Entity().Property(e => e.Id).HasConversion(); - modelBuilder.Entity().Property(e => e.Id).HasConversion(); + } + } - modelBuilder.Entity( - b => - { - var property = b.Property(e => e.Id).ValueGeneratedOnAdd().Metadata; - property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); - property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + protected class WrappedIntClassValueGenerator : ValueGenerator + { + public override WrappedIntClass Next(EntityEntry entry) + => new() { Value = 66 }; - property = b.Property(e => e.Identity).ValueGeneratedOnAdd().Metadata; - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + public override bool GeneratesTemporaryValues + => false; + } - property = b.Property(e => e.IdentityReadOnlyBeforeSave).ValueGeneratedOnAdd().Metadata; - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); - property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + protected struct WrappedIntStruct + { + public int Value { get; set; } + } - property = b.Property(e => e.IdentityReadOnlyAfterSave).ValueGeneratedOnAdd().Metadata; - property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + protected class WrappedIntStructConverter : ValueConverter + { + public WrappedIntStructConverter() + : base( + v => v.Value, + v => new WrappedIntStruct { Value = v }) + { + } + } - property = b.Property(e => e.AlwaysIdentity).ValueGeneratedOnAdd().Metadata; - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + protected class WrappedIntStructValueGenerator : ValueGenerator + { + public override WrappedIntStruct Next(EntityEntry entry) + => new() { Value = 66 }; - property = b.Property(e => e.AlwaysIdentityReadOnlyBeforeSave).ValueGeneratedOnAdd().Metadata; - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); - property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + public override bool GeneratesTemporaryValues + => false; + } - property = b.Property(e => e.AlwaysIdentityReadOnlyAfterSave).ValueGeneratedOnAdd().Metadata; - property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + protected record WrappedIntRecord + { + public int Value { get; set; } + } - property = b.Property(e => e.Computed).ValueGeneratedOnAddOrUpdate().Metadata; - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + protected class WrappedIntRecordConverter : ValueConverter + { + public WrappedIntRecordConverter() + : base( + v => v.Value, + v => new WrappedIntRecord { Value = v }) + { + } + } - property = b.Property(e => e.ComputedReadOnlyBeforeSave).ValueGeneratedOnAddOrUpdate().Metadata; - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); - property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + protected class WrappedIntRecordValueGenerator : ValueGenerator + { + public override WrappedIntRecord Next(EntityEntry entry) + => new() { Value = 66 }; - property = b.Property(e => e.ComputedReadOnlyAfterSave).ValueGeneratedOnAddOrUpdate().Metadata; - property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + public override bool GeneratesTemporaryValues + => false; + } - property = b.Property(e => e.AlwaysComputed).ValueGeneratedOnAddOrUpdate().Metadata; - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + protected class WrappedIntKeyClass + { + public int Value { get; set; } + } - property = b.Property(e => e.AlwaysComputedReadOnlyBeforeSave).ValueGeneratedOnAddOrUpdate().Metadata; - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); - property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + protected class WrappedIntKeyClassConverter : ValueConverter + { + public WrappedIntKeyClassConverter() + : base( + v => v.Value, + v => new WrappedIntKeyClass { Value = v }) + { + } + } - property = b.Property(e => e.AlwaysComputedReadOnlyAfterSave).ValueGeneratedOnAddOrUpdate().Metadata; - property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); - }); + protected class WrappedIntKeyClassComparer : ValueComparer + { + public WrappedIntKeyClassComparer() + : base( + (v1, v2) => (v1 == null && v2 == null) || (v1 != null && v2 != null && v1.Value.Equals(v2.Value)), + v => v != null ? v.Value : 0, + v => v == null ? null : new() { Value = v.Value }) + { + } + } - modelBuilder.Entity( - b => - { - b.Property(e => e.Never).ValueGeneratedNever(); + protected struct WrappedIntKeyStruct + { + public int Value { get; set; } - var property = b.Property(e => e.NeverUseBeforeUseAfter).ValueGeneratedNever().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + public override bool Equals(object? obj) + => obj is WrappedIntKeyStruct other && Value == other.Value; - property = b.Property(e => e.NeverIgnoreBeforeUseAfter).ValueGeneratedNever().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + public override int GetHashCode() + => Value; - property = b.Property(e => e.NeverThrowBeforeUseAfter).ValueGeneratedNever().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + public static bool operator ==(WrappedIntKeyStruct left, WrappedIntKeyStruct right) + => left.Equals(right); - property = b.Property(e => e.NeverUseBeforeIgnoreAfter).ValueGeneratedNever().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); - property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + public static bool operator !=(WrappedIntKeyStruct left, WrappedIntKeyStruct right) + => !left.Equals(right); + } - property = b.Property(e => e.NeverIgnoreBeforeIgnoreAfter).ValueGeneratedNever().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); - property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + protected class WrappedIntKeyStructConverter : ValueConverter + { + public WrappedIntKeyStructConverter() + : base( + v => v.Value, + v => new WrappedIntKeyStruct { Value = v }) + { + } + } - property = b.Property(e => e.NeverThrowBeforeIgnoreAfter).ValueGeneratedNever().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); - property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + protected record WrappedIntKeyRecord + { + public int Value { get; set; } + } - property = b.Property(e => e.NeverUseBeforeThrowAfter).ValueGeneratedNever().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); - property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + protected class WrappedIntKeyRecordConverter : ValueConverter + { + public WrappedIntKeyRecordConverter() + : base( + v => v.Value, + v => new WrappedIntKeyRecord { Value = v }) + { + } + } - property = b.Property(e => e.NeverIgnoreBeforeThrowAfter).ValueGeneratedNever().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); - property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + protected class WrappedIntClassPrincipal + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedIntKeyClass Id { get; set; } = null!; - property = b.Property(e => e.NeverThrowBeforeThrowAfter).ValueGeneratedNever().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); - property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + public WrappedIntClass? NonKey { get; set; } + public ICollection Dependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + } - b.Property(e => e.OnAdd).ValueGeneratedOnAdd(); + protected class WrappedIntClassDependentShadow + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedIntClass Id { get; set; } = null!; - property = b.Property(e => e.OnAddUseBeforeUseAfter).ValueGeneratedOnAdd().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + public WrappedIntClassPrincipal? Principal { get; set; } + } - property = b.Property(e => e.OnAddIgnoreBeforeUseAfter).ValueGeneratedOnAdd().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + protected class WrappedIntClassDependentRequired + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedIntClass Id { get; set; } = null!; - property = b.Property(e => e.OnAddThrowBeforeUseAfter).ValueGeneratedOnAdd().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + public WrappedIntKeyClass PrincipalId { get; set; } = null!; + public WrappedIntClassPrincipal Principal { get; set; } = null!; + } - property = b.Property(e => e.OnAddUseBeforeIgnoreAfter).ValueGeneratedOnAdd().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); - property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + protected class WrappedIntClassDependentOptional + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedIntClass Id { get; set; } = null!; - property = b.Property(e => e.OnAddIgnoreBeforeIgnoreAfter).ValueGeneratedOnAdd().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); - property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + public WrappedIntKeyClass? PrincipalId { get; set; } + public WrappedIntClassPrincipal? Principal { get; set; } + } - property = b.Property(e => e.OnAddThrowBeforeIgnoreAfter).ValueGeneratedOnAdd().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); - property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + protected class WrappedIntStructPrincipal + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedIntKeyStruct Id { get; set; } - property = b.Property(e => e.OnAddUseBeforeThrowAfter).ValueGeneratedOnAdd().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); - property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + public WrappedIntStruct NonKey { get; set; } + public ICollection Dependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + } - property = b.Property(e => e.OnAddIgnoreBeforeThrowAfter).ValueGeneratedOnAdd().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); - property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + protected class WrappedIntStructDependentShadow + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedIntStruct Id { get; set; } - property = b.Property(e => e.OnAddThrowBeforeThrowAfter).ValueGeneratedOnAdd().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); - property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + public WrappedIntStructPrincipal? Principal { get; set; } + } - b.Property(e => e.OnAddOrUpdate).ValueGeneratedOnAddOrUpdate(); + protected class WrappedIntStructDependentOptional + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedIntStruct Id { get; set; } - property = b.Property(e => e.OnAddOrUpdateUseBeforeUseAfter).ValueGeneratedOnAddOrUpdate().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + public WrappedIntKeyStruct? PrincipalId { get; set; } + public WrappedIntStructPrincipal? Principal { get; set; } + } - property = b.Property(e => e.OnAddOrUpdateIgnoreBeforeUseAfter).ValueGeneratedOnAddOrUpdate().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + protected class WrappedIntStructDependentRequired + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedIntStruct Id { get; set; } - property = b.Property(e => e.OnAddOrUpdateThrowBeforeUseAfter).ValueGeneratedOnAddOrUpdate().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + public WrappedIntKeyStruct PrincipalId { get; set; } + public WrappedIntStructPrincipal Principal { get; set; } = null!; + } - property = b.Property(e => e.OnAddOrUpdateUseBeforeIgnoreAfter).ValueGeneratedOnAddOrUpdate().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); - property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + protected class WrappedIntRecordPrincipal + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedIntKeyRecord Id { get; set; } = null!; - property = b.Property(e => e.OnAddOrUpdateIgnoreBeforeIgnoreAfter).ValueGeneratedOnAddOrUpdate().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); - property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + public WrappedIntRecord? NonKey { get; set; } + public ICollection Dependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + } - property = b.Property(e => e.OnAddOrUpdateThrowBeforeIgnoreAfter).ValueGeneratedOnAddOrUpdate().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); - property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + protected class WrappedIntRecordDependentShadow + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedIntRecord Id { get; set; } = null!; - property = b.Property(e => e.OnAddOrUpdateUseBeforeThrowAfter).ValueGeneratedOnAddOrUpdate().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); - property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + public WrappedIntRecordPrincipal? Principal { get; set; } + } - property = b.Property(e => e.OnAddOrUpdateIgnoreBeforeThrowAfter).ValueGeneratedOnAddOrUpdate().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); - property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + protected class WrappedIntRecordDependentOptional + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedIntRecord Id { get; set; } = null!; - property = b.Property(e => e.OnAddOrUpdateThrowBeforeThrowAfter).ValueGeneratedOnAddOrUpdate().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); - property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + public WrappedIntKeyRecord? PrincipalId { get; set; } + public WrappedIntRecordPrincipal? Principal { get; set; } + } - b.Property(e => e.OnUpdate).ValueGeneratedOnUpdate(); + protected class WrappedIntRecordDependentRequired + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedIntRecord Id { get; set; } = null!; - property = b.Property(e => e.OnUpdateUseBeforeUseAfter).ValueGeneratedOnUpdate().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + public WrappedIntKeyRecord PrincipalId { get; set; } = null!; + public WrappedIntRecordPrincipal Principal { get; set; } = null!; + } - property = b.Property(e => e.OnUpdateIgnoreBeforeUseAfter).ValueGeneratedOnUpdate().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + [ConditionalFact] + public virtual void Insert_update_and_delete_with_wrapped_int_key() + { + var id1 = 0; + var id2 = 0; + var id3 = 0; + ExecuteWithStrategyInTransaction( + context => + { + var principal1 = context.Add( + new WrappedIntClassPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; - property = b.Property(e => e.OnUpdateThrowBeforeUseAfter).ValueGeneratedOnUpdate().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); - property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + var principal2 = context.Add( + new WrappedIntStructPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; - property = b.Property(e => e.OnUpdateUseBeforeIgnoreAfter).ValueGeneratedOnUpdate().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); - property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + var principal3 = context.Add( + new WrappedIntRecordPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; - property = b.Property(e => e.OnUpdateIgnoreBeforeIgnoreAfter).ValueGeneratedOnUpdate().Metadata; - property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); - property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + context.SaveChanges(); - property = b.Property(e => e.OnUpdateThrowBeforeIgnoreAfter).ValueGeneratedOnUpdate().Metadata; + id1 = principal1.Id.Value; + Assert.NotEqual(0, id1); + foreach (var dependent in principal1.Dependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal1.OptionalDependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal1.RequiredDependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId.Value); + } + + Assert.Equal(66, principal1.NonKey!.Value); + + id2 = principal2.Id.Value; + Assert.NotEqual(0, id2); + foreach (var dependent in principal2.Dependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value.Value); + } + foreach (var dependent in principal2.OptionalDependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId!.Value.Value); + } + foreach (var dependent in principal2.RequiredDependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId.Value); + } + + Assert.Equal(66, principal2.NonKey.Value); + + id3 = principal3.Id.Value; + Assert.NotEqual(0, id3); + foreach (var dependent in principal3.Dependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal3.OptionalDependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal3.RequiredDependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId.Value); + } + + Assert.Equal(66, principal3.NonKey!.Value); + }, + context => + { + var principal1 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal1.Id.Value, id1); + foreach (var dependent in principal1.Dependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal1.OptionalDependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal1.RequiredDependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId.Value); + } + + var principal2 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal2.Id.Value, id2); + foreach (var dependent in principal2.Dependents) + { + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value.Value); + } + foreach (var dependent in principal2.OptionalDependents) + { + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId!.Value.Value); + } + foreach (var dependent in principal2.RequiredDependents) + { + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId.Value); + } + + var principal3 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal3.Id.Value, id3); + foreach (var dependent in principal3.Dependents) + { + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal3.OptionalDependents) + { + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal3.RequiredDependents) + { + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId.Value); + } + + principal1.Dependents.Remove(principal1.Dependents.First()); + principal2.Dependents.Remove(principal2.Dependents.First()); + principal3.Dependents.Remove(principal3.Dependents.First()); + + principal1.OptionalDependents.Remove(principal1.OptionalDependents.First()); + principal2.OptionalDependents.Remove(principal2.OptionalDependents.First()); + principal3.OptionalDependents.Remove(principal3.OptionalDependents.First()); + + principal1.RequiredDependents.Remove(principal1.RequiredDependents.First()); + principal2.RequiredDependents.Remove(principal2.RequiredDependents.First()); + principal3.RequiredDependents.Remove(principal3.RequiredDependents.First()); + + context.SaveChanges(); + }, + context => + { + var dependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents1.Count); + Assert.Null( + context.Entry(dependents1.Single(e => e.Principal == null)) + .Property("PrincipalId").CurrentValue); + + var optionalDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents1.Count); + Assert.Null(optionalDependents1.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(1, requiredDependents1.Count); + + var dependents2 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents2.Count); + Assert.Null( + context.Entry(dependents2.Single(e => e.Principal == null)) + .Property("PrincipalId").CurrentValue); + + var optionalDependents2 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents2.Count); + Assert.Null(optionalDependents2.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents2 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(1, requiredDependents2.Count); + + var dependents3 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents3.Count); + Assert.Null( + context.Entry(dependents3.Single(e => e.Principal == null)) + .Property("PrincipalId").CurrentValue); + + var optionalDependents3 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents3.Count); + Assert.Null(optionalDependents3.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents3 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(1, requiredDependents3.Count); + + context.Remove(dependents1.Single(e => e.Principal != null)); + context.Remove(optionalDependents1.Single(e => e.Principal != null)); + context.Remove(requiredDependents1.Single()); + context.Remove(requiredDependents1.Single().Principal); + + context.Remove(dependents2.Single(e => e.Principal != null)); + context.Remove(optionalDependents2.Single(e => e.Principal != null)); + context.Remove(requiredDependents2.Single()); + context.Remove(requiredDependents2.Single().Principal); + + context.Remove(dependents3.Single(e => e.Principal != null)); + context.Remove(optionalDependents3.Single(e => e.Principal != null)); + context.Remove(requiredDependents3.Single()); + context.Remove(requiredDependents3.Single().Principal); + + context.SaveChanges(); + }, + context => + { + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + + Assert.Equal(0, context.Set().Count()); + Assert.Equal(0, context.Set().Count()); + Assert.Equal(0, context.Set().Count()); + }); + } + + protected class WrappedStringClass + { + public string? Value { get; set; } + } + + protected class WrappedStringClassConverter : ValueConverter + { + public WrappedStringClassConverter() + : base( + v => v.Value!, + v => new WrappedStringClass { Value = v }) + { + } + } + + protected class WrappedStringClassComparer : ValueComparer + { + public WrappedStringClassComparer() + : base( + (v1, v2) => (v1 == null && v2 == null) || (v1 != null && v2 != null && v1.Value!.Equals(v2.Value)), + v => v != null ? v.Value!.GetHashCode() : 0, + v => v == null ? null : new() { Value = v.Value }) + { + } + } + + protected class WrappedStringClassValueGenerator : ValueGenerator + { + public override WrappedStringClass Next(EntityEntry entry) + => new() { Value = "66" }; + + public override bool GeneratesTemporaryValues + => false; + } + + protected struct WrappedStringStruct + { + public string? Value { get; set; } + } + + protected class WrappedStringStructConverter : ValueConverter + { + public WrappedStringStructConverter() + : base( + v => v.Value!, + v => new WrappedStringStruct { Value = v }) + { + } + } + + protected class WrappedStringStructValueGenerator : ValueGenerator + { + public override WrappedStringStruct Next(EntityEntry entry) + => new() { Value = "66" }; + + public override bool GeneratesTemporaryValues + => false; + } + + protected record WrappedStringRecord + { + public string? Value { get; set; } + } + + protected class WrappedStringRecordConverter : ValueConverter + { + public WrappedStringRecordConverter() + : base( + v => v.Value!, + v => new WrappedStringRecord { Value = v }) + { + } + } + + protected class WrappedStringRecordValueGenerator : ValueGenerator + { + public override WrappedStringRecord Next(EntityEntry entry) + => new() { Value = "66" }; + + public override bool GeneratesTemporaryValues + => false; + } + + protected class WrappedStringKeyClass + { + public string? Value { get; set; } + } + + protected class WrappedStringKeyClassConverter : ValueConverter + { + public WrappedStringKeyClassConverter() + : base( + v => v.Value!, + v => new WrappedStringKeyClass { Value = v }) + { + } + } + + protected class WrappedStringKeyClassComparer : ValueComparer + { + public WrappedStringKeyClassComparer() + : base( + (v1, v2) => (v1 == null && v2 == null) || (v1 != null && v2 != null && v1.Value!.Equals(v2.Value)), + v => v != null ? v.Value!.GetHashCode() : 0, + v => v == null ? null : new() { Value = v.Value }) + { + } + } + + protected struct WrappedStringKeyStruct + { + public string Value { get; set; } + + public override bool Equals(object? obj) + => obj is WrappedStringKeyStruct other && Value == other.Value; + + public override int GetHashCode() + => Value.GetHashCode(); + + public static bool operator ==(WrappedStringKeyStruct left, WrappedStringKeyStruct right) + => left.Equals(right); + + public static bool operator !=(WrappedStringKeyStruct left, WrappedStringKeyStruct right) + => !left.Equals(right); + } + + protected class WrappedStringKeyStructConverter : ValueConverter + { + public WrappedStringKeyStructConverter() + : base( + v => v.Value, + v => new WrappedStringKeyStruct { Value = v }) + { + } + } + + protected record WrappedStringKeyRecord + { + public string? Value { get; set; } + } + + protected class WrappedStringKeyRecordConverter : ValueConverter + { + public WrappedStringKeyRecordConverter() + : base( + v => v.Value!, + v => new WrappedStringKeyRecord { Value = v }) + { + } + } + + protected class WrappedStringClassPrincipal + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedStringKeyClass Id { get; set; } = null!; + + public WrappedStringClass? NonKey { get; set; } + public ICollection Dependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + } + + protected class WrappedStringClassDependentShadow + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedStringClass Id { get; set; } = null!; + + public WrappedStringClassPrincipal? Principal { get; set; } + } + + protected class WrappedStringClassDependentRequired + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedStringClass Id { get; set; } = null!; + + public WrappedStringKeyClass PrincipalId { get; set; } = null!; + public WrappedStringClassPrincipal Principal { get; set; } = null!; + } + + protected class WrappedStringClassDependentOptional + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedStringClass Id { get; set; } = null!; + + public WrappedStringKeyClass? PrincipalId { get; set; } + public WrappedStringClassPrincipal? Principal { get; set; } + } + + protected class WrappedStringStructPrincipal + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedStringKeyStruct Id { get; set; } + + public WrappedStringStruct NonKey { get; set; } + public ICollection Dependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + } + + protected class WrappedStringStructDependentShadow + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedStringStruct Id { get; set; } + + public WrappedStringStructPrincipal? Principal { get; set; } + } + + protected class WrappedStringStructDependentOptional + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedStringStruct Id { get; set; } + + public WrappedStringKeyStruct? PrincipalId { get; set; } + public WrappedStringStructPrincipal? Principal { get; set; } + } + + protected class WrappedStringStructDependentRequired + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedStringStruct Id { get; set; } + + public WrappedStringKeyStruct PrincipalId { get; set; } + public WrappedStringStructPrincipal Principal { get; set; } = null!; + } + + protected class WrappedStringRecordPrincipal + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedStringKeyRecord Id { get; set; } = null!; + + public WrappedStringRecord? NonKey { get; set; } + public ICollection Dependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + } + + protected class WrappedStringRecordDependentShadow + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedStringRecord Id { get; set; } = null!; + + public WrappedStringRecordPrincipal? Principal { get; set; } + } + + protected class WrappedStringRecordDependentOptional + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedStringRecord Id { get; set; } = null!; + + public WrappedStringKeyRecord? PrincipalId { get; set; } + public WrappedStringRecordPrincipal? Principal { get; set; } + } + + protected class WrappedStringRecordDependentRequired + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedStringRecord Id { get; set; } = null!; + + public WrappedStringKeyRecord PrincipalId { get; set; } = null!; + public WrappedStringRecordPrincipal Principal { get; set; } = null!; + } + + [ConditionalFact] + public virtual void Insert_update_and_delete_with_wrapped_string_key() + { + string? id1 = null; + string? id2 = null; + string? id3 = null; + ExecuteWithStrategyInTransaction( + context => + { + var principal1 = context.Add( + new WrappedStringClassPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; + + var principal2 = context.Add( + new WrappedStringStructPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; + + var principal3 = context.Add( + new WrappedStringRecordPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; + + context.SaveChanges(); + + id1 = principal1.Id.Value; + Assert.NotNull(id1); + foreach (var dependent in principal1.Dependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal1.OptionalDependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal1.RequiredDependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId.Value); + } + + Assert.Equal("66", principal1.NonKey!.Value); + + id2 = principal2.Id.Value; + Assert.NotNull(id2); + foreach (var dependent in principal2.Dependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value.Value); + } + foreach (var dependent in principal2.OptionalDependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId!.Value.Value); + } + foreach (var dependent in principal2.RequiredDependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId.Value); + } + + Assert.Equal("66", principal2.NonKey.Value); + + id3 = principal3.Id.Value; + Assert.NotNull(id3); + foreach (var dependent in principal3.Dependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal3.OptionalDependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal3.RequiredDependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId.Value); + } + + Assert.Equal("66", principal3.NonKey!.Value); + }, + context => + { + var principal1 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal1.Id.Value, id1); + foreach (var dependent in principal1.Dependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal1.OptionalDependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal1.RequiredDependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId.Value); + } + + var principal2 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal2.Id.Value, id2); + foreach (var dependent in principal2.Dependents) + { + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value.Value); + } + foreach (var dependent in principal2.OptionalDependents) + { + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId!.Value.Value); + } + foreach (var dependent in principal2.RequiredDependents) + { + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId.Value); + } + + var principal3 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal3.Id.Value, id3); + foreach (var dependent in principal3.Dependents) + { + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal3.OptionalDependents) + { + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal3.RequiredDependents) + { + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId.Value); + } + + principal1.Dependents.Remove(principal1.Dependents.First()); + principal2.Dependents.Remove(principal2.Dependents.First()); + principal3.Dependents.Remove(principal3.Dependents.First()); + + principal1.OptionalDependents.Remove(principal1.OptionalDependents.First()); + principal2.OptionalDependents.Remove(principal2.OptionalDependents.First()); + principal3.OptionalDependents.Remove(principal3.OptionalDependents.First()); + + principal1.RequiredDependents.Remove(principal1.RequiredDependents.First()); + principal2.RequiredDependents.Remove(principal2.RequiredDependents.First()); + principal3.RequiredDependents.Remove(principal3.RequiredDependents.First()); + + context.SaveChanges(); + }, + context => + { + var dependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents1.Count); + Assert.Null( + context.Entry(dependents1.Single(e => e.Principal == null)) + .Property("PrincipalId").CurrentValue); + + var optionalDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents1.Count); + Assert.Null(optionalDependents1.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(1, requiredDependents1.Count); + + var dependents2 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents2.Count); + Assert.Null( + context.Entry(dependents2.Single(e => e.Principal == null)) + .Property("PrincipalId").CurrentValue); + + var optionalDependents2 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents2.Count); + Assert.Null(optionalDependents2.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents2 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(1, requiredDependents2.Count); + + var dependents3 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents3.Count); + Assert.Null( + context.Entry(dependents3.Single(e => e.Principal == null)) + .Property("PrincipalId").CurrentValue); + + var optionalDependents3 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents3.Count); + Assert.Null(optionalDependents3.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents3 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(1, requiredDependents3.Count); + + context.Remove(dependents1.Single(e => e.Principal != null)); + context.Remove(optionalDependents1.Single(e => e.Principal != null)); + context.Remove(requiredDependents1.Single()); + context.Remove(requiredDependents1.Single().Principal); + + context.Remove(dependents2.Single(e => e.Principal != null)); + context.Remove(optionalDependents2.Single(e => e.Principal != null)); + context.Remove(requiredDependents2.Single()); + context.Remove(requiredDependents2.Single().Principal); + + context.Remove(dependents3.Single(e => e.Principal != null)); + context.Remove(optionalDependents3.Single(e => e.Principal != null)); + context.Remove(requiredDependents3.Single()); + context.Remove(requiredDependents3.Single().Principal); + + context.SaveChanges(); + }, + context => + { + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + + Assert.Equal(0, context.Set().Count()); + Assert.Equal(0, context.Set().Count()); + Assert.Equal(0, context.Set().Count()); + }); + } + + // ReSharper disable once StaticMemberInGenericType + protected static readonly Guid KnownGuid = Guid.Parse("E871CEA4-8DBE-4269-99F4-87F7128AF399"); + + protected class WrappedGuidClass + { + public Guid Value { get; set; } + } + + protected class WrappedGuidClassConverter : ValueConverter + { + public WrappedGuidClassConverter() + : base( + v => v.Value, + v => new WrappedGuidClass { Value = v }) + { + } + } + + protected class WrappedGuidClassComparer : ValueComparer + { + public WrappedGuidClassComparer() + : base( + (v1, v2) => (v1 == null && v2 == null) || (v1 != null && v2 != null && v1.Value.Equals(v2.Value)), + v => v != null ? v.Value.GetHashCode() : 0, + v => v == null ? null : new() { Value = v.Value }) + { + } + } + + protected class WrappedGuidClassValueGenerator : ValueGenerator + { + public override WrappedGuidClass Next(EntityEntry entry) + => new() { Value = KnownGuid }; + + public override bool GeneratesTemporaryValues + => false; + } + + protected struct WrappedGuidStruct + { + public Guid Value { get; set; } + } + + protected class WrappedGuidStructConverter : ValueConverter + { + public WrappedGuidStructConverter() + : base( + v => v.Value, + v => new WrappedGuidStruct { Value = v }) + { + } + } + + protected class WrappedGuidStructValueGenerator : ValueGenerator + { + public override WrappedGuidStruct Next(EntityEntry entry) + => new() { Value = KnownGuid }; + + public override bool GeneratesTemporaryValues + => false; + } + + protected record WrappedGuidRecord + { + public Guid Value { get; set; } + } + + protected class WrappedGuidRecordConverter : ValueConverter + { + public WrappedGuidRecordConverter() + : base( + v => v.Value, + v => new WrappedGuidRecord { Value = v }) + { + } + } + + protected class WrappedGuidRecordValueGenerator : ValueGenerator + { + public override WrappedGuidRecord Next(EntityEntry entry) + => new() { Value = KnownGuid }; + + public override bool GeneratesTemporaryValues + => false; + } + + protected class WrappedGuidKeyClass + { + public Guid Value { get; set; } + } + + protected class WrappedGuidKeyClassConverter : ValueConverter + { + public WrappedGuidKeyClassConverter() + : base( + v => v.Value, + v => new WrappedGuidKeyClass { Value = v }) + { + } + } + + protected class WrappedGuidKeyClassComparer : ValueComparer + { + public WrappedGuidKeyClassComparer() + : base( + (v1, v2) => (v1 == null && v2 == null) || (v1 != null && v2 != null && v1.Value.Equals(v2.Value)), + v => v != null ? v.Value.GetHashCode() : 0, + v => v == null ? null : new() { Value = v.Value }) + { + } + } + + protected struct WrappedGuidKeyStruct + { + public Guid Value { get; set; } + + public override bool Equals(object? obj) + => obj is WrappedGuidKeyStruct other && Value.Equals(other.Value); + + public override int GetHashCode() + => Value.GetHashCode(); + + public static bool operator ==(WrappedGuidKeyStruct left, WrappedGuidKeyStruct right) + => left.Equals(right); + + public static bool operator !=(WrappedGuidKeyStruct left, WrappedGuidKeyStruct right) + => !left.Equals(right); + } + + protected class WrappedGuidKeyStructConverter : ValueConverter + { + public WrappedGuidKeyStructConverter() + : base( + v => v.Value, + v => new WrappedGuidKeyStruct { Value = v }) + { + } + } + + protected record WrappedGuidKeyRecord + { + public Guid Value { get; set; } + } + + protected class WrappedGuidKeyRecordConverter : ValueConverter + { + public WrappedGuidKeyRecordConverter() + : base( + v => v.Value, + v => new WrappedGuidKeyRecord { Value = v }) + { + } + } + + protected class WrappedGuidClassPrincipal + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedGuidKeyClass Id { get; set; } = null!; + + public WrappedGuidClass? NonKey { get; set; } + public ICollection Dependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + } + + protected class WrappedGuidClassDependentShadow + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedGuidClass Id { get; set; } = null!; + + public WrappedGuidClassPrincipal? Principal { get; set; } + } + + protected class WrappedGuidClassDependentRequired + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedGuidClass Id { get; set; } = null!; + + public WrappedGuidKeyClass PrincipalId { get; set; } = null!; + public WrappedGuidClassPrincipal Principal { get; set; } = null!; + } + + protected class WrappedGuidClassDependentOptional + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedGuidClass Id { get; set; } = null!; + + public WrappedGuidKeyClass? PrincipalId { get; set; } + public WrappedGuidClassPrincipal? Principal { get; set; } + } + + protected class WrappedGuidStructPrincipal + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedGuidKeyStruct Id { get; set; } + + public WrappedGuidStruct NonKey { get; set; } + public ICollection Dependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + } + + protected class WrappedGuidStructDependentShadow + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedGuidStruct Id { get; set; } + + public WrappedGuidStructPrincipal? Principal { get; set; } + } + + protected class WrappedGuidStructDependentOptional + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedGuidStruct Id { get; set; } + + public WrappedGuidKeyStruct? PrincipalId { get; set; } + public WrappedGuidStructPrincipal? Principal { get; set; } + } + + protected class WrappedGuidStructDependentRequired + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedGuidStruct Id { get; set; } + + public WrappedGuidKeyStruct PrincipalId { get; set; } + public WrappedGuidStructPrincipal Principal { get; set; } = null!; + } + + protected class WrappedGuidRecordPrincipal + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedGuidKeyRecord Id { get; set; } = null!; + + public WrappedGuidRecord? NonKey { get; set; } + public ICollection Dependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + } + + protected class WrappedGuidRecordDependentShadow + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedGuidRecord Id { get; set; } = null!; + + public WrappedGuidRecordPrincipal? Principal { get; set; } + } + + protected class WrappedGuidRecordDependentOptional + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedGuidRecord Id { get; set; } = null!; + + public WrappedGuidKeyRecord? PrincipalId { get; set; } + public WrappedGuidRecordPrincipal? Principal { get; set; } + } + + protected class WrappedGuidRecordDependentRequired + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedGuidRecord Id { get; set; } = null!; + + public WrappedGuidKeyRecord PrincipalId { get; set; } = null!; + public WrappedGuidRecordPrincipal Principal { get; set; } = null!; + } + + [ConditionalFact] + public virtual void Insert_update_and_delete_with_wrapped_Guid_key() + { + var id1 = Guid.Empty; + var id2 = Guid.Empty; + var id3 = Guid.Empty; + ExecuteWithStrategyInTransaction( + context => + { + var principal1 = context.Add( + new WrappedGuidClassPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; + + var principal2 = context.Add( + new WrappedGuidStructPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; + + var principal3 = context.Add( + new WrappedGuidRecordPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; + + context.SaveChanges(); + + id1 = principal1.Id.Value; + Assert.NotEqual(Guid.Empty, id1); + foreach (var dependent in principal1.Dependents) + { + Assert.NotEqual(Guid.Empty, dependent.Id.Value); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal1.OptionalDependents) + { + Assert.NotEqual(Guid.Empty, dependent.Id.Value); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal1.RequiredDependents) + { + Assert.NotEqual(Guid.Empty, dependent.Id.Value); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId.Value); + } + + Assert.Equal(KnownGuid, principal1.NonKey!.Value); + + id2 = principal2.Id.Value; + Assert.NotEqual(Guid.Empty, id2); + foreach (var dependent in principal2.Dependents) + { + Assert.NotEqual(Guid.Empty, dependent.Id.Value); + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value.Value); + } + foreach (var dependent in principal2.OptionalDependents) + { + Assert.NotEqual(Guid.Empty, dependent.Id.Value); + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId!.Value.Value); + } + foreach (var dependent in principal2.RequiredDependents) + { + Assert.NotEqual(Guid.Empty, dependent.Id.Value); + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId.Value); + } + + Assert.Equal(KnownGuid, principal2.NonKey.Value); + + id3 = principal3.Id.Value; + Assert.NotEqual(Guid.Empty, id3); + foreach (var dependent in principal3.Dependents) + { + Assert.NotEqual(Guid.Empty, dependent.Id.Value); + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal3.OptionalDependents) + { + Assert.NotEqual(Guid.Empty, dependent.Id.Value); + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal3.RequiredDependents) + { + Assert.NotEqual(Guid.Empty, dependent.Id.Value); + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId.Value); + } + + Assert.Equal(KnownGuid, principal3.NonKey!.Value); + }, + context => + { + var principal1 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal1.Id.Value, id1); + foreach (var dependent in principal1.Dependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal1.OptionalDependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal1.RequiredDependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId.Value); + } + + var principal2 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal2.Id.Value, id2); + foreach (var dependent in principal2.Dependents) + { + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value.Value); + } + foreach (var dependent in principal2.OptionalDependents) + { + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId!.Value.Value); + } + foreach (var dependent in principal2.RequiredDependents) + { + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId.Value); + } + + var principal3 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal3.Id.Value, id3); + foreach (var dependent in principal3.Dependents) + { + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal3.OptionalDependents) + { + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal3.RequiredDependents) + { + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId.Value); + } + + principal1.Dependents.Remove(principal1.Dependents.First()); + principal2.Dependents.Remove(principal2.Dependents.First()); + principal3.Dependents.Remove(principal3.Dependents.First()); + + principal1.OptionalDependents.Remove(principal1.OptionalDependents.First()); + principal2.OptionalDependents.Remove(principal2.OptionalDependents.First()); + principal3.OptionalDependents.Remove(principal3.OptionalDependents.First()); + + principal1.RequiredDependents.Remove(principal1.RequiredDependents.First()); + principal2.RequiredDependents.Remove(principal2.RequiredDependents.First()); + principal3.RequiredDependents.Remove(principal3.RequiredDependents.First()); + + context.SaveChanges(); + }, + context => + { + var dependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents1.Count); + Assert.Null( + context.Entry(dependents1.Single(e => e.Principal == null)) + .Property("PrincipalId").CurrentValue); + + var optionalDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents1.Count); + Assert.Null(optionalDependents1.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(1, requiredDependents1.Count); + + var dependents2 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents2.Count); + Assert.Null( + context.Entry(dependents2.Single(e => e.Principal == null)) + .Property("PrincipalId").CurrentValue); + + var optionalDependents2 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents2.Count); + Assert.Null(optionalDependents2.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents2 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(1, requiredDependents2.Count); + + var dependents3 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents3.Count); + Assert.Null( + context.Entry(dependents3.Single(e => e.Principal == null)) + .Property("PrincipalId").CurrentValue); + + var optionalDependents3 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents3.Count); + Assert.Null(optionalDependents3.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents3 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(1, requiredDependents3.Count); + + context.Remove(dependents1.Single(e => e.Principal != null)); + context.Remove(optionalDependents1.Single(e => e.Principal != null)); + context.Remove(requiredDependents1.Single()); + context.Remove(requiredDependents1.Single().Principal); + + context.Remove(dependents2.Single(e => e.Principal != null)); + context.Remove(optionalDependents2.Single(e => e.Principal != null)); + context.Remove(requiredDependents2.Single()); + context.Remove(requiredDependents2.Single().Principal); + + context.Remove(dependents3.Single(e => e.Principal != null)); + context.Remove(optionalDependents3.Single(e => e.Principal != null)); + context.Remove(requiredDependents3.Single()); + context.Remove(requiredDependents3.Single().Principal); + + context.SaveChanges(); + }, + context => + { + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + + Assert.Equal(0, context.Set().Count()); + Assert.Equal(0, context.Set().Count()); + Assert.Equal(0, context.Set().Count()); + }); + } + + protected class WrappedUriClass + { + public Uri? Value { get; set; } + } + + protected class WrappedUriClassConverter : ValueConverter + { + public WrappedUriClassConverter() + : base( + v => v.Value!, + v => new WrappedUriClass { Value = v }) + { + } + } + + protected class WrappedUriClassComparer : ValueComparer + { + public WrappedUriClassComparer() + : base( + (v1, v2) => (v1 == null && v2 == null) || (v1 != null && v2 != null && v1.Value!.Equals(v2.Value)), + v => v != null ? v.Value!.GetHashCode() : 0, + v => v == null ? null : new() { Value = v.Value }) + { + } + } + + protected class WrappedUriClassValueGenerator : ValueGenerator + { + public override WrappedUriClass Next(EntityEntry entry) + => new() { Value = new Uri("https://www.example.com") }; + + public override bool GeneratesTemporaryValues + => false; + } + + protected struct WrappedUriStruct + { + public Uri Value { get; set; } + } + + protected class WrappedUriStructConverter : ValueConverter + { + public WrappedUriStructConverter() + : base( + v => v.Value, + v => new WrappedUriStruct { Value = v }) + { + } + } + + protected class WrappedUriStructValueGenerator : ValueGenerator + { + public override WrappedUriStruct Next(EntityEntry entry) + => new() { Value = new Uri("https://www.example.com") }; + + public override bool GeneratesTemporaryValues + => false; + } + + protected record WrappedUriRecord + { + public Uri? Value { get; set; } + } + + protected class WrappedUriRecordConverter : ValueConverter + { + public WrappedUriRecordConverter() + : base( + v => v.Value!, + v => new WrappedUriRecord { Value = v }) + { + } + } + + protected class WrappedUriRecordValueGenerator : ValueGenerator + { + public override WrappedUriRecord Next(EntityEntry entry) + => new() { Value = new Uri("https://www.example.com") }; + + public override bool GeneratesTemporaryValues + => false; + } + + protected class WrappedUriKeyClass + { + public Uri? Value { get; set; } + } + + protected class WrappedUriKeyClassConverter : ValueConverter + { + public WrappedUriKeyClassConverter() + : base( + v => v.Value!, + v => new WrappedUriKeyClass { Value = v }) + { + } + } + + protected class WrappedUriKeyClassComparer : ValueComparer + { + public WrappedUriKeyClassComparer() + : base( + (v1, v2) => (v1 == null && v2 == null) || (v1 != null && v2 != null && v1.Value!.Equals(v2.Value)), + v => v != null ? v.Value!.GetHashCode() : 0, + v => v == null ? null : new() { Value = v.Value }) + { + } + } + + protected struct WrappedUriKeyStruct + { + public Uri? Value { get; set; } + + public bool Equals(WrappedUriKeyStruct other) + => Equals(Value, other.Value); + + public override bool Equals(object? obj) + => obj is WrappedUriKeyStruct other && Equals(other); + + public override int GetHashCode() + => (Value != null ? Value.GetHashCode() : 0); + + public static bool operator ==(WrappedUriKeyStruct left, WrappedUriKeyStruct right) + => left.Equals(right); + + public static bool operator !=(WrappedUriKeyStruct left, WrappedUriKeyStruct right) + => !left.Equals(right); + } + + protected class WrappedUriKeyStructConverter : ValueConverter + { + public WrappedUriKeyStructConverter() + : base( + v => v.Value!, + v => new WrappedUriKeyStruct { Value = v }) + { + } + } + + protected record WrappedUriKeyRecord + { + public Uri? Value { get; set; } + } + + protected class WrappedUriKeyRecordConverter : ValueConverter + { + public WrappedUriKeyRecordConverter() + : base( + v => v.Value!, + v => new WrappedUriKeyRecord { Value = v }) + { + } + } + + protected class WrappedUriClassPrincipal + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedUriKeyClass Id { get; set; } = null!; + + public WrappedUriClass? NonKey { get; set; } + public ICollection Dependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + } + + protected class WrappedUriClassDependentShadow + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedUriClass Id { get; set; } = null!; + + public WrappedUriClassPrincipal? Principal { get; set; } + } + + protected class WrappedUriClassDependentRequired + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedUriClass Id { get; set; } = null!; + + public WrappedUriKeyClass PrincipalId { get; set; } = null!; + public WrappedUriClassPrincipal Principal { get; set; } = null!; + } + + protected class WrappedUriClassDependentOptional + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedUriClass Id { get; set; } = null!; + + public WrappedUriKeyClass? PrincipalId { get; set; } + public WrappedUriClassPrincipal? Principal { get; set; } + } + + protected class WrappedUriStructPrincipal + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedUriKeyStruct Id { get; set; } + + public WrappedUriStruct NonKey { get; set; } + public ICollection Dependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + } + + protected class WrappedUriStructDependentShadow + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedUriStruct Id { get; set; } + + public WrappedUriStructPrincipal? Principal { get; set; } + } + + protected class WrappedUriStructDependentOptional + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedUriStruct Id { get; set; } + + public WrappedUriKeyStruct? PrincipalId { get; set; } + public WrappedUriStructPrincipal? Principal { get; set; } + } + + protected class WrappedUriStructDependentRequired + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedUriStruct Id { get; set; } + + public WrappedUriKeyStruct PrincipalId { get; set; } + public WrappedUriStructPrincipal Principal { get; set; } = null!; + } + + protected class WrappedUriRecordPrincipal + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedUriKeyRecord Id { get; set; } = null!; + + public WrappedUriRecord? NonKey { get; set; } + public ICollection Dependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + } + + protected class WrappedUriRecordDependentShadow + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedUriRecord Id { get; set; } = null!; + + public WrappedUriRecordPrincipal? Principal { get; set; } + } + + protected class WrappedUriRecordDependentOptional + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedUriRecord Id { get; set; } = null!; + + public WrappedUriKeyRecord? PrincipalId { get; set; } + public WrappedUriRecordPrincipal? Principal { get; set; } + } + + protected class WrappedUriRecordDependentRequired + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public WrappedUriRecord Id { get; set; } = null!; + + public WrappedUriKeyRecord PrincipalId { get; set; } = null!; + public WrappedUriRecordPrincipal Principal { get; set; } = null!; + } + + [ConditionalFact] + public virtual void Insert_update_and_delete_with_wrapped_Uri_key() + { + Uri? id1 = null; + Uri? id2 = null; + Uri? id3 = null; + ExecuteWithStrategyInTransaction( + context => + { + var principal1 = context.Add( + new WrappedUriClassPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; + + var principal2 = context.Add( + new WrappedUriStructPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; + + var principal3 = context.Add( + new WrappedUriRecordPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; + + context.SaveChanges(); + + id1 = principal1.Id.Value; + Assert.NotNull(id1); + foreach (var dependent in principal1.Dependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal1.OptionalDependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal1.RequiredDependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId.Value); + } + + Assert.Equal(new Uri("https://www.example.com"), principal1.NonKey!.Value); + + id2 = principal2.Id.Value; + Assert.NotNull(id2); + foreach (var dependent in principal2.Dependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value.Value); + } + foreach (var dependent in principal2.OptionalDependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId!.Value.Value); + } + foreach (var dependent in principal2.RequiredDependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId.Value); + } + + Assert.Equal(new Uri("https://www.example.com"), principal2.NonKey.Value); + + id3 = principal3.Id.Value; + Assert.NotNull(id3); + foreach (var dependent in principal3.Dependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal3.OptionalDependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal3.RequiredDependents) + { + Assert.NotNull(dependent.Id.Value); + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId.Value); + } + + Assert.Equal(new Uri("https://www.example.com"), principal3.NonKey!.Value); + }, + context => + { + var principal1 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal1.Id.Value, id1); + foreach (var dependent in principal1.Dependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal1.OptionalDependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal1.RequiredDependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId.Value); + } + + var principal2 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal2.Id.Value, id2); + foreach (var dependent in principal2.Dependents) + { + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value.Value); + } + foreach (var dependent in principal2.OptionalDependents) + { + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId!.Value.Value); + } + foreach (var dependent in principal2.RequiredDependents) + { + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId.Value); + } + + var principal3 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal3.Id.Value, id3); + foreach (var dependent in principal3.Dependents) + { + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal3.OptionalDependents) + { + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal3.RequiredDependents) + { + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId.Value); + } + + principal1.Dependents.Remove(principal1.Dependents.First()); + principal2.Dependents.Remove(principal2.Dependents.First()); + principal3.Dependents.Remove(principal3.Dependents.First()); + + principal1.OptionalDependents.Remove(principal1.OptionalDependents.First()); + principal2.OptionalDependents.Remove(principal2.OptionalDependents.First()); + principal3.OptionalDependents.Remove(principal3.OptionalDependents.First()); + + principal1.RequiredDependents.Remove(principal1.RequiredDependents.First()); + principal2.RequiredDependents.Remove(principal2.RequiredDependents.First()); + principal3.RequiredDependents.Remove(principal3.RequiredDependents.First()); + + context.SaveChanges(); + }, + context => + { + var dependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents1.Count); + Assert.Null( + context.Entry(dependents1.Single(e => e.Principal == null)) + .Property("PrincipalId").CurrentValue); + + var optionalDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents1.Count); + Assert.Null(optionalDependents1.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(1, requiredDependents1.Count); + + var dependents2 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents2.Count); + Assert.Null( + context.Entry(dependents2.Single(e => e.Principal == null)) + .Property("PrincipalId").CurrentValue); + + var optionalDependents2 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents2.Count); + Assert.Null(optionalDependents2.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents2 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(1, requiredDependents2.Count); + + var dependents3 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents3.Count); + Assert.Null( + context.Entry(dependents3.Single(e => e.Principal == null)) + .Property("PrincipalId").CurrentValue); + + var optionalDependents3 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents3.Count); + Assert.Null(optionalDependents3.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents3 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(1, requiredDependents3.Count); + + context.Remove(dependents1.Single(e => e.Principal != null)); + context.Remove(optionalDependents1.Single(e => e.Principal != null)); + context.Remove(requiredDependents1.Single()); + context.Remove(requiredDependents1.Single().Principal); + + context.Remove(dependents2.Single(e => e.Principal != null)); + context.Remove(optionalDependents2.Single(e => e.Principal != null)); + context.Remove(requiredDependents2.Single()); + context.Remove(requiredDependents2.Single().Principal); + + context.Remove(dependents3.Single(e => e.Principal != null)); + context.Remove(optionalDependents3.Single(e => e.Principal != null)); + context.Remove(requiredDependents3.Single()); + context.Remove(requiredDependents3.Single().Principal); + + context.SaveChanges(); + }, + context => + { + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + + Assert.Equal(0, context.Set().Count()); + Assert.Equal(0, context.Set().Count()); + Assert.Equal(0, context.Set().Count()); + }); + } + + protected class UriPrincipal + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Uri Id { get; set; } = null!; + + public ICollection Dependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + } + + protected class UriDependentShadow + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Uri Id { get; set; } = null!; + + public UriPrincipal? Principal { get; set; } + } + + protected class UriDependentRequired + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Uri Id { get; set; } = null!; + + public Uri PrincipalId { get; set; } = null!; + public UriPrincipal Principal { get; set; } = null!; + } + + protected class UriDependentOptional + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Uri Id { get; set; } = null!; + + public Uri? PrincipalId { get; set; } + public UriPrincipal? Principal { get; set; } + } + + [ConditionalFact] + public virtual void Insert_update_and_delete_with_Uri_key() + { + Uri? id1 = null; + ExecuteWithStrategyInTransaction( + context => + { + var principal1 = context.Add( + new UriPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; + + context.SaveChanges(); + + id1 = principal1.Id; + Assert.NotNull(id1); + foreach (var dependent in principal1.Dependents) + { + Assert.NotNull(dependent.Id); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue); + } + }, + context => + { + var principal1 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal1.Id, id1); + foreach (var dependent in principal1.Dependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue); + } + + principal1.Dependents.Remove(principal1.Dependents.First()); + principal1.OptionalDependents.Remove(principal1.OptionalDependents.First()); + principal1.RequiredDependents.Remove(principal1.RequiredDependents.First()); + context.SaveChanges(); + }, + context => + { + var dependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents1.Count); + Assert.Null(context.Entry(dependents1.Single(e => e.Principal == null)).Property("PrincipalId").CurrentValue); + + var optionalDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents1.Count); + Assert.Null(optionalDependents1.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(1, requiredDependents1.Count); + + context.Remove(dependents1.Single(e => e.Principal != null)); + context.Remove(optionalDependents1.Single(e => e.Principal != null)); + context.Remove(requiredDependents1.Single()); + context.Remove(requiredDependents1.Single().Principal); + + context.SaveChanges(); + }, + context => + { + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + Assert.Equal(0, context.Set().Count()); + }); + } + + protected enum KeyEnum + { + A, + B, + C, + D, + E + } + + protected class EnumPrincipal + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public KeyEnum Id { get; set; } + + public ICollection Dependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + } + + protected class EnumDependentShadow + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public KeyEnum Id { get; set; } + + public EnumPrincipal? Principal { get; set; } + } + + protected class EnumDependentRequired + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public KeyEnum Id { get; set; } + + public KeyEnum PrincipalId { get; set; } + public EnumPrincipal Principal { get; set; } = null!; + } + + protected class EnumDependentOptional + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public KeyEnum Id { get; set; } + + public KeyEnum? PrincipalId { get; set; } + public EnumPrincipal? Principal { get; set; } + } + + [ConditionalFact] + public virtual void Insert_update_and_delete_with_enum_key() + { + KeyEnum? id1 = null; + ExecuteWithStrategyInTransaction( + context => + { + var principal1 = context.Add( + new EnumPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; + + context.SaveChanges(); + + id1 = principal1.Id; + Assert.NotNull(id1); + foreach (var dependent in principal1.Dependents) + { + Assert.NotNull(dependent.Id); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue); + } + }, + context => + { + var principal1 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal1.Id, id1); + foreach (var dependent in principal1.Dependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue); + } + + principal1.Dependents.Remove(principal1.Dependents.First()); + principal1.OptionalDependents.Remove(principal1.OptionalDependents.First()); + principal1.RequiredDependents.Remove(principal1.RequiredDependents.First()); + context.SaveChanges(); + }, + context => + { + var dependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents1.Count); + Assert.Null(context.Entry(dependents1.Single(e => e.Principal == null)).Property("PrincipalId").CurrentValue); + + var optionalDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents1.Count); + Assert.Null(optionalDependents1.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(1, requiredDependents1.Count); + + context.Remove(dependents1.Single(e => e.Principal != null)); + context.Remove(optionalDependents1.Single(e => e.Principal != null)); + context.Remove(requiredDependents1.Single()); + context.Remove(requiredDependents1.Single().Principal); + + context.SaveChanges(); + }, + context => + { + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + Assert.Equal(0, context.Set().Count()); + }); + } + + protected class GuidAsStringPrincipal + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + + public ICollection Dependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + } + + protected class GuidAsStringDependentShadow + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + + public GuidAsStringPrincipal? Principal { get; set; } + } + + protected class GuidAsStringDependentRequired + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + + public Guid PrincipalId { get; set; } + public GuidAsStringPrincipal Principal { get; set; } = null!; + } + + protected class GuidAsStringDependentOptional + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + + public Guid? PrincipalId { get; set; } + public GuidAsStringPrincipal? Principal { get; set; } + } + + [ConditionalFact] + public virtual void Insert_update_and_delete_with_GuidAsString_key() + { + Guid? id1 = null; + ExecuteWithStrategyInTransaction( + context => + { + var principal1 = context.Add( + new GuidAsStringPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; + + context.SaveChanges(); + + id1 = principal1.Id; + Assert.NotNull(id1); + foreach (var dependent in principal1.Dependents) + { + Assert.NotNull(dependent.Id); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue); + } + }, + context => + { + var principal1 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal1.Id, id1); + foreach (var dependent in principal1.Dependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue); + } + + principal1.Dependents.Remove(principal1.Dependents.First()); + principal1.OptionalDependents.Remove(principal1.OptionalDependents.First()); + principal1.RequiredDependents.Remove(principal1.RequiredDependents.First()); + context.SaveChanges(); + }, + context => + { + var dependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents1.Count); + Assert.Null(context.Entry(dependents1.Single(e => e.Principal == null)).Property("PrincipalId").CurrentValue); + + var optionalDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents1.Count); + Assert.Null(optionalDependents1.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(1, requiredDependents1.Count); + + context.Remove(dependents1.Single(e => e.Principal != null)); + context.Remove(optionalDependents1.Single(e => e.Principal != null)); + context.Remove(requiredDependents1.Single()); + context.Remove(requiredDependents1.Single().Principal); + + context.SaveChanges(); + }, + context => + { + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + Assert.Equal(0, context.Set().Count()); + }); + } + + protected class StringAsGuidPrincipal + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public string Id { get; set; } = null!; + + public ICollection Dependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + } + + protected class StringAsGuidDependentShadow + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public string Id { get; set; } = null!; + + public StringAsGuidPrincipal? Principal { get; set; } + } + + protected class StringAsGuidDependentRequired + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public string Id { get; set; } = null!; + + public string PrincipalId { get; set; } = null!; + public StringAsGuidPrincipal Principal { get; set; } = null!; + } + + protected class StringAsGuidDependentOptional + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public string Id { get; set; } = null!; + + public string? PrincipalId { get; set; } + public StringAsGuidPrincipal? Principal { get; set; } + } + + [ConditionalFact] + public virtual void Insert_update_and_delete_with_StringAsGuid_key() + { + string? id1 = null; + ExecuteWithStrategyInTransaction( + context => + { + var principal1 = context.Add( + new StringAsGuidPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; + + context.SaveChanges(); + + id1 = principal1.Id; + Assert.NotNull(id1); + foreach (var dependent in principal1.Dependents) + { + Assert.NotNull(dependent.Id); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue); + } + }, + context => + { + var principal1 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal1.Id, id1); + foreach (var dependent in principal1.Dependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue); + } + + principal1.Dependents.Remove(principal1.Dependents.First()); + principal1.OptionalDependents.Remove(principal1.OptionalDependents.First()); + principal1.RequiredDependents.Remove(principal1.RequiredDependents.First()); + context.SaveChanges(); + }, + context => + { + var dependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents1.Count); + Assert.Null(context.Entry(dependents1.Single(e => e.Principal == null)).Property("PrincipalId").CurrentValue); + + var optionalDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents1.Count); + Assert.Null(optionalDependents1.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(1, requiredDependents1.Count); + + context.Remove(dependents1.Single(e => e.Principal != null)); + context.Remove(optionalDependents1.Single(e => e.Principal != null)); + context.Remove(requiredDependents1.Single()); + context.Remove(requiredDependents1.Single().Principal); + + context.SaveChanges(); + }, + context => + { + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + Assert.Equal(0, context.Set().Count()); + }); + } + + protected virtual void ExecuteWithStrategyInTransaction( + Action testOperation, + Action? nestedTestOperation1 = null, + Action? nestedTestOperation2 = null, + Action? nestedTestOperation3 = null) + => TestHelpers.ExecuteWithStrategyInTransaction( + CreateContext, UseTransaction, + testOperation, nestedTestOperation1, nestedTestOperation2, nestedTestOperation3); + + protected virtual void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + { + } + + protected DbContext CreateContext() + => Fixture.CreateContext(); + + public abstract class StoreGeneratedFixtureBase : SharedStoreFixtureBase + { + protected override string StoreName { get; } = "StoreGeneratedTest"; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + modelBuilder.Entity().Property(e => e.Id).HasConversion(); + modelBuilder.Entity().Property(e => e.Id).HasConversion(); + modelBuilder.Entity().Property(e => e.Id).HasConversion(); + modelBuilder.Entity().Property(e => e.Id).HasConversion(); + + modelBuilder.Entity( + b => + { + var property = b.Property(e => e.Id).ValueGeneratedOnAdd().Metadata; + property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + + property = b.Property(e => e.Identity).ValueGeneratedOnAdd().Metadata; + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.IdentityReadOnlyBeforeSave).ValueGeneratedOnAdd().Metadata; + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + + property = b.Property(e => e.IdentityReadOnlyAfterSave).ValueGeneratedOnAdd().Metadata; + property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.AlwaysIdentity).ValueGeneratedOnAdd().Metadata; + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.AlwaysIdentityReadOnlyBeforeSave).ValueGeneratedOnAdd().Metadata; + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + + property = b.Property(e => e.AlwaysIdentityReadOnlyAfterSave).ValueGeneratedOnAdd().Metadata; + property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.Computed).ValueGeneratedOnAddOrUpdate().Metadata; + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.ComputedReadOnlyBeforeSave).ValueGeneratedOnAddOrUpdate().Metadata; + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + + property = b.Property(e => e.ComputedReadOnlyAfterSave).ValueGeneratedOnAddOrUpdate().Metadata; + property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.AlwaysComputed).ValueGeneratedOnAddOrUpdate().Metadata; + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.AlwaysComputedReadOnlyBeforeSave).ValueGeneratedOnAddOrUpdate().Metadata; + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + + property = b.Property(e => e.AlwaysComputedReadOnlyAfterSave).ValueGeneratedOnAddOrUpdate().Metadata; + property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + }); + + modelBuilder.Entity( + b => + { + b.Property(e => e.Never).ValueGeneratedNever(); + + var property = b.Property(e => e.NeverUseBeforeUseAfter).ValueGeneratedNever().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.NeverIgnoreBeforeUseAfter).ValueGeneratedNever().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.NeverThrowBeforeUseAfter).ValueGeneratedNever().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.NeverUseBeforeIgnoreAfter).ValueGeneratedNever().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + + property = b.Property(e => e.NeverIgnoreBeforeIgnoreAfter).ValueGeneratedNever().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); + property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + + property = b.Property(e => e.NeverThrowBeforeIgnoreAfter).ValueGeneratedNever().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + + property = b.Property(e => e.NeverUseBeforeThrowAfter).ValueGeneratedNever().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + + property = b.Property(e => e.NeverIgnoreBeforeThrowAfter).ValueGeneratedNever().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); + property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + + property = b.Property(e => e.NeverThrowBeforeThrowAfter).ValueGeneratedNever().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + + b.Property(e => e.OnAdd).ValueGeneratedOnAdd(); + + property = b.Property(e => e.OnAddUseBeforeUseAfter).ValueGeneratedOnAdd().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.OnAddIgnoreBeforeUseAfter).ValueGeneratedOnAdd().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.OnAddThrowBeforeUseAfter).ValueGeneratedOnAdd().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.OnAddUseBeforeIgnoreAfter).ValueGeneratedOnAdd().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + + property = b.Property(e => e.OnAddIgnoreBeforeIgnoreAfter).ValueGeneratedOnAdd().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); + property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + + property = b.Property(e => e.OnAddThrowBeforeIgnoreAfter).ValueGeneratedOnAdd().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + + property = b.Property(e => e.OnAddUseBeforeThrowAfter).ValueGeneratedOnAdd().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + + property = b.Property(e => e.OnAddIgnoreBeforeThrowAfter).ValueGeneratedOnAdd().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); + property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + + property = b.Property(e => e.OnAddThrowBeforeThrowAfter).ValueGeneratedOnAdd().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + + b.Property(e => e.OnAddOrUpdate).ValueGeneratedOnAddOrUpdate(); + + property = b.Property(e => e.OnAddOrUpdateUseBeforeUseAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.OnAddOrUpdateIgnoreBeforeUseAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.OnAddOrUpdateThrowBeforeUseAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.OnAddOrUpdateUseBeforeIgnoreAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + + property = b.Property(e => e.OnAddOrUpdateIgnoreBeforeIgnoreAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); + property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + + property = b.Property(e => e.OnAddOrUpdateThrowBeforeIgnoreAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + + property = b.Property(e => e.OnAddOrUpdateUseBeforeThrowAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + + property = b.Property(e => e.OnAddOrUpdateIgnoreBeforeThrowAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); + property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + + property = b.Property(e => e.OnAddOrUpdateThrowBeforeThrowAfter).ValueGeneratedOnAddOrUpdate().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + property.SetAfterSaveBehavior(PropertySaveBehavior.Throw); + + b.Property(e => e.OnUpdate).ValueGeneratedOnUpdate(); + + property = b.Property(e => e.OnUpdateUseBeforeUseAfter).ValueGeneratedOnUpdate().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.OnUpdateIgnoreBeforeUseAfter).ValueGeneratedOnUpdate().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.OnUpdateThrowBeforeUseAfter).ValueGeneratedOnUpdate().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); + property.SetAfterSaveBehavior(PropertySaveBehavior.Save); + + property = b.Property(e => e.OnUpdateUseBeforeIgnoreAfter).ValueGeneratedOnUpdate().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Save); + property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + + property = b.Property(e => e.OnUpdateIgnoreBeforeIgnoreAfter).ValueGeneratedOnUpdate().Metadata; + property.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); + property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); + + property = b.Property(e => e.OnUpdateThrowBeforeIgnoreAfter).ValueGeneratedOnUpdate().Metadata; property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw); property.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); @@ -1966,6 +4503,157 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .WithMany(x => x.Periods) .HasForeignKey(x => x.PrincipalId); }); + + modelBuilder.Entity( + entity => + { + entity.Property(e => e.NonKey).HasValueGenerator(); + }); + modelBuilder.Entity( + entity => + { + entity.Property(e => e.NonKey).HasValueGenerator(); + }); + modelBuilder.Entity( + entity => + { + entity.Property(e => e.NonKey).HasValueGenerator(); + }); + + modelBuilder.Entity( + entity => + { + entity.Property(e => e.NonKey).HasValueGenerator(); + }); + modelBuilder.Entity( + entity => + { + entity.Property(e => e.NonKey).HasValueGenerator(); + }); + modelBuilder.Entity( + entity => + { + entity.Property(e => e.NonKey).HasValueGenerator(); + }); + + modelBuilder.Entity( + entity => + { + entity.Property(e => e.NonKey).HasValueGenerator(); + }); + modelBuilder.Entity( + entity => + { + entity.Property(e => e.NonKey).HasValueGenerator(); + }); + modelBuilder.Entity( + entity => + { + entity.Property(e => e.NonKey).HasValueGenerator(); + }); + + modelBuilder.Entity( + entity => + { + entity.Property(e => e.NonKey).HasValueGenerator(); + }); + modelBuilder.Entity( + entity => + { + entity.Property(e => e.NonKey).HasValueGenerator(); + }); + modelBuilder.Entity( + entity => + { + entity.Property(e => e.NonKey).HasValueGenerator(); + }); + + modelBuilder.Entity(); + modelBuilder.Entity(); + + modelBuilder.Entity( + entity => + { + entity.Property(e => e.Id).HasConversion(); + }); + modelBuilder.Entity( + entity => + { + entity.Property(e => e.Id).HasConversion(); + }); + modelBuilder.Entity( + entity => + { + entity.Property(e => e.Id).HasConversion(); + entity.Property(e => e.PrincipalId).HasConversion(); + }); + modelBuilder.Entity( + entity => + { + entity.Property(e => e.Id).HasConversion(); + entity.Property(e => e.PrincipalId).HasConversion(); + }); + + var stringToGuidConverter = new ValueConverter( + v => new Guid(v), + v => v.ToString()); + + modelBuilder.Entity( + entity => + { + entity.Property(e => e.Id).HasConversion(stringToGuidConverter); + }); + modelBuilder.Entity( + entity => + { + entity.Property(e => e.Id).HasConversion(stringToGuidConverter); + }); + modelBuilder.Entity( + entity => + { + entity.Property(e => e.Id).HasConversion(stringToGuidConverter); + entity.Property(e => e.PrincipalId).HasConversion(stringToGuidConverter!); + }); + modelBuilder.Entity( + entity => + { + entity.Property(e => e.Id).HasConversion(stringToGuidConverter); + entity.Property(e => e.PrincipalId).HasConversion(stringToGuidConverter); + }); + } + + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties() + .HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties() + .HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties() + .HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.cs index ea50ad74e94..4df7f749ada 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.cs @@ -1500,7 +1500,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) v => v.Value, v => new NeedsConverter(v), new ValueComparer( - (l, r) => l.Value == r.Value, + (l, r) => (l == null && r == null) || (l != null && r != null && l.Value == r.Value), v => v.Value.GetHashCode(), v => new NeedsConverter(v.Value))) .HasDefaultValue(new NeedsConverter(999)); diff --git a/test/EFCore.SqlServer.FunctionalTests/StoreGeneratedSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/StoreGeneratedSqlServerTest.cs index 10dbbeeef3e..fbfaf41ab6e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/StoreGeneratedSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/StoreGeneratedSqlServerTest.cs @@ -1,11 +1,14 @@ // 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.Schema; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore; +#nullable enable + public class StoreGeneratedSqlServerTest : StoreGeneratedTestBase { public StoreGeneratedSqlServerTest(StoreGeneratedSqlServerFixture fixture) @@ -13,6 +16,498 @@ public StoreGeneratedSqlServerTest(StoreGeneratedSqlServerFixture fixture) { } + protected class WrappedIntHiLoClass + { + public int Value { get; set; } + } + + protected class WrappedIntHiLoClassConverter : ValueConverter + { + public WrappedIntHiLoClassConverter() + : base( + v => v.Value, + v => new WrappedIntHiLoClass { Value = v }) + { + } + } + + protected class WrappedIntHiLoClassComparer : ValueComparer + { + public WrappedIntHiLoClassComparer() + : base( + (v1, v2) => (v1 == null && v2 == null) || (v1 != null && v2 != null && v1.Value.Equals(v2.Value)), + v => v != null ? v.Value : 0, + v => v == null ? null : new() { Value = v.Value }) + { + } + } + + protected class WrappedIntHiLoClassValueGenerator : ValueGenerator + { + public override WrappedIntHiLoClass Next(EntityEntry entry) + => new() { Value = 66 }; + + public override bool GeneratesTemporaryValues + => false; + } + + protected struct WrappedIntHiLoStruct + { + public int Value { get; set; } + } + + protected class WrappedIntHiLoStructConverter : ValueConverter + { + public WrappedIntHiLoStructConverter() + : base( + v => v.Value, + v => new WrappedIntHiLoStruct { Value = v }) + { + } + } + + protected class WrappedIntHiLoStructValueGenerator : ValueGenerator + { + public override WrappedIntHiLoStruct Next(EntityEntry entry) + => new() { Value = 66 }; + + public override bool GeneratesTemporaryValues + => false; + } + + protected record WrappedIntHiLoRecord + { + public int Value { get; set; } + } + + protected class WrappedIntHiLoRecordConverter : ValueConverter + { + public WrappedIntHiLoRecordConverter() + : base( + v => v.Value, + v => new WrappedIntHiLoRecord { Value = v }) + { + } + } + + protected class WrappedIntHiLoRecordValueGenerator : ValueGenerator + { + public override WrappedIntHiLoRecord Next(EntityEntry entry) + => new() { Value = 66 }; + + public override bool GeneratesTemporaryValues + => false; + } + + protected class WrappedIntHiLoKeyClass + { + public int Value { get; set; } + } + + protected class WrappedIntHiLoKeyClassConverter : ValueConverter + { + public WrappedIntHiLoKeyClassConverter() + : base( + v => v.Value, + v => new WrappedIntHiLoKeyClass { Value = v }) + { + } + } + + protected class WrappedIntHiLoKeyClassComparer : ValueComparer + { + public WrappedIntHiLoKeyClassComparer() + : base( + (v1, v2) => (v1 == null && v2 == null) || (v1 != null && v2 != null && v1.Value.Equals(v2.Value)), + v => v != null ? v.Value : 0, + v => v == null ? null : new() { Value = v.Value }) + { + } + } + + protected struct WrappedIntHiLoKeyStruct + { + public int Value { get; set; } + + public override bool Equals(object? obj) + => obj is WrappedIntHiLoKeyStruct other && Value == other.Value; + + public override int GetHashCode() + => Value; + + public static bool operator ==(WrappedIntHiLoKeyStruct left, WrappedIntHiLoKeyStruct right) + => left.Equals(right); + + public static bool operator !=(WrappedIntHiLoKeyStruct left, WrappedIntHiLoKeyStruct right) + => !left.Equals(right); + } + + protected class WrappedIntHiLoKeyStructConverter : ValueConverter + { + public WrappedIntHiLoKeyStructConverter() + : base( + v => v.Value, + v => new WrappedIntHiLoKeyStruct { Value = v }) + { + } + } + + protected record WrappedIntHiLoKeyRecord + { + public int Value { get; set; } + } + + protected class WrappedIntHiLoKeyRecordConverter : ValueConverter + { + public WrappedIntHiLoKeyRecordConverter() + : base( + v => v.Value, + v => new WrappedIntHiLoKeyRecord { Value = v }) + { + } + } + + protected class WrappedIntHiLoClassPrincipal + { + public WrappedIntHiLoKeyClass Id { get; set; } = null!; + public ICollection Dependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + } + + protected class WrappedIntHiLoClassDependentShadow + { + public WrappedIntHiLoClass Id { get; set; } = null!; + public WrappedIntHiLoClassPrincipal? Principal { get; set; } + } + + protected class WrappedIntHiLoClassDependentRequired + { + public WrappedIntHiLoClass Id { get; set; } = null!; + public WrappedIntHiLoKeyClass PrincipalId { get; set; } = null!; + public WrappedIntHiLoClassPrincipal Principal { get; set; } = null!; + } + + protected class WrappedIntHiLoClassDependentOptional + { + public WrappedIntHiLoClass Id { get; set; } = null!; + public WrappedIntHiLoKeyClass? PrincipalId { get; set; } + public WrappedIntHiLoClassPrincipal? Principal { get; set; } + } + + protected class WrappedIntHiLoStructPrincipal + { + public WrappedIntHiLoKeyStruct Id { get; set; } + public ICollection Dependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + } + + protected class WrappedIntHiLoStructDependentShadow + { + public WrappedIntHiLoStruct Id { get; set; } + public WrappedIntHiLoStructPrincipal? Principal { get; set; } + } + + protected class WrappedIntHiLoStructDependentOptional + { + public WrappedIntHiLoStruct Id { get; set; } + public WrappedIntHiLoKeyStruct? PrincipalId { get; set; } + public WrappedIntHiLoStructPrincipal? Principal { get; set; } + } + + protected class WrappedIntHiLoStructDependentRequired + { + public WrappedIntHiLoStruct Id { get; set; } + public WrappedIntHiLoKeyStruct PrincipalId { get; set; } + public WrappedIntHiLoStructPrincipal Principal { get; set; } = null!; + } + + protected class WrappedIntHiLoRecordPrincipal + { + public WrappedIntHiLoKeyRecord Id { get; set; } = null!; + public ICollection Dependents { get; } = new List(); + public ICollection OptionalDependents { get; } = new List(); + public ICollection RequiredDependents { get; } = new List(); + } + + protected class WrappedIntHiLoRecordDependentShadow + { + public WrappedIntHiLoRecord Id { get; set; } = null!; + public WrappedIntHiLoRecordPrincipal? Principal { get; set; } + } + + protected class WrappedIntHiLoRecordDependentOptional + { + public WrappedIntHiLoRecord Id { get; set; } = null!; + public WrappedIntHiLoKeyRecord? PrincipalId { get; set; } + public WrappedIntHiLoRecordPrincipal? Principal { get; set; } + } + + protected class WrappedIntHiLoRecordDependentRequired + { + public WrappedIntHiLoRecord Id { get; set; } = null!; + public WrappedIntHiLoKeyRecord PrincipalId { get; set; } = null!; + public WrappedIntHiLoRecordPrincipal Principal { get; set; } = null!; + } + + [ConditionalFact] + public virtual void Insert_update_and_delete_with_wrapped_int_key_using_hi_lo() + { + var id1 = 0; + var id2 = 0; + var id3 = 0; + ExecuteWithStrategyInTransaction( + context => + { + var principal1 = context.Add( + new WrappedIntHiLoClassPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; + + var principal2 = context.Add( + new WrappedIntHiLoStructPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; + + var principal3 = context.Add( + new WrappedIntHiLoRecordPrincipal + { + Dependents = { new(), new() }, + OptionalDependents = { new(), new() }, + RequiredDependents = { new(), new() } + }).Entity; + + context.SaveChanges(); + + id1 = principal1.Id.Value; + Assert.NotEqual(0, id1); + foreach (var dependent in principal1.Dependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal1.OptionalDependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal1.RequiredDependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId.Value); + } + + id2 = principal2.Id.Value; + Assert.NotEqual(0, id2); + foreach (var dependent in principal2.Dependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value.Value); + } + foreach (var dependent in principal2.OptionalDependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId!.Value.Value); + } + foreach (var dependent in principal2.RequiredDependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId.Value); + } + + id3 = principal3.Id.Value; + Assert.NotEqual(0, id3); + foreach (var dependent in principal3.Dependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal3.OptionalDependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal3.RequiredDependents) + { + Assert.NotEqual(0, dependent.Id.Value); + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId.Value); + } + }, + context => + { + var principal1 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal1.Id.Value, id1); + foreach (var dependent in principal1.Dependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal1.OptionalDependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal1.RequiredDependents) + { + Assert.Same(principal1, dependent.Principal); + Assert.Equal(id1, dependent.PrincipalId.Value); + } + + var principal2 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal2.Id.Value, id2); + foreach (var dependent in principal2.Dependents) + { + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value.Value); + } + foreach (var dependent in principal2.OptionalDependents) + { + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId!.Value.Value); + } + foreach (var dependent in principal2.RequiredDependents) + { + Assert.Same(principal2, dependent.Principal); + Assert.Equal(id2, dependent.PrincipalId.Value); + } + + var principal3 = context.Set() + .Include(e => e.Dependents) + .Include(e => e.OptionalDependents) + .Include(e => e.RequiredDependents) + .Single(); + + Assert.Equal(principal3.Id.Value, id3); + foreach (var dependent in principal3.Dependents) + { + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, context.Entry(dependent).Property("PrincipalId").CurrentValue!.Value); + } + foreach (var dependent in principal3.OptionalDependents) + { + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId!.Value); + } + foreach (var dependent in principal3.RequiredDependents) + { + Assert.Same(principal3, dependent.Principal); + Assert.Equal(id3, dependent.PrincipalId.Value); + } + + principal1.Dependents.Remove(principal1.Dependents.First()); + principal2.Dependents.Remove(principal2.Dependents.First()); + principal3.Dependents.Remove(principal3.Dependents.First()); + + principal1.OptionalDependents.Remove(principal1.OptionalDependents.First()); + principal2.OptionalDependents.Remove(principal2.OptionalDependents.First()); + principal3.OptionalDependents.Remove(principal3.OptionalDependents.First()); + + principal1.RequiredDependents.Remove(principal1.RequiredDependents.First()); + principal2.RequiredDependents.Remove(principal2.RequiredDependents.First()); + principal3.RequiredDependents.Remove(principal3.RequiredDependents.First()); + + context.SaveChanges(); + }, + context => + { + var dependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents1.Count); + Assert.Null( + context.Entry(dependents1.Single(e => e.Principal == null)) + .Property("PrincipalId").CurrentValue); + + var optionalDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents1.Count); + Assert.Null(optionalDependents1.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents1 = context.Set().Include(e => e.Principal).ToList(); + Assert.Single(requiredDependents1); + + var dependents2 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents2.Count); + Assert.Null( + context.Entry(dependents2.Single(e => e.Principal == null)) + .Property("PrincipalId").CurrentValue); + + var optionalDependents2 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents2.Count); + Assert.Null(optionalDependents2.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents2 = context.Set().Include(e => e.Principal).ToList(); + Assert.Single(requiredDependents2); + + var dependents3 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, dependents3.Count); + Assert.Null( + context.Entry(dependents3.Single(e => e.Principal == null)) + .Property("PrincipalId").CurrentValue); + + var optionalDependents3 = context.Set().Include(e => e.Principal).ToList(); + Assert.Equal(2, optionalDependents3.Count); + Assert.Null(optionalDependents3.Single(e => e.Principal == null).PrincipalId); + + var requiredDependents3 = context.Set().Include(e => e.Principal).ToList(); + Assert.Single(requiredDependents3); + + context.Remove(dependents1.Single(e => e.Principal != null)); + context.Remove(optionalDependents1.Single(e => e.Principal != null)); + context.Remove(requiredDependents1.Single()); + context.Remove(requiredDependents1.Single().Principal); + + context.Remove(dependents2.Single(e => e.Principal != null)); + context.Remove(optionalDependents2.Single(e => e.Principal != null)); + context.Remove(requiredDependents2.Single()); + context.Remove(requiredDependents2.Single().Principal); + + context.Remove(dependents3.Single(e => e.Principal != null)); + context.Remove(optionalDependents3.Single(e => e.Principal != null)); + context.Remove(requiredDependents3.Single()); + context.Remove(requiredDependents3.Single().Principal); + + context.SaveChanges(); + }, + context => + { + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + Assert.Equal(1, context.Set().Count()); + + Assert.Equal(0, context.Set().Count()); + Assert.Equal(0, context.Set().Count()); + Assert.Equal(0, context.Set().Count()); + }); + } + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); @@ -70,7 +565,7 @@ public virtual void Exception_in_SaveChanges_causes_store_values_to_be_reverted( e => e); var stateManager = context.GetService(); - var key = context.Model.FindEntityType(typeof(Darwin)).FindPrimaryKey(); + var key = context.Model.FindEntityType(typeof(Darwin))!.FindPrimaryKey()!; foreach (var entity in entities) { @@ -78,7 +573,7 @@ public virtual void Exception_in_SaveChanges_causes_store_values_to_be_reverted( entity, stateManager.TryGetEntry( key, - new object[] { context.Entry(entity).Property(p => p.Id).CurrentValue }).Entity); + new object[] { context.Entry(entity).Property(p => p.Id).CurrentValue })!.Entity); } // DbUpdateException : An error occurred while updating the entries. See the @@ -92,7 +587,7 @@ public virtual void Exception_in_SaveChanges_causes_store_values_to_be_reverted( { Assert.Equal(0, entity.Id); Assert.Null(entity._id); - Assert.Null(entity.Species.DarwinId); + Assert.Null(entity.Species!.DarwinId); foreach (var species in entity.MixedMetaphors) { Assert.Null(species.MetaphoricId); @@ -100,7 +595,7 @@ public virtual void Exception_in_SaveChanges_causes_store_values_to_be_reverted( } Assert.Equal(1777, entities[100].Id); - Assert.Equal(1777, entities[100].Species.DarwinId); + Assert.Equal(1777, entities[100].Species!.DarwinId); foreach (var species in entities[100].MixedMetaphors) { Assert.Equal(1777, species.MetaphoricId); @@ -119,7 +614,7 @@ public virtual void Exception_in_SaveChanges_causes_store_values_to_be_reverted( entity, stateManager.TryGetEntry( key, - new object[] { context.Entry(entity).Property(p => p.Id).CurrentValue }).Entity); + new object[] { context.Entry(entity).Property(p => p.Id).CurrentValue })!.Entity); } }); } @@ -224,7 +719,32 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.Entity().Property(e => e.Id).UseIdentityColumn(); + modelBuilder.Entity().Property(e => e.Id).UseHiLo(); + modelBuilder.Entity().Property(e => e.Id).UseHiLo(); + modelBuilder.Entity().Property(e => e.Id).UseHiLo(); + modelBuilder.Entity().Property(e => e.Id).UseHiLo(); + modelBuilder.Entity().Property(e => e.Id).UseHiLo(); + modelBuilder.Entity().Property(e => e.Id).UseHiLo(); + modelBuilder.Entity().Property(e => e.Id).UseHiLo(); + modelBuilder.Entity().Property(e => e.Id).UseHiLo(); + modelBuilder.Entity().Property(e => e.Id).UseHiLo(); + modelBuilder.Entity().Property(e => e.Id).UseHiLo(); + modelBuilder.Entity().Property(e => e.Id).UseHiLo(); + modelBuilder.Entity().Property(e => e.Id).UseHiLo(); + base.OnModelCreating(modelBuilder, context); } + + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + base.ConfigureConventions(configurationBuilder); + + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + } } } diff --git a/test/EFCore.Sqlite.FunctionalTests/SqliteValueGenerationScenariosTest.cs b/test/EFCore.Sqlite.FunctionalTests/SqliteValueGenerationScenariosTest.cs index 62c6971e932..97efb0b4c23 100644 --- a/test/EFCore.Sqlite.FunctionalTests/SqliteValueGenerationScenariosTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/SqliteValueGenerationScenariosTest.cs @@ -715,7 +715,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) v => v.Value, v => new NeedsConverter(v), new ValueComparer( - (l, r) => l.Value == r.Value, + (l, r) => (l == null && r == null) || (l != null && r != null && l.Value == r.Value), v => v.Value.GetHashCode(), v => new NeedsConverter(v.Value))) .HasDefaultValue(new NeedsConverter(999)); diff --git a/test/EFCore.Tests/ValueGeneration/TemporaryNumberValueGeneratorFactoryTest.cs b/test/EFCore.Tests/ValueGeneration/TemporaryNumberValueGeneratorFactoryTest.cs index 2cc0fada708..ed1d7f4d1f2 100644 --- a/test/EFCore.Tests/ValueGeneration/TemporaryNumberValueGeneratorFactoryTest.cs +++ b/test/EFCore.Tests/ValueGeneration/TemporaryNumberValueGeneratorFactoryTest.cs @@ -11,6 +11,7 @@ public static IModel BuildModel() { var builder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); builder.Entity(); + builder.FinalizeModel(); return (IModel)builder.Model; } From 8e5dd44c0ca778b1cd97b8a407271f26eacd5ab1 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Thu, 7 Apr 2022 16:28:46 +0000 Subject: [PATCH 072/143] Update dependencies from https://github.com/dotnet/arcade build 20220406.7 (#27786) [release/6.0] Update dependencies from dotnet/arcade --- eng/Version.Details.xml | 8 ++++---- global.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 0e919b50495..23f63508f4b 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -47,13 +47,13 @@ - + https://github.com/dotnet/arcade - f8c0d51185208227e582f76ac3c5003db237b689 + 254113fd7c3ee04f832c165d6aec1a6077de0d63 - + https://github.com/dotnet/arcade - f8c0d51185208227e582f76ac3c5003db237b689 + 254113fd7c3ee04f832c165d6aec1a6077de0d63 diff --git a/global.json b/global.json index 21acb34daea..2c2c613ec63 100644 --- a/global.json +++ b/global.json @@ -18,7 +18,7 @@ "rollForward": "latestMajor" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.22178.5", - "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.22178.5" + "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.22206.7", + "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.22206.7" } } From d87a65a9f5cddd8dcfa4668226e923aaa151628d Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 13:14:48 +0000 Subject: [PATCH 073/143] Update dependencies from https://github.com/dotnet/runtime build 20220408.8 (#27799) [main] Update dependencies from dotnet/runtime --- eng/Version.Details.xml | 8 ++++---- eng/Versions.props | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 2372357fb3f..82237200561 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -22,17 +22,17 @@ https://github.com/dotnet/runtime e24f66dff0770eee344038da8c12476d8c450c41 - + https://github.com/dotnet/runtime - c3e44952bb75d054517f2a24943502d3a14a47f5 + b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 https://github.com/dotnet/runtime e24f66dff0770eee344038da8c12476d8c450c41 - + https://github.com/dotnet/runtime - c3e44952bb75d054517f2a24943502d3a14a47f5 + b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 https://github.com/dotnet/runtime diff --git a/eng/Versions.props b/eng/Versions.props index 85ea741a395..4ec602b6604 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -20,9 +20,9 @@ 7.0.0-preview.2.22152.2 7.0.0-preview.2.22152.2 7.0.0-preview.2.22152.2 - 7.0.0-preview.4.22201.3 + 7.0.0-preview.4.22208.8 7.0.0-preview.2.22152.2 - 7.0.0-preview.4.22201.3 + 7.0.0-preview.4.22208.8 7.0.0-preview.2.22152.2 From 13c36888b600781e5bfcc66f52f4612f6fe0b195 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 13:25:27 +0000 Subject: [PATCH 074/143] Update dependencies from https://github.com/dotnet/arcade build 20220406.10 (#27800) [main] Update dependencies from dotnet/arcade --- eng/Version.Details.xml | 8 +- eng/common/init-tools-native.ps1 | 137 ++++++++++++------ .../templates/job/publish-build-assets.yml | 50 +++++-- eng/common/templates/jobs/jobs.yml | 11 +- .../templates/post-build/post-build.yml | 76 +++++----- global.json | 4 +- 6 files changed, 188 insertions(+), 98 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 82237200561..b21f90a5229 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -40,13 +40,13 @@ - + https://github.com/dotnet/arcade - e0b311bcd81fc9e27bcf7715dcda62fa38dfa49a + 549523c3fc8929da1a3073d1a97f298e0d1dc342 - + https://github.com/dotnet/arcade - e0b311bcd81fc9e27bcf7715dcda62fa38dfa49a + 549523c3fc8929da1a3073d1a97f298e0d1dc342 diff --git a/eng/common/init-tools-native.ps1 b/eng/common/init-tools-native.ps1 index db830c00a6f..413adea4365 100644 --- a/eng/common/init-tools-native.ps1 +++ b/eng/common/init-tools-native.ps1 @@ -31,6 +31,10 @@ Wait time between retry attempts in seconds .PARAMETER GlobalJsonFile File path to global.json file +.PARAMETER PathPromotion +Optional switch to enable either promote native tools specified in the global.json to the path (in Azure Pipelines) +or break the build if a native tool is not found on the path (on a local dev machine) + .NOTES #> [CmdletBinding(PositionalBinding=$false)] @@ -41,7 +45,8 @@ Param ( [switch] $Force = $False, [int] $DownloadRetries = 5, [int] $RetryWaitTimeInSeconds = 30, - [string] $GlobalJsonFile + [string] $GlobalJsonFile, + [switch] $PathPromotion ) if (!$GlobalJsonFile) { @@ -77,53 +82,97 @@ try { ConvertFrom-Json | Select-Object -Expand 'native-tools' -ErrorAction SilentlyContinue if ($NativeTools) { - $NativeTools.PSObject.Properties | ForEach-Object { - $ToolName = $_.Name - $ToolVersion = $_.Value - $LocalInstallerArguments = @{ ToolName = "$ToolName" } - $LocalInstallerArguments += @{ InstallPath = "$InstallBin" } - $LocalInstallerArguments += @{ BaseUri = "$BaseUri" } - $LocalInstallerArguments += @{ CommonLibraryDirectory = "$EngCommonBaseDir" } - $LocalInstallerArguments += @{ Version = "$ToolVersion" } - - if ($Verbose) { - $LocalInstallerArguments += @{ Verbose = $True } - } - if (Get-Variable 'Force' -ErrorAction 'SilentlyContinue') { - if($Force) { - $LocalInstallerArguments += @{ Force = $True } - } - } - if ($Clean) { - $LocalInstallerArguments += @{ Clean = $True } - } - - Write-Verbose "Installing $ToolName version $ToolVersion" - Write-Verbose "Executing '$InstallerPath $($LocalInstallerArguments.Keys.ForEach({"-$_ '$($LocalInstallerArguments.$_)'"}) -join ' ')'" - & $InstallerPath @LocalInstallerArguments - if ($LASTEXITCODE -Ne "0") { - $errMsg = "$ToolName installation failed" - if ((Get-Variable 'DoNotAbortNativeToolsInstallationOnFailure' -ErrorAction 'SilentlyContinue') -and $DoNotAbortNativeToolsInstallationOnFailure) { - $showNativeToolsWarning = $true - if ((Get-Variable 'DoNotDisplayNativeToolsInstallationWarnings' -ErrorAction 'SilentlyContinue') -and $DoNotDisplayNativeToolsInstallationWarnings) { - $showNativeToolsWarning = $false + if ($PathPromotion -eq $True) { + if ($env:SYSTEM_TEAMPROJECT) { # check to see if we're in an Azure pipelines build + $NativeTools.PSObject.Properties | ForEach-Object { + $ToolName = $_.Name + $ToolVersion = $_.Value + + if ((Get-Command "$ToolName" -ErrorAction SilentlyContinue) -eq $null) { + if ($ToolVersion -eq "latest") { + $ToolVersion = "" + } + $ArcadeToolsDirectory = "C:\arcade-tools" + if (Test-Path $ArcadeToolsDirectory -eq $False) { + Write-Error "Arcade tools directory '$ArcadeToolsDirectory' was not found; artifacts were not properly installed." + exit 1 } - if ($showNativeToolsWarning) { - Write-Warning $errMsg + $ToolDirectory = (Get-ChildItem -Path "$ArcadeToolsDirectory" -Filter "$ToolName-$ToolVersion*" | Sort-Object -Descending)[0] + if ([string]::IsNullOrWhiteSpace($ToolDirectory)) { + Write-Error "Unable to find directory for $ToolName $ToolVersion; please make sure the tool is installed on this image." + exit 1 } - $toolInstallationFailure = $true - } else { - # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 - Write-Host $errMsg - exit 1 + $BinPathFile = "$($ToolDirectory.FullName)\binpath.txt" + if (Test-Path -Path "$BinPathFile" -eq $False) { + Write-Error "Unable to find binpath.txt in '$($ToolDirectory.FullName)' ($ToolName $ToolVersion); artifact is either installed incorrectly or is not a bootstrappable tool." + exit 1 + } + $BinPath = Get-Content "$BinPathFile" + Write-Host "Adding $ToolName to the path ($(Convert-Path -Path $BinPath))..." + Write-Host "##vso[task.prependpath]$(Convert-Path -Path $BinPath)" + } + } + exit 0 + } else { + $NativeTools.PSObject.Properties | ForEach-Object { + $ToolName = $_.Name + $ToolVersion = $_.Value + + if ((Get-Command "$ToolName" -ErrorAction SilentlyContinue) -eq $null) { + Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message "$ToolName not found on path. Please install $ToolName $ToolVersion before proceeding." + } } + exit 0 + } + } else { + $NativeTools.PSObject.Properties | ForEach-Object { + $ToolName = $_.Name + $ToolVersion = $_.Value + $LocalInstallerArguments = @{ ToolName = "$ToolName" } + $LocalInstallerArguments += @{ InstallPath = "$InstallBin" } + $LocalInstallerArguments += @{ BaseUri = "$BaseUri" } + $LocalInstallerArguments += @{ CommonLibraryDirectory = "$EngCommonBaseDir" } + $LocalInstallerArguments += @{ Version = "$ToolVersion" } + + if ($Verbose) { + $LocalInstallerArguments += @{ Verbose = $True } + } + if (Get-Variable 'Force' -ErrorAction 'SilentlyContinue') { + if($Force) { + $LocalInstallerArguments += @{ Force = $True } + } + } + if ($Clean) { + $LocalInstallerArguments += @{ Clean = $True } + } + + Write-Verbose "Installing $ToolName version $ToolVersion" + Write-Verbose "Executing '$InstallerPath $($LocalInstallerArguments.Keys.ForEach({"-$_ '$($LocalInstallerArguments.$_)'"}) -join ' ')'" + & $InstallerPath @LocalInstallerArguments + if ($LASTEXITCODE -Ne "0") { + $errMsg = "$ToolName installation failed" + if ((Get-Variable 'DoNotAbortNativeToolsInstallationOnFailure' -ErrorAction 'SilentlyContinue') -and $DoNotAbortNativeToolsInstallationOnFailure) { + $showNativeToolsWarning = $true + if ((Get-Variable 'DoNotDisplayNativeToolsInstallationWarnings' -ErrorAction 'SilentlyContinue') -and $DoNotDisplayNativeToolsInstallationWarnings) { + $showNativeToolsWarning = $false + } + if ($showNativeToolsWarning) { + Write-Warning $errMsg + } + $toolInstallationFailure = $true + } else { + # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 + Write-Host $errMsg + exit 1 + } + } + } + + if ((Get-Variable 'toolInstallationFailure' -ErrorAction 'SilentlyContinue') -and $toolInstallationFailure) { + # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 + Write-Host 'Native tools bootstrap failed' + exit 1 } - } - - if ((Get-Variable 'toolInstallationFailure' -ErrorAction 'SilentlyContinue') -and $toolInstallationFailure) { - # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 - Write-Host 'Native tools bootstrap failed' - exit 1 } } else { diff --git a/eng/common/templates/job/publish-build-assets.yml b/eng/common/templates/job/publish-build-assets.yml index d91bf914711..1cbb6a0c560 100644 --- a/eng/common/templates/job/publish-build-assets.yml +++ b/eng/common/templates/job/publish-build-assets.yml @@ -23,23 +23,33 @@ parameters: # 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 }} - displayName: Publish to Build Asset Registry + ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + displayName: Publish Assets + ${{ else }}: + displayName: Publish to Build Asset Registry pool: ${{ parameters.pool }} variables: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - name: _BuildConfig - value: ${{ parameters.configuration }} - group: Publish-Build-Assets - group: AzureDevOps-Artifact-Feeds-Pats - name: runCodesignValidationInjection value: false + - ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + - template: /eng/common/templates/post-build/common-variables.yml steps: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: @@ -52,14 +62,13 @@ jobs: condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - task: NuGetAuthenticate@0 + - task: NuGetAuthenticate@0 - - task: PowerShell@2 - displayName: Enable cross-org NuGet feed authentication - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/enable-cross-org-publishing.ps1 - arguments: -token $(dn-bot-all-orgs-artifact-feeds-rw) + - task: PowerShell@2 + displayName: Enable cross-org NuGet feed authentication + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/enable-cross-org-publishing.ps1 + arguments: -token $(dn-bot-all-orgs-artifact-feeds-rw) - task: PowerShell@2 displayName: Publish Build Assets @@ -70,7 +79,6 @@ jobs: /p:BuildAssetRegistryToken=$(MaestroAccessToken) /p:MaestroApiEndpoint=https://maestro-prod.westus2.cloudapp.azure.com /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} - /p:Configuration=$(_BuildConfig) /p:OfficialBuildId=$(Build.BuildNumber) condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} @@ -114,7 +122,25 @@ jobs: PathtoPublish: '$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt' PublishLocation: Container ArtifactName: ReleaseConfigs - + + - ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + - template: /eng/common/templates/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/steps/publish-logs.yml parameters: diff --git a/eng/common/templates/jobs/jobs.yml b/eng/common/templates/jobs/jobs.yml index 554e71cfc43..2cca53c2d1d 100644 --- a/eng/common/templates/jobs/jobs.yml +++ b/eng/common/templates/jobs/jobs.yml @@ -27,6 +27,13 @@ parameters: # 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 @@ -68,7 +75,6 @@ jobs: ${{ 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: @@ -94,4 +100,7 @@ jobs: runAsPublic: ${{ parameters.runAsPublic }} publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} + publishAssetsImmediately: ${{ parameters.publishAssetsImmediately }} enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + signingValidationAdditionalParameters: ${{ parameters.signingValidationAdditionalParameters }} diff --git a/eng/common/templates/post-build/post-build.yml b/eng/common/templates/post-build/post-build.yml index 2f176571f02..5a9056f6b2f 100644 --- a/eng/common/templates/post-build/post-build.yml +++ b/eng/common/templates/post-build/post-build.yml @@ -82,6 +82,11 @@ parameters: 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 @@ -99,7 +104,7 @@ stages: name: VSEngSS-MicroBuild2022-1ES demands: Cmd # If it's not devdiv, it's dnceng - ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + ${{ else }}: name: NetCore1ESPool-Internal demands: ImageOverride -equals Build.Server.Amd64.VS2019 @@ -136,7 +141,7 @@ stages: name: VSEngSS-MicroBuild2022-1ES demands: Cmd # If it's not devdiv, it's dnceng - ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + ${{ else }}: name: NetCore1ESPool-Internal demands: ImageOverride -equals Build.Server.Amd64.VS2019 steps: @@ -196,7 +201,7 @@ stages: name: VSEngSS-MicroBuild2022-1ES demands: Cmd # If it's not devdiv, it's dnceng - ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + ${{ else }}: name: NetCore1ESPool-Internal demands: ImageOverride -equals Build.Server.Amd64.VS2019 steps: @@ -235,43 +240,44 @@ stages: artifactNames: ${{ parameters.SDLValidationParameters.artifactNames }} downloadArtifacts: ${{ parameters.SDLValidationParameters.downloadArtifacts }} -- 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 }} - ${{ if and(ne(parameters.enableNugetValidation, 'true'), ne(parameters.enableSigningValidation, 'true'), ne(parameters.enableSourceLinkValidation, 'true'), ne(parameters.SDLValidationParameters.enable, 'true')) }}: - dependsOn: ${{ parameters.validateDependsOn }} - displayName: Publish using Darc - variables: - - template: common-variables.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 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 + 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: VSEngSS-MicroBuild2022-1ES demands: Cmd # If it's not devdiv, it's dnceng - ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + ${{ else }}: name: NetCore1ESPool-Internal demands: ImageOverride -equals Build.Server.Amd64.VS2019 - steps: - - template: setup-maestro-vars.yml - parameters: - BARBuildId: ${{ parameters.BARBuildId }} - PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} - - task: NuGetAuthenticate@0 + - task: NuGetAuthenticate@0 - - 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 }}' \ No newline at end of file + - 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 }}' \ No newline at end of file diff --git a/global.json b/global.json index 679c061a34a..14a653d6598 100644 --- a/global.json +++ b/global.json @@ -13,7 +13,7 @@ "rollForward": "latestMajor" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22181.2", - "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22181.2" + "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22206.10", + "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22206.10" } } From bfeaed151f127f46ee2aedf7e5f28c957f893ca5 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Tue, 12 Apr 2022 12:07:48 -0700 Subject: [PATCH 075/143] Update SDK --- global.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/global.json b/global.json index bdf9083e426..995ba152300 100644 --- a/global.json +++ b/global.json @@ -1,17 +1,17 @@ { "tools": { - "dotnet": "5.0.406", + "dotnet": "5.0.407", "runtimes": { "dotnet": [ - "3.1.23" + "3.1.24" ], "aspnetcore": [ - "3.1.23" + "3.1.24" ] } }, "sdk": { - "version": "5.0.406", + "version": "5.0.407", "allowPrerelease": true, "rollForward": "latestMajor" }, From e85817998f7bb223f31a83b23ab910a9aa9220c1 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Tue, 12 Apr 2022 12:08:50 -0700 Subject: [PATCH 076/143] Update SDK --- global.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/global.json b/global.json index 7793fd043a8..886c2f7081d 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "tools": { - "dotnet": "3.1.417", + "dotnet": "3.1.418", "runtimes": { "dotnet": [ "2.1.30", @@ -9,7 +9,7 @@ } }, "sdk": { - "version": "3.1.417" + "version": "3.1.418" }, "msbuild-sdks": { "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.22159.6" From cf6945c7e4337e7cb36505faa8d3f62e1a9f327f Mon Sep 17 00:00:00 2001 From: DotNet-Bot Date: Tue, 12 Apr 2022 20:54:39 +0000 Subject: [PATCH 077/143] Update dependencies from https://dev.azure.com/dnceng/internal/_git/dotnet-extensions build 20220412.1 Microsoft.JSInterop , Microsoft.Extensions.ValueStopwatch.Sources , Microsoft.Extensions.FileProviders.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks , Microsoft.Extensions.DiagnosticAdapter , Microsoft.Extensions.DependencyInjection.Abstractions , Microsoft.Extensions.DependencyInjection , Microsoft.Extensions.Configuration.Xml , Microsoft.Extensions.Configuration.UserSecrets , Microsoft.Extensions.Configuration.KeyPerFile , Microsoft.Extensions.Configuration.Json , Microsoft.Extensions.Configuration.Ini , Microsoft.Extensions.Configuration.FileExtensions , Microsoft.Extensions.Configuration.EnvironmentVariables , Microsoft.Extensions.Configuration.CommandLine , Microsoft.Extensions.Configuration.Binder , Internal.AspNetCore.Analyzers , Microsoft.AspNetCore.Analyzer.Testing , Microsoft.AspNetCore.BenchmarkRunner.Sources , Microsoft.AspNetCore.Testing , Microsoft.Extensions.ActivatorUtilities.Sources , Microsoft.Extensions.FileProviders.Composite , Microsoft.Extensions.Caching.Abstractions , Microsoft.Extensions.Caching.SqlServer , Microsoft.Extensions.Caching.StackExchangeRedis , Microsoft.Extensions.CommandLineUtils.Sources , Microsoft.Extensions.Configuration , Microsoft.Extensions.Configuration.Abstractions , Microsoft.Extensions.Configuration.AzureKeyVault , Microsoft.Extensions.Caching.Memory , Microsoft.Extensions.WebEncoders , Microsoft.Extensions.FileProviders.Embedded , Microsoft.Extensions.FileSystemGlobbing , Microsoft.Extensions.TypeNameHelper.Sources , Microsoft.Extensions.Primitives , Microsoft.Extensions.ParameterDefaultValue.Sources , Microsoft.Extensions.Options.DataAnnotations , Microsoft.Extensions.Options.ConfigurationExtensions , Microsoft.Extensions.Options , Microsoft.Extensions.ObjectPool , Microsoft.Extensions.Logging.TraceSource , Microsoft.Extensions.Logging.Testing , Microsoft.Extensions.Logging.EventSource , Microsoft.Extensions.Logging.EventLog , Microsoft.Extensions.FileProviders.Physical , Microsoft.Extensions.Logging.Console , Microsoft.Extensions.Logging.Configuration , Microsoft.Extensions.Logging.Debug , Microsoft.Extensions.HashCodeCombiner.Sources , Microsoft.Extensions.HostFactoryResolver.Sources , Microsoft.Extensions.Hosting , Microsoft.Extensions.Hosting.Abstractions , Microsoft.Extensions.Logging.AzureAppServices , Microsoft.Extensions.Http , Microsoft.Extensions.Localization , Microsoft.Extensions.Localization.Abstractions , Microsoft.Extensions.Logging , Microsoft.Extensions.Logging.Abstractions From Version 3.1.25 -> To Version 3.1.25 --- NuGet.config | 5 +- eng/Version.Details.xml | 144 ++++++++++++++++++++-------------------- eng/Versions.props | 2 +- 3 files changed, 76 insertions(+), 75 deletions(-) diff --git a/NuGet.config b/NuGet.config index 76288f71c0a..963e07e91cc 100644 --- a/NuGet.config +++ b/NuGet.config @@ -9,7 +9,7 @@ - + @@ -24,13 +24,14 @@ - + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index dcee7e25855..af80f04614c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,21 +1,21 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx @@ -33,85 +33,85 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup 3acd9b0cd16596bad450c82be08780875a73c05c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 @@ -124,143 +124,143 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 5161783136fc968ceda3aba3ea07232b00deaea2 + c246118a78fa6a0b2935eaf684642aea7f4da0e9 https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup diff --git a/eng/Versions.props b/eng/Versions.props index 0d2c56c5150..6359e73f894 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -37,7 +37,7 @@ 3.1.25 3.1.25 3.1.25 - 3.1.25-servicing.22206.2 + 3.1.25-servicing.22212.1 3.1.25 From d9a2ddb5e3413c50e16a9656d7a108760f52d307 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 13 Apr 2022 16:13:34 +0000 Subject: [PATCH 078/143] Update dependencies from https://github.com/dotnet/arcade build 20220412.5 (#27819) [release/6.0] Update dependencies from dotnet/arcade --- eng/Version.Details.xml | 8 ++++---- global.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 23f63508f4b..19880d25e7b 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -47,13 +47,13 @@ - + https://github.com/dotnet/arcade - 254113fd7c3ee04f832c165d6aec1a6077de0d63 + 1a6b24397e50146d0fece9cfb9c0b87275691e6f - + https://github.com/dotnet/arcade - 254113fd7c3ee04f832c165d6aec1a6077de0d63 + 1a6b24397e50146d0fece9cfb9c0b87275691e6f diff --git a/global.json b/global.json index 2c2c613ec63..28b8ecb223c 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "tools": { - "dotnet": "6.0.103", + "dotnet": "6.0.104", "runtimes": { "dotnet": [ "3.1.23", @@ -13,12 +13,12 @@ } }, "sdk": { - "version": "6.0.103", + "version": "6.0.104", "allowPrerelease": true, "rollForward": "latestMajor" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.22206.7", - "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.22206.7" + "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.22212.5", + "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.22212.5" } } From 863382e245891cdd6ceef7dd5f232b2faad78ef6 Mon Sep 17 00:00:00 2001 From: DotNet-Bot Date: Wed, 13 Apr 2022 19:00:35 +0000 Subject: [PATCH 079/143] Update dependencies from https://dev.azure.com/dnceng/internal/_git/dotnet-extensions build 20220413.2 Microsoft.JSInterop , Microsoft.Extensions.ValueStopwatch.Sources , Microsoft.Extensions.FileProviders.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks , Microsoft.Extensions.DiagnosticAdapter , Microsoft.Extensions.DependencyInjection.Abstractions , Microsoft.Extensions.DependencyInjection , Microsoft.Extensions.Configuration.Xml , Microsoft.Extensions.Configuration.UserSecrets , Microsoft.Extensions.Configuration.KeyPerFile , Microsoft.Extensions.Configuration.Json , Microsoft.Extensions.Configuration.Ini , Microsoft.Extensions.Configuration.FileExtensions , Microsoft.Extensions.Configuration.EnvironmentVariables , Microsoft.Extensions.Configuration.CommandLine , Microsoft.Extensions.Configuration.Binder , Internal.AspNetCore.Analyzers , Microsoft.AspNetCore.Analyzer.Testing , Microsoft.AspNetCore.BenchmarkRunner.Sources , Microsoft.AspNetCore.Testing , Microsoft.Extensions.ActivatorUtilities.Sources , Microsoft.Extensions.FileProviders.Composite , Microsoft.Extensions.Caching.Abstractions , Microsoft.Extensions.Caching.SqlServer , Microsoft.Extensions.Caching.StackExchangeRedis , Microsoft.Extensions.CommandLineUtils.Sources , Microsoft.Extensions.Configuration , Microsoft.Extensions.Configuration.Abstractions , Microsoft.Extensions.Configuration.AzureKeyVault , Microsoft.Extensions.Caching.Memory , Microsoft.Extensions.WebEncoders , Microsoft.Extensions.FileProviders.Embedded , Microsoft.Extensions.FileSystemGlobbing , Microsoft.Extensions.TypeNameHelper.Sources , Microsoft.Extensions.Primitives , Microsoft.Extensions.ParameterDefaultValue.Sources , Microsoft.Extensions.Options.DataAnnotations , Microsoft.Extensions.Options.ConfigurationExtensions , Microsoft.Extensions.Options , Microsoft.Extensions.ObjectPool , Microsoft.Extensions.Logging.TraceSource , Microsoft.Extensions.Logging.Testing , Microsoft.Extensions.Logging.EventSource , Microsoft.Extensions.Logging.EventLog , Microsoft.Extensions.FileProviders.Physical , Microsoft.Extensions.Logging.Console , Microsoft.Extensions.Logging.Configuration , Microsoft.Extensions.Logging.Debug , Microsoft.Extensions.HashCodeCombiner.Sources , Microsoft.Extensions.HostFactoryResolver.Sources , Microsoft.Extensions.Hosting , Microsoft.Extensions.Hosting.Abstractions , Microsoft.Extensions.Logging.AzureAppServices , Microsoft.Extensions.Http , Microsoft.Extensions.Localization , Microsoft.Extensions.Localization.Abstractions , Microsoft.Extensions.Logging , Microsoft.Extensions.Logging.Abstractions From Version 3.1.25 -> To Version 3.1.25 Dependency coherency updates Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.Extensions.DependencyModel,Microsoft.NETCore.App.Internal,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64 From Version 3.1.25 -> To Version 3.1.25 (parent: Microsoft.Extensions.Logging --- NuGet.config | 7 +- eng/Version.Details.xml | 152 ++++++++++++++++++++-------------------- eng/Versions.props | 4 +- 3 files changed, 82 insertions(+), 81 deletions(-) diff --git a/NuGet.config b/NuGet.config index 963e07e91cc..7330385173d 100644 --- a/NuGet.config +++ b/NuGet.config @@ -6,10 +6,10 @@ - + - + @@ -24,12 +24,13 @@ - + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index af80f04614c..a84ba451919 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,21 +1,21 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx @@ -33,85 +33,85 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup 3acd9b0cd16596bad450c82be08780875a73c05c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 @@ -120,155 +120,155 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - d651d1c30aa3ad9368a35abc425ab28270f2b0ca + f60eb2fbdc72f46c694a7287cd9dfc1abe757484 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - c246118a78fa6a0b2935eaf684642aea7f4da0e9 + 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - d651d1c30aa3ad9368a35abc425ab28270f2b0ca + f60eb2fbdc72f46c694a7287cd9dfc1abe757484 - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - d651d1c30aa3ad9368a35abc425ab28270f2b0ca + f60eb2fbdc72f46c694a7287cd9dfc1abe757484 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx diff --git a/eng/Versions.props b/eng/Versions.props index 6359e73f894..366d8615643 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -37,7 +37,7 @@ 3.1.25 3.1.25 3.1.25 - 3.1.25-servicing.22212.1 + 3.1.25-servicing.22213.2 3.1.25 @@ -56,7 +56,7 @@ 3.1.25 3.1.0 3.1.25 - 3.1.25-servicing.22206.2 + 3.1.25-servicing.22213.1 2.1.0 From 04536f8f020af795903e82355ce16287539df501 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 13 Apr 2022 19:36:43 +0000 Subject: [PATCH 080/143] Update dependencies from https://github.com/dotnet/arcade build 20220412.4 (#27818) [release/3.1] Update dependencies from dotnet/arcade --- NuGet.config | 3 --- eng/Version.Details.xml | 4 ++-- global.json | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/NuGet.config b/NuGet.config index 0dff8158a3d..ab04a09b3d6 100644 --- a/NuGet.config +++ b/NuGet.config @@ -6,10 +6,8 @@ - - @@ -24,7 +22,6 @@ - diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 2df11025b4c..fba5f29ae78 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -321,9 +321,9 @@ - + https://github.com/dotnet/arcade - aebcd10d76469b2e84cffd39d043574bc5357d22 + 110afd89a939ac6476282e27d7290583ad3b1715 https://github.com/dotnet/roslyn diff --git a/global.json b/global.json index 886c2f7081d..f5e4a4ca749 100644 --- a/global.json +++ b/global.json @@ -12,6 +12,6 @@ "version": "3.1.418" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.22159.6" + "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.22212.4" } } From f07ba76344d22e5b6f26325f15951d3f9fd5aca5 Mon Sep 17 00:00:00 2001 From: DotNet-Bot Date: Wed, 13 Apr 2022 21:17:24 +0000 Subject: [PATCH 081/143] Update dependencies from https://dev.azure.com/dnceng/internal/_git/dotnet-extensions build 20220413.8 Microsoft.JSInterop , Microsoft.Extensions.ValueStopwatch.Sources , Microsoft.Extensions.FileProviders.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks , Microsoft.Extensions.DiagnosticAdapter , Microsoft.Extensions.DependencyInjection.Abstractions , Microsoft.Extensions.DependencyInjection , Microsoft.Extensions.Configuration.Xml , Microsoft.Extensions.Configuration.UserSecrets , Microsoft.Extensions.Configuration.KeyPerFile , Microsoft.Extensions.Configuration.Json , Microsoft.Extensions.Configuration.Ini , Microsoft.Extensions.Configuration.FileExtensions , Microsoft.Extensions.Configuration.EnvironmentVariables , Microsoft.Extensions.Configuration.CommandLine , Microsoft.Extensions.Configuration.Binder , Internal.AspNetCore.Analyzers , Microsoft.AspNetCore.Analyzer.Testing , Microsoft.AspNetCore.BenchmarkRunner.Sources , Microsoft.AspNetCore.Testing , Microsoft.Extensions.ActivatorUtilities.Sources , Microsoft.Extensions.FileProviders.Composite , Microsoft.Extensions.Caching.Abstractions , Microsoft.Extensions.Caching.SqlServer , Microsoft.Extensions.Caching.StackExchangeRedis , Microsoft.Extensions.CommandLineUtils.Sources , Microsoft.Extensions.Configuration , Microsoft.Extensions.Configuration.Abstractions , Microsoft.Extensions.Configuration.AzureKeyVault , Microsoft.Extensions.Caching.Memory , Microsoft.Extensions.WebEncoders , Microsoft.Extensions.FileProviders.Embedded , Microsoft.Extensions.FileSystemGlobbing , Microsoft.Extensions.TypeNameHelper.Sources , Microsoft.Extensions.Primitives , Microsoft.Extensions.ParameterDefaultValue.Sources , Microsoft.Extensions.Options.DataAnnotations , Microsoft.Extensions.Options.ConfigurationExtensions , Microsoft.Extensions.Options , Microsoft.Extensions.ObjectPool , Microsoft.Extensions.Logging.TraceSource , Microsoft.Extensions.Logging.Testing , Microsoft.Extensions.Logging.EventSource , Microsoft.Extensions.Logging.EventLog , Microsoft.Extensions.FileProviders.Physical , Microsoft.Extensions.Logging.Console , Microsoft.Extensions.Logging.Configuration , Microsoft.Extensions.Logging.Debug , Microsoft.Extensions.HashCodeCombiner.Sources , Microsoft.Extensions.HostFactoryResolver.Sources , Microsoft.Extensions.Hosting , Microsoft.Extensions.Hosting.Abstractions , Microsoft.Extensions.Logging.AzureAppServices , Microsoft.Extensions.Http , Microsoft.Extensions.Localization , Microsoft.Extensions.Localization.Abstractions , Microsoft.Extensions.Logging , Microsoft.Extensions.Logging.Abstractions From Version 3.1.25 -> To Version 3.1.25 Dependency coherency updates Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.Extensions.DependencyModel,Microsoft.NETCore.App.Internal,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64 From Version 3.1.25 -> To Version 3.1.25 (parent: Microsoft.Extensions.Logging --- NuGet.config | 7 +- eng/Version.Details.xml | 152 ++++++++++++++++++++-------------------- eng/Versions.props | 4 +- 3 files changed, 82 insertions(+), 81 deletions(-) diff --git a/NuGet.config b/NuGet.config index 7330385173d..964e3f1c005 100644 --- a/NuGet.config +++ b/NuGet.config @@ -6,10 +6,10 @@ - + - + @@ -24,12 +24,13 @@ - + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index a84ba451919..6a270efaeed 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,21 +1,21 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-corefx @@ -33,85 +33,85 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup 3acd9b0cd16596bad450c82be08780875a73c05c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df @@ -120,155 +120,155 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - f60eb2fbdc72f46c694a7287cd9dfc1abe757484 + 669de1a9f81c6a28f659b0ee38fc0d3e4781144d https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 7c9add1e4ee1ad73a40d27bc00a863dd801b35b4 + 845e756bb65755234ff8746eecb1188c8731d2df https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - f60eb2fbdc72f46c694a7287cd9dfc1abe757484 + 669de1a9f81c6a28f659b0ee38fc0d3e4781144d - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - f60eb2fbdc72f46c694a7287cd9dfc1abe757484 + 669de1a9f81c6a28f659b0ee38fc0d3e4781144d https://dev.azure.com/dnceng/internal/_git/dotnet-corefx diff --git a/eng/Versions.props b/eng/Versions.props index 366d8615643..8798d6346a6 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -37,7 +37,7 @@ 3.1.25 3.1.25 3.1.25 - 3.1.25-servicing.22213.2 + 3.1.25-servicing.22213.8 3.1.25 @@ -56,7 +56,7 @@ 3.1.25 3.1.0 3.1.25 - 3.1.25-servicing.22213.1 + 3.1.25-servicing.22213.5 2.1.0 From d3b6deaf43e84c234b01ea23f9964b01abbf0463 Mon Sep 17 00:00:00 2001 From: DotNet-Bot Date: Thu, 14 Apr 2022 04:02:43 +0000 Subject: [PATCH 082/143] Update dependencies from https://dev.azure.com/dnceng/internal/_git/dotnet-extensions build 20220413.12 Microsoft.JSInterop , Microsoft.Extensions.ValueStopwatch.Sources , Microsoft.Extensions.FileProviders.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks , Microsoft.Extensions.DiagnosticAdapter , Microsoft.Extensions.DependencyInjection.Abstractions , Microsoft.Extensions.DependencyInjection , Microsoft.Extensions.Configuration.Xml , Microsoft.Extensions.Configuration.UserSecrets , Microsoft.Extensions.Configuration.KeyPerFile , Microsoft.Extensions.Configuration.Json , Microsoft.Extensions.Configuration.Ini , Microsoft.Extensions.Configuration.FileExtensions , Microsoft.Extensions.Configuration.EnvironmentVariables , Microsoft.Extensions.Configuration.CommandLine , Microsoft.Extensions.Configuration.Binder , Internal.AspNetCore.Analyzers , Microsoft.AspNetCore.Analyzer.Testing , Microsoft.AspNetCore.BenchmarkRunner.Sources , Microsoft.AspNetCore.Testing , Microsoft.Extensions.ActivatorUtilities.Sources , Microsoft.Extensions.FileProviders.Composite , Microsoft.Extensions.Caching.Abstractions , Microsoft.Extensions.Caching.SqlServer , Microsoft.Extensions.Caching.StackExchangeRedis , Microsoft.Extensions.CommandLineUtils.Sources , Microsoft.Extensions.Configuration , Microsoft.Extensions.Configuration.Abstractions , Microsoft.Extensions.Configuration.AzureKeyVault , Microsoft.Extensions.Caching.Memory , Microsoft.Extensions.WebEncoders , Microsoft.Extensions.FileProviders.Embedded , Microsoft.Extensions.FileSystemGlobbing , Microsoft.Extensions.TypeNameHelper.Sources , Microsoft.Extensions.Primitives , Microsoft.Extensions.ParameterDefaultValue.Sources , Microsoft.Extensions.Options.DataAnnotations , Microsoft.Extensions.Options.ConfigurationExtensions , Microsoft.Extensions.Options , Microsoft.Extensions.ObjectPool , Microsoft.Extensions.Logging.TraceSource , Microsoft.Extensions.Logging.Testing , Microsoft.Extensions.Logging.EventSource , Microsoft.Extensions.Logging.EventLog , Microsoft.Extensions.FileProviders.Physical , Microsoft.Extensions.Logging.Console , Microsoft.Extensions.Logging.Configuration , Microsoft.Extensions.Logging.Debug , Microsoft.Extensions.HashCodeCombiner.Sources , Microsoft.Extensions.HostFactoryResolver.Sources , Microsoft.Extensions.Hosting , Microsoft.Extensions.Hosting.Abstractions , Microsoft.Extensions.Logging.AzureAppServices , Microsoft.Extensions.Http , Microsoft.Extensions.Localization , Microsoft.Extensions.Localization.Abstractions , Microsoft.Extensions.Logging , Microsoft.Extensions.Logging.Abstractions From Version 3.1.25 -> To Version 3.1.25 Dependency coherency updates Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.Extensions.DependencyModel,Microsoft.NETCore.App.Internal,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64 From Version 3.1.25 -> To Version 3.1.25 (parent: Microsoft.Extensions.Logging --- NuGet.config | 7 +- eng/Version.Details.xml | 152 ++++++++++++++++++++-------------------- eng/Versions.props | 4 +- 3 files changed, 82 insertions(+), 81 deletions(-) diff --git a/NuGet.config b/NuGet.config index 964e3f1c005..a20dd366274 100644 --- a/NuGet.config +++ b/NuGet.config @@ -6,10 +6,10 @@ - + - + @@ -24,12 +24,13 @@ - + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 6a270efaeed..0e571d62389 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,21 +1,21 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-corefx @@ -33,85 +33,85 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup 3acd9b0cd16596bad450c82be08780875a73c05c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e @@ -120,155 +120,155 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 669de1a9f81c6a28f659b0ee38fc0d3e4781144d + ab0779153fef8417d1b333461da7f9cb241d976e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 845e756bb65755234ff8746eecb1188c8731d2df + e32a0b960d56b1d1f2fd46ff958c2cf83508424e https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 669de1a9f81c6a28f659b0ee38fc0d3e4781144d + ab0779153fef8417d1b333461da7f9cb241d976e - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 669de1a9f81c6a28f659b0ee38fc0d3e4781144d + ab0779153fef8417d1b333461da7f9cb241d976e https://dev.azure.com/dnceng/internal/_git/dotnet-corefx diff --git a/eng/Versions.props b/eng/Versions.props index 8798d6346a6..9ee460c4830 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -37,7 +37,7 @@ 3.1.25 3.1.25 3.1.25 - 3.1.25-servicing.22213.8 + 3.1.25-servicing.22213.12 3.1.25 @@ -56,7 +56,7 @@ 3.1.25 3.1.0 3.1.25 - 3.1.25-servicing.22213.5 + 3.1.25-servicing.22213.9 2.1.0 From 849027124392aef7cc002e0176d5410dd3729ae3 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Thu, 14 Apr 2022 15:20:06 -0700 Subject: [PATCH 083/143] Query: Introduce UpdateQueryExpression on ShapedQueryExpression (#27824) This makes sure that shaper expression has right query expression --- ...eConverterCompensatingExpressionVisitor.cs | 11 +------ .../Query/Internal/SqlExpressionVisitor.cs | 3 +- ...eConverterCompensatingExpressionVisitor.cs | 11 +------ ...sionProjectionApplyingExpressionVisitor.cs | 3 +- ...lExpressionSimplifyingExpressionVisitor.cs | 2 +- ...yableMethodTranslatingExpressionVisitor.cs | 31 ++++++++----------- .../Query/SqlExpressionVisitor.cs | 3 +- .../Query/SqlExpressions/SelectExpression.cs | 7 ++--- src/EFCore/Query/ShapedQueryExpression.cs | 12 +++++++ 9 files changed, 34 insertions(+), 49 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosValueConverterCompensatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosValueConverterCompensatingExpressionVisitor.cs index 49e246054ff..7ab2250452a 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosValueConverterCompensatingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosValueConverterCompensatingExpressionVisitor.cs @@ -42,16 +42,7 @@ protected override Expression VisitExtension(Expression extensionExpression) }; private Expression VisitShapedQueryExpression(ShapedQueryExpression shapedQueryExpression) - { - var selectExpression = shapedQueryExpression.QueryExpression; - var updatedSelectExpression = Visit(selectExpression); - return updatedSelectExpression != selectExpression - ? shapedQueryExpression.Update( - updatedSelectExpression, - ReplacingExpressionVisitor.Replace( - selectExpression, updatedSelectExpression, shapedQueryExpression.ShaperExpression)) - : shapedQueryExpression; - } + => shapedQueryExpression.UpdateQueryExpression(Visit(shapedQueryExpression.QueryExpression)); private Expression VisitSelect(SelectExpression selectExpression) { diff --git a/src/EFCore.Cosmos/Query/Internal/SqlExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/SqlExpressionVisitor.cs index 60017c8b670..6d5748089ed 100644 --- a/src/EFCore.Cosmos/Query/Internal/SqlExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/SqlExpressionVisitor.cs @@ -22,8 +22,7 @@ protected override Expression VisitExtension(Expression extensionExpression) switch (extensionExpression) { case ShapedQueryExpression shapedQueryExpression: - return shapedQueryExpression.Update( - Visit(shapedQueryExpression.QueryExpression), shapedQueryExpression.ShaperExpression); + return shapedQueryExpression.UpdateQueryExpression(Visit(shapedQueryExpression.QueryExpression)); case ReadItemExpression readItemExpression: return readItemExpression; diff --git a/src/EFCore.Relational/Query/Internal/RelationalValueConverterCompensatingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalValueConverterCompensatingExpressionVisitor.cs index 0fd2135393b..805297b7e10 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalValueConverterCompensatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalValueConverterCompensatingExpressionVisitor.cs @@ -46,16 +46,7 @@ protected override Expression VisitExtension(Expression extensionExpression) }; private Expression VisitShapedQueryExpression(ShapedQueryExpression shapedQueryExpression) - { - var selectExpression = shapedQueryExpression.QueryExpression; - var updatedSelectExpression = Visit(selectExpression); - return updatedSelectExpression != selectExpression - ? shapedQueryExpression.Update( - updatedSelectExpression, - ReplacingExpressionVisitor.Replace( - selectExpression, updatedSelectExpression, shapedQueryExpression.ShaperExpression)) - : shapedQueryExpression; - } + => shapedQueryExpression.UpdateQueryExpression(Visit(shapedQueryExpression.QueryExpression)); private Expression VisitCase(CaseExpression caseExpression) { diff --git a/src/EFCore.Relational/Query/Internal/SelectExpressionProjectionApplyingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/SelectExpressionProjectionApplyingExpressionVisitor.cs index b5614f67b23..130250095b0 100644 --- a/src/EFCore.Relational/Query/Internal/SelectExpressionProjectionApplyingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/SelectExpressionProjectionApplyingExpressionVisitor.cs @@ -35,8 +35,7 @@ public SelectExpressionProjectionApplyingExpressionVisitor(QuerySplittingBehavio protected override Expression VisitExtension(Expression extensionExpression) => extensionExpression is ShapedQueryExpression shapedQueryExpression && shapedQueryExpression.QueryExpression is SelectExpression selectExpression - ? shapedQueryExpression.Update( - selectExpression, + ? shapedQueryExpression.UpdateShaperExpression( selectExpression.ApplyProjection( shapedQueryExpression.ShaperExpression, shapedQueryExpression.ResultCardinality, _querySplittingBehavior)) : base.VisitExtension(extensionExpression); diff --git a/src/EFCore.Relational/Query/Internal/SqlExpressionSimplifyingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/SqlExpressionSimplifyingExpressionVisitor.cs index d45017786db..c31df74cd72 100644 --- a/src/EFCore.Relational/Query/Internal/SqlExpressionSimplifyingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/SqlExpressionSimplifyingExpressionVisitor.cs @@ -40,7 +40,7 @@ protected override Expression VisitExtension(Expression extensionExpression) { if (extensionExpression is ShapedQueryExpression shapedQueryExpression) { - return shapedQueryExpression.Update(Visit(shapedQueryExpression.QueryExpression), shapedQueryExpression.ShaperExpression); + return shapedQueryExpression.UpdateQueryExpression(Visit(shapedQueryExpression.QueryExpression)); } // Only applies to 'CASE WHEN condition...' not 'CASE operand WHEN...' diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 00629d05c22..b8bad0935f7 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -193,12 +193,10 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent } translation = _sqlExpressionFactory.Exists(selectExpression, true); + selectExpression = _sqlExpressionFactory.Select(translation); - return source.Update( - _sqlExpressionFactory.Select(translation), - Expression.Convert( - new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool?)), - typeof(bool))); + return source.Update(selectExpression, + Expression.Convert(new ProjectionBindingExpression(selectExpression, new ProjectionMember(), typeof(bool?)), typeof(bool))); } /// @@ -225,12 +223,10 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent } var translation = _sqlExpressionFactory.Exists(selectExpression, false); + selectExpression = _sqlExpressionFactory.Select(translation); - return source.Update( - _sqlExpressionFactory.Select(translation), - Expression.Convert( - new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool?)), - typeof(bool))); + return source.Update(selectExpression, + Expression.Convert(new ProjectionBindingExpression(selectExpression, new ProjectionMember(), typeof(bool?)), typeof(bool))); } /// @@ -290,12 +286,11 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent selectExpression.ApplyProjection(); translation = _sqlExpressionFactory.In(translation, selectExpression, false); + selectExpression = _sqlExpressionFactory.Select(translation); - return source.Update( - _sqlExpressionFactory.Select(translation), + return source.Update(selectExpression, Expression.Convert( - new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool?)), - typeof(bool))); + new ProjectionBindingExpression(selectExpression, new ProjectionMember(), typeof(bool?)), typeof(bool))); } } @@ -1089,9 +1084,9 @@ protected override Expression VisitExtension(Expression extensionExpression) var innerSelectExpression = BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable( entityProjectionExpression, navigation); - - var innerShapedQuery = CreateShapedQueryExpression( - targetEntityType, innerSelectExpression); + + var innerShapedQuery = CreateShapedQueryExpression( + targetEntityType, innerSelectExpression); var makeNullable = foreignKey.PrincipalKey.Properties .Concat(foreignKey.Properties) @@ -1181,7 +1176,7 @@ outerKey is NewArrayExpression newArrayExpression // Owned types don't support inheritance See https://github.com/dotnet/efcore/issues/9630 // So there is no handling for dependent having TPT table = targetEntityType.GetViewOrTableMappings().Single().Table; - var innerSelectExpression = BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable( + var innerSelectExpression = BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable( entityProjectionExpression, navigation); diff --git a/src/EFCore.Relational/Query/SqlExpressionVisitor.cs b/src/EFCore.Relational/Query/SqlExpressionVisitor.cs index 340f02ff56c..765d865c33b 100644 --- a/src/EFCore.Relational/Query/SqlExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/SqlExpressionVisitor.cs @@ -22,8 +22,7 @@ protected override Expression VisitExtension(Expression extensionExpression) switch (extensionExpression) { case ShapedQueryExpression shapedQueryExpression: - return shapedQueryExpression.Update( - Visit(shapedQueryExpression.QueryExpression), shapedQueryExpression.ShaperExpression); + return shapedQueryExpression.UpdateQueryExpression(Visit(shapedQueryExpression.QueryExpression)); case CaseExpression caseExpression: return VisitCase(caseExpression); diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index bbfc2c85534..35efbfc23ea 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -2893,8 +2893,8 @@ private SqlRemappingVisitor PushdownIntoSubqueryInternal() { if (_clientProjections[i] is ShapedQueryExpression shapedQueryExpression) { - _clientProjections[i] = shapedQueryExpression.Update( - sqlRemappingVisitor.Visit(shapedQueryExpression.QueryExpression), shapedQueryExpression.ShaperExpression); + _clientProjections[i] = shapedQueryExpression.UpdateQueryExpression( + sqlRemappingVisitor.Visit(shapedQueryExpression.QueryExpression)); } } } @@ -3398,8 +3398,7 @@ List VisitList(List list, bool inPlace, out bool changed) { var item = list[i]; var newItem = item is ShapedQueryExpression shapedQueryExpression - ? shapedQueryExpression.Update( - visitor.Visit(shapedQueryExpression.QueryExpression), shapedQueryExpression.ShaperExpression) + ? shapedQueryExpression.UpdateQueryExpression(visitor.Visit(shapedQueryExpression.QueryExpression)) : visitor.Visit(item); if (newItem != item && newList == list) diff --git a/src/EFCore/Query/ShapedQueryExpression.cs b/src/EFCore/Query/ShapedQueryExpression.cs index 886162a25ef..5f60f9b36b3 100644 --- a/src/EFCore/Query/ShapedQueryExpression.cs +++ b/src/EFCore/Query/ShapedQueryExpression.cs @@ -83,6 +83,18 @@ public virtual ShapedQueryExpression Update(Expression queryExpression, Expressi ? new ShapedQueryExpression(queryExpression, shaperExpression, ResultCardinality) : this; + /// + /// Creates a new expression that is like this one, but using the supplied query expression. If query expression is the same, it will + /// return this expression. + /// + /// The property of the result. + /// This expression if shaper expression did not change, or an expression with the updated shaper expression. + public virtual ShapedQueryExpression UpdateQueryExpression(Expression queryExpression) + => !ReferenceEquals(queryExpression, QueryExpression) + ? new ShapedQueryExpression(queryExpression, + ReplacingExpressionVisitor.Replace(QueryExpression, queryExpression, ShaperExpression), ResultCardinality) + : this; + /// /// Creates a new expression that is like this one, but using the supplied shaper expression. If shaper expression is the same, it will /// return this expression. From 17f802a9367e8146e030a82f4944cedca26ac23e Mon Sep 17 00:00:00 2001 From: Brice Lambson Date: Tue, 12 Apr 2022 14:30:13 -0700 Subject: [PATCH 084/143] :arrow_up: Update to .NET SDK 7.0.100-preview.3 --- eng/Version.Details.xml | 29 +++++++++---------- eng/Versions.props | 14 ++++----- global.json | 9 ++---- .../Design/Internal/OperationLogger.cs | 1 + .../EFCore.Trimming.Tests.csproj | 2 ++ 5 files changed, 26 insertions(+), 29 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index b21f90a5229..6f957c095be 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,42 +1,41 @@ - - + https://github.com/dotnet/runtime - e24f66dff0770eee344038da8c12476d8c450c41 + b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 - + https://github.com/dotnet/runtime - e24f66dff0770eee344038da8c12476d8c450c41 + b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 - + https://github.com/dotnet/runtime - e24f66dff0770eee344038da8c12476d8c450c41 + b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 - + https://github.com/dotnet/runtime - e24f66dff0770eee344038da8c12476d8c450c41 + b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 - + https://github.com/dotnet/runtime - e24f66dff0770eee344038da8c12476d8c450c41 + b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 https://github.com/dotnet/runtime b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 - + https://github.com/dotnet/runtime - e24f66dff0770eee344038da8c12476d8c450c41 + b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 https://github.com/dotnet/runtime b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 - + https://github.com/dotnet/runtime - e24f66dff0770eee344038da8c12476d8c450c41 + b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 diff --git a/eng/Versions.props b/eng/Versions.props index 4ec602b6604..40cb327dc0e 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -15,15 +15,15 @@ False - 7.0.0-preview.2.22152.2 - 7.0.0-preview.2.22152.2 - 7.0.0-preview.2.22152.2 - 7.0.0-preview.2.22152.2 - 7.0.0-preview.2.22152.2 + 7.0.0-preview.4.22208.8 + 7.0.0-preview.4.22208.8 + 7.0.0-preview.4.22208.8 + 7.0.0-preview.4.22208.8 + 7.0.0-preview.4.22208.8 7.0.0-preview.4.22208.8 - 7.0.0-preview.2.22152.2 + 7.0.0-preview.4.22208.8 7.0.0-preview.4.22208.8 - 7.0.0-preview.2.22152.2 + 7.0.0-preview.4.22208.8 4.0.1 diff --git a/global.json b/global.json index 14a653d6598..a678546a4a2 100644 --- a/global.json +++ b/global.json @@ -1,14 +1,9 @@ { "tools": { - "dotnet": "7.0.100-preview.2.22153.17", - "runtimes": { - "aspnetcore": [ - "7.0.0-preview.2.22153.2" - ] - } + "dotnet": "7.0.100-preview.3.22179.4" }, "sdk": { - "version": "7.0.100-preview.2.22153.17", + "version": "7.0.100-preview.3.22179.4", "allowPrerelease": true, "rollForward": "latestMajor" }, diff --git a/src/EFCore.Design/Design/Internal/OperationLogger.cs b/src/EFCore.Design/Design/Internal/OperationLogger.cs index 64a082402e2..9e71b1f87b9 100644 --- a/src/EFCore.Design/Design/Internal/OperationLogger.cs +++ b/src/EFCore.Design/Design/Internal/OperationLogger.cs @@ -44,6 +44,7 @@ public virtual bool IsEnabled(LogLevel logLevel) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IDisposable BeginScope(TState state) + where TState : notnull => NullScope.Instance; /// diff --git a/test/EFCore.Trimming.Tests/EFCore.Trimming.Tests.csproj b/test/EFCore.Trimming.Tests/EFCore.Trimming.Tests.csproj index 33e2f616fb4..68354e7a641 100644 --- a/test/EFCore.Trimming.Tests/EFCore.Trimming.Tests.csproj +++ b/test/EFCore.Trimming.Tests/EFCore.Trimming.Tests.csproj @@ -5,6 +5,8 @@ net7.0 true false + + false From 637d7efa1d2921214f4579beb5925da74d64e05e Mon Sep 17 00:00:00 2001 From: DotNet-Bot Date: Sat, 16 Apr 2022 01:31:09 +0000 Subject: [PATCH 085/143] Update dependencies from https://dev.azure.com/dnceng/internal/_git/dotnet-extensions build 20220415.2 Microsoft.JSInterop , Microsoft.Extensions.ValueStopwatch.Sources , Microsoft.Extensions.FileProviders.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks , Microsoft.Extensions.DiagnosticAdapter , Microsoft.Extensions.DependencyInjection.Abstractions , Microsoft.Extensions.DependencyInjection , Microsoft.Extensions.Configuration.Xml , Microsoft.Extensions.Configuration.UserSecrets , Microsoft.Extensions.Configuration.KeyPerFile , Microsoft.Extensions.Configuration.Json , Microsoft.Extensions.Configuration.Ini , Microsoft.Extensions.Configuration.FileExtensions , Microsoft.Extensions.Configuration.EnvironmentVariables , Microsoft.Extensions.Configuration.CommandLine , Microsoft.Extensions.Configuration.Binder , Internal.AspNetCore.Analyzers , Microsoft.AspNetCore.Analyzer.Testing , Microsoft.AspNetCore.BenchmarkRunner.Sources , Microsoft.AspNetCore.Testing , Microsoft.Extensions.ActivatorUtilities.Sources , Microsoft.Extensions.FileProviders.Composite , Microsoft.Extensions.Caching.Abstractions , Microsoft.Extensions.Caching.SqlServer , Microsoft.Extensions.Caching.StackExchangeRedis , Microsoft.Extensions.CommandLineUtils.Sources , Microsoft.Extensions.Configuration , Microsoft.Extensions.Configuration.Abstractions , Microsoft.Extensions.Configuration.AzureKeyVault , Microsoft.Extensions.Caching.Memory , Microsoft.Extensions.WebEncoders , Microsoft.Extensions.FileProviders.Embedded , Microsoft.Extensions.FileSystemGlobbing , Microsoft.Extensions.TypeNameHelper.Sources , Microsoft.Extensions.Primitives , Microsoft.Extensions.ParameterDefaultValue.Sources , Microsoft.Extensions.Options.DataAnnotations , Microsoft.Extensions.Options.ConfigurationExtensions , Microsoft.Extensions.Options , Microsoft.Extensions.ObjectPool , Microsoft.Extensions.Logging.TraceSource , Microsoft.Extensions.Logging.Testing , Microsoft.Extensions.Logging.EventSource , Microsoft.Extensions.Logging.EventLog , Microsoft.Extensions.FileProviders.Physical , Microsoft.Extensions.Logging.Console , Microsoft.Extensions.Logging.Configuration , Microsoft.Extensions.Logging.Debug , Microsoft.Extensions.HashCodeCombiner.Sources , Microsoft.Extensions.HostFactoryResolver.Sources , Microsoft.Extensions.Hosting , Microsoft.Extensions.Hosting.Abstractions , Microsoft.Extensions.Logging.AzureAppServices , Microsoft.Extensions.Http , Microsoft.Extensions.Localization , Microsoft.Extensions.Localization.Abstractions , Microsoft.Extensions.Logging , Microsoft.Extensions.Logging.Abstractions From Version 3.1.25 -> To Version 3.1.25 Dependency coherency updates Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.Extensions.DependencyModel,Microsoft.NETCore.App.Internal,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64 From Version 3.1.25 -> To Version 3.1.25 (parent: Microsoft.Extensions.Logging --- NuGet.config | 7 +- eng/Version.Details.xml | 152 ++++++++++++++++++++-------------------- eng/Versions.props | 4 +- 3 files changed, 82 insertions(+), 81 deletions(-) diff --git a/NuGet.config b/NuGet.config index a20dd366274..6f7c1ea809e 100644 --- a/NuGet.config +++ b/NuGet.config @@ -6,10 +6,10 @@ - + - + @@ -24,12 +24,13 @@ - + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 0e571d62389..47cd44544a1 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,21 +1,21 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx @@ -33,85 +33,85 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup 3acd9b0cd16596bad450c82be08780875a73c05c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 @@ -120,155 +120,155 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - ab0779153fef8417d1b333461da7f9cb241d976e + 93ed159eb031e8a5623759e888b3c8344d161198 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - e32a0b960d56b1d1f2fd46ff958c2cf83508424e + 6010fcb647e1bfdc310d353c9c8a324d6745bf07 https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - ab0779153fef8417d1b333461da7f9cb241d976e + 93ed159eb031e8a5623759e888b3c8344d161198 - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - ab0779153fef8417d1b333461da7f9cb241d976e + 93ed159eb031e8a5623759e888b3c8344d161198 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx diff --git a/eng/Versions.props b/eng/Versions.props index 9ee460c4830..c6dac8598a8 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -37,7 +37,7 @@ 3.1.25 3.1.25 3.1.25 - 3.1.25-servicing.22213.12 + 3.1.25-servicing.22215.2 3.1.25 @@ -56,7 +56,7 @@ 3.1.25 3.1.0 3.1.25 - 3.1.25-servicing.22213.9 + 3.1.25-servicing.22215.3 2.1.0 From ba6b5928cde6d7cd44a0285b49b864cf0175f88d Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Mon, 18 Apr 2022 11:17:54 +0100 Subject: [PATCH 086/143] Remove quirks (#27775) --- ...ionalProjectionBindingExpressionVisitor.cs | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index 80a66abe7dd..7be353baa27 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -181,26 +181,10 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio _clientProjections!.Add(subquery); var type = expression.Type; - if (!(AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue27105", out var enabled27105) - && enabled27105)) + if (type.IsGenericType + && type.GetGenericTypeDefinition() == typeof(IQueryable<>)) { - if (!(AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue27600", out var enabled27600) - && enabled27600)) - { - if (type.IsGenericType - && type.GetGenericTypeDefinition() == typeof(IQueryable<>)) - { - type = typeof(List<>).MakeGenericType(type.GetSequenceType()); - } - } - else - { - if (type.IsGenericType - && type.GetGenericTypeDefinition() == typeof(IQueryable<>)) - { - type = typeof(IEnumerable<>).MakeGenericType(type.GetSequenceType()); - } - } + type = typeof(List<>).MakeGenericType(type.GetSequenceType()); } var projectionBindingExpression = new ProjectionBindingExpression( From ae3c9054aa51e9f64c9ed1f701e181e1f53e60be Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 18 Apr 2022 13:14:42 +0000 Subject: [PATCH 087/143] Update dependencies from https://github.com/dotnet/runtime build 20220417.5 (#27833) [main] Update dependencies from dotnet/runtime --- eng/Version.Details.xml | 36 ++++++++++++++++++------------------ eng/Versions.props | 18 +++++++++--------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 6f957c095be..a2bd8312fd5 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,41 +1,41 @@ - + https://github.com/dotnet/runtime - b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 + 74e1d4df5733dda34a601a004aae720718184788 - + https://github.com/dotnet/runtime - b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 + 74e1d4df5733dda34a601a004aae720718184788 - + https://github.com/dotnet/runtime - b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 + 74e1d4df5733dda34a601a004aae720718184788 - + https://github.com/dotnet/runtime - b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 + 74e1d4df5733dda34a601a004aae720718184788 - + https://github.com/dotnet/runtime - b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 + 74e1d4df5733dda34a601a004aae720718184788 - + https://github.com/dotnet/runtime - b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 + 74e1d4df5733dda34a601a004aae720718184788 - + https://github.com/dotnet/runtime - b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 + 74e1d4df5733dda34a601a004aae720718184788 - + https://github.com/dotnet/runtime - b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 + 74e1d4df5733dda34a601a004aae720718184788 - + https://github.com/dotnet/runtime - b8dc04b445fc98d24fd92262364a9e5b5e23a5b9 + 74e1d4df5733dda34a601a004aae720718184788 diff --git a/eng/Versions.props b/eng/Versions.props index 40cb327dc0e..bf59e0c1e0d 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -15,15 +15,15 @@ False - 7.0.0-preview.4.22208.8 - 7.0.0-preview.4.22208.8 - 7.0.0-preview.4.22208.8 - 7.0.0-preview.4.22208.8 - 7.0.0-preview.4.22208.8 - 7.0.0-preview.4.22208.8 - 7.0.0-preview.4.22208.8 - 7.0.0-preview.4.22208.8 - 7.0.0-preview.4.22208.8 + 7.0.0-preview.4.22217.5 + 7.0.0-preview.4.22217.5 + 7.0.0-preview.4.22217.5 + 7.0.0-preview.4.22217.5 + 7.0.0-preview.4.22217.5 + 7.0.0-preview.4.22217.5 + 7.0.0-preview.4.22217.5 + 7.0.0-preview.4.22217.5 + 7.0.0-preview.4.22217.5 4.0.1 From 2a9e7c67627ebf5c6944e93e191c634e89a138a6 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 18 Apr 2022 13:15:27 +0000 Subject: [PATCH 088/143] Update dependencies from https://github.com/dotnet/arcade build 20220415.2 (#27834) [main] Update dependencies from dotnet/arcade --- eng/Version.Details.xml | 8 ++++---- eng/common/dotnet-install.sh | 4 ++-- global.json | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index a2bd8312fd5..ce9cf64ae90 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -39,13 +39,13 @@ - + https://github.com/dotnet/arcade - 549523c3fc8929da1a3073d1a97f298e0d1dc342 + 4000024394df3049886c50e54ad0a2b903221ef0 - + https://github.com/dotnet/arcade - 549523c3fc8929da1a3073d1a97f298e0d1dc342 + 4000024394df3049886c50e54ad0a2b903221ef0 diff --git a/eng/common/dotnet-install.sh b/eng/common/dotnet-install.sh index 5c94e98632a..abd045a3247 100755 --- a/eng/common/dotnet-install.sh +++ b/eng/common/dotnet-install.sh @@ -52,7 +52,7 @@ done # Use uname to determine what the CPU is, see https://en.wikipedia.org/wiki/Uname#Examples cpuname=$(uname -m) case $cpuname in - aarch64) + arm64|aarch64) buildarch=arm64 ;; loongarch64) @@ -64,7 +64,7 @@ case $cpuname in armv*l) buildarch=arm ;; - i686) + i[3-6]86) buildarch=x86 ;; *) diff --git a/global.json b/global.json index a678546a4a2..9c26b9eb59b 100644 --- a/global.json +++ b/global.json @@ -8,7 +8,7 @@ "rollForward": "latestMajor" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22206.10", - "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22206.10" + "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22215.2", + "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22215.2" } } From 7c4f6293143724d909fbe309fe27e3f0c525445f Mon Sep 17 00:00:00 2001 From: DotNet-Bot Date: Mon, 18 Apr 2022 20:13:53 +0000 Subject: [PATCH 089/143] Update dependencies from https://dev.azure.com/dnceng/internal/_git/dotnet-extensions build 20220418.2 Microsoft.JSInterop , Microsoft.Extensions.ValueStopwatch.Sources , Microsoft.Extensions.FileProviders.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks , Microsoft.Extensions.DiagnosticAdapter , Microsoft.Extensions.DependencyInjection.Abstractions , Microsoft.Extensions.DependencyInjection , Microsoft.Extensions.Configuration.Xml , Microsoft.Extensions.Configuration.UserSecrets , Microsoft.Extensions.Configuration.KeyPerFile , Microsoft.Extensions.Configuration.Json , Microsoft.Extensions.Configuration.Ini , Microsoft.Extensions.Configuration.FileExtensions , Microsoft.Extensions.Configuration.EnvironmentVariables , Microsoft.Extensions.Configuration.CommandLine , Microsoft.Extensions.Configuration.Binder , Internal.AspNetCore.Analyzers , Microsoft.AspNetCore.Analyzer.Testing , Microsoft.AspNetCore.BenchmarkRunner.Sources , Microsoft.AspNetCore.Testing , Microsoft.Extensions.ActivatorUtilities.Sources , Microsoft.Extensions.FileProviders.Composite , Microsoft.Extensions.Caching.Abstractions , Microsoft.Extensions.Caching.SqlServer , Microsoft.Extensions.Caching.StackExchangeRedis , Microsoft.Extensions.CommandLineUtils.Sources , Microsoft.Extensions.Configuration , Microsoft.Extensions.Configuration.Abstractions , Microsoft.Extensions.Configuration.AzureKeyVault , Microsoft.Extensions.Caching.Memory , Microsoft.Extensions.WebEncoders , Microsoft.Extensions.FileProviders.Embedded , Microsoft.Extensions.FileSystemGlobbing , Microsoft.Extensions.TypeNameHelper.Sources , Microsoft.Extensions.Primitives , Microsoft.Extensions.ParameterDefaultValue.Sources , Microsoft.Extensions.Options.DataAnnotations , Microsoft.Extensions.Options.ConfigurationExtensions , Microsoft.Extensions.Options , Microsoft.Extensions.ObjectPool , Microsoft.Extensions.Logging.TraceSource , Microsoft.Extensions.Logging.Testing , Microsoft.Extensions.Logging.EventSource , Microsoft.Extensions.Logging.EventLog , Microsoft.Extensions.FileProviders.Physical , Microsoft.Extensions.Logging.Console , Microsoft.Extensions.Logging.Configuration , Microsoft.Extensions.Logging.Debug , Microsoft.Extensions.HashCodeCombiner.Sources , Microsoft.Extensions.HostFactoryResolver.Sources , Microsoft.Extensions.Hosting , Microsoft.Extensions.Hosting.Abstractions , Microsoft.Extensions.Logging.AzureAppServices , Microsoft.Extensions.Http , Microsoft.Extensions.Localization , Microsoft.Extensions.Localization.Abstractions , Microsoft.Extensions.Logging , Microsoft.Extensions.Logging.Abstractions From Version 3.1.25 -> To Version 3.1.25 Dependency coherency updates Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.Extensions.DependencyModel,Microsoft.NETCore.App.Internal,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64 From Version 3.1.25 -> To Version 3.1.25 (parent: Microsoft.Extensions.Logging --- NuGet.config | 7 +- eng/Version.Details.xml | 152 ++++++++++++++++++++-------------------- eng/Versions.props | 4 +- 3 files changed, 82 insertions(+), 81 deletions(-) diff --git a/NuGet.config b/NuGet.config index 6f7c1ea809e..a4daaa6c8e3 100644 --- a/NuGet.config +++ b/NuGet.config @@ -6,10 +6,10 @@ - + - + @@ -24,12 +24,13 @@ - + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 47cd44544a1..de01ac9a51a 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,21 +1,21 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx @@ -33,85 +33,85 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup 3acd9b0cd16596bad450c82be08780875a73c05c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 @@ -120,155 +120,155 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 93ed159eb031e8a5623759e888b3c8344d161198 + 4c6d1f5f3e5caa268fbdb4fa9d777bd61334eb98 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 6010fcb647e1bfdc310d353c9c8a324d6745bf07 + 4f59f4157855417499a3bfd6f749151001b52fa9 https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 93ed159eb031e8a5623759e888b3c8344d161198 + 4c6d1f5f3e5caa268fbdb4fa9d777bd61334eb98 - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 93ed159eb031e8a5623759e888b3c8344d161198 + 4c6d1f5f3e5caa268fbdb4fa9d777bd61334eb98 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx diff --git a/eng/Versions.props b/eng/Versions.props index c6dac8598a8..01f5c9c8af1 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -37,7 +37,7 @@ 3.1.25 3.1.25 3.1.25 - 3.1.25-servicing.22215.2 + 3.1.25-servicing.22218.2 3.1.25 @@ -56,7 +56,7 @@ 3.1.25 3.1.0 3.1.25 - 3.1.25-servicing.22215.3 + 3.1.25-servicing.22218.1 2.1.0 From 229f1b3a365882bb978c22565c6854d8457c8012 Mon Sep 17 00:00:00 2001 From: DotNet-Bot Date: Mon, 18 Apr 2022 23:15:08 +0000 Subject: [PATCH 090/143] Update dependencies from https://dev.azure.com/dnceng/internal/_git/dotnet-extensions build 20220418.4 Microsoft.JSInterop , Microsoft.Extensions.ValueStopwatch.Sources , Microsoft.Extensions.FileProviders.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions , Microsoft.Extensions.Diagnostics.HealthChecks , Microsoft.Extensions.DiagnosticAdapter , Microsoft.Extensions.DependencyInjection.Abstractions , Microsoft.Extensions.DependencyInjection , Microsoft.Extensions.Configuration.Xml , Microsoft.Extensions.Configuration.UserSecrets , Microsoft.Extensions.Configuration.KeyPerFile , Microsoft.Extensions.Configuration.Json , Microsoft.Extensions.Configuration.Ini , Microsoft.Extensions.Configuration.FileExtensions , Microsoft.Extensions.Configuration.EnvironmentVariables , Microsoft.Extensions.Configuration.CommandLine , Microsoft.Extensions.Configuration.Binder , Internal.AspNetCore.Analyzers , Microsoft.AspNetCore.Analyzer.Testing , Microsoft.AspNetCore.BenchmarkRunner.Sources , Microsoft.AspNetCore.Testing , Microsoft.Extensions.ActivatorUtilities.Sources , Microsoft.Extensions.FileProviders.Composite , Microsoft.Extensions.Caching.Abstractions , Microsoft.Extensions.Caching.SqlServer , Microsoft.Extensions.Caching.StackExchangeRedis , Microsoft.Extensions.CommandLineUtils.Sources , Microsoft.Extensions.Configuration , Microsoft.Extensions.Configuration.Abstractions , Microsoft.Extensions.Configuration.AzureKeyVault , Microsoft.Extensions.Caching.Memory , Microsoft.Extensions.WebEncoders , Microsoft.Extensions.FileProviders.Embedded , Microsoft.Extensions.FileSystemGlobbing , Microsoft.Extensions.TypeNameHelper.Sources , Microsoft.Extensions.Primitives , Microsoft.Extensions.ParameterDefaultValue.Sources , Microsoft.Extensions.Options.DataAnnotations , Microsoft.Extensions.Options.ConfigurationExtensions , Microsoft.Extensions.Options , Microsoft.Extensions.ObjectPool , Microsoft.Extensions.Logging.TraceSource , Microsoft.Extensions.Logging.Testing , Microsoft.Extensions.Logging.EventSource , Microsoft.Extensions.Logging.EventLog , Microsoft.Extensions.FileProviders.Physical , Microsoft.Extensions.Logging.Console , Microsoft.Extensions.Logging.Configuration , Microsoft.Extensions.Logging.Debug , Microsoft.Extensions.HashCodeCombiner.Sources , Microsoft.Extensions.HostFactoryResolver.Sources , Microsoft.Extensions.Hosting , Microsoft.Extensions.Hosting.Abstractions , Microsoft.Extensions.Logging.AzureAppServices , Microsoft.Extensions.Http , Microsoft.Extensions.Localization , Microsoft.Extensions.Localization.Abstractions , Microsoft.Extensions.Logging , Microsoft.Extensions.Logging.Abstractions From Version 3.1.25 -> To Version 3.1.25 Dependency coherency updates Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.Extensions.DependencyModel,Microsoft.NETCore.App.Internal,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64,Microsoft.NETCore.App.Runtime.win-x64 From Version 3.1.25 -> To Version 3.1.25 (parent: Microsoft.Extensions.Logging --- NuGet.config | 7 +- eng/Version.Details.xml | 152 ++++++++++++++++++++-------------------- eng/Versions.props | 4 +- 3 files changed, 82 insertions(+), 81 deletions(-) diff --git a/NuGet.config b/NuGet.config index a4daaa6c8e3..7236a647ee9 100644 --- a/NuGet.config +++ b/NuGet.config @@ -6,10 +6,10 @@ - + - + @@ -24,12 +24,13 @@ - + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index de01ac9a51a..00657a6aa95 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,21 +1,21 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-corefx @@ -33,85 +33,85 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup 3acd9b0cd16596bad450c82be08780875a73c05c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c @@ -120,155 +120,155 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 4c6d1f5f3e5caa268fbdb4fa9d777bd61334eb98 + 9165b88e05fc37585aa1c3fc9585b5a7d751bb21 https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c - + https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-extensions - 4f59f4157855417499a3bfd6f749151001b52fa9 + fc49d8e29a7fb8d87d1210396cc3b90e8982934c https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 4c6d1f5f3e5caa268fbdb4fa9d777bd61334eb98 + 9165b88e05fc37585aa1c3fc9585b5a7d751bb21 - + https://dev.azure.com/dnceng/internal/_git/dotnet-core-setup - 4c6d1f5f3e5caa268fbdb4fa9d777bd61334eb98 + 9165b88e05fc37585aa1c3fc9585b5a7d751bb21 https://dev.azure.com/dnceng/internal/_git/dotnet-corefx diff --git a/eng/Versions.props b/eng/Versions.props index 01f5c9c8af1..9ab36983810 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -37,7 +37,7 @@ 3.1.25 3.1.25 3.1.25 - 3.1.25-servicing.22218.2 + 3.1.25-servicing.22218.4 3.1.25 @@ -56,7 +56,7 @@ 3.1.25 3.1.0 3.1.25 - 3.1.25-servicing.22218.1 + 3.1.25-servicing.22218.2 2.1.0 From 8887ae1cc04179c7cae9fe51b033793d4cfbca96 Mon Sep 17 00:00:00 2001 From: William Godbe Date: Tue, 19 Apr 2022 17:02:17 -0700 Subject: [PATCH 091/143] Update branding to preview5 (#27843) --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index bf59e0c1e0d..20c05bb5bc0 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -2,7 +2,7 @@ 7.0.0 preview - 4 + 5 False true $(OutDir) Microsoft.EntityFrameworkCore.Tools diff --git a/src/ef/ef.csproj b/src/ef/ef.csproj index 77a7d8d87fe..a75e3579617 100644 --- a/src/ef/ef.csproj +++ b/src/ef/ef.csproj @@ -8,6 +8,7 @@ Microsoft.EntityFrameworkCore.Tools False $(MSBuildThisFileDirectory)..\..\rulesets\EFCore.noxmldocs.ruleset + Major From 22adf73b2549fffedf10a32f8bff4d5bc99dd200 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Fri, 13 May 2022 16:41:17 -0700 Subject: [PATCH 139/143] Query: API review changes (#28023) Part of #27588 --- .../Query/Internal/InMemoryQueryExpression.cs | 1 - .../Query/EnumerableExpression.cs | 103 +++++++++++---- .../RelationalGroupByShaperExpression.cs | 57 +++++++++ ...lationalSqlTranslatingExpressionVisitor.cs | 121 ++++++++---------- .../Query/SqlExpressions/SelectExpression.cs | 8 +- src/EFCore/Query/GroupByShaperExpression.cs | 33 +++-- 6 files changed, 208 insertions(+), 115 deletions(-) create mode 100644 src/EFCore.Relational/Query/RelationalGroupByShaperExpression.cs diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index 7abfecc053d..31b864eb51f 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs @@ -600,7 +600,6 @@ public virtual GroupByShaperExpression ApplyGrouping( return new GroupByShaperExpression( groupingKey, - shaperExpression, new ShapedQueryExpression( clonedInMemoryQueryExpression, new QueryExpressionReplacingExpressionVisitor(this, clonedInMemoryQueryExpression).Visit(shaperExpression))); diff --git a/src/EFCore.Relational/Query/EnumerableExpression.cs b/src/EFCore.Relational/Query/EnumerableExpression.cs index 743136edeed..b1a75e49a73 100644 --- a/src/EFCore.Relational/Query/EnumerableExpression.cs +++ b/src/EFCore.Relational/Query/EnumerableExpression.cs @@ -2,8 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.CompilerServices; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; -namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; +namespace Microsoft.EntityFrameworkCore.Query; /// /// @@ -16,8 +17,6 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class EnumerableExpression : Expression, IPrintableExpression { - private readonly List _orderings = new(); - /// /// Creates a new instance of the class. /// @@ -25,59 +24,73 @@ public class EnumerableExpression : Expression, IPrintableExpression public EnumerableExpression(Expression selector) { Selector = selector; + IsDistinct = false; + Predicate = null; + Orderings = new List(); + } + + private EnumerableExpression( + Expression selector, + bool distinct, + SqlExpression? predicate, + IReadOnlyList orderings) + { + Selector = selector; + IsDistinct = distinct; + Predicate = predicate; + Orderings = orderings; } /// /// The underlying expression being enumerated. /// - public virtual Expression Selector { get; private set; } + public virtual Expression Selector { get; } /// /// The value indicating if distinct operator is applied on the enumerable or not. /// - public virtual bool IsDistinct { get; private set; } + public virtual bool IsDistinct { get; } /// /// The value indicating any predicate applied on the enumerable. /// - public virtual SqlExpression? Predicate { get; private set; } + public virtual SqlExpression? Predicate { get; } /// /// The list of orderings to be applied to the enumerable. /// - public virtual IReadOnlyList Orderings => _orderings; + public virtual IReadOnlyList Orderings { get; } /// /// Applies new selector to the . /// - public virtual void ApplySelector(Expression expression) - { - Selector = expression; - } + /// The new expression with specified component updated. + public virtual EnumerableExpression ApplySelector(Expression expression) + => new(expression, IsDistinct, Predicate, Orderings); /// /// Applies DISTINCT operator to the selector of the . /// - public virtual void ApplyDistinct() - { - IsDistinct = true; - } + /// The new expression with specified component updated. + public virtual EnumerableExpression ApplyDistinct() + => new(Selector, distinct: true, Predicate, Orderings); /// /// Applies filter predicate to the . /// /// An expression to use for filtering. - public virtual void ApplyPredicate(SqlExpression sqlExpression) + /// The new expression with specified component updated. + public virtual EnumerableExpression ApplyPredicate(SqlExpression sqlExpression) { if (sqlExpression is SqlConstantExpression sqlConstant && sqlConstant.Value is bool boolValue && boolValue) { - return; + return this; } - Predicate = Predicate == null + var predicate = Predicate == null ? sqlExpression : new SqlBinaryExpression( ExpressionType.AndAlso, @@ -85,27 +98,41 @@ public virtual void ApplyPredicate(SqlExpression sqlExpression) sqlExpression, typeof(bool), sqlExpression.TypeMapping); + + return new(Selector, IsDistinct, predicate, Orderings); } /// /// Applies ordering to the . This overwrites any previous ordering specified. /// /// An ordering expression to use for ordering. - public virtual void ApplyOrdering(OrderingExpression orderingExpression) + /// The new expression with specified component updated. + public virtual EnumerableExpression ApplyOrdering(OrderingExpression orderingExpression) { - _orderings.Clear(); - AppendOrdering(orderingExpression); + var orderings = new List(); + AppendOrdering(orderings, orderingExpression); + + return new EnumerableExpression(Selector, IsDistinct, Predicate, orderings); } /// /// Appends ordering to the existing orderings of the . /// /// An ordering expression to use for ordering. - public virtual void AppendOrdering(OrderingExpression orderingExpression) + /// The new expression with specified component updated. + public virtual EnumerableExpression AppendOrdering(OrderingExpression orderingExpression) + { + var orderings = Orderings.ToList(); + AppendOrdering(orderings, orderingExpression); + + return new EnumerableExpression(Selector, IsDistinct, Predicate, orderings); + } + + private static void AppendOrdering(List orderings, OrderingExpression orderingExpression) { - if (!_orderings.Any(o => o.Expression.Equals(orderingExpression.Expression))) + if (!orderings.Any(o => o.Expression.Equals(orderingExpression.Expression))) { - _orderings.Add(orderingExpression.Update(orderingExpression.Expression)); + orderings.Add(orderingExpression.Update(orderingExpression.Expression)); } } @@ -151,8 +178,32 @@ public virtual void Print(ExpressionPrinter expressionPrinter) } /// - public override bool Equals(object? obj) => ReferenceEquals(this, obj); + public override bool Equals(object? obj) + => obj != null + && (ReferenceEquals(this, obj) + || obj is EnumerableExpression enumerableExpression + && Equals(enumerableExpression)); + + private bool Equals(EnumerableExpression enumerableExpression) + => IsDistinct == enumerableExpression.IsDistinct + && (Predicate == null + ? enumerableExpression.Predicate == null + : Predicate.Equals(enumerableExpression.Predicate)) + && ExpressionEqualityComparer.Instance.Equals(Selector, enumerableExpression.Selector) + && Orderings.SequenceEqual(enumerableExpression.Orderings); /// - public override int GetHashCode() => RuntimeHelpers.GetHashCode(this); + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(IsDistinct); + hashCode.Add(Selector); + hashCode.Add(Predicate); + foreach (var ordering in Orderings) + { + hashCode.Add(ordering); + } + + return hashCode.ToHashCode(); + } } diff --git a/src/EFCore.Relational/Query/RelationalGroupByShaperExpression.cs b/src/EFCore.Relational/Query/RelationalGroupByShaperExpression.cs new file mode 100644 index 00000000000..fe752b73483 --- /dev/null +++ b/src/EFCore.Relational/Query/RelationalGroupByShaperExpression.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query; + +/// +/// +/// An expression that represents creation of a grouping element in +/// for relational providers. +/// +/// +/// This type is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +public class RelationalGroupByShaperExpression : GroupByShaperExpression +{ + /// + /// Creates a new instance of the class. + /// + /// An expression representing key selector for the grouping result. + /// An expression representing element selector for the grouping result. + /// An expression representing subquery for enumerable over the grouping result. + public RelationalGroupByShaperExpression( + Expression keySelector, + Expression elementSelector, + ShapedQueryExpression groupingEnumerable) + : base(keySelector, groupingEnumerable) + { + ElementSelector = elementSelector; + } + + /// + /// The expression representing the element selector for this grouping result. + /// + public virtual Expression ElementSelector { get; } + + /// + protected override Expression VisitChildren(ExpressionVisitor visitor) + => throw new InvalidOperationException( + CoreStrings.VisitIsNotAllowed($"{nameof(RelationalGroupByShaperExpression)}.{nameof(VisitChildren)}")); + + /// + public override void Print(ExpressionPrinter expressionPrinter) + { + expressionPrinter.AppendLine($"{nameof(RelationalGroupByShaperExpression)}:"); + expressionPrinter.Append("KeySelector: "); + expressionPrinter.Visit(KeySelector); + expressionPrinter.AppendLine(", "); + expressionPrinter.Append("ElementSelector: "); + expressionPrinter.Visit(ElementSelector); + expressionPrinter.AppendLine(", "); + expressionPrinter.Append("GroupingEnumerable:"); + expressionPrinter.Visit(GroupingEnumerable); + expressionPrinter.AppendLine(); + } +} diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index 693995e67ba..fb1205c170f 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -503,7 +503,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp { if (methodCallExpression.Method.IsGenericMethod && methodCallExpression.Method.GetGenericMethodDefinition() == QueryableMethods.AsQueryable - && methodCallExpression.Arguments[0] is GroupByShaperExpression groupByShaperExpression) + && methodCallExpression.Arguments[0] is RelationalGroupByShaperExpression groupByShaperExpression) { return new EnumerableExpression(groupByShaperExpression.ElementSelector); } @@ -517,17 +517,24 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp case nameof(Queryable.Average): if (methodCallExpression.Arguments.Count == 2) { - ProcessSelector(enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); + enumerableExpression = ProcessSelector( + enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); } result = TranslateAggregate(methodCallExpression.Method, enumerableExpression); break; case nameof(Queryable.Count): - if (methodCallExpression.Arguments.Count == 2 - && !ProcessPredicate(enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote())) + if (methodCallExpression.Arguments.Count == 2) { - break; + var newEnumerableExpression = ProcessPredicate( + enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); + if (newEnumerableExpression == null) + { + break; + } + + enumerableExpression = newEnumerableExpression; } result = TranslateAggregate(methodCallExpression.Method, enumerableExpression); @@ -535,27 +542,25 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp case nameof(Queryable.Distinct): - if (enumerableExpression.Selector is EntityShaperExpression entityShaperExpression - && entityShaperExpression.EntityType.FindPrimaryKey() != null) - { - result = enumerableExpression; - } - else if (!enumerableExpression.IsDistinct) - { - enumerableExpression.ApplyDistinct(); - result = enumerableExpression; - } - else - { - result = null; - } + result = enumerableExpression.Selector is EntityShaperExpression entityShaperExpression + && entityShaperExpression.EntityType.FindPrimaryKey() != null + ? enumerableExpression + : !enumerableExpression.IsDistinct + ? enumerableExpression.ApplyDistinct() + : (Expression?)null; break; case nameof(Queryable.LongCount): - if (methodCallExpression.Arguments.Count == 2 - && !ProcessPredicate(enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote())) + if (methodCallExpression.Arguments.Count == 2) { - break; + var newEnumerableExpression = ProcessPredicate( + enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); + if (newEnumerableExpression == null) + { + break; + } + + enumerableExpression = newEnumerableExpression; } result = TranslateAggregate(methodCallExpression.Method, enumerableExpression); @@ -564,7 +569,8 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp case nameof(Queryable.Max): if (methodCallExpression.Arguments.Count == 2) { - ProcessSelector(enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); + enumerableExpression = ProcessSelector( + enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); } result = TranslateAggregate(methodCallExpression.Method, enumerableExpression); @@ -573,42 +579,31 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp case nameof(Queryable.Min): if (methodCallExpression.Arguments.Count == 2) { - ProcessSelector(enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); + enumerableExpression = ProcessSelector( + enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); } result = TranslateAggregate(methodCallExpression.Method, enumerableExpression); break; case nameof(Queryable.OrderBy): - if (ProcessOrderByThenBy( - enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote(), thenBy: false, ascending: true)) - { - result = enumerableExpression; - } + result = ProcessOrderByThenBy( + enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote(), thenBy: false, ascending: true); break; case nameof(Queryable.OrderByDescending): - if (ProcessOrderByThenBy( - enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote(), thenBy: false, ascending: false)) - { - result = enumerableExpression; - } + result = ProcessOrderByThenBy( + enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote(), thenBy: false, ascending: false); break; case nameof(Queryable.ThenBy): - if (ProcessOrderByThenBy( - enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote(), thenBy: true, ascending: true)) - { - result = enumerableExpression; - } + result = ProcessOrderByThenBy( + enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote(), thenBy: true, ascending: true); break; case nameof(Queryable.ThenByDescending): - if (ProcessOrderByThenBy( - enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote(), thenBy: true, ascending: false)) - { - result = enumerableExpression; - } + result = ProcessOrderByThenBy( + enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote(), thenBy: true, ascending: false); break; case nameof(Queryable.Select): @@ -618,17 +613,15 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp case nameof(Queryable.Sum): if (methodCallExpression.Arguments.Count == 2) { - ProcessSelector(enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); + enumerableExpression = ProcessSelector( + enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); } result = TranslateAggregate(methodCallExpression.Method, enumerableExpression); break; case nameof(Queryable.Where): - if (ProcessPredicate(enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote())) - { - result = enumerableExpression; - } + result = ProcessPredicate(enumerableExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); break; } @@ -1151,46 +1144,34 @@ private static Expression RemapLambda(EnumerableExpression enumerableExpression, => ReplacingExpressionVisitor.Replace(lambdaExpression.Parameters[0], enumerableExpression.Selector, lambdaExpression.Body); private static EnumerableExpression ProcessSelector(EnumerableExpression enumerableExpression, LambdaExpression lambdaExpression) - { - var selectorBody = RemapLambda(enumerableExpression, lambdaExpression); - enumerableExpression.ApplySelector(selectorBody); - return enumerableExpression; - } + => enumerableExpression.ApplySelector(RemapLambda(enumerableExpression, lambdaExpression)); - private bool ProcessOrderByThenBy( + private EnumerableExpression? ProcessOrderByThenBy( EnumerableExpression enumerableExpression, LambdaExpression lambdaExpression, bool thenBy, bool ascending) { var lambdaBody = RemapLambda(enumerableExpression, lambdaExpression); var keySelector = TranslateInternal(lambdaBody); if (keySelector == null) { - return false; + return null; } var orderingExpression = new OrderingExpression(keySelector, ascending); - if (thenBy) - { - enumerableExpression.AppendOrdering(orderingExpression); - } - else - { - enumerableExpression.ApplyOrdering(orderingExpression); - } - - return true; + return thenBy + ? enumerableExpression.AppendOrdering(orderingExpression) + : enumerableExpression.ApplyOrdering(orderingExpression); } - private bool ProcessPredicate(EnumerableExpression enumerableExpression, LambdaExpression lambdaExpression) + private EnumerableExpression? ProcessPredicate(EnumerableExpression enumerableExpression, LambdaExpression lambdaExpression) { var lambdaBody = RemapLambda(enumerableExpression, lambdaExpression); var predicate = TranslateInternal(lambdaBody); if (predicate == null) { - return false; + return null; } - enumerableExpression.ApplyPredicate(predicate); - return true; + return enumerableExpression.ApplyPredicate(predicate); } private SqlExpression? TranslateAggregate(MethodInfo methodInfo, EnumerableExpression enumerableExpression) diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index dfd08191308..53373a1dc7f 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -1456,13 +1456,13 @@ public void ApplyGrouping(Expression keySelector) } /// - /// Applies grouping from given key selector and generate to shape results. + /// Applies grouping from given key selector and generate to shape results. /// /// An key selector expression for the GROUP BY. /// The shaper expression for current query. /// The sql expression factory to use. - /// A which represents the result of the grouping operation. - public GroupByShaperExpression ApplyGrouping( + /// A which represents the result of the grouping operation. + public RelationalGroupByShaperExpression ApplyGrouping( Expression keySelector, Expression shaperExpression, ISqlExpressionFactory sqlExpressionFactory) @@ -1524,7 +1524,7 @@ public GroupByShaperExpression ApplyGrouping( } } - return new GroupByShaperExpression( + return new RelationalGroupByShaperExpression( keySelector, shaperExpression, new ShapedQueryExpression( diff --git a/src/EFCore/Query/GroupByShaperExpression.cs b/src/EFCore/Query/GroupByShaperExpression.cs index 078791d5532..456208b643b 100644 --- a/src/EFCore/Query/GroupByShaperExpression.cs +++ b/src/EFCore/Query/GroupByShaperExpression.cs @@ -22,15 +22,12 @@ public class GroupByShaperExpression : Expression, IPrintableExpression /// Creates a new instance of the class. /// /// An expression representing key selector for the grouping result. - /// An expression representing element selector for the grouping result. /// An expression representing subquery for enumerable over the grouping result. public GroupByShaperExpression( Expression keySelector, - Expression elementSelector, ShapedQueryExpression groupingEnumerable) { KeySelector = keySelector; - ElementSelector = elementSelector; GroupingEnumerable = groupingEnumerable; } @@ -39,11 +36,6 @@ public GroupByShaperExpression( /// public virtual Expression KeySelector { get; } - /// - /// The expression representing the element selector for this grouping result. - /// - public virtual Expression ElementSelector { get; } - /// /// The expression representing the subquery for the enumerable over this grouping result. /// @@ -59,19 +51,32 @@ public sealed override ExpressionType NodeType /// protected override Expression VisitChildren(ExpressionVisitor visitor) - => throw new InvalidOperationException( - CoreStrings.VisitIsNotAllowed($"{nameof(GroupByShaperExpression)}.{nameof(VisitChildren)}")); + { + var keySelector = visitor.Visit(KeySelector); + var groupingEnumerable = (ShapedQueryExpression)visitor.Visit(GroupingEnumerable); + + return Update(keySelector, groupingEnumerable); + } + + /// + /// Creates a new expression that is like this one, but using the supplied children. If all of the children are the same, it will + /// return this expression. + /// + /// The property of the result. + /// The property of the result. + /// This expression if no children changed, or an expression with the updated children. + public virtual GroupByShaperExpression Update(Expression keySelector, ShapedQueryExpression groupingEnumerable) + => keySelector != KeySelector || groupingEnumerable != GroupingEnumerable + ? new GroupByShaperExpression(keySelector, groupingEnumerable) + : this; /// - void IPrintableExpression.Print(ExpressionPrinter expressionPrinter) + public virtual void Print(ExpressionPrinter expressionPrinter) { expressionPrinter.AppendLine($"{nameof(GroupByShaperExpression)}:"); expressionPrinter.Append("KeySelector: "); expressionPrinter.Visit(KeySelector); expressionPrinter.AppendLine(", "); - expressionPrinter.Append("ElementSelector: "); - expressionPrinter.Visit(ElementSelector); - expressionPrinter.AppendLine(", "); expressionPrinter.Append("GroupingEnumerable:"); expressionPrinter.Visit(GroupingEnumerable); expressionPrinter.AppendLine(); From d32a86e6fc1ebcd925c5604861fcf924a1ec1baf Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Fri, 13 May 2022 18:44:26 -0700 Subject: [PATCH 140/143] Query: Add interface to get ITableBase for root level table sources (#28020) --- ...yableMethodTranslatingExpressionVisitor.cs | 14 +++++--- .../Query/SqlExpressionFactory.cs | 8 +---- .../Query/SqlExpressions/FromSqlExpression.cs | 32 ++++++++++++++----- .../SqlExpressions/ITableBasedExpression.cs | 21 ++++++++++++ .../Query/SqlExpressions/SelectExpression.cs | 8 +---- .../Query/SqlExpressions/TableExpression.cs | 5 ++- .../TableValuedFunctionExpression.cs | 5 ++- 7 files changed, 64 insertions(+), 29 deletions(-) create mode 100644 src/EFCore.Relational/Query/SqlExpressions/ITableBasedExpression.cs diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 36eb7b2ce46..31a745a013b 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -77,8 +77,7 @@ protected override Expression VisitExtension(Expression extensionExpression) _sqlExpressionFactory.Select( fromSqlQueryRootExpression.EntityType, new FromSqlExpression( - fromSqlQueryRootExpression.EntityType.GetDefaultMappings().Single().Table.Name[..1] - .ToLowerInvariant(), + fromSqlQueryRootExpression.EntityType.GetDefaultMappings().Single().Table, fromSqlQueryRootExpression.Sql, fromSqlQueryRootExpression.Argument))); @@ -127,9 +126,14 @@ protected override Expression VisitExtension(Expression extensionExpression) when queryRootExpression.GetType() == typeof(QueryRootExpression) && queryRootExpression.EntityType.GetSqlQueryMappings().FirstOrDefault(m => m.IsDefaultSqlQueryMapping)?.SqlQuery is ISqlQuery sqlQuery: - return Visit( - new FromSqlQueryRootExpression( - queryRootExpression.EntityType, sqlQuery.Sql, Expression.Constant(Array.Empty(), typeof(object[])))); + return CreateShapedQueryExpression( + queryRootExpression.EntityType, + _sqlExpressionFactory.Select( + queryRootExpression.EntityType, + new FromSqlExpression( + queryRootExpression.EntityType.GetDefaultMappings().Single().Table, + sqlQuery.Sql, + Expression.Constant(Array.Empty(), typeof(object[]))))); case GroupByShaperExpression groupByShaperExpression: var groupShapedQueryExpression = groupByShaperExpression.GroupingEnumerable; diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index d0f8491b8f5..1f0ff84c295 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -637,13 +637,7 @@ private void AddConditions(SelectExpression selectExpression, IEntityType entity return; } - var table = selectExpression.Tables[0] switch - { - TableExpression te => te.Table, - TableValuedFunctionExpression tvfe => tvfe.StoreFunction, - _ => entityType.GetDefaultMappings().Single().Table, - }; - + var table = ((ITableBasedExpression)selectExpression.Tables[0]).Table; if (table.IsOptional(entityType)) { var entityProjectionExpression = GetMappedEntityProjectionExpression(selectExpression); diff --git a/src/EFCore.Relational/Query/SqlExpressions/FromSqlExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/FromSqlExpression.cs index 41cd4879957..344ff9bb7f8 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/FromSqlExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/FromSqlExpression.cs @@ -14,28 +14,40 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// not used in application code. /// /// -public class FromSqlExpression : TableExpressionBase, IClonableTableExpressionBase +public class FromSqlExpression : TableExpressionBase, IClonableTableExpressionBase, ITableBasedExpression { + private readonly ITableBase _table; + /// /// Creates a new instance of the class. /// - /// A string alias for the table source. + /// A default table base associated with this table source. /// A user-provided custom SQL for the table source. /// A user-provided parameters to pass to the custom SQL. - public FromSqlExpression(string alias, string sql, Expression arguments) - : this(alias, sql, arguments, annotations: null) + public FromSqlExpression(ITableBase defaultTableBase, string sql, Expression arguments) + : this(defaultTableBase.Name[..1].ToLowerInvariant(), defaultTableBase, sql, arguments, annotations: null) { - Sql = sql; - Arguments = arguments; } + // See issue#21660/21627 + ///// + ///// Creates a new instance of the class. + ///// + ///// A sql query associated with this table source. + //public FromSqlExpression(ISqlQuery sqlQuery) + // : this(sqlQuery, sqlQuery.Sql, Constant(Array.Empty(), typeof(object[]))) + //{ + //} + private FromSqlExpression( string alias, + ITableBase tableBase, string sql, Expression arguments, IEnumerable? annotations) : base(alias, annotations) { + _table = tableBase; Sql = sql; Arguments = arguments; } @@ -68,16 +80,19 @@ public override string? Alias /// This expression if no children changed, or an expression with the updated children. public virtual FromSqlExpression Update(Expression arguments) => arguments != Arguments - ? new FromSqlExpression(Alias, Sql, arguments) + ? new FromSqlExpression(Alias, _table, Sql, arguments, GetAnnotations()) : this; + /// + ITableBase ITableBasedExpression.Table => _table; + /// protected override Expression VisitChildren(ExpressionVisitor visitor) => this; /// public virtual TableExpressionBase Clone() - => new FromSqlExpression(Alias, Sql, Arguments, GetAnnotations()); + => new FromSqlExpression(Alias, _table, Sql, Arguments, GetAnnotations()); /// protected override void Print(ExpressionPrinter expressionPrinter) @@ -95,6 +110,7 @@ public override bool Equals(object? obj) private bool Equals(FromSqlExpression fromSqlExpression) => base.Equals(fromSqlExpression) + && _table == fromSqlExpression._table && Sql == fromSqlExpression.Sql && ExpressionEqualityComparer.Instance.Equals(Arguments, fromSqlExpression.Arguments); diff --git a/src/EFCore.Relational/Query/SqlExpressions/ITableBasedExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/ITableBasedExpression.cs new file mode 100644 index 00000000000..5940d74c7cd --- /dev/null +++ b/src/EFCore.Relational/Query/SqlExpressions/ITableBasedExpression.cs @@ -0,0 +1,21 @@ +// 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.SqlExpressions; + +/// +/// +/// An interface that gives access to associated with given table source. +/// +/// +/// This type is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +public interface ITableBasedExpression +{ + /// + /// The associated with given table source. + /// + ITableBase Table { get; } +} diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 53373a1dc7f..d835a971062 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -374,13 +374,7 @@ internal SelectExpression(IEntityType entityType, TableExpressionBase tableExpre throw new InvalidOperationException(RelationalStrings.SelectExpressionNonTphWithCustomTable(entityType.DisplayName())); } - var table = tableExpressionBase switch - { - TableExpression tableExpression => tableExpression.Table, - TableValuedFunctionExpression tableValuedFunctionExpression => tableValuedFunctionExpression.StoreFunction, - _ => entityType.GetDefaultMappings().Single().Table - }; - + var table = ((ITableBasedExpression)tableExpressionBase).Table; var tableReferenceExpression = new TableReferenceExpression(this, tableExpressionBase.Alias!); AddTable(tableExpressionBase, tableReferenceExpression); diff --git a/src/EFCore.Relational/Query/SqlExpressions/TableExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/TableExpression.cs index dabf91e9df9..959cfdd2f34 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/TableExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/TableExpression.cs @@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// application or database provider code. If this is a problem for your application or provider, then please file /// an issue at github.com/dotnet/efcore. /// -public sealed class TableExpression : TableExpressionBase, IClonableTableExpressionBase +public sealed class TableExpression : TableExpressionBase, IClonableTableExpressionBase, ITableBasedExpression { internal TableExpression(ITableBase table) : this(table, annotations: null) @@ -28,6 +28,9 @@ private TableExpression(ITableBase table, IEnumerable? annotations) Table = table; } + /// + ITableBase ITableBasedExpression.Table => Table; + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/TableValuedFunctionExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/TableValuedFunctionExpression.cs index 97df39b551f..db40d23cd9d 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/TableValuedFunctionExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/TableValuedFunctionExpression.cs @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// not used in application code. /// /// -public class TableValuedFunctionExpression : TableExpressionBase +public class TableValuedFunctionExpression : TableExpressionBase, ITableBasedExpression { /// /// Creates a new instance of the class. @@ -61,6 +61,9 @@ public override string? Alias /// public virtual IReadOnlyList Arguments { get; } + /// + ITableBase ITableBasedExpression.Table => StoreFunction; + /// protected override Expression VisitChildren(ExpressionVisitor visitor) { From 813322f86dddcec20543c2ba2b6b9b8b3d7e034a Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 16 May 2022 17:29:37 +0200 Subject: [PATCH 141/143] Force NullabilityInfoContextSupport=true in user projects (#27916) To make NRT-based model building work correctly in aggressively trimmed applications (e.g. MAUI). Fixes #27474 --- src/EFCore/EFCore.csproj | 7 +++++++ .../net6.0/Microsoft.EntityFrameworkCore.props | 9 +++++++++ 2 files changed, 16 insertions(+) create mode 100644 src/EFCore/buildTransitive/net6.0/Microsoft.EntityFrameworkCore.props diff --git a/src/EFCore/EFCore.csproj b/src/EFCore/EFCore.csproj index 038f1d38f95..b082dc6927f 100644 --- a/src/EFCore/EFCore.csproj +++ b/src/EFCore/EFCore.csproj @@ -31,6 +31,13 @@ Microsoft.EntityFrameworkCore.DbSet + + + True + buildTransitive + + + TextTemplatingFileGenerator diff --git a/src/EFCore/buildTransitive/net6.0/Microsoft.EntityFrameworkCore.props b/src/EFCore/buildTransitive/net6.0/Microsoft.EntityFrameworkCore.props new file mode 100644 index 00000000000..9a8580ae56b --- /dev/null +++ b/src/EFCore/buildTransitive/net6.0/Microsoft.EntityFrameworkCore.props @@ -0,0 +1,9 @@ + + + + true + + From 82d59b569859c02295e78a2bd806f628e1f9bad6 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 16 May 2022 17:58:52 +0000 Subject: [PATCH 142/143] Update dependencies from https://github.com/dotnet/arcade build 20220512.8 (#28033) [main] Update dependencies from dotnet/arcade --- NuGet.config | 2 -- eng/Version.Details.xml | 8 ++++---- eng/common/init-tools-native.ps1 | 11 ++++++----- eng/common/internal/Tools.csproj | 3 +++ global.json | 4 ++-- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/NuGet.config b/NuGet.config index a172239a72c..e1d761f641c 100644 --- a/NuGet.config +++ b/NuGet.config @@ -20,8 +20,6 @@ - - diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index af3af341d30..0f27bd333be 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -39,13 +39,13 @@ - + https://github.com/dotnet/arcade - ba1c3aff4be864c493031d989259ef92aaa23fc3 + b7796f653e48e001123963f17387c052891b48e6 - + https://github.com/dotnet/arcade - ba1c3aff4be864c493031d989259ef92aaa23fc3 + b7796f653e48e001123963f17387c052891b48e6 diff --git a/eng/common/init-tools-native.ps1 b/eng/common/init-tools-native.ps1 index 413adea4365..24a5e65de1b 100644 --- a/eng/common/init-tools-native.ps1 +++ b/eng/common/init-tools-native.ps1 @@ -93,7 +93,7 @@ try { $ToolVersion = "" } $ArcadeToolsDirectory = "C:\arcade-tools" - if (Test-Path $ArcadeToolsDirectory -eq $False) { + if (-not (Test-Path $ArcadeToolsDirectory)) { Write-Error "Arcade tools directory '$ArcadeToolsDirectory' was not found; artifacts were not properly installed." exit 1 } @@ -103,13 +103,14 @@ try { exit 1 } $BinPathFile = "$($ToolDirectory.FullName)\binpath.txt" - if (Test-Path -Path "$BinPathFile" -eq $False) { + if (-not (Test-Path -Path "$BinPathFile")) { Write-Error "Unable to find binpath.txt in '$($ToolDirectory.FullName)' ($ToolName $ToolVersion); artifact is either installed incorrectly or is not a bootstrappable tool." exit 1 } $BinPath = Get-Content "$BinPathFile" - Write-Host "Adding $ToolName to the path ($(Convert-Path -Path $BinPath))..." - Write-Host "##vso[task.prependpath]$(Convert-Path -Path $BinPath)" + $ToolPath = Convert-Path -Path $BinPath + Write-Host "Adding $ToolName to the path ($ToolPath)..." + Write-Host "##vso[task.prependpath]$ToolPath" } } exit 0 @@ -188,7 +189,7 @@ try { Write-Host "##vso[task.prependpath]$(Convert-Path -Path $InstallBin)" return $InstallBin } - else { + elseif (-not ($PathPromotion)) { Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message 'Native tools install directory does not exist, installation failed' exit 1 } diff --git a/eng/common/internal/Tools.csproj b/eng/common/internal/Tools.csproj index beb9c4648ea..7f5ce6d6081 100644 --- a/eng/common/internal/Tools.csproj +++ b/eng/common/internal/Tools.csproj @@ -8,6 +8,9 @@ + + + diff --git a/global.json b/global.json index b702c951952..d76f859b578 100644 --- a/global.json +++ b/global.json @@ -8,7 +8,7 @@ "rollForward": "latestMajor" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22255.2", - "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22255.2" + "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22262.8", + "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22262.8" } } From 9b596aec88eac3f1ac0243e1bd4303863aa5635c Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 16 May 2022 18:23:17 +0000 Subject: [PATCH 143/143] Update dependencies from https://github.com/dotnet/runtime build 20220513.6 (#28032) [main] Update dependencies from dotnet/runtime --- eng/Version.Details.xml | 36 ++++++++++++++++++------------------ eng/Versions.props | 18 +++++++++--------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 0f27bd333be..ce93d830326 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,41 +1,41 @@ - + https://github.com/dotnet/runtime - 3e5517beb897faf4592d23f036446561da1e5c23 + c96e47047af2816cac1f2e240068f71628ef105d - + https://github.com/dotnet/runtime - 3e5517beb897faf4592d23f036446561da1e5c23 + c96e47047af2816cac1f2e240068f71628ef105d - + https://github.com/dotnet/runtime - 3e5517beb897faf4592d23f036446561da1e5c23 + c96e47047af2816cac1f2e240068f71628ef105d - + https://github.com/dotnet/runtime - 3e5517beb897faf4592d23f036446561da1e5c23 + c96e47047af2816cac1f2e240068f71628ef105d - + https://github.com/dotnet/runtime - 3e5517beb897faf4592d23f036446561da1e5c23 + c96e47047af2816cac1f2e240068f71628ef105d - + https://github.com/dotnet/runtime - 3e5517beb897faf4592d23f036446561da1e5c23 + c96e47047af2816cac1f2e240068f71628ef105d - + https://github.com/dotnet/runtime - 3e5517beb897faf4592d23f036446561da1e5c23 + c96e47047af2816cac1f2e240068f71628ef105d - + https://github.com/dotnet/runtime - 3e5517beb897faf4592d23f036446561da1e5c23 + c96e47047af2816cac1f2e240068f71628ef105d - + https://github.com/dotnet/runtime - 3e5517beb897faf4592d23f036446561da1e5c23 + c96e47047af2816cac1f2e240068f71628ef105d diff --git a/eng/Versions.props b/eng/Versions.props index 1fcfc1894a8..15fd7515ff0 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -15,15 +15,15 @@ False - 7.0.0-preview.5.22258.4 - 7.0.0-preview.5.22258.4 - 7.0.0-preview.5.22258.4 - 7.0.0-preview.5.22258.4 - 7.0.0-preview.5.22258.4 - 7.0.0-preview.5.22258.4 - 7.0.0-preview.5.22258.4 - 7.0.0-preview.5.22258.4 - 7.0.0-preview.5.22258.4 + 7.0.0-preview.5.22263.6 + 7.0.0-preview.5.22263.6 + 7.0.0-preview.5.22263.6 + 7.0.0-preview.5.22263.6 + 7.0.0-preview.5.22263.6 + 7.0.0-preview.5.22263.6 + 7.0.0-preview.5.22263.6 + 7.0.0-preview.5.22263.6 + 7.0.0-preview.5.22263.6 4.1.0