diff --git a/test/EFCore.SqlServer.FunctionalTests/CommandConfigurationTest.cs b/test/EFCore.SqlServer.FunctionalTests/CommandConfigurationTest.cs index 90c28e93028..b58f1961073 100644 --- a/test/EFCore.SqlServer.FunctionalTests/CommandConfigurationTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/CommandConfigurationTest.cs @@ -30,7 +30,6 @@ public void Constructed_select_query_CommandBuilder_throws_when_negative_Command } [ConditionalTheory] - [SqlServerCondition(SqlServerCondition.SupportsSequences)] [InlineData(59, 6)] [InlineData(50, 5)] [InlineData(20, 2)] @@ -82,10 +81,7 @@ public ChipsContext(DbContextOptions options) protected override void OnModelCreating(ModelBuilder modelBuilder) { - if (TestEnvironment.GetFlag(nameof(SqlServerCondition.SupportsSequences)) ?? true) - { - modelBuilder.UseHiLo(); - } + modelBuilder.UseHiLo(); } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs index c4349c3cd44..d9f830da8d9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs @@ -37,7 +37,6 @@ public SqlServerDatabaseModelFactoryTest(SqlServerDatabaseModelFixture fixture) #region Sequences [ConditionalFact] - [SqlServerCondition(SqlServerCondition.SupportsSequences)] public void Create_sequences_with_facets() { Test( @@ -82,7 +81,6 @@ MAXVALUE 8 } [ConditionalFact] - [SqlServerCondition(SqlServerCondition.SupportsSequences)] public void Sequence_min_max_start_values_are_null_if_default() { Test( @@ -118,7 +116,6 @@ public void Sequence_min_max_start_values_are_null_if_default() } [ConditionalFact] - [SqlServerCondition(SqlServerCondition.SupportsSequences)] public void Sequence_min_max_start_values_are_not_null_if_decimal() { Test( @@ -146,7 +143,6 @@ public void Sequence_min_max_start_values_are_not_null_if_decimal() } [ConditionalFact] - [SqlServerCondition(SqlServerCondition.SupportsSequences)] public void Sequence_using_type_alias() { Fixture.TestStore.ExecuteNonQuery( @@ -177,7 +173,6 @@ public void Sequence_using_type_alias() } [ConditionalFact] - [SqlServerCondition(SqlServerCondition.SupportsSequences)] public void Sequence_using_type_with_facets() { Test( @@ -200,7 +195,6 @@ public void Sequence_using_type_with_facets() } [ConditionalFact] - [SqlServerCondition(SqlServerCondition.SupportsSequences)] public void Filter_sequences_based_on_schema() { Test( diff --git a/test/EFCore.SqlServer.FunctionalTests/SequenceEndToEndTest.cs b/test/EFCore.SqlServer.FunctionalTests/SequenceEndToEndTest.cs index 0277d1c0ba4..ae7e2426bdd 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SequenceEndToEndTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SequenceEndToEndTest.cs @@ -13,7 +13,6 @@ // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore { - [SqlServerCondition(SqlServerCondition.SupportsSequences)] public class SequenceEndToEndTest : IDisposable { [ConditionalFact] diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.cs index a14b612730e..e51cae2eb3d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTest.cs @@ -51,7 +51,6 @@ public BlogContextIdentity(string databaseName) } } - [SqlServerCondition(SqlServerCondition.SupportsSequences)] [ConditionalFact] public void Insert_with_sequence_HiLo() { @@ -99,7 +98,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [ConditionalFact] - [SqlServerCondition(SqlServerCondition.SupportsSequences)] public void Insert_with_default_value_from_sequence() { using var testStore = SqlServerTestStore.CreateInitialized(DatabaseName); @@ -177,7 +175,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [ConditionalFact] - [SqlServerCondition(SqlServerCondition.SupportsSequences)] public void Insert_with_default_string_value_from_sequence() { using var testStore = SqlServerTestStore.CreateInitialized(DatabaseName); @@ -231,7 +228,6 @@ public class BlogWithStringKey } [ConditionalFact] - [SqlServerCondition(SqlServerCondition.SupportsSequences)] public void Insert_with_key_default_value_from_sequence() { using var testStore = SqlServerTestStore.CreateInitialized(DatabaseName); @@ -872,7 +868,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [ConditionalFact] - [SqlServerCondition(SqlServerCondition.SupportsSequences)] public void Insert_explicit_value_throws_when_readonly_sequence_before_save() { using var testStore = SqlServerTestStore.CreateInitialized(DatabaseName); diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerCondition.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerCondition.cs index b588c2fa69c..71d2ac81819 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerCondition.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerCondition.cs @@ -8,14 +8,13 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities [Flags] public enum SqlServerCondition { - SupportsSequences = 1 << 0, - IsSqlAzure = 1 << 1, - IsNotSqlAzure = 1 << 2, - SupportsMemoryOptimized = 1 << 3, - SupportsAttach = 1 << 4, - SupportsHiddenColumns = 1 << 5, - IsNotCI = 1 << 6, - SupportsFullTextSearch = 1 << 7, - SupportsOnlineIndexes = 1 << 8 + IsSqlAzure = 1 << 0, + IsNotSqlAzure = 1 << 1, + SupportsMemoryOptimized = 1 << 2, + SupportsAttach = 1 << 3, + SupportsHiddenColumns = 1 << 4, + IsNotCI = 1 << 5, + SupportsFullTextSearch = 1 << 6, + SupportsOnlineIndexes = 1 << 7, } } diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerConditionAttribute.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerConditionAttribute.cs index 4690375c1c3..b37c2585ef9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerConditionAttribute.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerConditionAttribute.cs @@ -22,19 +22,15 @@ public SqlServerConditionAttribute(SqlServerCondition conditions) public ValueTask IsMetAsync() { var isMet = true; - if (Conditions.HasFlag(SqlServerCondition.SupportsSequences)) - { - isMet &= TestEnvironment.GetFlag(nameof(SqlServerCondition.SupportsSequences)) ?? true; - } if (Conditions.HasFlag(SqlServerCondition.SupportsHiddenColumns)) { - isMet &= TestEnvironment.GetFlag(nameof(SqlServerCondition.SupportsHiddenColumns)) ?? false; + isMet &= TestEnvironment.IsHiddenColumnsSupported; } if (Conditions.HasFlag(SqlServerCondition.SupportsMemoryOptimized)) { - isMet &= TestEnvironment.GetFlag(nameof(SqlServerCondition.SupportsMemoryOptimized)) ?? false; + isMet &= TestEnvironment.IsMemoryOptimizedTablesSupported; } if (Conditions.HasFlag(SqlServerCondition.IsSqlAzure)) @@ -50,7 +46,7 @@ public ValueTask IsMetAsync() if (Conditions.HasFlag(SqlServerCondition.SupportsAttach)) { var defaultConnection = new SqlConnectionStringBuilder(TestEnvironment.DefaultConnection); - isMet &= defaultConnection.DataSource.Contains("(localdb)") + isMet &= defaultConnection.DataSource.Contains("(localdb)", StringComparison.OrdinalIgnoreCase) || defaultConnection.UserInstance; } @@ -64,6 +60,11 @@ public ValueTask IsMetAsync() isMet &= TestEnvironment.IsFullTextSearchSupported; } + if (Conditions.HasFlag(SqlServerCondition.SupportsOnlineIndexes)) + { + isMet &= TestEnvironment.IsOnlineIndexingSupported; + } + return new ValueTask(isMet); } diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestEnvironment.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestEnvironment.cs index a49b88b5776..a8f3fef272c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestEnvironment.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestEnvironment.cs @@ -25,14 +25,53 @@ public static class TestEnvironment public static bool IsConfigured { get; } = !string.IsNullOrEmpty(_dataSource); - public static bool IsLocalDb { get; } = _dataSource.StartsWith("(localdb)", StringComparison.OrdinalIgnoreCase); - - public static bool IsSqlAzure { get; } = _dataSource.Contains("database.windows.net"); - public static bool IsCI { get; } = Environment.GetEnvironmentVariable("PIPELINE_WORKSPACE") != null; + private static bool? _isAzureSqlDb; + private static bool? _fullTextInstalled; + private static bool? _supportsHiddenColumns; + + private static bool? _supportsOnlineIndexing; + + private static bool? _supportsMemoryOptimizedTables; + + private static byte? _productMajorVersion; + + private static int? _engineEdition; + + public static bool IsSqlAzure + { + get + { + if (!IsConfigured) + { + return false; + } + + if (_isAzureSqlDb.HasValue) + { + return _isAzureSqlDb.Value; + } + + try + { + _engineEdition = GetEngineEdition(); + + _isAzureSqlDb = (_engineEdition == 5 || _engineEdition == 8); + } + catch (PlatformNotSupportedException) + { + _isAzureSqlDb = false; + } + + return _isAzureSqlDb.Value; + } + } + + public static bool IsLocalDb { get; } = _dataSource.StartsWith("(localdb)", StringComparison.OrdinalIgnoreCase); + public static bool IsFullTextSearchSupported { get @@ -49,23 +88,119 @@ public static bool IsFullTextSearchSupported try { - using (var sqlConnection = new SqlConnection(SqlServerTestStore.CreateConnectionString("master"))) - { - sqlConnection.Open(); + using var sqlConnection = new SqlConnection(SqlServerTestStore.CreateConnectionString("master")); + sqlConnection.Open(); + + using var command = new SqlCommand( + "SELECT FULLTEXTSERVICEPROPERTY('IsFullTextInstalled')", sqlConnection); + var result = (int)command.ExecuteScalar(); + + _fullTextInstalled = result == 1; + } + catch (PlatformNotSupportedException) + { + _fullTextInstalled = false; + } + + return _fullTextInstalled.Value; + } + } + + public static bool IsHiddenColumnsSupported + { + get + { + if (!IsConfigured) + { + return false; + } + + if (_supportsHiddenColumns.HasValue) + { + return _supportsHiddenColumns.Value; + } + + try + { + _engineEdition = GetEngineEdition(); + _productMajorVersion = GetProductMajorVersion(); + + _supportsHiddenColumns = (_productMajorVersion >= 13 && _engineEdition != 6) || IsSqlAzure; + } + catch (PlatformNotSupportedException) + { + _supportsHiddenColumns = false; + } + + return _supportsHiddenColumns.Value; + } + } + + public static bool IsOnlineIndexingSupported + { + get + { + if (!IsConfigured) + { + return false; + } + + if (_supportsOnlineIndexing.HasValue) + { + return _supportsOnlineIndexing.Value; + } + + try + { + _engineEdition = GetEngineEdition(); + + _supportsOnlineIndexing = _engineEdition == 3 || IsSqlAzure; + } + catch (PlatformNotSupportedException) + { + _supportsOnlineIndexing = false; + } + + return _supportsOnlineIndexing.Value; + } + } + + public static bool IsMemoryOptimizedTablesSupported + { + get + { + if (!IsConfigured) + { + return false; + } - using var command = new SqlCommand( - "SELECT FULLTEXTSERVICEPROPERTY('IsFullTextInstalled')", sqlConnection); - var result = (int)command.ExecuteScalar(); + var supported = GetFlag(nameof(SqlServerCondition.SupportsMemoryOptimized)); + if (supported.HasValue) + { + _supportsMemoryOptimizedTables = supported.Value; + } - _fullTextInstalled = result == 1; - } + if (_supportsMemoryOptimizedTables.HasValue) + { + return _supportsMemoryOptimizedTables.Value; + } + + try + { + using var sqlConnection = new SqlConnection(SqlServerTestStore.CreateConnectionString("master")); + sqlConnection.Open(); + + using var command = new SqlCommand( + "SELECT SERVERPROPERTY('IsXTPSupported');", sqlConnection); + var result = command.ExecuteScalar(); + _supportsMemoryOptimizedTables = (result != null ? Convert.ToInt32(result) : 0) == 1 && !IsSqlAzure && !IsLocalDb; } catch (PlatformNotSupportedException) { + _supportsMemoryOptimizedTables = false; } - _fullTextInstalled = false; - return false; + return _supportsMemoryOptimizedTables.Value; } } @@ -76,5 +211,39 @@ public static bool IsFullTextSearchSupported public static int? GetInt(string key) => int.TryParse(Config[key], out var value) ? value : (int?)null; + + private static int GetEngineEdition() + { + if (_engineEdition.HasValue) + { + return _engineEdition.Value; + } + + using var sqlConnection = new SqlConnection(SqlServerTestStore.CreateConnectionString("master")); + sqlConnection.Open(); + + using var command = new SqlCommand( + "SELECT SERVERPROPERTY('EngineEdition');", sqlConnection); + _engineEdition = (int)command.ExecuteScalar(); + + return _engineEdition.Value; + } + + private static byte GetProductMajorVersion() + { + if (_productMajorVersion.HasValue) + { + return _productMajorVersion.Value; + } + + using var sqlConnection = new SqlConnection(SqlServerTestStore.CreateConnectionString("master")); + sqlConnection.Open(); + + using var command = new SqlCommand( + "SELECT SERVERPROPERTY('ProductVersion');", sqlConnection); + _productMajorVersion = (byte)Version.Parse((string)command.ExecuteScalar()).Major; + + return _productMajorVersion.Value; + } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/config.json b/test/EFCore.SqlServer.FunctionalTests/config.json index e25ce2277f0..f4880f2152f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/config.json +++ b/test/EFCore.SqlServer.FunctionalTests/config.json @@ -3,10 +3,7 @@ "SqlServer": { "DefaultConnection": "Data Source=(localdb)\\MSSQLLocalDB;Database=master;Integrated Security=True;Connect Timeout=60;ConnectRetryCount=0", "ElasticPoolName": "", - "SupportsSequences": true, - "SupportsMemoryOptimized": false, - "SupportsHiddenColumns": true, - "SupportsOnlineIndexes": false + "SupportsMemoryOptimized": null } } }