From a4db3b78e910f5745514fdd3d22fcf2a53dde801 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Thu, 14 Sep 2023 03:40:57 +0000 Subject: [PATCH 1/6] Update dependencies from https://github.com/dotnet/runtime build 20230913.10 (#31732) [release/8.0] Update dependencies from dotnet/runtime --- eng/Version.Details.xml | 52 ++++++++++++++++++++--------------------- eng/Versions.props | 26 ++++++++++----------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 79e82071daa..da8fbb7253c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,58 +1,58 @@ - + https://github.com/dotnet/runtime - 0f3d1ed15f64cb9a9d4f47d1a1ed433e0047e16c + f490d66ef5e1b90af1a0fad86702c9c54caaa606 - + https://github.com/dotnet/runtime - 0f3d1ed15f64cb9a9d4f47d1a1ed433e0047e16c + f490d66ef5e1b90af1a0fad86702c9c54caaa606 - + https://github.com/dotnet/runtime - 0f3d1ed15f64cb9a9d4f47d1a1ed433e0047e16c + f490d66ef5e1b90af1a0fad86702c9c54caaa606 - + https://github.com/dotnet/runtime - 0f3d1ed15f64cb9a9d4f47d1a1ed433e0047e16c + f490d66ef5e1b90af1a0fad86702c9c54caaa606 - + https://github.com/dotnet/runtime - 0f3d1ed15f64cb9a9d4f47d1a1ed433e0047e16c + f490d66ef5e1b90af1a0fad86702c9c54caaa606 - + https://github.com/dotnet/runtime - 0f3d1ed15f64cb9a9d4f47d1a1ed433e0047e16c + f490d66ef5e1b90af1a0fad86702c9c54caaa606 - + https://github.com/dotnet/runtime - 0f3d1ed15f64cb9a9d4f47d1a1ed433e0047e16c + f490d66ef5e1b90af1a0fad86702c9c54caaa606 - + https://github.com/dotnet/runtime - 0f3d1ed15f64cb9a9d4f47d1a1ed433e0047e16c + f490d66ef5e1b90af1a0fad86702c9c54caaa606 - + https://github.com/dotnet/runtime - 0f3d1ed15f64cb9a9d4f47d1a1ed433e0047e16c + f490d66ef5e1b90af1a0fad86702c9c54caaa606 - + https://github.com/dotnet/runtime - 0f3d1ed15f64cb9a9d4f47d1a1ed433e0047e16c + f490d66ef5e1b90af1a0fad86702c9c54caaa606 - + https://github.com/dotnet/runtime - 0f3d1ed15f64cb9a9d4f47d1a1ed433e0047e16c + f490d66ef5e1b90af1a0fad86702c9c54caaa606 - + https://github.com/dotnet/runtime - 0f3d1ed15f64cb9a9d4f47d1a1ed433e0047e16c + f490d66ef5e1b90af1a0fad86702c9c54caaa606 - + https://github.com/dotnet/runtime - 0f3d1ed15f64cb9a9d4f47d1a1ed433e0047e16c + f490d66ef5e1b90af1a0fad86702c9c54caaa606 diff --git a/eng/Versions.props b/eng/Versions.props index 2c2b3a3c908..1bbf3ef9f37 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -16,19 +16,19 @@ False - 8.0.0-rc.2.23462.12 - 8.0.0-rc.2.23462.12 - 8.0.0-rc.2.23462.12 - 8.0.0-rc.2.23462.12 - 8.0.0-rc.2.23462.12 - 8.0.0-rc.2.23462.12 - 8.0.0-rc.2.23462.12 - 8.0.0-rc.2.23462.12 - 8.0.0-rc.2.23462.12 - 8.0.0-rc.2.23462.12 - 8.0.0-rc.2.23462.12 - 8.0.0-rc.2.23462.12 - 8.0.0-rc.2.23462.12 + 8.0.0-rc.2.23463.10 + 8.0.0-rc.2.23463.10 + 8.0.0-rc.2.23463.10 + 8.0.0-rc.2.23463.10 + 8.0.0-rc.2.23463.10 + 8.0.0-rc.2.23463.10 + 8.0.0-rc.2.23463.10 + 8.0.0-rc.2.23463.10 + 8.0.0-rc.2.23463.10 + 8.0.0-rc.2.23463.10 + 8.0.0-rc.2.23463.10 + 8.0.0-rc.2.23463.10 + 8.0.0-rc.2.23463.10 8.0.0-beta.23451.1 From 301b01b5f4f2fe30f5bc417c1ee2503433018628 Mon Sep 17 00:00:00 2001 From: Chad Gilbert Date: Thu, 14 Sep 2023 08:40:46 -0400 Subject: [PATCH 2/6] Treat Sql Error Number 1222 as Transient (#31725) Error number 1222 is defined as > Lock request time out period exceeded And official Microsoft recommendation is to retry the transaction after it has been aborted [Source](https://learn.microsoft.com/en-us/sql/relational-databases/errors-events/mssqlserver-1222-database-engine-error) Fixes #31724 --- .../Storage/Internal/SqlServerTransientExceptionDetector.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerTransientExceptionDetector.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerTransientExceptionDetector.cs index 0c186588fb9..e41457194f3 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerTransientExceptionDetector.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerTransientExceptionDetector.cs @@ -590,6 +590,9 @@ public static bool ShouldRetryOn(Exception? ex) // SQL Error Code: 1232 // Failed to acquire lock with lock manager service, it could be due to many reasons including transient service failure. case 1232: + // SQL Error Code: 1222 + // Lock request time out period exceeded. + case 1222: // SQL Error Code: 1221 // The Database Engine is attempting to release a group of locks that are not currently held by the transaction. // Retry the transaction. If the problem persists, contact your support provider. From 47f8393cc97491c6212771b9038a803890d58686 Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Thu, 14 Sep 2023 08:57:31 -0700 Subject: [PATCH 3/6] Fix to #30996 - Incorrect translation of comparison of current value with owned type default value (#31714) In optional dependent table sharing scenario, when comparing the dependent to null we first look for any required properties (at least one of them needs to be null), and if we don't have any required properties we look at optional ones (even though this is somewhat ambiguous). Error was that we did similar check as with required properties (i.e. at least one of them must be null), but what we should be doing is checking that all of them are null. Fixes #30996 --- ...lationalSqlTranslatingExpressionVisitor.cs | 14 ++-- .../OwnedEntityQueryRelationalTestBase.cs | 64 +++++++++++++++++++ .../Query/OwnedEntityQuerySqlServerTest.cs | 32 +++++++++- 3 files changed, 104 insertions(+), 6 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index fa573b9b99d..d9e35874574 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -1791,19 +1791,23 @@ bool TryRewriteEntityEquality([NotNullWhen(true)] out Expression? result) if (allNonPrincipalSharedNonPkProperties.Count != 0 && allNonPrincipalSharedNonPkProperties.All(p => p.IsNullable)) { - var atLeastOneNonNullValueInNullablePropertyCondition = allNonPrincipalSharedNonPkProperties + // if we don't have any required properties to properly check the nullability, + // we rely on optional ones (somewhat unreliably) + // - if entity is to be null, all the properties must be null + // - if the entity is to be not null, at least one property must be not null + var optionalPropertiesCondition = allNonPrincipalSharedNonPkProperties .Select( p => Infrastructure.ExpressionExtensions.CreateEqualsExpression( CreatePropertyAccessExpression(nonNullEntityReference, p), Expression.Constant(null, p.ClrType.MakeNullable()), nodeType != ExpressionType.Equal)) - .Aggregate((l, r) => nodeType == ExpressionType.Equal ? Expression.OrElse(l, r) : Expression.AndAlso(l, r)); + .Aggregate((l, r) => nodeType == ExpressionType.Equal ? Expression.AndAlso(l, r) : Expression.OrElse(l, r)); condition = condition == null - ? atLeastOneNonNullValueInNullablePropertyCondition + ? optionalPropertiesCondition : nodeType == ExpressionType.Equal - ? Expression.OrElse(condition, atLeastOneNonNullValueInNullablePropertyCondition) - : Expression.AndAlso(condition, atLeastOneNonNullValueInNullablePropertyCondition); + ? Expression.OrElse(condition, optionalPropertiesCondition) + : Expression.AndAlso(condition, optionalPropertiesCondition); } if (condition != null) diff --git a/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs index 54e78c7f789..f559e7266ea 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs @@ -284,6 +284,64 @@ public virtual async Task Owned_entity_with_all_null_properties_entity_equality_ }); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Owned_entity_with_all_null_properties_in_compared_to_null_in_conditional_projection(bool async) + { + var contextFactory = await InitializeAsync(seed: c => c.Seed()); + + using var context = contextFactory.CreateContext(); + var query = context.RotRutCases + .AsNoTracking() + .OrderBy(e => e.Id) + .Select(e => e.Rot == null ? null : new RotDto { MyApartmentNo = e.Rot.ApartmentNo, MyServiceType = e.Rot.ServiceType }); + + var result = async + ? await query.ToListAsync() + : query.ToList(); + + Assert.Collection( + result, + t => + { + Assert.Equal("1", t.MyApartmentNo); + Assert.Equal(1, t.MyServiceType); + }, + t => + { + Assert.Null(t); + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Owned_entity_with_all_null_properties_in_compared_to_non_null_in_conditional_projection(bool async) + { + var contextFactory = await InitializeAsync(seed: c => c.Seed()); + + using var context = contextFactory.CreateContext(); + var query = context.RotRutCases + .AsNoTracking() + .OrderBy(e => e.Id) + .Select(e => e.Rot != null ? new RotDto { MyApartmentNo = e.Rot.ApartmentNo, MyServiceType = e.Rot.ServiceType } : null); + + var result = async + ? await query.ToListAsync() + : query.ToList(); + + Assert.Collection( + result, + t => + { + Assert.Equal("1", t.MyApartmentNo); + Assert.Equal(1, t.MyServiceType); + }, + t => + { + Assert.Null(t); + }); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual async Task Owned_entity_with_all_null_properties_property_access_when_not_containing_another_owned_entity(bool async) @@ -364,6 +422,12 @@ public class Rot public string ApartmentNo { get; set; } } + public class RotDto + { + public int? MyServiceType { get; set; } + public string MyApartmentNo { get; set; } + } + public class Rut { public int? Value { get; set; } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedEntityQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedEntityQuerySqlServerTest.cs index 804c9ea3db0..12c52e09b30 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedEntityQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedEntityQuerySqlServerTest.cs @@ -166,7 +166,37 @@ public override async Task Owned_entity_with_all_null_properties_entity_equality """ SELECT [r].[Id], [r].[Rot_ApartmentNo], [r].[Rot_ServiceType] FROM [RotRutCases] AS [r] -WHERE [r].[Rot_ApartmentNo] IS NOT NULL AND [r].[Rot_ServiceType] IS NOT NULL +WHERE [r].[Rot_ApartmentNo] IS NOT NULL OR [r].[Rot_ServiceType] IS NOT NULL +"""); + } + + public override async Task Owned_entity_with_all_null_properties_in_compared_to_null_in_conditional_projection(bool async) + { + await base.Owned_entity_with_all_null_properties_in_compared_to_null_in_conditional_projection(async); + + AssertSql( +""" +SELECT CASE + WHEN [r].[Rot_ApartmentNo] IS NULL AND [r].[Rot_ServiceType] IS NULL THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END, [r].[Rot_ApartmentNo], [r].[Rot_ServiceType] +FROM [RotRutCases] AS [r] +ORDER BY [r].[Id] +"""); + } + + public override async Task Owned_entity_with_all_null_properties_in_compared_to_non_null_in_conditional_projection(bool async) + { + await base.Owned_entity_with_all_null_properties_in_compared_to_non_null_in_conditional_projection(async); + + AssertSql( +""" +SELECT CASE + WHEN [r].[Rot_ApartmentNo] IS NOT NULL OR [r].[Rot_ServiceType] IS NOT NULL THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END, [r].[Rot_ApartmentNo], [r].[Rot_ServiceType] +FROM [RotRutCases] AS [r] +ORDER BY [r].[Id] """); } From be31c610510b653d168515d6f77bcb7e3080edaf Mon Sep 17 00:00:00 2001 From: Harry Date: Thu, 14 Sep 2023 11:56:49 -0500 Subject: [PATCH 4/6] Add support for DATA_COMPRESSION and SORT_IN_TEMPDB SQL Server index options (#30831) Closes #30408 --- src/EFCore.SqlServer/DataCompressionType.cs | 29 +++ .../SqlServerAnnotationCodeGenerator.cs | 10 + ...verCSharpRuntimeAnnotationCodeGenerator.cs | 4 + .../SqlServerIndexBuilderExtensions.cs | 172 +++++++++++++ .../Extensions/SqlServerIndexExtensions.cs | 132 ++++++++++ .../SqlServerRuntimeModelConvention.cs | 2 + .../Internal/SqlServerAnnotationNames.cs | 16 ++ .../Internal/SqlServerAnnotationProvider.cs | 10 + .../Internal/SqlServerIndexExtensions.cs | 34 +++ .../SqlServerMigrationsSqlGenerator.cs | 21 ++ .../Properties/SqlServerStrings.Designer.cs | 16 ++ .../Properties/SqlServerStrings.resx | 6 + .../Migrations/ModelSnapshotSqlServerTest.cs | 111 ++++++++ .../CSharpRuntimeModelCodeGeneratorTest.cs | 12 +- .../Migrations/MigrationsSqlServerTest.cs | 99 ++++++++ .../SqlServerMigrationsSqlGeneratorTest.cs | 43 ++++ .../SqlServerModelValidatorTest.cs | 32 +++ .../SqlServerBuilderExtensionsTest.cs | 66 +++++ .../Migrations/SqlServerModelDifferTest.cs | 237 ++++++++++++++++++ 19 files changed, 1051 insertions(+), 1 deletion(-) create mode 100644 src/EFCore.SqlServer/DataCompressionType.cs diff --git a/src/EFCore.SqlServer/DataCompressionType.cs b/src/EFCore.SqlServer/DataCompressionType.cs new file mode 100644 index 00000000000..058f8f0567d --- /dev/null +++ b/src/EFCore.SqlServer/DataCompressionType.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. + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore; + +/// +/// Indicates type of data compression used on a index. +/// +/// +/// See Data Compression for more information on data compression. +/// +public enum DataCompressionType +{ + /// + /// Index is not compressed. + /// + None, + + /// + /// Index is compressed by using row compression. + /// + Row, + + /// + /// Index is compressed by using page compression. + /// + Page +} diff --git a/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs b/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs index bb46a3e6be7..a1b3180f385 100644 --- a/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs +++ b/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs @@ -79,6 +79,14 @@ private static readonly MethodInfo IndexHasFillFactorMethodInfo = typeof(SqlServerIndexBuilderExtensions).GetRuntimeMethod( nameof(SqlServerIndexBuilderExtensions.HasFillFactor), new[] { typeof(IndexBuilder), typeof(int) })!; + private static readonly MethodInfo IndexIsSortedInTempDbInfo + = typeof(SqlServerIndexBuilderExtensions).GetRuntimeMethod( + nameof(SqlServerIndexBuilderExtensions.IsSortedInTempDb), new[] { typeof(IndexBuilder), typeof(bool) })!; + + private static readonly MethodInfo IndexUseDataCompressionInfo + = typeof(SqlServerIndexBuilderExtensions).GetRuntimeMethod( + nameof(SqlServerIndexBuilderExtensions.UseDataCompression), new[] { typeof(IndexBuilder), typeof(DataCompressionType) })!; + private static readonly MethodInfo KeyIsClusteredMethodInfo = typeof(SqlServerKeyBuilderExtensions).GetRuntimeMethod( nameof(SqlServerKeyBuilderExtensions.IsClustered), new[] { typeof(KeyBuilder), typeof(bool) })!; @@ -341,6 +349,8 @@ protected override bool IsHandledByConvention(IProperty property, IAnnotation an SqlServerAnnotationNames.Include => new MethodCallCodeFragment(IndexIncludePropertiesMethodInfo, annotation.Value), SqlServerAnnotationNames.FillFactor => new MethodCallCodeFragment(IndexHasFillFactorMethodInfo, annotation.Value), + SqlServerAnnotationNames.SortedInTempDb => new MethodCallCodeFragment(IndexIsSortedInTempDbInfo, annotation.Value), + SqlServerAnnotationNames.DataCompression => new MethodCallCodeFragment(IndexUseDataCompressionInfo, annotation.Value), _ => null }; diff --git a/src/EFCore.SqlServer/Design/Internal/SqlServerCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.SqlServer/Design/Internal/SqlServerCSharpRuntimeAnnotationCodeGenerator.cs index 793eb75ca00..7b1b13d36de 100644 --- a/src/EFCore.SqlServer/Design/Internal/SqlServerCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.SqlServer/Design/Internal/SqlServerCSharpRuntimeAnnotationCodeGenerator.cs @@ -104,6 +104,8 @@ public override void Generate(IIndex index, CSharpRuntimeAnnotationCodeGenerator annotations.Remove(SqlServerAnnotationNames.CreatedOnline); annotations.Remove(SqlServerAnnotationNames.Include); annotations.Remove(SqlServerAnnotationNames.FillFactor); + annotations.Remove(SqlServerAnnotationNames.SortedInTempDb); + annotations.Remove(SqlServerAnnotationNames.DataCompression); } base.Generate(index, parameters); @@ -119,6 +121,8 @@ public override void Generate(ITableIndex index, CSharpRuntimeAnnotationCodeGene annotations.Remove(SqlServerAnnotationNames.CreatedOnline); annotations.Remove(SqlServerAnnotationNames.Include); annotations.Remove(SqlServerAnnotationNames.FillFactor); + annotations.Remove(SqlServerAnnotationNames.SortedInTempDb); + annotations.Remove(SqlServerAnnotationNames.DataCompression); } base.Generate(index, parameters); diff --git a/src/EFCore.SqlServer/Extensions/SqlServerIndexBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerIndexBuilderExtensions.cs index d0ef0e19227..c5030dc4974 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerIndexBuilderExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerIndexBuilderExtensions.cs @@ -391,4 +391,176 @@ public static bool CanSetFillFactor( int? fillFactor, bool fromDataAnnotation = false) => indexBuilder.CanSetAnnotation(SqlServerAnnotationNames.FillFactor, fillFactor, fromDataAnnotation); + + /// + /// Configures whether the index is created with sort in tempdb option when targeting SQL Server. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the index being configured. + /// A value indicating whether the index is created with sort in tempdb option. + /// A builder to further configure the index. + public static IndexBuilder IsSortedInTempDb(this IndexBuilder indexBuilder, bool sortedInTempDb = true) + { + indexBuilder.Metadata.SetIsSortedInTempDb(sortedInTempDb); + + return indexBuilder; + } + + /// + /// Configures whether the index is created with sort in tempdb option when targeting SQL Server. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the index being configured. + /// A value indicating whether the index is created with sort in tempdb option. + /// A builder to further configure the index. + public static IndexBuilder IsSortedInTempDb( + this IndexBuilder indexBuilder, + bool sortedInTempDb = true) + => (IndexBuilder)IsSortedInTempDb((IndexBuilder)indexBuilder, sortedInTempDb); + + /// + /// Configures whether the index is created with sort in tempdb option when targeting SQL Server. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the index being configured. + /// A value indicating whether the index is created with sort in tempdb option. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionIndexBuilder? IsSortedInTempDb( + this IConventionIndexBuilder indexBuilder, + bool? sortedInTempDb, + bool fromDataAnnotation = false) + { + if (indexBuilder.CanSetIsSortedInTempDb(sortedInTempDb, fromDataAnnotation)) + { + indexBuilder.Metadata.SetIsSortedInTempDb(sortedInTempDb, fromDataAnnotation); + + return indexBuilder; + } + + return null; + } + + /// + /// Returns a value indicating whether the index can be configured with sort in tempdb option when targeting SQL Server. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the index being configured. + /// A value indicating whether the index is created with sort in tempdb option. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + /// if the index can be configured with sort in tempdb option when targeting SQL Server. + public static bool CanSetIsSortedInTempDb( + this IConventionIndexBuilder indexBuilder, + bool? sortedInTempDb, + bool fromDataAnnotation = false) + => indexBuilder.CanSetAnnotation(SqlServerAnnotationNames.SortedInTempDb, sortedInTempDb, fromDataAnnotation); + + /// + /// Configures whether the index is created with data compression option when targeting SQL Server. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the index being configured. + /// A value indicating the data compression option to be used. + /// A builder to further configure the index. + public static IndexBuilder UseDataCompression(this IndexBuilder indexBuilder, DataCompressionType dataCompressionType) + { + indexBuilder.Metadata.SetDataCompression(dataCompressionType); + + return indexBuilder; + } + + /// + /// Configures whether the index is created with data compression option when targeting SQL Server. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the index being configured. + /// A value indicating the data compression option to be used. + /// A builder to further configure the index. + public static IndexBuilder UseDataCompression( + this IndexBuilder indexBuilder, + DataCompressionType dataCompressionType) + => (IndexBuilder)UseDataCompression((IndexBuilder)indexBuilder, dataCompressionType); + + /// + /// Configures whether the index is created with data compression option when targeting SQL Server. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the index being configured. + /// A value indicating the data compression option to be used. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionIndexBuilder? UseDataCompression( + this IConventionIndexBuilder indexBuilder, + DataCompressionType? dataCompressionType, + bool fromDataAnnotation = false) + { + if (indexBuilder.CanSetDataCompression(dataCompressionType, fromDataAnnotation)) + { + indexBuilder.Metadata.SetDataCompression(dataCompressionType, fromDataAnnotation); + + return indexBuilder; + } + + return null; + } + + /// + /// Returns a value indicating whether the index can be configured with data compression option when targeting SQL Server. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the index being configured. + /// A value indicating the data compression option to be used. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + /// if the index can be configured with data compression option when targeting SQL Server. + public static bool CanSetDataCompression( + this IConventionIndexBuilder indexBuilder, + DataCompressionType? dataCompressionType, + bool fromDataAnnotation = false) + => indexBuilder.CanSetAnnotation(SqlServerAnnotationNames.DataCompression, dataCompressionType, fromDataAnnotation); } diff --git a/src/EFCore.SqlServer/Extensions/SqlServerIndexExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerIndexExtensions.cs index 6d47396a9b2..591e7ba1e1f 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerIndexExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerIndexExtensions.cs @@ -297,4 +297,136 @@ public static void SetFillFactor(this IMutableIndex index, int? fillFactor) /// The for whether the index uses the fill factor. public static ConfigurationSource? GetFillFactorConfigurationSource(this IConventionIndex index) => index.FindAnnotation(SqlServerAnnotationNames.FillFactor)?.GetConfigurationSource(); + + /// + /// Returns a value indicating whether the index is sorted in tempdb. + /// + /// The index. + /// if the index is sorted in tempdb. + public static bool? GetIsSortedInTempDb(this IReadOnlyIndex index) + => (index is RuntimeIndex) + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (bool?)index[SqlServerAnnotationNames.SortedInTempDb]; + + /// + /// Returns a value indicating whether the index is sorted in tempdb. + /// + /// The index. + /// The identifier of the store object. + /// if the index is sorted in tempdb. + public static bool? GetIsSortedInTempDb(this IReadOnlyIndex index, in StoreObjectIdentifier storeObject) + { + if (index is RuntimeIndex) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + + var annotation = index.FindAnnotation(SqlServerAnnotationNames.SortedInTempDb); + if (annotation != null) + { + return (bool?)annotation.Value; + } + + var sharedTableRootIndex = index.FindSharedObjectRootIndex(storeObject); + return sharedTableRootIndex?.GetIsSortedInTempDb(storeObject); + } + + /// + /// Sets a value indicating whether the index is sorted in tempdb. + /// + /// The index. + /// The value to set. + public static void SetIsSortedInTempDb(this IMutableIndex index, bool? sortedInTempDb) + => index.SetAnnotation( + SqlServerAnnotationNames.SortedInTempDb, + sortedInTempDb); + + /// + /// Sets a value indicating whether the index is sorted in tempdb. + /// + /// The index. + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static bool? SetIsSortedInTempDb( + this IConventionIndex index, + bool? sortedInTempDb, + bool fromDataAnnotation = false) + => (bool?)index.SetAnnotation( + SqlServerAnnotationNames.SortedInTempDb, + sortedInTempDb, + fromDataAnnotation)?.Value; + + /// + /// Returns the for whether the index is sorted in tempdb. + /// + /// The index. + /// The for whether the index is sorted in tempdb. + public static ConfigurationSource? GetIsSortedInTempDbConfigurationSource(this IConventionIndex index) + => index.FindAnnotation(SqlServerAnnotationNames.SortedInTempDb)?.GetConfigurationSource(); + + /// + /// Returns the data compression that the index uses. + /// + /// The index. + /// The data compression that the index uses + public static DataCompressionType? GetDataCompression(this IReadOnlyIndex index) + => (index is RuntimeIndex) + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (DataCompressionType?)index[SqlServerAnnotationNames.DataCompression]; + + /// + /// Returns the data compression that the index uses. + /// + /// The index. + /// The identifier of the store object. + /// The data compression that the index uses + public static DataCompressionType? GetDataCompression(this IReadOnlyIndex index, in StoreObjectIdentifier storeObject) + { + if (index is RuntimeIndex) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + + var annotation = index.FindAnnotation(SqlServerAnnotationNames.DataCompression); + if (annotation != null) + { + return (DataCompressionType?)annotation.Value; + } + + var sharedTableRootIndex = index.FindSharedObjectRootIndex(storeObject); + return sharedTableRootIndex?.GetDataCompression(storeObject); + } + + /// + /// Sets a value indicating the data compression the index uses. + /// + /// The index. + /// The value to set. + public static void SetDataCompression(this IMutableIndex index, DataCompressionType? dataCompression) => index.SetAnnotation( + SqlServerAnnotationNames.DataCompression, + dataCompression); + + /// + /// Sets a value indicating the data compression the index uses. + /// + /// The index. + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static DataCompressionType? SetDataCompression( + this IConventionIndex index, + DataCompressionType? dataCompression, + bool fromDataAnnotation = false) => (DataCompressionType?)index.SetAnnotation( + SqlServerAnnotationNames.DataCompression, + dataCompression, + fromDataAnnotation)?.Value; + + /// + /// Returns the for the data compression the index uses. + /// + /// The index. + /// The for the data compression the index uses. + public static ConfigurationSource? GetDataCompressionConfigurationSource(this IConventionIndex index) + => index.FindAnnotation(SqlServerAnnotationNames.DataCompression)?.GetConfigurationSource(); } diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerRuntimeModelConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerRuntimeModelConvention.cs index b64397045a7..6a8c04f778f 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerRuntimeModelConvention.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerRuntimeModelConvention.cs @@ -101,6 +101,8 @@ protected override void ProcessIndexAnnotations( annotations.Remove(SqlServerAnnotationNames.CreatedOnline); annotations.Remove(SqlServerAnnotationNames.Include); annotations.Remove(SqlServerAnnotationNames.FillFactor); + annotations.Remove(SqlServerAnnotationNames.SortedInTempDb); + annotations.Remove(SqlServerAnnotationNames.DataCompression); } } diff --git a/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationNames.cs b/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationNames.cs index 8bf33226c28..4352a29ab49 100644 --- a/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationNames.cs +++ b/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationNames.cs @@ -51,6 +51,22 @@ public static class SqlServerAnnotationNames /// public const string FillFactor = Prefix + "FillFactor"; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public const string SortedInTempDb = Prefix + "SortInTempDb"; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public const string DataCompression = Prefix + "DataCompression"; + /// /// This is an internal API that supports the 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.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs b/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs index 40ed99df85c..54d27c68c79 100644 --- a/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs +++ b/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs @@ -203,6 +203,16 @@ public override IEnumerable For(ITableIndex index, bool designTime) { yield return new Annotation(SqlServerAnnotationNames.FillFactor, fillFactor); } + + if (modelIndex.GetIsSortedInTempDb(table) is bool isSortedInTempDb) + { + yield return new Annotation(SqlServerAnnotationNames.SortedInTempDb, isSortedInTempDb); + } + + if (modelIndex.GetDataCompression(table) is DataCompressionType dataCompressionType) + { + yield return new Annotation(SqlServerAnnotationNames.DataCompression, dataCompressionType); + } } /// diff --git a/src/EFCore.SqlServer/Metadata/Internal/SqlServerIndexExtensions.cs b/src/EFCore.SqlServer/Metadata/Internal/SqlServerIndexExtensions.cs index 49f21891482..369a26a0dbc 100644 --- a/src/EFCore.SqlServer/Metadata/Internal/SqlServerIndexExtensions.cs +++ b/src/EFCore.SqlServer/Metadata/Internal/SqlServerIndexExtensions.cs @@ -100,6 +100,40 @@ public static bool AreCompatibleForSqlServer( return false; } + if (index.GetIsSortedInTempDb() != duplicateIndex.GetIsSortedInTempDb()) + { + if (shouldThrow) + { + throw new InvalidOperationException( + SqlServerStrings.DuplicateIndexSortInTempDbMismatch( + index.DisplayName(), + index.DeclaringEntityType.DisplayName(), + duplicateIndex.DisplayName(), + duplicateIndex.DeclaringEntityType.DisplayName(), + index.DeclaringEntityType.GetSchemaQualifiedTableName(), + index.GetDatabaseName(storeObject))); + } + + return false; + } + + if (index.GetDataCompression() != duplicateIndex.GetDataCompression()) + { + if (shouldThrow) + { + throw new InvalidOperationException( + SqlServerStrings.DuplicateIndexDataCompressionMismatch( + index.DisplayName(), + index.DeclaringEntityType.DisplayName(), + duplicateIndex.DisplayName(), + duplicateIndex.DeclaringEntityType.DisplayName(), + index.DeclaringEntityType.GetSchemaQualifiedTableName(), + index.GetDatabaseName(storeObject))); + } + + return false; + } + return true; static bool SameColumnNames(IReadOnlyIndex index, IReadOnlyIndex duplicateIndex, StoreObjectIdentifier storeObject) diff --git a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs index 03a02ec72e4..3d9f370dc24 100644 --- a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs +++ b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs @@ -1851,6 +1851,27 @@ private static void IndexWithOptions(CreateIndexOperation operation, MigrationCo options.Add("ONLINE = ON"); } + if (operation[SqlServerAnnotationNames.SortedInTempDb] is bool isSortedInTempDb && isSortedInTempDb) + { + options.Add("SORT_IN_TEMPDB = ON"); + } + + if (operation[SqlServerAnnotationNames.DataCompression] is DataCompressionType dataCompressionType) + { + switch (dataCompressionType) + { + case DataCompressionType.None: + options.Add("DATA_COMPRESSION = NONE"); + break; + case DataCompressionType.Row: + options.Add("DATA_COMPRESSION = ROW"); + break; + case DataCompressionType.Page: + options.Add("DATA_COMPRESSION = PAGE"); + break; + } + } + if (options.Count > 0) { builder diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs index 22c3a3a7fe8..a36445d1869 100644 --- a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs +++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs @@ -99,6 +99,14 @@ public static string DuplicateIndexClusteredMismatch(object? index1, object? ent GetString("DuplicateIndexClusteredMismatch", nameof(index1), nameof(entityType1), nameof(index2), nameof(entityType2), nameof(table), nameof(indexName)), index1, entityType1, index2, entityType2, table, indexName); + /// + /// The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but have different data compression configurations. + /// + public static string DuplicateIndexDataCompressionMismatch(object? index1, object? entityType1, object? index2, object? entityType2, object? table, object? indexName) + => string.Format( + GetString("DuplicateIndexDataCompressionMismatch", nameof(index1), nameof(entityType1), nameof(index2), nameof(entityType2), nameof(table), nameof(indexName)), + index1, entityType1, index2, entityType2, table, indexName); + /// /// The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but have different fill factor configurations. /// @@ -123,6 +131,14 @@ public static string DuplicateIndexOnlineMismatch(object? index1, object? entity GetString("DuplicateIndexOnlineMismatch", nameof(index1), nameof(entityType1), nameof(index2), nameof(entityType2), nameof(table), nameof(indexName)), index1, entityType1, index2, entityType2, table, indexName); + /// + /// The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but have different sort in tempdb configurations. + /// + public static string DuplicateIndexSortInTempDbMismatch(object? index1, object? entityType1, object? index2, object? entityType2, object? table, object? indexName) + => string.Format( + GetString("DuplicateIndexSortInTempDbMismatch", nameof(index1), nameof(entityType1), nameof(index2), nameof(entityType2), nameof(table), nameof(indexName)), + index1, entityType1, index2, entityType2, table, indexName); + /// /// The keys {key1} on '{entityType1}' and {key2} on '{entityType2}' are both mapped to '{table}.{keyName}', but have different clustering configurations. /// diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx index beb38d33c4e..59c92b4e2b0 100644 --- a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx +++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx @@ -156,6 +156,12 @@ The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but have different online configurations. + + The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but have different sort in tempdb configurations. + + + The indexes {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{indexName}', but have different data compression configurations. + The keys {key1} on '{entityType1}' and {key2} on '{entityType2}' are both mapped to '{table}.{keyName}', but have different clustering configurations. diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index e8d7423f232..b57455d4e9b 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -6209,6 +6209,117 @@ public virtual void IndexAttribute_IncludeProperties_generated_without_fluent_ap Assert.Equal("Name", Assert.Single(index.GetIncludeProperties())); }); + [ConditionalFact] + public virtual void IndexAttribute_HasFillFactor_is_stored_in_snapshot() + => Test( + builder => builder.Entity( + x => + { + x.HasIndex(e => e.Id).HasFillFactor(29); + }), + AddBoilerPlate( + GetHeading() + +""" + modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithStringProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + SqlServerIndexBuilderExtensions.HasFillFactor(b.HasIndex("Id"), 29); + + b.ToTable("EntityWithStringProperty", "DefaultSchema"); + }); +"""), + model => + { + var index = model.GetEntityTypes().First().GetIndexes().First(); + Assert.Equal(29, index.GetFillFactor()); + }); + + [ConditionalFact] + public virtual void IndexAttribute_UseDataCompression_is_stored_in_snapshot() + => Test( + builder => builder.Entity( + x => + { + x.HasIndex(e => e.Id).UseDataCompression(DataCompressionType.Row); + }), + AddBoilerPlate( + GetHeading() + +""" + modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithStringProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + SqlServerIndexBuilderExtensions.UseDataCompression(b.HasIndex("Id"), DataCompressionType.Row); + + b.ToTable("EntityWithStringProperty", "DefaultSchema"); + }); +"""), + model => + { + var index = model.GetEntityTypes().First().GetIndexes().First(); + Assert.Equal(DataCompressionType.Row, index.GetDataCompression()); + }); + + [ConditionalFact] + public virtual void IndexAttribute_IsSortedInTempDb_is_stored_in_snapshot() + => Test( + builder => builder.Entity( + x => + { + x.HasIndex(e => e.Id).IsSortedInTempDb(true); + }), + AddBoilerPlate( + GetHeading() + +""" + modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithStringProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + SqlServerIndexBuilderExtensions.IsSortedInTempDb(b.HasIndex("Id"), true); + + b.ToTable("EntityWithStringProperty", "DefaultSchema"); + }); +"""), + model => + { + var index = model.GetEntityTypes().First().GetIndexes().First(); + Assert.True(index.GetIsSortedInTempDb()); + }); + #endregion #region ForeignKey diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index b4cb8aaa382..3083acf1f14 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -14827,6 +14827,14 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Equal( CoreStrings.RuntimeModelMissingData, Assert.Throws(() => alternateIndex.GetIncludeProperties()).Message); + Assert.Null(alternateIndex[SqlServerAnnotationNames.SortedInTempDb]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => alternateIndex.GetIsSortedInTempDb()).Message); + Assert.Null(alternateIndex[SqlServerAnnotationNames.DataCompression]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => alternateIndex.GetDataCompression()).Message); Assert.Equal(new[] { alternateIndex }, principalBaseId.GetContainingIndexes()); @@ -15045,7 +15053,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasFilter("AlternateId <> NULL") .IsCreatedOnline() .HasFillFactor(40) - .IncludeProperties(e => e.Id); + .IncludeProperties(e => e.Id) + .IsSortedInTempDb() + .UseDataCompression(DataCompressionType.Page); }); modelBuilder.Entity>>( diff --git a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs index fed07e2f342..af9d4fedb2d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs @@ -2225,6 +2225,105 @@ FROM [sys].[default_constraints] [d] """); } + [ConditionalFact] + public virtual async Task Create_index_unique_with_include_fillfactor_and_sortintempdb() + { + await Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("FirstName"); + e.Property("LastName"); + e.Property("Name").IsRequired(); + }), + builder => { }, + builder => builder.Entity("People").HasIndex("Name") + .IsUnique() + .IncludeProperties("FirstName", "LastName") + .HasFillFactor(75) + .IsSortedInTempDb(), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.True(index.IsUnique); + Assert.Null(index.Filter); + Assert.Equal(1, index.Columns.Count); + Assert.Contains(table.Columns.Single(c => c.Name == "Name"), index.Columns); + var includedColumns = (IReadOnlyList?)index[SqlServerAnnotationNames.Include]; + Assert.Null(includedColumns); + Assert.Equal(75, index[SqlServerAnnotationNames.FillFactor]); + Assert.Null(index[SqlServerAnnotationNames.SortedInTempDb]); + }); + + AssertSql( +""" +DECLARE @var0 sysname; +SELECT @var0 = [d].[name] +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NOT NULL; +""", +// +""" +CREATE UNIQUE INDEX [IX_People_Name] ON [People] ([Name]) INCLUDE ([FirstName], [LastName]) WITH (FILLFACTOR = 75, SORT_IN_TEMPDB = ON); +"""); + } + + [ConditionalTheory] + [InlineData(DataCompressionType.None, "NONE")] + [InlineData(DataCompressionType.Row, "ROW")] + [InlineData(DataCompressionType.Page, "PAGE")] + public virtual async Task Create_index_unique_with_include_sortintempdb_and_datacompression(DataCompressionType dataCompression, string dataCompressionSql) + { + await Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("FirstName"); + e.Property("LastName"); + e.Property("Name").IsRequired(); + }), + builder => { }, + builder => builder.Entity("People").HasIndex("Name") + .IsUnique() + .IncludeProperties("FirstName", "LastName") + .IsSortedInTempDb() + .UseDataCompression(dataCompression), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.True(index.IsUnique); + Assert.Null(index.Filter); + Assert.Equal(1, index.Columns.Count); + Assert.Contains(table.Columns.Single(c => c.Name == "Name"), index.Columns); + var includedColumns = (IReadOnlyList?)index[SqlServerAnnotationNames.Include]; + Assert.Null(includedColumns); + Assert.Null(index[SqlServerAnnotationNames.SortedInTempDb]); + Assert.Null(index[SqlServerAnnotationNames.DataCompression]); + }); + + AssertSql( +""" +DECLARE @var0 sysname; +SELECT @var0 = [d].[name] +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NOT NULL; +""", +// +$""" +CREATE UNIQUE INDEX [IX_People_Name] ON [People] ([Name]) INCLUDE ([FirstName], [LastName]) WITH (SORT_IN_TEMPDB = ON, DATA_COMPRESSION = {dataCompressionSql}); +"""); + } + [ConditionalFact] [SqlServerCondition(SqlServerCondition.SupportsMemoryOptimized)] public virtual async Task Create_index_memoryOptimized_unique_nullable() diff --git a/test/EFCore.SqlServer.FunctionalTests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs b/test/EFCore.SqlServer.FunctionalTests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs index e53c068bb06..856732091d4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs @@ -29,6 +29,49 @@ public void CreateIndexOperation_unique_online() """); } + [ConditionalFact] + public void CreateIndexOperation_unique_sortintempdb() + { + Generate( + new CreateIndexOperation + { + Name = "IX_People_Name", + Table = "People", + Schema = "dbo", + Columns = new[] { "FirstName", "LastName" }, + IsUnique = true, + [SqlServerAnnotationNames.SortedInTempDb] = true + }); + + AssertSql( +""" +CREATE UNIQUE INDEX [IX_People_Name] ON [dbo].[People] ([FirstName], [LastName]) WHERE [FirstName] IS NOT NULL AND [LastName] IS NOT NULL WITH (SORT_IN_TEMPDB = ON); +"""); + } + + [ConditionalTheory] + [InlineData(DataCompressionType.None, "NONE")] + [InlineData(DataCompressionType.Row, "ROW")] + [InlineData(DataCompressionType.Page, "PAGE")] + public void CreateIndexOperation_unique_datacompression(DataCompressionType dataCompression, string dataCompressionSql) + { + Generate( + new CreateIndexOperation + { + Name = "IX_People_Name", + Table = "People", + Schema = "dbo", + Columns = new[] { "FirstName", "LastName" }, + IsUnique = true, + [SqlServerAnnotationNames.DataCompression] = dataCompression + }); + + AssertSql( +$""" +CREATE UNIQUE INDEX [IX_People_Name] ON [dbo].[People] ([FirstName], [LastName]) WHERE [FirstName] IS NOT NULL AND [LastName] IS NOT NULL WITH (DATA_COMPRESSION = {dataCompressionSql}); +"""); + } + [ConditionalFact] public virtual void AddColumnOperation_identity_legacy() { diff --git a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs index 0e2c71e8591..eb028b7c292 100644 --- a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs +++ b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs @@ -402,6 +402,38 @@ public virtual void Detects_duplicate_index_names_within_hierarchy_differently_o modelBuilder); } + [ConditionalFact] + public virtual void Detects_duplicate_index_names_within_hierarchy_different_sort_in_tempdb() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Entity(); + modelBuilder.Entity().HasIndex(c => c.Name).HasDatabaseName("IX_Animal_Name"); + modelBuilder.Entity().HasIndex(d => d.Name).HasDatabaseName("IX_Animal_Name").IsSortedInTempDb(); + + VerifyError( + SqlServerStrings.DuplicateIndexSortInTempDbMismatch( + "{'" + nameof(Dog.Name) + "'}", nameof(Dog), + "{'" + nameof(Cat.Name) + "'}", nameof(Cat), + nameof(Animal), "IX_Animal_Name"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_duplicate_index_names_within_hierarchy_different_data_compression() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Entity(); + modelBuilder.Entity().HasIndex(c => c.Name).HasDatabaseName("IX_Animal_Name"); + modelBuilder.Entity().HasIndex(d => d.Name).HasDatabaseName("IX_Animal_Name").UseDataCompression(DataCompressionType.Page); + + VerifyError( + SqlServerStrings.DuplicateIndexDataCompressionMismatch( + "{'" + nameof(Dog.Name) + "'}", nameof(Dog), + "{'" + nameof(Cat.Name) + "'}", nameof(Cat), + nameof(Animal), "IX_Animal_Name"), + modelBuilder); + } + [ConditionalFact] public virtual void Detects_duplicate_index_names_within_hierarchy_with_different_different_include() { diff --git a/test/EFCore.SqlServer.Tests/Metadata/SqlServerBuilderExtensionsTest.cs b/test/EFCore.SqlServer.Tests/Metadata/SqlServerBuilderExtensionsTest.cs index 96614ca705f..08390ed516b 100644 --- a/test/EFCore.SqlServer.Tests/Metadata/SqlServerBuilderExtensionsTest.cs +++ b/test/EFCore.SqlServer.Tests/Metadata/SqlServerBuilderExtensionsTest.cs @@ -1078,6 +1078,72 @@ public void Throws_if_attempt_to_set_fillfactor_with_argument_out_of_range(int f }); } + [ConditionalFact] + public void Can_set_index_with_sortintempdb() + { + var modelBuilder = CreateConventionModelBuilder(); + + modelBuilder + .Entity() + .HasIndex(e => e.Name) + .IsSortedInTempDb(); + + var index = modelBuilder.Model.FindEntityType(typeof(Customer)).GetIndexes().Single(); + + Assert.True(index.GetIsSortedInTempDb()); + } + + [ConditionalFact] + public void Can_set_index_with_sortintempdb_non_generic() + { + var modelBuilder = CreateConventionModelBuilder(); + + modelBuilder + .Entity(typeof(Customer)) + .HasIndex("Name") + .IsSortedInTempDb(); + + var index = modelBuilder.Model.FindEntityType(typeof(Customer)).GetIndexes().Single(); + + Assert.True(index.GetIsSortedInTempDb()); + } + + [ConditionalTheory] + [InlineData(DataCompressionType.None)] + [InlineData(DataCompressionType.Row)] + [InlineData(DataCompressionType.Page)] + public void Can_set_index_with_datacompression(DataCompressionType dataCompression) + { + var modelBuilder = CreateConventionModelBuilder(); + + modelBuilder + .Entity() + .HasIndex(e => e.Name) + .UseDataCompression(dataCompression); + + var index = modelBuilder.Model.FindEntityType(typeof(Customer)).GetIndexes().Single(); + + Assert.Equal(dataCompression, index.GetDataCompression()); + } + + [ConditionalTheory] + [InlineData(DataCompressionType.None)] + [InlineData(DataCompressionType.Row)] + [InlineData(DataCompressionType.Page)] + public void Can_set_index_with_datacompression_non_generic(DataCompressionType dataCompression) + { + var modelBuilder = CreateConventionModelBuilder(); + + modelBuilder + .Entity(typeof(Customer)) + .HasIndex("Name") + .UseDataCompression(dataCompression); + + var index = modelBuilder.Model.FindEntityType(typeof(Customer)).GetIndexes().Single(); + + Assert.Equal(dataCompression, index.GetDataCompression()); + } + #region UseSqlOutputClause [ConditionalFact] diff --git a/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs b/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs index b99f98fdc9c..4171dac604e 100644 --- a/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs +++ b/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs @@ -1390,4 +1390,241 @@ public void Rebuild_index_with_different_fillfactor_value() Assert.Equal(90, annotationValue); }); + + [ConditionalFact] + public void Dont_rebuild_index_with_unchanged_sortintempdb_option() + => Execute( + source => source + .Entity( + "Address", + x => + { + x.Property("Id"); + x.Property("Zip"); + x.Property("City"); + x.HasIndex("Zip") + .IsSortedInTempDb(); + }), + target => target + .Entity( + "Address", + x => + { + x.Property("Id"); + x.Property("Zip"); + x.Property("City"); + x.HasIndex("Zip") + .IsSortedInTempDb(); + }), + operations => Assert.Equal(0, operations.Count)); + + [ConditionalFact] + public void Rebuild_index_when_changing_sortintempdb_option() + => Execute( + _ => { }, + source => source + .Entity( + "Address", + x => + { + x.Property("Id"); + x.Property("Zip"); + x.Property("City"); + x.Property("Street"); + x.HasIndex("Zip"); + }), + target => target + .Entity( + "Address", + x => + { + x.Property("Id"); + x.Property("Zip"); + x.Property("City"); + x.Property("Street"); + x.HasIndex("Zip") + .IsSortedInTempDb(); + }), + upOps => + { + Assert.Equal(2, upOps.Count); + + var operation1 = Assert.IsType(upOps[0]); + Assert.Equal("Address", operation1.Table); + Assert.Equal("IX_Address_Zip", operation1.Name); + + Assert.Empty(operation1.GetAnnotations()); + + var operation2 = Assert.IsType(upOps[1]); + Assert.Equal("Address", operation1.Table); + Assert.Equal("IX_Address_Zip", operation1.Name); + + var annotation = operation2.GetAnnotation(SqlServerAnnotationNames.SortedInTempDb); + Assert.NotNull(annotation); + + var annotationValue = Assert.IsType(annotation.Value); + Assert.True(annotationValue); + }, + downOps => + { + Assert.Equal(2, downOps.Count); + + var operation1 = Assert.IsType(downOps[0]); + Assert.Equal("Address", operation1.Table); + Assert.Equal("IX_Address_Zip", operation1.Name); + + Assert.Empty(operation1.GetAnnotations()); + + var operation2 = Assert.IsType(downOps[1]); + Assert.Equal("Address", operation1.Table); + Assert.Equal("IX_Address_Zip", operation1.Name); + + Assert.Empty(operation2.GetAnnotations()); + }); + + [ConditionalTheory] + [InlineData(DataCompressionType.None)] + [InlineData(DataCompressionType.Row)] + [InlineData(DataCompressionType.Page)] + public void Dont_rebuild_index_with_unchanged_datacompression_option(DataCompressionType dataCompression) + => Execute( + source => source + .Entity( + "Address", + x => + { + x.Property("Id"); + x.Property("Zip"); + x.Property("City"); + x.HasIndex("Zip") + .UseDataCompression(dataCompression); + }), + target => target + .Entity( + "Address", + x => + { + x.Property("Id"); + x.Property("Zip"); + x.Property("City"); + x.HasIndex("Zip") + .UseDataCompression(dataCompression); + }), + operations => Assert.Equal(0, operations.Count)); + + [ConditionalTheory] + [InlineData(DataCompressionType.None)] + [InlineData(DataCompressionType.Row)] + [InlineData(DataCompressionType.Page)] + public void Rebuild_index_when_adding_datacompression_option(DataCompressionType dataCompression) + => Execute( + _ => { }, + source => source + .Entity( + "Address", + x => + { + x.Property("Id"); + x.Property("Zip"); + x.Property("City"); + x.Property("Street"); + x.HasIndex("Zip"); + }), + target => target + .Entity( + "Address", + x => + { + x.Property("Id"); + x.Property("Zip"); + x.Property("City"); + x.Property("Street"); + x.HasIndex("Zip") + .UseDataCompression(dataCompression); + }), + upOps => + { + Assert.Equal(2, upOps.Count); + + var operation1 = Assert.IsType(upOps[0]); + Assert.Equal("Address", operation1.Table); + Assert.Equal("IX_Address_Zip", operation1.Name); + + Assert.Empty(operation1.GetAnnotations()); + + var operation2 = Assert.IsType(upOps[1]); + Assert.Equal("Address", operation1.Table); + Assert.Equal("IX_Address_Zip", operation1.Name); + + var annotation = operation2.GetAnnotation(SqlServerAnnotationNames.DataCompression); + Assert.NotNull(annotation); + + var annotationValue = Assert.IsType(annotation.Value); + Assert.Equal(dataCompression, annotationValue); + }, + downOps => + { + Assert.Equal(2, downOps.Count); + + var operation1 = Assert.IsType(downOps[0]); + Assert.Equal("Address", operation1.Table); + Assert.Equal("IX_Address_Zip", operation1.Name); + + Assert.Empty(operation1.GetAnnotations()); + + var operation2 = Assert.IsType(downOps[1]); + Assert.Equal("Address", operation1.Table); + Assert.Equal("IX_Address_Zip", operation1.Name); + + Assert.Empty(operation2.GetAnnotations()); + }); + + [ConditionalFact] + public void Rebuild_index_with_different_datacompression_value() + => Execute( + source => source + .Entity( + "Address", + x => + { + x.Property("Id"); + x.Property("Zip"); + x.Property("City"); + x.Property("Street"); + x.HasIndex("Zip") + .UseDataCompression(DataCompressionType.Row); + }), + target => target + .Entity( + "Address", + x => + { + x.Property("Id"); + x.Property("Zip"); + x.Property("City"); + x.Property("Street"); + x.HasIndex("Zip") + .UseDataCompression(DataCompressionType.Page); + }), + operations => + { + Assert.Equal(2, operations.Count); + + var operation1 = Assert.IsType(operations[0]); + Assert.Equal("Address", operation1.Table); + Assert.Equal("IX_Address_Zip", operation1.Name); + + Assert.Empty(operation1.GetAnnotations()); + + var operation2 = Assert.IsType(operations[1]); + Assert.Equal("Address", operation1.Table); + Assert.Equal("IX_Address_Zip", operation1.Name); + + var annotation = operation2.GetAnnotation(SqlServerAnnotationNames.DataCompression); + Assert.NotNull(annotation); + + var annotationValue = Assert.IsType(annotation.Value); + + Assert.Equal(DataCompressionType.Page, annotationValue); + }); } From be32922cd44be7a2c403930d3f521a62f0b4e7c9 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Thu, 14 Sep 2023 22:46:18 +0000 Subject: [PATCH 5/6] Update dependencies from https://github.com/dotnet/runtime build 20230913.13 (#31744) [release/8.0] Update dependencies from dotnet/runtime --- eng/Version.Details.xml | 52 ++++++++++++++++++++--------------------- eng/Versions.props | 26 ++++++++++----------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index da8fbb7253c..c50c852cb04 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,58 +1,58 @@ - + https://github.com/dotnet/runtime - f490d66ef5e1b90af1a0fad86702c9c54caaa606 + 412de0457f42c007eebf19c6e779c75a1699027a - + https://github.com/dotnet/runtime - f490d66ef5e1b90af1a0fad86702c9c54caaa606 + 412de0457f42c007eebf19c6e779c75a1699027a - + https://github.com/dotnet/runtime - f490d66ef5e1b90af1a0fad86702c9c54caaa606 + 412de0457f42c007eebf19c6e779c75a1699027a - + https://github.com/dotnet/runtime - f490d66ef5e1b90af1a0fad86702c9c54caaa606 + 412de0457f42c007eebf19c6e779c75a1699027a - + https://github.com/dotnet/runtime - f490d66ef5e1b90af1a0fad86702c9c54caaa606 + 412de0457f42c007eebf19c6e779c75a1699027a - + https://github.com/dotnet/runtime - f490d66ef5e1b90af1a0fad86702c9c54caaa606 + 412de0457f42c007eebf19c6e779c75a1699027a - + https://github.com/dotnet/runtime - f490d66ef5e1b90af1a0fad86702c9c54caaa606 + 412de0457f42c007eebf19c6e779c75a1699027a - + https://github.com/dotnet/runtime - f490d66ef5e1b90af1a0fad86702c9c54caaa606 + 412de0457f42c007eebf19c6e779c75a1699027a - + https://github.com/dotnet/runtime - f490d66ef5e1b90af1a0fad86702c9c54caaa606 + 412de0457f42c007eebf19c6e779c75a1699027a - + https://github.com/dotnet/runtime - f490d66ef5e1b90af1a0fad86702c9c54caaa606 + 412de0457f42c007eebf19c6e779c75a1699027a - + https://github.com/dotnet/runtime - f490d66ef5e1b90af1a0fad86702c9c54caaa606 + 412de0457f42c007eebf19c6e779c75a1699027a - + https://github.com/dotnet/runtime - f490d66ef5e1b90af1a0fad86702c9c54caaa606 + 412de0457f42c007eebf19c6e779c75a1699027a - + https://github.com/dotnet/runtime - f490d66ef5e1b90af1a0fad86702c9c54caaa606 + 412de0457f42c007eebf19c6e779c75a1699027a diff --git a/eng/Versions.props b/eng/Versions.props index 1bbf3ef9f37..9addf885612 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -16,19 +16,19 @@ False - 8.0.0-rc.2.23463.10 - 8.0.0-rc.2.23463.10 - 8.0.0-rc.2.23463.10 - 8.0.0-rc.2.23463.10 - 8.0.0-rc.2.23463.10 - 8.0.0-rc.2.23463.10 - 8.0.0-rc.2.23463.10 - 8.0.0-rc.2.23463.10 - 8.0.0-rc.2.23463.10 - 8.0.0-rc.2.23463.10 - 8.0.0-rc.2.23463.10 - 8.0.0-rc.2.23463.10 - 8.0.0-rc.2.23463.10 + 8.0.0-rc.2.23463.13 + 8.0.0-rc.2.23463.13 + 8.0.0-rc.2.23463.13 + 8.0.0-rc.2.23463.13 + 8.0.0-rc.2.23463.13 + 8.0.0-rc.2.23463.13 + 8.0.0-rc.2.23463.13 + 8.0.0-rc.2.23463.13 + 8.0.0-rc.2.23463.13 + 8.0.0-rc.2.23463.13 + 8.0.0-rc.2.23463.13 + 8.0.0-rc.2.23463.13 + 8.0.0-rc.2.23463.13 8.0.0-beta.23451.1 From d229a4324418a56736f6880e7a593a98abbfee0c Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Fri, 15 Sep 2023 05:09:02 +0000 Subject: [PATCH 6/6] Update dependencies from https://github.com/dotnet/runtime build 20230914.16 (#31746) [release/8.0] Update dependencies from dotnet/runtime --- eng/Version.Details.xml | 52 ++++++++++++++++++++--------------------- eng/Versions.props | 26 ++++++++++----------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index c50c852cb04..576a513efed 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,58 +1,58 @@ - + https://github.com/dotnet/runtime - 412de0457f42c007eebf19c6e779c75a1699027a + 9cdbc87dadbf358206f20f17fed005cdcb253452 - + https://github.com/dotnet/runtime - 412de0457f42c007eebf19c6e779c75a1699027a + 9cdbc87dadbf358206f20f17fed005cdcb253452 - + https://github.com/dotnet/runtime - 412de0457f42c007eebf19c6e779c75a1699027a + 9cdbc87dadbf358206f20f17fed005cdcb253452 - + https://github.com/dotnet/runtime - 412de0457f42c007eebf19c6e779c75a1699027a + 9cdbc87dadbf358206f20f17fed005cdcb253452 - + https://github.com/dotnet/runtime - 412de0457f42c007eebf19c6e779c75a1699027a + 9cdbc87dadbf358206f20f17fed005cdcb253452 - + https://github.com/dotnet/runtime - 412de0457f42c007eebf19c6e779c75a1699027a + 9cdbc87dadbf358206f20f17fed005cdcb253452 - + https://github.com/dotnet/runtime - 412de0457f42c007eebf19c6e779c75a1699027a + 9cdbc87dadbf358206f20f17fed005cdcb253452 - + https://github.com/dotnet/runtime - 412de0457f42c007eebf19c6e779c75a1699027a + 9cdbc87dadbf358206f20f17fed005cdcb253452 - + https://github.com/dotnet/runtime - 412de0457f42c007eebf19c6e779c75a1699027a + 9cdbc87dadbf358206f20f17fed005cdcb253452 - + https://github.com/dotnet/runtime - 412de0457f42c007eebf19c6e779c75a1699027a + 9cdbc87dadbf358206f20f17fed005cdcb253452 - + https://github.com/dotnet/runtime - 412de0457f42c007eebf19c6e779c75a1699027a + 9cdbc87dadbf358206f20f17fed005cdcb253452 - + https://github.com/dotnet/runtime - 412de0457f42c007eebf19c6e779c75a1699027a + 9cdbc87dadbf358206f20f17fed005cdcb253452 - + https://github.com/dotnet/runtime - 412de0457f42c007eebf19c6e779c75a1699027a + 9cdbc87dadbf358206f20f17fed005cdcb253452 diff --git a/eng/Versions.props b/eng/Versions.props index 9addf885612..d88c8479d97 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -16,19 +16,19 @@ False - 8.0.0-rc.2.23463.13 - 8.0.0-rc.2.23463.13 - 8.0.0-rc.2.23463.13 - 8.0.0-rc.2.23463.13 - 8.0.0-rc.2.23463.13 - 8.0.0-rc.2.23463.13 - 8.0.0-rc.2.23463.13 - 8.0.0-rc.2.23463.13 - 8.0.0-rc.2.23463.13 - 8.0.0-rc.2.23463.13 - 8.0.0-rc.2.23463.13 - 8.0.0-rc.2.23463.13 - 8.0.0-rc.2.23463.13 + 8.0.0-rc.2.23464.16 + 8.0.0-rc.2.23464.16 + 8.0.0-rc.2.23464.16 + 8.0.0-rc.2.23464.16 + 8.0.0-rc.2.23464.16 + 8.0.0-rc.2.23464.16 + 8.0.0-rc.2.23464.16 + 8.0.0-rc.2.23464.16 + 8.0.0-rc.2.23464.16 + 8.0.0-rc.2.23464.16 + 8.0.0-rc.2.23464.16 + 8.0.0-rc.2.23464.16 + 8.0.0-rc.2.23464.16 8.0.0-beta.23451.1