From e23ac06b9ad7c7024347052c6cbced8e2e7b28d9 Mon Sep 17 00:00:00 2001 From: Jiri Cincura Date: Mon, 17 Jun 2024 13:50:47 +0200 Subject: [PATCH 1/6] Implement UseAzureSql/UseAzureSynapse. --- ...ServerDbContextOptionsBuilderExtensions.cs | 613 +++++++++++++++++- .../SqlServerServiceCollectionExtensions.cs | 157 ++++- .../Internal/ISqlServerSingletonOptions.cs | 44 +- .../Internal/SqlServerEngineType.cs | 33 + .../Internal/SqlServerOptionsExtension.cs | 244 ++++++- .../Internal/SqlServerSingletonOptions.cs | 68 +- .../SqlServerDbContextOptionsBuilder.cs | 54 +- .../Properties/SqlServerStrings.Designer.cs | 19 +- .../Properties/SqlServerStrings.resx | 6 + .../Internal/SqlServerQuerySqlGenerator.cs | 32 +- ...yableMethodTranslatingExpressionVisitor.cs | 18 +- .../Internal/SqlServerSqlExpressionFactory.cs | 12 +- ...qlServerSqlTranslatingExpressionVisitor.cs | 8 +- .../SqlServerStringMethodTranslator.cs | 8 +- .../Internal/DbContextOperationsTest.cs | 4 +- .../LoggingSqlServerTest.cs | 3 + ...piledSqlPregenerationQuerySqlServerTest.cs | 2 +- ...imitiveCollectionsQueryOldSqlServerTest.cs | 2 +- .../SqlServerConfigPatternsTest.cs | 320 ++++++++- .../SqlServerOptionsExtensionTest.cs | 7 +- 20 files changed, 1511 insertions(+), 143 deletions(-) create mode 100644 src/EFCore.SqlServer/Infrastructure/Internal/SqlServerEngineType.cs diff --git a/src/EFCore.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs index 7214fd81382..fe8bfb1cbba 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Internal; // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore; @@ -11,13 +12,13 @@ namespace Microsoft.EntityFrameworkCore; /// /// /// See Using DbContextOptions, and -/// Accessing SQL Server and Azure SQL databases with EF Core +/// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core /// for more information and examples. /// public static class SqlServerDbContextOptionsExtensions { /// - /// Configures the context to connect to a Microsoft SQL Server database, but without initially setting any + /// Configures the context to connect to a SQL Server database, but without initially setting any /// or connection string. /// /// @@ -28,52 +29,64 @@ public static class SqlServerDbContextOptionsExtensions /// /// /// See Using DbContextOptions, and - /// Accessing SQL Server and Azure SQL databases with EF Core + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core /// for more information and examples. /// /// /// The builder being used to configure the context. - /// An optional action to allow additional SQL Server specific configuration. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. /// The options builder so that further configuration can be chained. public static DbContextOptionsBuilder UseSqlServer( this DbContextOptionsBuilder optionsBuilder, Action? sqlServerOptionsAction = null) { - ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(GetOrCreateExtension(optionsBuilder)); - + var extension = GetOrCreateExtension(optionsBuilder); + if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.SqlServer) + { + throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.SqlServer, extension.EngineType)); + } + extension = extension + .WithEngineType(SqlServerEngineType.SqlServer); + ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); return ApplyConfiguration(optionsBuilder, sqlServerOptionsAction); } /// - /// Configures the context to connect to a Microsoft SQL Server database. + /// Configures the context to connect to a SQL Server database. /// /// /// See Using DbContextOptions, and - /// Accessing SQL Server and Azure SQL databases with EF Core + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core /// for more information and examples. /// /// The builder being used to configure the context. /// The connection string of the database to connect to. - /// An optional action to allow additional SQL Server specific configuration. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. /// The options builder so that further configuration can be chained. public static DbContextOptionsBuilder UseSqlServer( this DbContextOptionsBuilder optionsBuilder, string? connectionString, Action? sqlServerOptionsAction = null) { - var extension = (SqlServerOptionsExtension)GetOrCreateExtension(optionsBuilder).WithConnectionString(connectionString); + var extension = GetOrCreateExtension(optionsBuilder); + if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.SqlServer) + { + throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.SqlServer, extension.EngineType)); + } + extension = (SqlServerOptionsExtension)extension + .WithEngineType(SqlServerEngineType.SqlServer) + .WithConnectionString(connectionString); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); - return ApplyConfiguration(optionsBuilder, sqlServerOptionsAction); } // Note: Decision made to use DbConnection not SqlConnection: Issue #772 /// - /// Configures the context to connect to a Microsoft SQL Server database. + /// Configures the context to connect to a SQL Server database. /// /// /// See Using DbContextOptions, and - /// Accessing SQL Server and Azure SQL databases with EF Core + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core /// for more information and examples. /// /// The builder being used to configure the context. @@ -83,7 +96,7 @@ public static DbContextOptionsBuilder UseSqlServer( /// state then EF will open and close the connection as needed. The caller owns the connection and is /// responsible for its disposal. /// - /// An optional action to allow additional SQL Server specific configuration. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. /// The options builder so that further configuration can be chained. public static DbContextOptionsBuilder UseSqlServer( this DbContextOptionsBuilder optionsBuilder, @@ -93,11 +106,11 @@ public static DbContextOptionsBuilder UseSqlServer( // Note: Decision made to use DbConnection not SqlConnection: Issue #772 /// - /// Configures the context to connect to a Microsoft SQL Server database. + /// Configures the context to connect to a SQL Server database. /// /// /// See Using DbContextOptions, and - /// Accessing SQL Server and Azure SQL databases with EF Core + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core /// for more information and examples. /// /// The builder being used to configure the context. @@ -111,7 +124,7 @@ public static DbContextOptionsBuilder UseSqlServer( /// dispose it in the same way it would dispose a connection created by EF. If , then the caller still /// owns the connection and is responsible for its disposal. /// - /// An optional action to allow additional SQL Server specific configuration. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. /// The options builder so that further configuration can be chained. public static DbContextOptionsBuilder UseSqlServer( this DbContextOptionsBuilder optionsBuilder, @@ -121,14 +134,20 @@ public static DbContextOptionsBuilder UseSqlServer( { Check.NotNull(connection, nameof(connection)); - var extension = (SqlServerOptionsExtension)GetOrCreateExtension(optionsBuilder).WithConnection(connection, contextOwnsConnection); + var extension = GetOrCreateExtension(optionsBuilder); + if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.SqlServer) + { + throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.SqlServer, extension.EngineType)); + } + extension = (SqlServerOptionsExtension)extension + .WithEngineType(SqlServerEngineType.SqlServer) + .WithConnection(connection, contextOwnsConnection); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); - return ApplyConfiguration(optionsBuilder, sqlServerOptionsAction); } /// - /// Configures the context to connect to a Microsoft SQL Server database, but without initially setting any + /// Configures the context to connect to a SQL Server database, but without initially setting any /// or connection string. /// /// @@ -139,12 +158,12 @@ public static DbContextOptionsBuilder UseSqlServer( /// /// /// See Using DbContextOptions, and - /// Accessing SQL Server and Azure SQL databases with EF Core + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core /// for more information and examples. /// /// /// The builder being used to configure the context. - /// An optional action to allow additional SQL Server specific configuration. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. /// The options builder so that further configuration can be chained. public static DbContextOptionsBuilder UseSqlServer( this DbContextOptionsBuilder optionsBuilder, @@ -154,17 +173,17 @@ public static DbContextOptionsBuilder UseSqlServer( (DbContextOptionsBuilder)optionsBuilder, sqlServerOptionsAction); /// - /// Configures the context to connect to a Microsoft SQL Server database. + /// Configures the context to connect to a SQL Server database. /// /// /// See Using DbContextOptions, and - /// Accessing SQL Server and Azure SQL databases with EF Core + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core /// for more information and examples. /// /// The type of context to be configured. /// The builder being used to configure the context. /// The connection string of the database to connect to. - /// An optional action to allow additional SQL Server specific configuration. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. /// The options builder so that further configuration can be chained. public static DbContextOptionsBuilder UseSqlServer( this DbContextOptionsBuilder optionsBuilder, @@ -176,11 +195,11 @@ public static DbContextOptionsBuilder UseSqlServer( // Note: Decision made to use DbConnection not SqlConnection: Issue #772 /// - /// Configures the context to connect to a Microsoft SQL Server database. + /// Configures the context to connect to a SQL Server database. /// /// /// See Using DbContextOptions, and - /// Accessing SQL Server and Azure SQL databases with EF Core + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core /// for more information and examples. /// /// The type of context to be configured. @@ -191,7 +210,7 @@ public static DbContextOptionsBuilder UseSqlServer( /// state then EF will open and close the connection as needed. The caller owns the connection and is /// responsible for its disposal. /// - /// An optional action to allow additional SQL Server specific configuration. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. /// The options builder so that further configuration can be chained. public static DbContextOptionsBuilder UseSqlServer( this DbContextOptionsBuilder optionsBuilder, @@ -203,11 +222,11 @@ public static DbContextOptionsBuilder UseSqlServer( // Note: Decision made to use DbConnection not SqlConnection: Issue #772 /// - /// Configures the context to connect to a Microsoft SQL Server database. + /// Configures the context to connect to a SQL Server database. /// /// /// See Using DbContextOptions, and - /// Accessing SQL Server and Azure SQL databases with EF Core + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core /// for more information and examples. /// /// The type of context to be configured. @@ -222,7 +241,7 @@ public static DbContextOptionsBuilder UseSqlServer( /// dispose it in the same way it would dispose a connection created by EF. If , then the caller still /// owns the connection and is responsible for its disposal. /// - /// An optional action to allow additional SQL Server specific configuration. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. /// The options builder so that further configuration can be chained. public static DbContextOptionsBuilder UseSqlServer( this DbContextOptionsBuilder optionsBuilder, @@ -233,9 +252,533 @@ public static DbContextOptionsBuilder UseSqlServer( => (DbContextOptionsBuilder)UseSqlServer( (DbContextOptionsBuilder)optionsBuilder, connection, contextOwnsConnection, sqlServerOptionsAction); - private static SqlServerOptionsExtension GetOrCreateExtension(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.Options.FindExtension() - ?? new SqlServerOptionsExtension(); + /// + /// Configures the context to connect to a Azure SQL database, but without initially setting any + /// or connection string. + /// + /// + /// + /// The connection or connection string must be set before the is used to connect + /// to a database. Set a connection using . + /// Set a connection string using . + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// + /// The builder being used to configure the context. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseAzureSql( + this DbContextOptionsBuilder optionsBuilder, + Action? sqlServerOptionsAction = null) + { + var extension = GetOrCreateExtension(optionsBuilder); + if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.AzureSql) + { + throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.AzureSql, extension.EngineType)); + } + extension = extension + .WithEngineType(SqlServerEngineType.AzureSql); + ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); + return ApplyConfiguration(optionsBuilder, sqlServerOptionsAction); + } + + /// + /// Configures the context to connect to a Azure SQL database. + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// The builder being used to configure the context. + /// The connection string of the database to connect to. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseAzureSql( + this DbContextOptionsBuilder optionsBuilder, + string? connectionString, + Action? sqlServerOptionsAction = null) + { + var extension = GetOrCreateExtension(optionsBuilder); + if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.AzureSql) + { + throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.AzureSql, extension.EngineType)); + } + extension = (SqlServerOptionsExtension)extension + .WithEngineType(SqlServerEngineType.AzureSql) + .WithConnectionString(connectionString); + ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); + return ApplyConfiguration(optionsBuilder, sqlServerOptionsAction); + } + + // Note: Decision made to use DbConnection not SqlConnection: Issue #772 + /// + /// Configures the context to connect to a Azure SQL database. + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// The builder being used to configure the context. + /// + /// An existing to be used to connect to the database. If the connection is + /// in the open state then EF will not open or close the connection. If the connection is in the closed + /// state then EF will open and close the connection as needed. The caller owns the connection and is + /// responsible for its disposal. + /// + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseAzureSql( + this DbContextOptionsBuilder optionsBuilder, + DbConnection connection, + Action? sqlServerOptionsAction = null) + => UseAzureSql(optionsBuilder, connection, false, sqlServerOptionsAction); + + // Note: Decision made to use DbConnection not SqlConnection: Issue #772 + /// + /// Configures the context to connect to a Azure SQL database. + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// The builder being used to configure the context. + /// + /// An existing to be used to connect to the database. If the connection is + /// in the open state then EF will not open or close the connection. If the connection is in the closed + /// state then EF will open and close the connection as needed. + /// + /// + /// If , then EF will take ownership of the connection and will + /// dispose it in the same way it would dispose a connection created by EF. If , then the caller still + /// owns the connection and is responsible for its disposal. + /// + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseAzureSql( + this DbContextOptionsBuilder optionsBuilder, + DbConnection connection, + bool contextOwnsConnection, + Action? sqlServerOptionsAction = null) + { + Check.NotNull(connection, nameof(connection)); + + var extension = GetOrCreateExtension(optionsBuilder); + if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.AzureSql) + { + throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.AzureSql, extension.EngineType)); + } + extension = (SqlServerOptionsExtension)extension + .WithEngineType(SqlServerEngineType.AzureSql) + .WithConnection(connection, contextOwnsConnection); + ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); + return ApplyConfiguration(optionsBuilder, sqlServerOptionsAction); + } + + /// + /// Configures the context to connect to a Azure SQL database, but without initially setting any + /// or connection string. + /// + /// + /// + /// The connection or connection string must be set before the is used to connect + /// to a database. Set a connection using . + /// Set a connection string using . + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// + /// The builder being used to configure the context. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseAzureSql( + this DbContextOptionsBuilder optionsBuilder, + Action? sqlServerOptionsAction = null) + where TContext : DbContext + => (DbContextOptionsBuilder)UseAzureSql( + (DbContextOptionsBuilder)optionsBuilder, sqlServerOptionsAction); + + /// + /// Configures the context to connect to a Azure SQL database. + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// The type of context to be configured. + /// The builder being used to configure the context. + /// The connection string of the database to connect to. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseAzureSql( + this DbContextOptionsBuilder optionsBuilder, + string? connectionString, + Action? sqlServerOptionsAction = null) + where TContext : DbContext + => (DbContextOptionsBuilder)UseAzureSql( + (DbContextOptionsBuilder)optionsBuilder, connectionString, sqlServerOptionsAction); + + // Note: Decision made to use DbConnection not SqlConnection: Issue #772 + /// + /// Configures the context to connect to a Azure SQL database. + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// The type of context to be configured. + /// The builder being used to configure the context. + /// + /// An existing to be used to connect to the database. If the connection is + /// in the open state then EF will not open or close the connection. If the connection is in the closed + /// state then EF will open and close the connection as needed. The caller owns the connection and is + /// responsible for its disposal. + /// + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseAzureSql( + this DbContextOptionsBuilder optionsBuilder, + DbConnection connection, + Action? sqlServerOptionsAction = null) + where TContext : DbContext + => (DbContextOptionsBuilder)UseAzureSql( + (DbContextOptionsBuilder)optionsBuilder, connection, sqlServerOptionsAction); + + // Note: Decision made to use DbConnection not SqlConnection: Issue #772 + /// + /// Configures the context to connect to a Azure SQL database. + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// The type of context to be configured. + /// The builder being used to configure the context. + /// + /// An existing to be used to connect to the database. If the connection is + /// in the open state then EF will not open or close the connection. If the connection is in the closed + /// state then EF will open and close the connection as needed. + /// + /// + /// If , then EF will take ownership of the connection and will + /// dispose it in the same way it would dispose a connection created by EF. If , then the caller still + /// owns the connection and is responsible for its disposal. + /// + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseAzureSql( + this DbContextOptionsBuilder optionsBuilder, + DbConnection connection, + bool contextOwnsConnection, + Action? sqlServerOptionsAction = null) + where TContext : DbContext + => (DbContextOptionsBuilder)UseAzureSql( + (DbContextOptionsBuilder)optionsBuilder, connection, contextOwnsConnection, sqlServerOptionsAction); + + /// + /// Configures the context to connect to a Azure Synapse database, but without initially setting any + /// or connection string. + /// + /// + /// + /// The connection or connection string must be set before the is used to connect + /// to a database. Set a connection using . + /// Set a connection string using . + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// + /// The builder being used to configure the context. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseAzureSynapse( + this DbContextOptionsBuilder optionsBuilder, + Action? sqlServerOptionsAction = null) + { + var extension = GetOrCreateExtension(optionsBuilder); + if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.AzureSynapse) + { + throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.AzureSynapse, extension.EngineType)); + } + extension = extension + .WithEngineType(SqlServerEngineType.AzureSynapse); + ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); + return ApplyConfiguration(optionsBuilder, sqlServerOptionsAction); + } + + /// + /// Configures the context to connect to a Azure Synapse database. + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// The builder being used to configure the context. + /// The connection string of the database to connect to. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseAzureSynapse( + this DbContextOptionsBuilder optionsBuilder, + string? connectionString, + Action? sqlServerOptionsAction = null) + { + var extension = GetOrCreateExtension(optionsBuilder); + if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.AzureSynapse) + { + throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.AzureSynapse, extension.EngineType)); + } + extension = (SqlServerOptionsExtension)extension + .WithEngineType(SqlServerEngineType.AzureSynapse) + .WithConnectionString(connectionString); + ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); + return ApplyConfiguration(optionsBuilder, sqlServerOptionsAction); + } + + // Note: Decision made to use DbConnection not SqlConnection: Issue #772 + /// + /// Configures the context to connect to a Azure Synapse database. + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// The builder being used to configure the context. + /// + /// An existing to be used to connect to the database. If the connection is + /// in the open state then EF will not open or close the connection. If the connection is in the closed + /// state then EF will open and close the connection as needed. The caller owns the connection and is + /// responsible for its disposal. + /// + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseAzureSynapse( + this DbContextOptionsBuilder optionsBuilder, + DbConnection connection, + Action? sqlServerOptionsAction = null) + => UseAzureSynapse(optionsBuilder, connection, false, sqlServerOptionsAction); + + // Note: Decision made to use DbConnection not SqlConnection: Issue #772 + /// + /// Configures the context to connect to a Azure Synapse database. + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// The builder being used to configure the context. + /// + /// An existing to be used to connect to the database. If the connection is + /// in the open state then EF will not open or close the connection. If the connection is in the closed + /// state then EF will open and close the connection as needed. + /// + /// + /// If , then EF will take ownership of the connection and will + /// dispose it in the same way it would dispose a connection created by EF. If , then the caller still + /// owns the connection and is responsible for its disposal. + /// + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseAzureSynapse( + this DbContextOptionsBuilder optionsBuilder, + DbConnection connection, + bool contextOwnsConnection, + Action? sqlServerOptionsAction = null) + { + Check.NotNull(connection, nameof(connection)); + + var extension = GetOrCreateExtension(optionsBuilder); + if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.AzureSynapse) + { + throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.AzureSynapse, extension.EngineType)); + } + extension = (SqlServerOptionsExtension)extension + .WithEngineType(SqlServerEngineType.AzureSynapse) + .WithConnection(connection, contextOwnsConnection); + ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); + return ApplyConfiguration(optionsBuilder, sqlServerOptionsAction); + } + + /// + /// Configures the context to connect to a Azure Synapse database, but without initially setting any + /// or connection string. + /// + /// + /// + /// The connection or connection string must be set before the is used to connect + /// to a database. Set a connection using . + /// Set a connection string using . + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// + /// The builder being used to configure the context. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseAzureSynapse( + this DbContextOptionsBuilder optionsBuilder, + Action? sqlServerOptionsAction = null) + where TContext : DbContext + => (DbContextOptionsBuilder)UseAzureSynapse( + (DbContextOptionsBuilder)optionsBuilder, sqlServerOptionsAction); + + /// + /// Configures the context to connect to a Azure Synapse database. + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// The type of context to be configured. + /// The builder being used to configure the context. + /// The connection string of the database to connect to. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseAzureSynapse( + this DbContextOptionsBuilder optionsBuilder, + string? connectionString, + Action? sqlServerOptionsAction = null) + where TContext : DbContext + => (DbContextOptionsBuilder)UseAzureSynapse( + (DbContextOptionsBuilder)optionsBuilder, connectionString, sqlServerOptionsAction); + + // Note: Decision made to use DbConnection not SqlConnection: Issue #772 + /// + /// Configures the context to connect to a Azure Synapse database. + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// The type of context to be configured. + /// The builder being used to configure the context. + /// + /// An existing to be used to connect to the database. If the connection is + /// in the open state then EF will not open or close the connection. If the connection is in the closed + /// state then EF will open and close the connection as needed. The caller owns the connection and is + /// responsible for its disposal. + /// + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseAzureSynapse( + this DbContextOptionsBuilder optionsBuilder, + DbConnection connection, + Action? sqlServerOptionsAction = null) + where TContext : DbContext + => (DbContextOptionsBuilder)UseAzureSynapse( + (DbContextOptionsBuilder)optionsBuilder, connection, sqlServerOptionsAction); + + // Note: Decision made to use DbConnection not SqlConnection: Issue #772 + /// + /// Configures the context to connect to a Azure Synapse database. + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// The type of context to be configured. + /// The builder being used to configure the context. + /// + /// An existing to be used to connect to the database. If the connection is + /// in the open state then EF will not open or close the connection. If the connection is in the closed + /// state then EF will open and close the connection as needed. + /// + /// + /// If , then EF will take ownership of the connection and will + /// dispose it in the same way it would dispose a connection created by EF. If , then the caller still + /// owns the connection and is responsible for its disposal. + /// + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseAzureSynapse( + this DbContextOptionsBuilder optionsBuilder, + DbConnection connection, + bool contextOwnsConnection, + Action? sqlServerOptionsAction = null) + where TContext : DbContext + => (DbContextOptionsBuilder)UseAzureSynapse( + (DbContextOptionsBuilder)optionsBuilder, connection, contextOwnsConnection, sqlServerOptionsAction); + + /// + /// Configures the context to connect to any of SQL Server, Azure SQL, Azure Synapse databases, but without initially setting any + /// or connection string. + /// + /// + /// + /// The connection or connection string must be set before the is used to connect + /// to a database. Set a connection using . + /// Set a connection string using . + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// + /// The builder being used to configure the context. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder ConfigureSqlEngine( + this DbContextOptionsBuilder optionsBuilder, + Action? sqlServerOptionsAction = null) + { + ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(GetOrCreateExtension(optionsBuilder)); + return ApplyConfiguration(optionsBuilder, sqlServerOptionsAction); + } + + /// + /// Configures the context to connect to any of SQL Server, Azure SQL, Azure Synapse databases, but without initially setting any + /// or connection string. + /// + /// + /// + /// The connection or connection string must be set before the is used to connect + /// to a database. Set a connection using . + /// Set a connection string using . + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// + /// The builder being used to configure the context. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder ConfigureSqlEngine( + this DbContextOptionsBuilder optionsBuilder, + Action? sqlServerOptionsAction = null) + where TContext : DbContext + => (DbContextOptionsBuilder)ConfigureSqlEngine( + (DbContextOptionsBuilder)optionsBuilder, sqlServerOptionsAction); + + private static T GetOrCreateExtension(DbContextOptionsBuilder optionsBuilder) + where T : RelationalOptionsExtension, new() + => optionsBuilder.Options.FindExtension() + ?? new T(); private static DbContextOptionsBuilder ApplyConfiguration( DbContextOptionsBuilder optionsBuilder, @@ -245,7 +788,7 @@ private static DbContextOptionsBuilder ApplyConfiguration( sqlServerOptionsAction?.Invoke(new SqlServerDbContextOptionsBuilder(optionsBuilder)); - var extension = (SqlServerOptionsExtension)GetOrCreateExtension(optionsBuilder).ApplyDefaults(optionsBuilder.Options); + var extension = GetOrCreateExtension(optionsBuilder).ApplyDefaults(optionsBuilder.Options); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); return optionsBuilder; diff --git a/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs index 12f2d4cf882..87f220451ed 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs @@ -15,7 +15,7 @@ namespace Microsoft.Extensions.DependencyInjection; /// -/// SQL Server specific extension methods for . +/// SQL Server, Azure SQL, Azure Synapse specific extension methods for . /// public static class SqlServerServiceCollectionExtensions { @@ -45,14 +45,14 @@ public static class SqlServerServiceCollectionExtensions /// /// /// See Using DbContextOptions, and - /// Accessing SQL Server and Azure SQL databases with EF Core + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core /// for more information and examples. /// /// /// The type of context to be registered. /// The to add services to. /// The connection string of the database to connect to. - /// An optional action to allow additional SQL Server specific configuration. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. /// An optional action to configure the for the context. /// The same service collection so that multiple calls can be chained. public static IServiceCollection AddSqlServer( @@ -91,6 +91,157 @@ public static IServiceCollection AddSqlServer( /// [EditorBrowsable(EditorBrowsableState.Never)] public static IServiceCollection AddEntityFrameworkSqlServer(this IServiceCollection serviceCollection) + => AddEntityFrameworkSqlEngine(serviceCollection); + + /// + /// Registers the given Entity Framework as a service in the + /// and configures it to connect to a Azure SQL database. + /// + /// + /// + /// This method is a shortcut for configuring a to use Azure SQL. It does not support all options. + /// Use and related methods for full control of + /// this process. + /// + /// + /// Use this method when using dependency injection in your application, such as with ASP.NET Core. + /// For applications that don't use dependency injection, consider creating + /// instances directly with its constructor. The method can then be + /// overridden to configure the Azure SQL provider and connection string. + /// + /// + /// To configure the for the context, either override the + /// method in your derived context, or supply + /// an optional action to configure the for the context. + /// + /// + /// See Using DbContext with dependency injection for more information and examples. + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// + /// The type of context to be registered. + /// The to add services to. + /// The connection string of the database to connect to. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// An optional action to configure the for the context. + /// The same service collection so that multiple calls can be chained. + public static IServiceCollection AddAzureSql( + this IServiceCollection serviceCollection, + string? connectionString, + Action? sqlServerOptionsAction = null, + Action? optionsAction = null) + where TContext : DbContext + => serviceCollection.AddDbContext( + (_, options) => + { + optionsAction?.Invoke(options); + options.UseAzureSql(connectionString, sqlServerOptionsAction); + }); + + /// + /// + /// Adds the services required by the Microsoft Azure SQL database provider for Entity Framework + /// to an . + /// + /// + /// Warning: Do not call this method accidentally. It is much more likely you need + /// to call . + /// + /// + /// + /// Calling this method is no longer necessary when building most applications, including those that + /// use dependency injection in ASP.NET or elsewhere. + /// It is only needed when building the internal service provider for use with + /// the method. + /// This is not recommend other than for some advanced scenarios. + /// + /// The to add services to. + /// + /// The same service collection so that multiple calls can be chained. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static IServiceCollection AddEntityFrameworkAzureSql(this IServiceCollection serviceCollection) + => AddEntityFrameworkSqlEngine(serviceCollection); + + /// + /// Registers the given Entity Framework as a service in the + /// and configures it to connect to a Azure Synapse database. + /// + /// + /// + /// This method is a shortcut for configuring a to use Azure Synapse. It does not support all options. + /// Use and related methods for full control of + /// this process. + /// + /// + /// Use this method when using dependency injection in your application, such as with ASP.NET Core. + /// For applications that don't use dependency injection, consider creating + /// instances directly with its constructor. The method can then be + /// overridden to configure the Azure Synapse provider and connection string. + /// + /// + /// To configure the for the context, either override the + /// method in your derived context, or supply + /// an optional action to configure the for the context. + /// + /// + /// See Using DbContext with dependency injection for more information and examples. + /// + /// + /// See Using DbContextOptions, and + /// Accessing SQL Server, Azure SQL, Azure Synapse databases with EF Core + /// for more information and examples. + /// + /// + /// The type of context to be registered. + /// The to add services to. + /// The connection string of the database to connect to. + /// An optional action to allow additional SQL Server, Azure SQL, Azure Synapse specific configuration. + /// An optional action to configure the for the context. + /// The same service collection so that multiple calls can be chained. + public static IServiceCollection AddAzureSynapse( + this IServiceCollection serviceCollection, + string? connectionString, + Action? sqlServerOptionsAction = null, + Action? optionsAction = null) + where TContext : DbContext + => serviceCollection.AddDbContext( + (_, options) => + { + optionsAction?.Invoke(options); + options.UseAzureSynapse(connectionString, sqlServerOptionsAction); + }); + + /// + /// + /// Adds the services required by the Microsoft Azure Synapse database provider for Entity Framework + /// to an . + /// + /// + /// Warning: Do not call this method accidentally. It is much more likely you need + /// to call . + /// + /// + /// + /// Calling this method is no longer necessary when building most applications, including those that + /// use dependency injection in ASP.NET or elsewhere. + /// It is only needed when building the internal service provider for use with + /// the method. + /// This is not recommend other than for some advanced scenarios. + /// + /// The to add services to. + /// + /// The same service collection so that multiple calls can be chained. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static IServiceCollection AddEntityFrameworkAzureSynapse(this IServiceCollection serviceCollection) + => AddEntityFrameworkSqlEngine(serviceCollection); + + private static IServiceCollection AddEntityFrameworkSqlEngine(IServiceCollection serviceCollection) { new EntityFrameworkRelationalServicesBuilder(serviceCollection) .TryAdd() diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/ISqlServerSingletonOptions.cs b/src/EFCore.SqlServer/Infrastructure/Internal/ISqlServerSingletonOptions.cs index f5f661d65d3..6bb88309a8a 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/ISqlServerSingletonOptions.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/ISqlServerSingletonOptions.cs @@ -17,7 +17,7 @@ public interface ISqlServerSingletonOptions : ISingletonOptions /// 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. /// - int CompatibilityLevel { get; } + public SqlServerEngineType EngineType { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -25,5 +25,45 @@ public interface ISqlServerSingletonOptions : ISingletonOptions /// 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. /// - int? CompatibilityLevelWithoutDefault { get; } + public int SqlServerCompatibilityLevel { 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 int? SqlServerCompatibilityLevelWithoutDefault { 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 int AzureSqlCompatibilityLevel { 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 int? AzureSqlCompatibilityLevelWithoutDefault { 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 int AzureSynapseCompatibilityLevel { 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 int? AzureSynapseCompatibilityLevelWithoutDefault { get; } } diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerEngineType.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerEngineType.cs new file mode 100644 index 00000000000..63f9c9816f4 --- /dev/null +++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerEngineType.cs @@ -0,0 +1,33 @@ +// 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.SqlServer.Infrastructure.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 enum SqlServerEngineType +{ + /// + /// Unknown SQL engine type. + /// + Unknown = 0, + + /// + /// SQL Server. + /// + SqlServer = 1, + + /// + /// Azure SQL. + /// + AzureSql = 2, + + /// + /// Azure Synapse. + /// + AzureSynapse = 3, +} diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs index 50e26c4e6a0..2124d248505 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Text; +using Microsoft.EntityFrameworkCore.SqlServer.Internal; namespace Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; @@ -14,8 +15,11 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; public class SqlServerOptionsExtension : RelationalOptionsExtension, IDbContextOptionsExtension { private DbContextOptionsExtensionInfo? _info; - private int? _compatibilityLevel; - private bool? _azureSql; + private SqlServerEngineType _engineType; + private bool _legacyAzureSql; + private int? _sqlServerCompatibilityLevel; + private int? _azureSqlCompatibilityLevel; + private int? _azureSynapseCompatibilityLevel; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -29,7 +33,30 @@ public class SqlServerOptionsExtension : RelationalOptionsExtension, IDbContextO // SQL Server 2017 (14.x): compatibility level 140, start date 2017-09-29, mainstream end date 2022-10-11, extended end date 2027-10-12 // SQL Server 2016 (13.x): compatibility level 130, start date 2016-06-01, mainstream end date 2021-07-13, extended end date 2026-07-14 // SQL Server 2014 (12.x): compatibility level 120, start date 2014-06-05, mainstream end date 2019-07-09, extended end date 2024-07-09 - public static readonly int DefaultCompatibilityLevel = 160; + public static readonly int SqlServerDefaultCompatibilityLevel = 160; + + /// + /// 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. + /// + // See https://learn.microsoft.com/sql/t-sql/statements/alter-database-transact-sql-compatibility-level + // SQL Server 2022 (16.x): compatibility level 160, start date 2022-11-16, mainstream end date 2028-01-11, extended end date 2033-01-11 + // SQL Server 2019 (15.x): compatibility level 150, start date 2019-11-04, mainstream end date 2025-02-28, extended end date 2030-01-08 + // SQL Server 2017 (14.x): compatibility level 140, start date 2017-09-29, mainstream end date 2022-10-11, extended end date 2027-10-12 + // SQL Server 2016 (13.x): compatibility level 130, start date 2016-06-01, mainstream end date 2021-07-13, extended end date 2026-07-14 + // SQL Server 2014 (12.x): compatibility level 120, start date 2014-06-05, mainstream end date 2019-07-09, extended end date 2024-07-09 + public static readonly int AzureSqlDefaultCompatibilityLevel = 160; + + /// + /// 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. + /// + // See https://learn.microsoft.com/en-us/sql/t-sql/statements/alter-database-scoped-configuration-transact-sql + public static readonly int AzureSynapseDefaultCompatibilityLevel = 30; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -52,8 +79,11 @@ public SqlServerOptionsExtension() protected SqlServerOptionsExtension(SqlServerOptionsExtension copyFrom) : base(copyFrom) { - _compatibilityLevel = copyFrom._compatibilityLevel; - _azureSql = copyFrom._azureSql; + _engineType = copyFrom._engineType; + _legacyAzureSql = copyFrom._legacyAzureSql; + _sqlServerCompatibilityLevel = copyFrom._sqlServerCompatibilityLevel; + _azureSqlCompatibilityLevel = copyFrom._azureSqlCompatibilityLevel; + _azureSynapseCompatibilityLevel = copyFrom._azureSynapseCompatibilityLevel; } /// @@ -80,8 +110,17 @@ protected override RelationalOptionsExtension Clone() /// 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 int CompatibilityLevel - => _compatibilityLevel ?? DefaultCompatibilityLevel; + public virtual SqlServerEngineType EngineType + => _engineType; + + /// + /// 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 LegacyAzureSql + => _legacyAzureSql; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -89,8 +128,8 @@ public virtual int CompatibilityLevel /// 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 int? CompatibilityLevelWithoutDefault - => _compatibilityLevel; + public virtual int SqlServerCompatibilityLevel + => _sqlServerCompatibilityLevel ?? SqlServerDefaultCompatibilityLevel; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -98,11 +137,56 @@ public virtual int? CompatibilityLevelWithoutDefault /// 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 SqlServerOptionsExtension WithCompatibilityLevel(int? compatibilityLevel) + public virtual int? SqlServerCompatibilityLevelWithoutDefault + => _sqlServerCompatibilityLevel; + + /// + /// 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 int AzureSqlCompatibilityLevel + => _azureSqlCompatibilityLevel ?? AzureSqlDefaultCompatibilityLevel; + + /// + /// 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 int? AzureSqlCompatibilityLevelWithoutDefault + => _azureSqlCompatibilityLevel; + + /// + /// 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 int AzureSynapseCompatibilityLevel + => _azureSynapseCompatibilityLevel ?? AzureSynapseDefaultCompatibilityLevel; + + /// + /// 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 int? AzureSynapseCompatibilityLevelWithoutDefault + => _azureSynapseCompatibilityLevel; + + /// + /// 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 SqlServerOptionsExtension WithEngineType(SqlServerEngineType engineType) { var clone = (SqlServerOptionsExtension)Clone(); - clone._compatibilityLevel = compatibilityLevel; + clone._engineType = engineType; return clone; } @@ -113,8 +197,45 @@ public virtual SqlServerOptionsExtension WithCompatibilityLevel(int? compatibili /// 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 IsAzureSql - => _azureSql ?? false; + public virtual SqlServerOptionsExtension WithLegacyAzureSql(bool enable) + { + var clone = (SqlServerOptionsExtension)Clone(); + + clone._engineType = SqlServerEngineType.SqlServer; + clone._legacyAzureSql = enable; + + return clone; + } + + /// + /// 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 SqlServerOptionsExtension WithSqlServerCompatibilityLevel(int? sqlServerCompatibilityLevel) + { + var clone = (SqlServerOptionsExtension)Clone(); + + clone._sqlServerCompatibilityLevel = sqlServerCompatibilityLevel; + + return clone; + } + + /// + /// 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 SqlServerOptionsExtension WithAzureSqlCompatibilityLevel(int? azureSqlCompatibilityLevel) + { + var clone = (SqlServerOptionsExtension)Clone(); + + clone._azureSqlCompatibilityLevel = azureSqlCompatibilityLevel; + + return clone; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -122,11 +243,11 @@ public virtual bool IsAzureSql /// 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 SqlServerOptionsExtension WithAzureSql(bool enable) + public virtual SqlServerOptionsExtension WithAzureSynapseCompatibilityLevel(int? azureSynapseCompatibilityLevel) { var clone = (SqlServerOptionsExtension)Clone(); - clone._azureSql = enable; + clone._azureSynapseCompatibilityLevel = _azureSynapseCompatibilityLevel; return clone; } @@ -134,12 +255,8 @@ public virtual SqlServerOptionsExtension WithAzureSql(bool enable) /// public virtual IDbContextOptionsExtension ApplyDefaults(IDbContextOptions options) { - if (!IsAzureSql) - { - return this; - } - - if (ExecutionStrategyFactory == null) + if (ExecutionStrategyFactory == null + && (EngineType == SqlServerEngineType.AzureSql || EngineType == SqlServerEngineType.AzureSynapse || LegacyAzureSql)) { return WithExecutionStrategyFactory(c => new SqlServerRetryingExecutionStrategy(c)); } @@ -154,7 +271,35 @@ public virtual IDbContextOptionsExtension ApplyDefaults(IDbContextOptions option /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override void ApplyServices(IServiceCollection services) - => services.AddEntityFrameworkSqlServer(); + { + switch (_engineType) + { + case SqlServerEngineType.SqlServer: + services.AddEntityFrameworkSqlServer(); + break; + case SqlServerEngineType.AzureSql: + services.AddEntityFrameworkAzureSql(); + break; + case SqlServerEngineType.AzureSynapse: + services.AddEntityFrameworkAzureSynapse(); + break; + } + } + + /// + /// 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 Validate(IDbContextOptions options) + { + base.Validate(options); + if (EngineType == SqlServerEngineType.Unknown) + { + throw new InvalidOperationException(SqlServerStrings.InvalidEngineType($"{nameof(SqlServerDbContextOptionsExtensions.UseSqlServer)}/{nameof(SqlServerDbContextOptionsExtensions.UseAzureSql)}/{nameof(SqlServerDbContextOptionsExtensions.UseAzureSynapse)}")); + } + } private sealed class ExtensionInfo : RelationalExtensionInfo { @@ -168,12 +313,13 @@ public ExtensionInfo(IDbContextOptionsExtension extension) private new SqlServerOptionsExtension Extension => (SqlServerOptionsExtension)base.Extension; - public override bool IsDatabaseProvider - => true; - public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) => other is ExtensionInfo otherInfo - && Extension.CompatibilityLevel == otherInfo.Extension.CompatibilityLevel; + && Extension.EngineType == otherInfo.Extension.EngineType + && Extension.LegacyAzureSql == otherInfo.Extension.LegacyAzureSql + && Extension.SqlServerCompatibilityLevel == otherInfo.Extension.SqlServerCompatibilityLevel + && Extension.AzureSqlCompatibilityLevel == otherInfo.Extension.AzureSqlCompatibilityLevel + && Extension.AzureSynapseCompatibilityLevel == otherInfo.Extension.AzureSynapseCompatibilityLevel; public override string LogFragment { @@ -185,11 +331,36 @@ public override string LogFragment builder.Append(base.LogFragment); - if (Extension._compatibilityLevel is int compatibilityLevel) + builder + .Append("EngineType=") + .Append(Extension._engineType) + .Append(' '); + + builder + .Append("LegacyAzureSql=") + .Append(Extension._legacyAzureSql) + .Append(' '); + + if (Extension._sqlServerCompatibilityLevel != null) + { + builder + .Append("SqlServerCompatibilityLevel=") + .Append(Extension._sqlServerCompatibilityLevel) + .Append(' '); + } + if (Extension._azureSqlCompatibilityLevel != null) { builder - .Append("CompatibilityLevel=") - .Append(compatibilityLevel); + .Append("AzureSqlCompatibilityLevel=") + .Append(Extension._azureSqlCompatibilityLevel) + .Append(' '); + } + if (Extension._azureSynapseCompatibilityLevel != null) + { + builder + .Append("AzureSynapseCompatibilityLevel=") + .Append(Extension._azureSynapseCompatibilityLevel) + .Append(' '); } _logFragment = builder.ToString(); @@ -201,11 +372,20 @@ public override string LogFragment public override void PopulateDebugInfo(IDictionary debugInfo) { - debugInfo["SqlServer"] = "1"; + debugInfo["EngineType"] = Extension.EngineType.ToString(); + debugInfo["LegacyAzureSql"] = Extension.LegacyAzureSql.ToString(); - if (Extension.CompatibilityLevel is int compatibilityLevel) + if (Extension.SqlServerCompatibilityLevel is int sqlServerCompatibilityLevel) + { + debugInfo["SqlServerCompatibilityLevel"] = sqlServerCompatibilityLevel.ToString(); + } + if (Extension.AzureSqlCompatibilityLevel is int azureSqlCompatibilityLevel) + { + debugInfo["AzureSqlCompatibilityLevel"] = azureSqlCompatibilityLevel.ToString(); + } + if (Extension.AzureSynapseCompatibilityLevel is int azureSynapseCompatibilityLevel) { - debugInfo["CompatibilityLevel"] = compatibilityLevel.ToString(); + debugInfo["AzureSynapseCompatibilityLevel"] = azureSynapseCompatibilityLevel.ToString(); } } } diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerSingletonOptions.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerSingletonOptions.cs index cba62d8cd69..b27d8f685d0 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerSingletonOptions.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerSingletonOptions.cs @@ -17,7 +17,7 @@ public class SqlServerSingletonOptions : ISqlServerSingletonOptions /// 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 int CompatibilityLevel { get; private set; } = SqlServerOptionsExtension.DefaultCompatibilityLevel; + public virtual SqlServerEngineType EngineType { get; private set; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -25,7 +25,47 @@ public class SqlServerSingletonOptions : ISqlServerSingletonOptions /// 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 int? CompatibilityLevelWithoutDefault { get; private set; } + public virtual int SqlServerCompatibilityLevel { get; private set; } = SqlServerOptionsExtension.SqlServerDefaultCompatibilityLevel; + + /// + /// 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 int? SqlServerCompatibilityLevelWithoutDefault { get; private set; } + + /// + /// 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 int AzureSqlCompatibilityLevel { get; private set; } = SqlServerOptionsExtension.AzureSqlDefaultCompatibilityLevel; + + /// + /// 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 int? AzureSqlCompatibilityLevelWithoutDefault { get; private set; } + + /// + /// 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 int AzureSynapseCompatibilityLevel { get; private set; } = SqlServerOptionsExtension.AzureSynapseDefaultCompatibilityLevel; + + /// + /// 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 int? AzureSynapseCompatibilityLevelWithoutDefault { get; private set; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -38,8 +78,13 @@ public virtual void Initialize(IDbContextOptions options) var sqlServerOptions = options.FindExtension(); if (sqlServerOptions != null) { - CompatibilityLevel = sqlServerOptions.CompatibilityLevel; - CompatibilityLevelWithoutDefault = sqlServerOptions.CompatibilityLevelWithoutDefault; + EngineType = sqlServerOptions.EngineType; + SqlServerCompatibilityLevel = sqlServerOptions.SqlServerCompatibilityLevel; + SqlServerCompatibilityLevelWithoutDefault = sqlServerOptions.SqlServerCompatibilityLevelWithoutDefault; + AzureSqlCompatibilityLevel = sqlServerOptions.AzureSqlCompatibilityLevel; + AzureSqlCompatibilityLevelWithoutDefault = sqlServerOptions.AzureSqlCompatibilityLevelWithoutDefault; + AzureSynapseCompatibilityLevel = sqlServerOptions.AzureSynapseCompatibilityLevel; + AzureSynapseCompatibilityLevelWithoutDefault = sqlServerOptions.AzureSynapseCompatibilityLevelWithoutDefault; } } @@ -51,15 +96,20 @@ public virtual void Initialize(IDbContextOptions options) /// public virtual void Validate(IDbContextOptions options) { - var sqlserverOptions = options.FindExtension(); + var sqlServerOptions = options.FindExtension(); - if (sqlserverOptions != null - && (CompatibilityLevelWithoutDefault != sqlserverOptions.CompatibilityLevelWithoutDefault - || CompatibilityLevel != sqlserverOptions.CompatibilityLevel)) + if (sqlServerOptions != null + && (EngineType != sqlServerOptions.EngineType + || SqlServerCompatibilityLevelWithoutDefault != sqlServerOptions.SqlServerCompatibilityLevelWithoutDefault + || SqlServerCompatibilityLevel != sqlServerOptions.SqlServerCompatibilityLevel + || AzureSqlCompatibilityLevelWithoutDefault != sqlServerOptions.AzureSqlCompatibilityLevelWithoutDefault + || AzureSqlCompatibilityLevel != sqlServerOptions.AzureSqlCompatibilityLevel + || AzureSynapseCompatibilityLevelWithoutDefault != sqlServerOptions.AzureSynapseCompatibilityLevelWithoutDefault + || AzureSynapseCompatibilityLevel != sqlServerOptions.AzureSynapseCompatibilityLevel)) { throw new InvalidOperationException( CoreStrings.SingletonOptionChanged( - nameof(SqlServerDbContextOptionsExtensions.UseSqlServer), + $"{nameof(SqlServerDbContextOptionsExtensions.UseSqlServer)}/{nameof(SqlServerDbContextOptionsExtensions.UseAzureSql)}/{nameof(SqlServerDbContextOptionsExtensions.UseAzureSynapse)}", nameof(DbContextOptionsBuilder.UseInternalServiceProvider))); } } diff --git a/src/EFCore.SqlServer/Infrastructure/SqlServerDbContextOptionsBuilder.cs b/src/EFCore.SqlServer/Infrastructure/SqlServerDbContextOptionsBuilder.cs index 3a4345aa804..08805643353 100644 --- a/src/EFCore.SqlServer/Infrastructure/SqlServerDbContextOptionsBuilder.cs +++ b/src/EFCore.SqlServer/Infrastructure/SqlServerDbContextOptionsBuilder.cs @@ -6,10 +6,13 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure; /// -/// Allows SQL Server specific configuration to be performed on . +/// Allows SQL Server, Azure SQL, Azure Synapse specific configuration to be performed on . /// /// -/// Instances of this class are returned from a call to +/// Instances of this class are returned from a call to +/// , +/// , +/// /// and it is not designed to be directly constructed in your application code. /// public class SqlServerDbContextOptionsBuilder @@ -29,7 +32,7 @@ public SqlServerDbContextOptionsBuilder(DbContextOptionsBuilder optionsBuilder) /// /// /// - /// This strategy is specifically tailored to SQL Server (including Azure SQL). It is pre-configured with + /// This strategy is specifically tailored to SQL Server, Azure SQL, Azure Synapse. It is pre-configured with /// error numbers for transient errors that can be retried. /// /// @@ -48,7 +51,7 @@ public virtual SqlServerDbContextOptionsBuilder EnableRetryOnFailure() /// /// /// - /// This strategy is specifically tailored to SQL Server (including Azure SQL). It is pre-configured with + /// This strategy is specifically tailored to SQL Server, Azure SQL, Azure Synapse. It is pre-configured with /// error numbers for transient errors that can be retried. /// /// @@ -67,7 +70,7 @@ public virtual SqlServerDbContextOptionsBuilder EnableRetryOnFailure(int maxRetr /// /// /// - /// This strategy is specifically tailored to SQL Server (including Azure SQL). It is pre-configured with + /// This strategy is specifically tailored to SQL Server, Azure SQL, Azure Synapse. It is pre-configured with /// error numbers for transient errors that can be retried. /// /// @@ -87,7 +90,7 @@ public virtual SqlServerDbContextOptionsBuilder EnableRetryOnFailure(ICollection /// /// /// - /// This strategy is specifically tailored to SQL Server (including Azure SQL). It is pre-configured with + /// This strategy is specifically tailored to SQL Server, Azure SQL, Azure Synapse. It is pre-configured with /// error numbers for transient errors that can be retried, but additional error numbers can also be supplied. /// /// @@ -116,14 +119,45 @@ public virtual SqlServerDbContextOptionsBuilder EnableRetryOnFailure( /// /// for more information and examples. /// - /// to have null resource - public virtual SqlServerDbContextOptionsBuilder UseCompatibilityLevel(int compatibilityLevel) - => WithOption(e => e.WithCompatibilityLevel(compatibilityLevel)); + /// to have null resource + public virtual SqlServerDbContextOptionsBuilder UseSqlServerCompatibilityLevel(int sqlServerCompatibilityLevel) + => WithOption(e => e.WithSqlServerCompatibilityLevel(sqlServerCompatibilityLevel)); /// /// Configures the context to use defaults optimized for Azure SQL, including retries on errors. /// /// Whether the defaults should be enabled. + [Obsolete("Use UseAzureSql instead of UseSqlServer with UseAzureSqlDefaults.")] public virtual SqlServerDbContextOptionsBuilder UseAzureSqlDefaults(bool enable = true) - => WithOption(e => e.WithAzureSql(enable)); + => WithOption(e => e.WithLegacyAzureSql(enable)); + + /// + /// Sets the Azure SQL compatibility level that EF Core will use when interacting with the database. This allows configuring EF + /// Core to work with older (or newer) versions of Azure SQL. Defaults to 160. + /// + /// + /// See Using DbContextOptions, and + /// + /// Azure SQL documentation on compatibility level + /// + /// for more information and examples. + /// + /// to have null resource + public virtual SqlServerDbContextOptionsBuilder UseAzureSqlCompatibilityLevel(int azureSqlCompatibilityLevel) + => WithOption(e => e.WithAzureSqlCompatibilityLevel(azureSqlCompatibilityLevel)); + + /// + /// Sets the Azure Synapse compatibility level that EF Core will use when interacting with the database. This allows configuring EF + /// Core to work with older (or newer) versions of Azure Synapse. Defaults to 30. + /// + /// + /// See Using DbContextOptions, and + /// + /// Azure Synapse documentation on compatibility level + /// + /// for more information and examples. + /// + /// to have null resource + public virtual SqlServerDbContextOptionsBuilder UseAzureSynapseCompatibilityLevel(int azureSynapseCompatibilityLevel) + => WithOption(e => e.WithAzureSynapseCompatibilityLevel(azureSynapseCompatibilityLevel)); } diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs index 0401a37e299..e0e0c1da387 100644 --- a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs +++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs @@ -23,6 +23,14 @@ public static class SqlServerStrings private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.EntityFrameworkCore.SqlServer.Properties.SqlServerStrings", typeof(SqlServerStrings).Assembly); + /// + /// Cannot configure engine type '{newEngineType}', because engine type was already configured as '{oldEngineType}'. + /// + public static string AlreadyConfiguredEngineType(object? newEngineType, object? oldEngineType) + => string.Format( + GetString("AlreadyConfiguredEngineType", nameof(newEngineType), nameof(oldEngineType)), + newEngineType, oldEngineType); + /// /// To change the IDENTITY property of a column, the column needs to be dropped and recreated. /// @@ -207,6 +215,14 @@ public static string IndexTableRequired public static string InvalidColumnNameForFreeText => GetString("InvalidColumnNameForFreeText"); + /// + /// Engine type was not configured. Use one of {methods} to configure it. + /// + public static string InvalidEngineType(object? methods) + => string.Format( + GetString("InvalidEngineType", nameof(methods)), + methods); + /// /// The specified table '{table}' is not in a valid format. Specify tables using the format '[schema].[table]'. /// @@ -706,8 +722,7 @@ public static EventDefinition LogFoundPrimaryKey(IDiagnosticsLog } /// - /// Found sequence with '{name}', data type: {dataType}, cyclic: {isCyclic}, increment: {increment}, start: {start}, minimum: {min}, maximum: {max}, - /// cached: {cached}, cache size: {cacheSize}. + /// Found sequence with '{name}', data type: {dataType}, cyclic: {isCyclic}, increment: {increment}, start: {start}, minimum: {min}, maximum: {max}, cached: {cached}, cacheSize: {cacheSize}. /// public static FallbackEventDefinition LogFoundSequence(IDiagnosticsLogger logger) { diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx index 8a229138734..a0761566689 100644 --- a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx +++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Cannot configure engine type '{newEngineType}', because engine type was already configured as '{oldEngineType}'. + To change the IDENTITY property of a column, the column needs to be dropped and recreated. @@ -189,6 +192,9 @@ The expression passed to the 'propertyReference' parameter of the 'FreeText' method is not a valid reference to a property. The expression must represent a reference to a full-text indexed property on the object referenced in the from clause: 'from e in context.Entities where EF.Functions.FreeText(e.SomeProperty, textToSearchFor) select e' + + Engine type was not configured. Use one of {methods} to configure it. + The specified table '{table}' is not in a valid format. Specify tables using the format '[schema].[table]'. diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs index 717d31dad4f..c381edb4ab0 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs @@ -19,7 +19,7 @@ public class SqlServerQuerySqlGenerator : QuerySqlGenerator { private readonly IRelationalTypeMappingSource _typeMappingSource; private readonly ISqlGenerationHelper _sqlGenerationHelper; - private readonly int _sqlServerCompatibilityLevel; + private readonly ISqlServerSingletonOptions _sqlServerSingletonOptions; private bool _withinTable; @@ -37,7 +37,7 @@ public SqlServerQuerySqlGenerator( { _typeMappingSource = typeMappingSource; _sqlGenerationHelper = dependencies.SqlGenerationHelper; - _sqlServerCompatibilityLevel = sqlServerSingletonOptions.CompatibilityLevel; + _sqlServerSingletonOptions = sqlServerSingletonOptions; } /// @@ -518,18 +518,26 @@ private void GenerateJsonPath(IReadOnlyList path) { Visit(arrayIndex); } - else if (_sqlServerCompatibilityLevel >= 140) - { - Sql.Append("' + CAST("); - Visit(arrayIndex); - Sql.Append(" AS "); - Sql.Append(_typeMappingSource.GetMapping(typeof(string)).StoreType); - Sql.Append(") + '"); - } else { - throw new InvalidOperationException( - SqlServerStrings.JsonValuePathExpressionsNotSupported(_sqlServerCompatibilityLevel)); + switch (_sqlServerSingletonOptions.EngineType) + { + case SqlServerEngineType.SqlServer when _sqlServerSingletonOptions.SqlServerCompatibilityLevel >= 140: + case SqlServerEngineType.AzureSql when _sqlServerSingletonOptions.AzureSqlCompatibilityLevel >= 140: + case SqlServerEngineType.AzureSynapse: + Sql.Append("' + CAST("); + Visit(arrayIndex); + Sql.Append(" AS "); + Sql.Append(_typeMappingSource.GetMapping(typeof(string)).StoreType); + Sql.Append(") + '"); + break; + case SqlServerEngineType.SqlServer: + throw new InvalidOperationException( + SqlServerStrings.JsonValuePathExpressionsNotSupported(_sqlServerSingletonOptions.SqlServerCompatibilityLevel)); + case SqlServerEngineType.AzureSql: + throw new InvalidOperationException( + SqlServerStrings.JsonValuePathExpressionsNotSupported(_sqlServerSingletonOptions.AzureSqlCompatibilityLevel)); + } } Sql.Append("]"); diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs index b5abe1a98c1..2c28eacdb37 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs @@ -20,7 +20,7 @@ public class SqlServerQueryableMethodTranslatingExpressionVisitor : RelationalQu private readonly SqlServerQueryCompilationContext _queryCompilationContext; private readonly IRelationalTypeMappingSource _typeMappingSource; private readonly ISqlExpressionFactory _sqlExpressionFactory; - private readonly int _sqlServerCompatibilityLevel; + private readonly ISqlServerSingletonOptions _sqlServerSingletonOptions; private RelationalTypeMapping? _nvarcharMaxTypeMapping; @@ -40,8 +40,7 @@ public SqlServerQueryableMethodTranslatingExpressionVisitor( _queryCompilationContext = queryCompilationContext; _typeMappingSource = relationalDependencies.TypeMappingSource; _sqlExpressionFactory = relationalDependencies.SqlExpressionFactory; - - _sqlServerCompatibilityLevel = sqlServerSingletonOptions.CompatibilityLevel; + _sqlServerSingletonOptions = sqlServerSingletonOptions; } /// @@ -57,8 +56,7 @@ protected SqlServerQueryableMethodTranslatingExpressionVisitor( _queryCompilationContext = parentVisitor._queryCompilationContext; _typeMappingSource = parentVisitor._typeMappingSource; _sqlExpressionFactory = parentVisitor._sqlExpressionFactory; - - _sqlServerCompatibilityLevel = parentVisitor._sqlServerCompatibilityLevel; + _sqlServerSingletonOptions = parentVisitor._sqlServerSingletonOptions; } /// @@ -228,9 +226,15 @@ protected override Expression VisitExtension(Expression extensionExpression) IProperty? property, string tableAlias) { - if (_sqlServerCompatibilityLevel < 130) + if (_sqlServerSingletonOptions.EngineType == SqlServerEngineType.SqlServer && _sqlServerSingletonOptions.SqlServerCompatibilityLevel < 130) + { + AddTranslationErrorDetails(SqlServerStrings.CompatibilityLevelTooLowForScalarCollections(_sqlServerSingletonOptions.SqlServerCompatibilityLevel)); + + return null; + } + if (_sqlServerSingletonOptions.EngineType == SqlServerEngineType.AzureSql && _sqlServerSingletonOptions.AzureSqlCompatibilityLevel < 130) { - AddTranslationErrorDetails(SqlServerStrings.CompatibilityLevelTooLowForScalarCollections(_sqlServerCompatibilityLevel)); + AddTranslationErrorDetails(SqlServerStrings.CompatibilityLevelTooLowForScalarCollections(_sqlServerSingletonOptions.AzureSqlCompatibilityLevel)); return null; } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlExpressionFactory.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlExpressionFactory.cs index 7673a0ce3f1..0865e6392c0 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlExpressionFactory.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlExpressionFactory.cs @@ -16,7 +16,7 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; public class SqlServerSqlExpressionFactory : SqlExpressionFactory { private readonly IRelationalTypeMappingSource _typeMappingSource; - private readonly int _sqlServerCompatibilityLevel; + private readonly ISqlServerSingletonOptions _sqlServerSingletonOptions; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -30,7 +30,7 @@ public SqlServerSqlExpressionFactory( : base(dependencies) { _typeMappingSource = dependencies.TypeMappingSource; - _sqlServerCompatibilityLevel = sqlServerSingletonOptions.CompatibilityLevel; + _sqlServerSingletonOptions = sqlServerSingletonOptions; } /// @@ -74,7 +74,9 @@ public override bool TryCreateLeast( Type resultType, [NotNullWhen(true)] out SqlExpression? leastExpression) { - if (_sqlServerCompatibilityLevel >= 160) + if ((_sqlServerSingletonOptions.EngineType == SqlServerEngineType.SqlServer && _sqlServerSingletonOptions.SqlServerCompatibilityLevel >= 160) + || (_sqlServerSingletonOptions.EngineType == SqlServerEngineType.AzureSql && _sqlServerSingletonOptions.AzureSqlCompatibilityLevel >= 160) + || (_sqlServerSingletonOptions.EngineType == SqlServerEngineType.AzureSynapse)) { return base.TryCreateLeast(expressions, resultType, out leastExpression); } @@ -89,7 +91,9 @@ public override bool TryCreateGreatest( Type resultType, [NotNullWhen(true)] out SqlExpression? greatestExpression) { - if (_sqlServerCompatibilityLevel >= 160) + if ((_sqlServerSingletonOptions.EngineType == SqlServerEngineType.SqlServer && _sqlServerSingletonOptions.SqlServerCompatibilityLevel >= 160) + || (_sqlServerSingletonOptions.EngineType == SqlServerEngineType.AzureSql && _sqlServerSingletonOptions.AzureSqlCompatibilityLevel >= 160) + || (_sqlServerSingletonOptions.EngineType == SqlServerEngineType.AzureSynapse)) { return base.TryCreateGreatest(expressions, resultType, out greatestExpression); } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs index 873ca89fa1d..55aa0d27c61 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs @@ -21,7 +21,7 @@ public class SqlServerSqlTranslatingExpressionVisitor : RelationalSqlTranslating private readonly SqlServerQueryCompilationContext _queryCompilationContext; private readonly ISqlExpressionFactory _sqlExpressionFactory; private readonly IRelationalTypeMappingSource _typeMappingSource; - private readonly int _sqlServerCompatibilityLevel; + private readonly ISqlServerSingletonOptions _sqlServerSingletonOptions; private static readonly HashSet DateTimeDataTypes = @@ -87,7 +87,7 @@ public SqlServerSqlTranslatingExpressionVisitor( _queryCompilationContext = queryCompilationContext; _sqlExpressionFactory = dependencies.SqlExpressionFactory; _typeMappingSource = dependencies.TypeMappingSource; - _sqlServerCompatibilityLevel = sqlServerSingletonOptions.CompatibilityLevel; + _sqlServerSingletonOptions = sqlServerSingletonOptions; } /// @@ -211,7 +211,9 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp // Translate non-aggregate string.Join to CONCAT_WS (for aggregate string.Join, see SqlServerStringAggregateMethodTranslator) if (method == StringJoinMethodInfo && methodCallExpression.Arguments[1] is NewArrayExpression newArrayExpression - && _sqlServerCompatibilityLevel >= 140) + && ((_sqlServerSingletonOptions.EngineType == SqlServerEngineType.SqlServer && _sqlServerSingletonOptions.SqlServerCompatibilityLevel >= 140) + || (_sqlServerSingletonOptions.EngineType == SqlServerEngineType.AzureSql && _sqlServerSingletonOptions.AzureSqlCompatibilityLevel >= 140) + || (_sqlServerSingletonOptions.EngineType == SqlServerEngineType.AzureSynapse))) { if (TranslationFailed(methodCallExpression.Arguments[0], Visit(methodCallExpression.Arguments[0]), out var delimiter)) { diff --git a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerStringMethodTranslator.cs b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerStringMethodTranslator.cs index 49064a8293d..f8dd1a44713 100644 --- a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerStringMethodTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerStringMethodTranslator.cs @@ -202,7 +202,9 @@ public SqlServerStringMethodTranslator(ISqlExpressionFactory sqlExpressionFactor // an overload that accepts the characters to trim. if (method == TrimStartMethodInfoWithoutArgs || (method == TrimStartMethodInfoWithCharArrayArg && arguments[0] is SqlConstantExpression { Value: char[] { Length: 0 } }) - || (_sqlServerSingletonOptions.CompatibilityLevel >= 160 + || (((_sqlServerSingletonOptions.EngineType == SqlServerEngineType.SqlServer && _sqlServerSingletonOptions.SqlServerCompatibilityLevel >= 160) + || (_sqlServerSingletonOptions.EngineType == SqlServerEngineType.AzureSql && _sqlServerSingletonOptions.AzureSqlCompatibilityLevel >= 160) + || (_sqlServerSingletonOptions.EngineType == SqlServerEngineType.AzureSynapse)) && (method == TrimStartMethodInfoWithCharArg || method == TrimStartMethodInfoWithCharArrayArg))) { return ProcessTrimStartEnd(instance, arguments, "LTRIM"); @@ -210,7 +212,9 @@ public SqlServerStringMethodTranslator(ISqlExpressionFactory sqlExpressionFactor if (method == TrimEndMethodInfoWithoutArgs || (method == TrimEndMethodInfoWithCharArrayArg && arguments[0] is SqlConstantExpression { Value: char[] { Length: 0 } }) - || (_sqlServerSingletonOptions.CompatibilityLevel >= 160 + || (((_sqlServerSingletonOptions.EngineType == SqlServerEngineType.SqlServer && _sqlServerSingletonOptions.SqlServerCompatibilityLevel >= 160) + || (_sqlServerSingletonOptions.EngineType == SqlServerEngineType.AzureSql && _sqlServerSingletonOptions.AzureSqlCompatibilityLevel >= 160) + || (_sqlServerSingletonOptions.EngineType == SqlServerEngineType.AzureSynapse)) && (method == TrimEndMethodInfoWithCharArg || method == TrimEndMethodInfoWithCharArrayArg))) { return ProcessTrimStartEnd(instance, arguments, "RTRIM"); diff --git a/test/EFCore.Design.Tests/Design/Internal/DbContextOperationsTest.cs b/test/EFCore.Design.Tests/Design/Internal/DbContextOperationsTest.cs index 26b8a37c323..6dca2b7d4a6 100644 --- a/test/EFCore.Design.Tests/Design/Internal/DbContextOperationsTest.cs +++ b/test/EFCore.Design.Tests/Design/Internal/DbContextOperationsTest.cs @@ -270,7 +270,7 @@ public void GetContextInfo_returns_correct_info() Assert.Equal("Test", info.DatabaseName); Assert.Equal(@"(localdb)\mssqllocaldb", info.DataSource); - Assert.Equal("None", info.Options); + Assert.Equal("EngineType=SqlServer LegacyAzureSql=False", info.Options); Assert.Equal("Microsoft.EntityFrameworkCore.SqlServer", info.ProviderName); } @@ -291,7 +291,7 @@ public void GetContextInfo_does_not_throw_if_DbConnection_cannot_be_created() Assert.Equal(DesignStrings.BadConnection(expected.Message), info.DatabaseName); Assert.Equal(DesignStrings.BadConnection(expected.Message), info.DataSource); - Assert.Equal("None", info.Options); + Assert.Equal("EngineType=SqlServer LegacyAzureSql=False", info.Options); Assert.Equal("Microsoft.EntityFrameworkCore.SqlServer", info.ProviderName); } diff --git a/test/EFCore.SqlServer.FunctionalTests/LoggingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/LoggingSqlServerTest.cs index c73b6d512ae..43d95bc3b74 100644 --- a/test/EFCore.SqlServer.FunctionalTests/LoggingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/LoggingSqlServerTest.cs @@ -61,4 +61,7 @@ protected override string ProviderName protected override string ProviderVersion => typeof(SqlServerOptionsExtension).Assembly .GetCustomAttribute()?.InformationalVersion; + + protected override string DefaultOptions + => "EngineType=SqlServer LegacyAzureSql=False "; } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrecompiledSqlPregenerationQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrecompiledSqlPregenerationQuerySqlServerTest.cs index b187615aee8..93022636239 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrecompiledSqlPregenerationQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrecompiledSqlPregenerationQuerySqlServerTest.cs @@ -258,7 +258,7 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build // TODO: Figure out if there's a nice way to continue using the retrying strategy var sqlServerOptionsBuilder = new SqlServerDbContextOptionsBuilder(builder); sqlServerOptionsBuilder - .UseCompatibilityLevel(120) + .UseSqlServerCompatibilityLevel(120) .ExecutionStrategy(d => new NonRetryingExecutionStrategy(d)); return builder; } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs index 655aef626db..6e4a2d5dd2d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs @@ -1059,6 +1059,6 @@ protected override ITestStoreFactory TestStoreFactory // Compatibility level 120 (SQL Server 2014) doesn't support OPENJSON public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) - => base.AddOptions(builder).UseSqlServer(o => o.UseCompatibilityLevel(120)); + => base.AddOptions(builder).UseSqlServer(o => o.UseSqlServerCompatibilityLevel(120)); } } diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerConfigPatternsTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerConfigPatternsTest.cs index d438844b951..26c498706f9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerConfigPatternsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerConfigPatternsTest.cs @@ -424,16 +424,16 @@ public class AzureSqlDatabase [InlineData(true)] [InlineData(false)] [ConditionalTheory] - public void Retry_on_failure_not_enabled_by_default_on_Azure_SQL(bool configured) + public void Retry_on_failure_not_enabled_by_default_on_Azure_SQL(bool useAzure) { - using var context = new NorthwindContext(configured); + using var context = new NorthwindContext(useAzure); Assert.IsType(context.Database.CreateExecutionStrategy()); } - private class NorthwindContext(bool configured) : DbContext + private class NorthwindContext(bool useAzure) : DbContext { - private readonly bool _azureConfigured = configured; + private readonly bool _useAzure = useAzure; public DbSet Customers { get; set; } @@ -444,9 +444,11 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) @"Server=test.database.windows.net:4040;Database=Test;ConnectRetryCount=0", a => { - if (_azureConfigured) + if (_useAzure) { +#pragma warning disable CS0618 // Type or member is obsolete a.UseAzureSqlDefaults(false); +#pragma warning restore CS0618 // Type or member is obsolete } }); @@ -460,10 +462,10 @@ public class NonDefaultAzureSqlDatabase [InlineData(true)] [InlineData(false)] [ConditionalTheory] - public void Retry_on_failure_enabled_if_Azure_SQL_configured(bool configured) + public void Retry_on_failure_enabled_if_Azure_SQL_configured(bool useAzure) { - using var context = new NorthwindContext(configured); - if (configured) + using var context = new NorthwindContext(useAzure); + if (useAzure) { Assert.IsType(context.Database.CreateExecutionStrategy()); } @@ -473,24 +475,302 @@ public void Retry_on_failure_enabled_if_Azure_SQL_configured(bool configured) } } - private class NorthwindContext(bool azure) : DbContext + private class NorthwindContext(bool useAzure) : DbContext { - private readonly bool _isAzure = azure; + private readonly bool _useAzure = useAzure; public DbSet Customers { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder + { + optionsBuilder + .EnableServiceProviderCaching(false); + if (_useAzure) + { + optionsBuilder + .UseAzureSql(SqlServerNorthwindTestStoreFactory.NorthwindConnectionString); + } + else + { + optionsBuilder + .UseSqlServer(SqlServerNorthwindTestStoreFactory.NorthwindConnectionString); + } + } + } + } + + public class ExplicitExecutionStrategies_SqlServer + { + [InlineData(true)] + [InlineData(false)] + [ConditionalTheory] + public void Retry_strategy_properly_handled(bool before) + { + using var context = new NorthwindContext(before); + if (before) + { + Assert.IsType(context.Database.CreateExecutionStrategy()); + } + else + { + Assert.IsType(context.Database.CreateExecutionStrategy()); + } + } + + private class NorthwindContext(bool before) : DbContext + { + public DbSet Customers { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder .EnableServiceProviderCaching(false) - .UseSqlServer( - SqlServerNorthwindTestStoreFactory.NorthwindConnectionString, - a => + .UseSqlServer(SqlServerNorthwindTestStoreFactory.NorthwindConnectionString, + b => { - if (_isAzure) + if (before) { - a.UseAzureSqlDefaults(); + b.ExecutionStrategy(_ => new DummyExecutionStrategy()); + } + b.EnableRetryOnFailure(); + if (!before) + { + b.ExecutionStrategy(_ => new DummyExecutionStrategy()); } }); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + => ConfigureModel(modelBuilder); + } + } + public class ExplicitExecutionStrategies_AzureSql + { + [InlineData(true)] + [InlineData(false)] + [ConditionalTheory] + public void Retry_strategy_properly_handled(bool before) + { + using var context = new NorthwindContext(before); + if (before) + { + Assert.IsType(context.Database.CreateExecutionStrategy()); + } + else + { + Assert.IsType(context.Database.CreateExecutionStrategy()); + } + } + + private class NorthwindContext(bool before) : DbContext + { + public DbSet Customers { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder + .EnableServiceProviderCaching(false) + .UseAzureSql(SqlServerNorthwindTestStoreFactory.NorthwindConnectionString, + b => + { + if (before) + { + b.ExecutionStrategy(_ => new DummyExecutionStrategy()); + } + b.EnableRetryOnFailure(); + if (!before) + { + b.ExecutionStrategy(_ => new DummyExecutionStrategy()); + } + }); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + => ConfigureModel(modelBuilder); + } + } + public class ExplicitExecutionStrategies_AzureSynapse + { + [InlineData(true)] + [InlineData(false)] + [ConditionalTheory] + public void Retry_strategy_properly_handled(bool before) + { + using var context = new NorthwindContext(before); + if (before) + { + Assert.IsType(context.Database.CreateExecutionStrategy()); + } + else + { + Assert.IsType(context.Database.CreateExecutionStrategy()); + } + } + + private class NorthwindContext(bool before) : DbContext + { + public DbSet Customers { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder + .EnableServiceProviderCaching(false) + .UseAzureSynapse(SqlServerNorthwindTestStoreFactory.NorthwindConnectionString, + b => + { + if (before) + { + b.ExecutionStrategy(_ => new DummyExecutionStrategy()); + } + b.EnableRetryOnFailure(); + if (!before) + { + b.ExecutionStrategy(_ => new DummyExecutionStrategy()); + } + }); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + => ConfigureModel(modelBuilder); + } + } + public class ExplicitExecutionStrategies_ConfigureSqlEngine_AzureSql + { + [InlineData(true)] + [InlineData(false)] + [ConditionalTheory] + public void Retry_strategy_properly_handled(bool before) + { + using var context = new NorthwindContext(before); + if (before) + { + Assert.IsType(context.Database.CreateExecutionStrategy()); + } + else + { + Assert.IsType(context.Database.CreateExecutionStrategy()); + } + } + + private class NorthwindContext(bool before) : DbContext + { + public DbSet Customers { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder + .EnableServiceProviderCaching(false) + .ConfigureSqlEngine( + b => + { + if (before) + { + b.ExecutionStrategy(_ => new DummyExecutionStrategy()); + } + b.EnableRetryOnFailure(); + if (!before) + { + b.ExecutionStrategy(_ => new DummyExecutionStrategy()); + } + }) + .UseAzureSql(); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + => ConfigureModel(modelBuilder); + } + } + + public class ConfigureTwoEngines + { + [Fact] + public void Throws_when_two_engines_used() + { + using var context = new NorthwindContext(); + Assert.Throws(() => { _ = context.Model; }); + } + + private class NorthwindContext : DbContext + { + public DbSet Customers { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder + .EnableServiceProviderCaching(false) + .UseSqlServer() + .UseAzureSql(); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + => ConfigureModel(modelBuilder); + } + } + + public class NoEngineConfigured + { + [Fact] + public void Throws_when_no_engine_configured() + { + using var context = new NorthwindContext(); + Assert.Throws(() => { _ = context.Model; }); + } + + private class NorthwindContext : DbContext + { + public DbSet Customers { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder + .EnableServiceProviderCaching(false) + .ConfigureSqlEngine(); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + => ConfigureModel(modelBuilder); + } + } + + public class AddConfigureDbContext + { + [Fact] + public void Does_not_throw_for_Add_Configure() + { + using var scope = new ServiceCollection() + .AddDbContext(b => b.UseSqlServer()) + .ConfigureDbContext(b => b.ConfigureSqlEngine(o => o.EnableRetryOnFailure())) + .BuildServiceProvider(validateScopes: true) + .CreateScope(); + + var serviceProvider = scope.ServiceProvider; + + var exception = Record.Exception(serviceProvider.GetRequiredService); + Assert.Null(exception); + } + + [Fact] + public void Proper_execution_strategy() + { + using var scope = new ServiceCollection() + .AddDbContext(b => b.UseSqlServer()) + .ConfigureDbContext(b => b.ConfigureSqlEngine(o => o.EnableRetryOnFailure())) + .BuildServiceProvider(validateScopes: true) + .CreateScope(); + + var serviceProvider = scope.ServiceProvider; + + var context = serviceProvider.GetRequiredService(); + Assert.IsType(context.Database.CreateExecutionStrategy()); + } + + private class NorthwindContext : DbContext + { + public DbSet Customers { get; set; } + + public NorthwindContext(DbContextOptions options) + : base(options) + { } protected override void OnModelCreating(ModelBuilder modelBuilder) => ConfigureModel(modelBuilder); @@ -516,4 +796,12 @@ private static void ConfigureModel(ModelBuilder builder) b.HasKey(c => c.CustomerID); b.ToTable("Customers"); }); + + private class DummyExecutionStrategy : IExecutionStrategy + { + public bool RetriesOnFailure => true; + + public TResult Execute(TState state, Func operation, Func> verifySucceeded) => throw new NotImplementedException(); + public Task ExecuteAsync(TState state, Func> operation, Func>> verifySucceeded, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + } } diff --git a/test/EFCore.SqlServer.Tests/SqlServerOptionsExtensionTest.cs b/test/EFCore.SqlServer.Tests/SqlServerOptionsExtensionTest.cs index e1de681c655..38db2102555 100644 --- a/test/EFCore.SqlServer.Tests/SqlServerOptionsExtensionTest.cs +++ b/test/EFCore.SqlServer.Tests/SqlServerOptionsExtensionTest.cs @@ -52,13 +52,16 @@ public static IModel Instance } [ConditionalFact] - public void ApplyServices_adds_SQL_server_services() + public void ApplyServices_adds_correct_services() { var services = new ServiceCollection(); - new SqlServerOptionsExtension().ApplyServices(services); + new SqlServerOptionsExtension() + .WithEngineType(SqlServerEngineType.SqlServer) + .ApplyServices(services); Assert.Contains(services, sd => sd.ServiceType == typeof(ISqlServerConnection)); + Assert.Contains(services, sd => sd.ServiceType == typeof(ISqlServerSingletonOptions)); } private class ChangedRowNumberContext(bool setInternalServiceProvider) : DbContext From 15796ac3bd51a410b6dcad0489325e86ba15006c Mon Sep 17 00:00:00 2001 From: Jiri Cincura Date: Fri, 2 Aug 2024 18:41:10 +0200 Subject: [PATCH 2/6] Feedback --- ...ServerDbContextOptionsBuilderExtensions.cs | 36 ------------------- .../Internal/SqlServerOptionsExtension.cs | 5 +++ 2 files changed, 5 insertions(+), 36 deletions(-) diff --git a/src/EFCore.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs index fe8bfb1cbba..5e5ecf8127d 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs @@ -41,10 +41,6 @@ public static DbContextOptionsBuilder UseSqlServer( Action? sqlServerOptionsAction = null) { var extension = GetOrCreateExtension(optionsBuilder); - if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.SqlServer) - { - throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.SqlServer, extension.EngineType)); - } extension = extension .WithEngineType(SqlServerEngineType.SqlServer); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); @@ -69,10 +65,6 @@ public static DbContextOptionsBuilder UseSqlServer( Action? sqlServerOptionsAction = null) { var extension = GetOrCreateExtension(optionsBuilder); - if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.SqlServer) - { - throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.SqlServer, extension.EngineType)); - } extension = (SqlServerOptionsExtension)extension .WithEngineType(SqlServerEngineType.SqlServer) .WithConnectionString(connectionString); @@ -135,10 +127,6 @@ public static DbContextOptionsBuilder UseSqlServer( Check.NotNull(connection, nameof(connection)); var extension = GetOrCreateExtension(optionsBuilder); - if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.SqlServer) - { - throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.SqlServer, extension.EngineType)); - } extension = (SqlServerOptionsExtension)extension .WithEngineType(SqlServerEngineType.SqlServer) .WithConnection(connection, contextOwnsConnection); @@ -276,10 +264,6 @@ public static DbContextOptionsBuilder UseAzureSql( Action? sqlServerOptionsAction = null) { var extension = GetOrCreateExtension(optionsBuilder); - if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.AzureSql) - { - throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.AzureSql, extension.EngineType)); - } extension = extension .WithEngineType(SqlServerEngineType.AzureSql); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); @@ -304,10 +288,6 @@ public static DbContextOptionsBuilder UseAzureSql( Action? sqlServerOptionsAction = null) { var extension = GetOrCreateExtension(optionsBuilder); - if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.AzureSql) - { - throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.AzureSql, extension.EngineType)); - } extension = (SqlServerOptionsExtension)extension .WithEngineType(SqlServerEngineType.AzureSql) .WithConnectionString(connectionString); @@ -370,10 +350,6 @@ public static DbContextOptionsBuilder UseAzureSql( Check.NotNull(connection, nameof(connection)); var extension = GetOrCreateExtension(optionsBuilder); - if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.AzureSql) - { - throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.AzureSql, extension.EngineType)); - } extension = (SqlServerOptionsExtension)extension .WithEngineType(SqlServerEngineType.AzureSql) .WithConnection(connection, contextOwnsConnection); @@ -511,10 +487,6 @@ public static DbContextOptionsBuilder UseAzureSynapse( Action? sqlServerOptionsAction = null) { var extension = GetOrCreateExtension(optionsBuilder); - if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.AzureSynapse) - { - throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.AzureSynapse, extension.EngineType)); - } extension = extension .WithEngineType(SqlServerEngineType.AzureSynapse); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); @@ -539,10 +511,6 @@ public static DbContextOptionsBuilder UseAzureSynapse( Action? sqlServerOptionsAction = null) { var extension = GetOrCreateExtension(optionsBuilder); - if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.AzureSynapse) - { - throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.AzureSynapse, extension.EngineType)); - } extension = (SqlServerOptionsExtension)extension .WithEngineType(SqlServerEngineType.AzureSynapse) .WithConnectionString(connectionString); @@ -605,10 +573,6 @@ public static DbContextOptionsBuilder UseAzureSynapse( Check.NotNull(connection, nameof(connection)); var extension = GetOrCreateExtension(optionsBuilder); - if (extension.EngineType != SqlServerEngineType.Unknown && extension.EngineType != SqlServerEngineType.AzureSynapse) - { - throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(SqlServerEngineType.AzureSynapse, extension.EngineType)); - } extension = (SqlServerOptionsExtension)extension .WithEngineType(SqlServerEngineType.AzureSynapse) .WithConnection(connection, contextOwnsConnection); diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs index 2124d248505..0a4a9a110af 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs @@ -184,6 +184,11 @@ public virtual int? AzureSynapseCompatibilityLevelWithoutDefault /// public virtual SqlServerOptionsExtension WithEngineType(SqlServerEngineType engineType) { + if (EngineType != SqlServerEngineType.Unknown && EngineType != engineType) + { + throw new InvalidOperationException(SqlServerStrings.AlreadyConfiguredEngineType(engineType, EngineType)); + } + var clone = (SqlServerOptionsExtension)Clone(); clone._engineType = engineType; From 6924e1ccb7176a48f955cacb0cbcdcdb63e9c498 Mon Sep 17 00:00:00 2001 From: Jiri Cincura Date: Sun, 4 Aug 2024 09:34:01 +0200 Subject: [PATCH 3/6] Feedback --- .../Internal/ISqlServerSingletonOptions.cs | 24 --------------- .../Internal/SqlServerOptionsExtension.cs | 27 ----------------- .../Internal/SqlServerSingletonOptions.cs | 30 ------------------- 3 files changed, 81 deletions(-) diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/ISqlServerSingletonOptions.cs b/src/EFCore.SqlServer/Infrastructure/Internal/ISqlServerSingletonOptions.cs index 6bb88309a8a..47728ddbc9d 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/ISqlServerSingletonOptions.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/ISqlServerSingletonOptions.cs @@ -27,14 +27,6 @@ public interface ISqlServerSingletonOptions : ISingletonOptions /// public int SqlServerCompatibilityLevel { 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 int? SqlServerCompatibilityLevelWithoutDefault { 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 @@ -43,14 +35,6 @@ public interface ISqlServerSingletonOptions : ISingletonOptions /// public int AzureSqlCompatibilityLevel { 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 int? AzureSqlCompatibilityLevelWithoutDefault { 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 @@ -58,12 +42,4 @@ public interface ISqlServerSingletonOptions : ISingletonOptions /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public int AzureSynapseCompatibilityLevel { 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 int? AzureSynapseCompatibilityLevelWithoutDefault { get; } } diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs index 9e105fac394..dc3d3d7c8b0 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs @@ -131,15 +131,6 @@ public virtual bool LegacyAzureSql public virtual int SqlServerCompatibilityLevel => _sqlServerCompatibilityLevel ?? SqlServerDefaultCompatibilityLevel; - /// - /// 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 int? SqlServerCompatibilityLevelWithoutDefault - => _sqlServerCompatibilityLevel; - /// /// 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 @@ -149,15 +140,6 @@ public virtual int? SqlServerCompatibilityLevelWithoutDefault public virtual int AzureSqlCompatibilityLevel => _azureSqlCompatibilityLevel ?? AzureSqlDefaultCompatibilityLevel; - /// - /// 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 int? AzureSqlCompatibilityLevelWithoutDefault - => _azureSqlCompatibilityLevel; - /// /// 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 @@ -167,15 +149,6 @@ public virtual int? AzureSqlCompatibilityLevelWithoutDefault public virtual int AzureSynapseCompatibilityLevel => _azureSynapseCompatibilityLevel ?? AzureSynapseDefaultCompatibilityLevel; - /// - /// 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 int? AzureSynapseCompatibilityLevelWithoutDefault - => _azureSynapseCompatibilityLevel; - /// /// 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/Infrastructure/Internal/SqlServerSingletonOptions.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerSingletonOptions.cs index b27d8f685d0..102515130c9 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerSingletonOptions.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerSingletonOptions.cs @@ -27,14 +27,6 @@ public class SqlServerSingletonOptions : ISqlServerSingletonOptions /// public virtual int SqlServerCompatibilityLevel { get; private set; } = SqlServerOptionsExtension.SqlServerDefaultCompatibilityLevel; - /// - /// 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 int? SqlServerCompatibilityLevelWithoutDefault { get; private set; } - /// /// 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 @@ -43,14 +35,6 @@ public class SqlServerSingletonOptions : ISqlServerSingletonOptions /// public virtual int AzureSqlCompatibilityLevel { get; private set; } = SqlServerOptionsExtension.AzureSqlDefaultCompatibilityLevel; - /// - /// 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 int? AzureSqlCompatibilityLevelWithoutDefault { get; private set; } - /// /// 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 @@ -59,14 +43,6 @@ public class SqlServerSingletonOptions : ISqlServerSingletonOptions /// public virtual int AzureSynapseCompatibilityLevel { get; private set; } = SqlServerOptionsExtension.AzureSynapseDefaultCompatibilityLevel; - /// - /// 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 int? AzureSynapseCompatibilityLevelWithoutDefault { get; private set; } - /// /// 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 @@ -80,11 +56,8 @@ public virtual void Initialize(IDbContextOptions options) { EngineType = sqlServerOptions.EngineType; SqlServerCompatibilityLevel = sqlServerOptions.SqlServerCompatibilityLevel; - SqlServerCompatibilityLevelWithoutDefault = sqlServerOptions.SqlServerCompatibilityLevelWithoutDefault; AzureSqlCompatibilityLevel = sqlServerOptions.AzureSqlCompatibilityLevel; - AzureSqlCompatibilityLevelWithoutDefault = sqlServerOptions.AzureSqlCompatibilityLevelWithoutDefault; AzureSynapseCompatibilityLevel = sqlServerOptions.AzureSynapseCompatibilityLevel; - AzureSynapseCompatibilityLevelWithoutDefault = sqlServerOptions.AzureSynapseCompatibilityLevelWithoutDefault; } } @@ -100,11 +73,8 @@ public virtual void Validate(IDbContextOptions options) if (sqlServerOptions != null && (EngineType != sqlServerOptions.EngineType - || SqlServerCompatibilityLevelWithoutDefault != sqlServerOptions.SqlServerCompatibilityLevelWithoutDefault || SqlServerCompatibilityLevel != sqlServerOptions.SqlServerCompatibilityLevel - || AzureSqlCompatibilityLevelWithoutDefault != sqlServerOptions.AzureSqlCompatibilityLevelWithoutDefault || AzureSqlCompatibilityLevel != sqlServerOptions.AzureSqlCompatibilityLevel - || AzureSynapseCompatibilityLevelWithoutDefault != sqlServerOptions.AzureSynapseCompatibilityLevelWithoutDefault || AzureSynapseCompatibilityLevel != sqlServerOptions.AzureSynapseCompatibilityLevel)) { throw new InvalidOperationException( From 7a22f943f37c7d1c2dedbdb395a9920c3b13528c Mon Sep 17 00:00:00 2001 From: Jiri Cincura Date: Sun, 4 Aug 2024 09:48:12 +0200 Subject: [PATCH 4/6] Feedback --- .../Internal/SqlServerOptionsExtension.cs | 12 +++++++----- .../Design/Internal/DbContextOperationsTest.cs | 4 ++-- .../LoggingSqlServerTest.cs | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs index dc3d3d7c8b0..2fb912637a7 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs @@ -294,7 +294,6 @@ public ExtensionInfo(IDbContextOptionsExtension extension) public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) => other is ExtensionInfo otherInfo && Extension.EngineType == otherInfo.Extension.EngineType - && Extension.LegacyAzureSql == otherInfo.Extension.LegacyAzureSql && Extension.SqlServerCompatibilityLevel == otherInfo.Extension.SqlServerCompatibilityLevel && Extension.AzureSqlCompatibilityLevel == otherInfo.Extension.AzureSqlCompatibilityLevel && Extension.AzureSynapseCompatibilityLevel == otherInfo.Extension.AzureSynapseCompatibilityLevel; @@ -314,10 +313,13 @@ public override string LogFragment .Append(Extension._engineType) .Append(' '); - builder - .Append("LegacyAzureSql=") - .Append(Extension._legacyAzureSql) - .Append(' '); + if (Extension._legacyAzureSql) + { + builder + .Append("LegacyAzureSql=") + .Append(Extension._legacyAzureSql) + .Append(' '); + } if (Extension._sqlServerCompatibilityLevel != null) { diff --git a/test/EFCore.Design.Tests/Design/Internal/DbContextOperationsTest.cs b/test/EFCore.Design.Tests/Design/Internal/DbContextOperationsTest.cs index a43c2af0740..68e7662ccef 100644 --- a/test/EFCore.Design.Tests/Design/Internal/DbContextOperationsTest.cs +++ b/test/EFCore.Design.Tests/Design/Internal/DbContextOperationsTest.cs @@ -270,7 +270,7 @@ public void GetContextInfo_returns_correct_info() Assert.Equal("Test", info.DatabaseName); Assert.Equal(@"(localdb)\mssqllocaldb", info.DataSource); - Assert.Equal("EngineType=SqlServer LegacyAzureSql=False", info.Options); + Assert.Equal("EngineType=SqlServer", info.Options); Assert.Equal("Microsoft.EntityFrameworkCore.SqlServer", info.ProviderName); } @@ -291,7 +291,7 @@ public void GetContextInfo_does_not_throw_if_DbConnection_cannot_be_created() Assert.Equal(DesignStrings.BadConnection(expected.Message), info.DatabaseName); Assert.Equal(DesignStrings.BadConnection(expected.Message), info.DataSource); - Assert.Equal("EngineType=SqlServer LegacyAzureSql=False", info.Options); + Assert.Equal("EngineType=SqlServer", info.Options); Assert.Equal("Microsoft.EntityFrameworkCore.SqlServer", info.ProviderName); } diff --git a/test/EFCore.SqlServer.FunctionalTests/LoggingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/LoggingSqlServerTest.cs index 43d95bc3b74..64c82f18cdb 100644 --- a/test/EFCore.SqlServer.FunctionalTests/LoggingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/LoggingSqlServerTest.cs @@ -63,5 +63,5 @@ protected override string ProviderVersion .GetCustomAttribute()?.InformationalVersion; protected override string DefaultOptions - => "EngineType=SqlServer LegacyAzureSql=False "; + => "EngineType=SqlServer "; } From 5a0439868c24fbfb8b4cd3f8e5bb0c7c40aee5be Mon Sep 17 00:00:00 2001 From: Jiri Cincura Date: Sun, 4 Aug 2024 10:03:29 +0200 Subject: [PATCH 5/6] Feedback --- .../Query/Internal/SqlServerSqlExpressionFactory.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlExpressionFactory.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlExpressionFactory.cs index 35b0007ec22..f84f3e1abb8 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlExpressionFactory.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlExpressionFactory.cs @@ -16,7 +16,6 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; public class SqlServerSqlExpressionFactory : SqlExpressionFactory { private readonly IRelationalTypeMappingSource _typeMappingSource; - private readonly ISqlServerSingletonOptions _sqlServerSingletonOptions; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -25,12 +24,10 @@ public class SqlServerSqlExpressionFactory : SqlExpressionFactory /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public SqlServerSqlExpressionFactory( - SqlExpressionFactoryDependencies dependencies, - ISqlServerSingletonOptions sqlServerSingletonOptions) + SqlExpressionFactoryDependencies dependencies) : base(dependencies) { _typeMappingSource = dependencies.TypeMappingSource; - _sqlServerSingletonOptions = sqlServerSingletonOptions; } /// From f73141ad84e04199b2a8e43156ed181214cddb48 Mon Sep 17 00:00:00 2001 From: Jiri Cincura Date: Mon, 5 Aug 2024 10:35:31 +0200 Subject: [PATCH 6/6] Feedback. --- .../Internal/SqlServerSingletonOptions.cs | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerSingletonOptions.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerSingletonOptions.cs index 102515130c9..3af5096042c 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerSingletonOptions.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerSingletonOptions.cs @@ -71,16 +71,32 @@ public virtual void Validate(IDbContextOptions options) { var sqlServerOptions = options.FindExtension(); - if (sqlServerOptions != null - && (EngineType != sqlServerOptions.EngineType - || SqlServerCompatibilityLevel != sqlServerOptions.SqlServerCompatibilityLevel - || AzureSqlCompatibilityLevel != sqlServerOptions.AzureSqlCompatibilityLevel - || AzureSynapseCompatibilityLevel != sqlServerOptions.AzureSynapseCompatibilityLevel)) + if (sqlServerOptions != null) { - throw new InvalidOperationException( - CoreStrings.SingletonOptionChanged( - $"{nameof(SqlServerDbContextOptionsExtensions.UseSqlServer)}/{nameof(SqlServerDbContextOptionsExtensions.UseAzureSql)}/{nameof(SqlServerDbContextOptionsExtensions.UseAzureSynapse)}", - nameof(DbContextOptionsBuilder.UseInternalServiceProvider))); + if (EngineType == SqlServerEngineType.SqlServer + && (EngineType != sqlServerOptions.EngineType || SqlServerCompatibilityLevel != sqlServerOptions.SqlServerCompatibilityLevel)) + { + throw new InvalidOperationException( + CoreStrings.SingletonOptionChanged( + $"{nameof(SqlServerDbContextOptionsExtensions.UseSqlServer)}", + nameof(DbContextOptionsBuilder.UseInternalServiceProvider))); + } + if (EngineType == SqlServerEngineType.AzureSql + && (EngineType != sqlServerOptions.EngineType || AzureSqlCompatibilityLevel != sqlServerOptions.AzureSqlCompatibilityLevel)) + { + throw new InvalidOperationException( + CoreStrings.SingletonOptionChanged( + $"{nameof(SqlServerDbContextOptionsExtensions.UseAzureSql)}", + nameof(DbContextOptionsBuilder.UseInternalServiceProvider))); + } + if (EngineType == SqlServerEngineType.AzureSynapse + && (EngineType != sqlServerOptions.EngineType || AzureSynapseCompatibilityLevel != sqlServerOptions.AzureSynapseCompatibilityLevel)) + { + throw new InvalidOperationException( + CoreStrings.SingletonOptionChanged( + $"{nameof(SqlServerDbContextOptionsExtensions.UseAzureSynapse)}", + nameof(DbContextOptionsBuilder.UseInternalServiceProvider))); + } } } }