Skip to content

Commit

Permalink
Implement SQL Server compatibility level
Browse files Browse the repository at this point in the history
  • Loading branch information
roji committed Apr 27, 2023
1 parent bcc870a commit 1c4b749
Show file tree
Hide file tree
Showing 17 changed files with 231 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,10 @@ public static IServiceCollection AddEntityFrameworkSqlServer(this IServiceCollec
.TryAdd<INavigationExpansionExtensibilityHelper, SqlServerNavigationExpansionExtensibilityHelper>()
.TryAdd<IQueryableMethodTranslatingExpressionVisitorFactory, SqlServerQueryableMethodTranslatingExpressionVisitorFactory>()
.TryAdd<IExceptionDetector, SqlServerExceptionDetector>()
.TryAdd<ISingletonOptions, ISqlServerSingletonOptions>(p => p.GetRequiredService<ISqlServerSingletonOptions>())
.TryAddProviderSpecificServices(
b => b
.TryAddSingleton<ISqlServerSingletonOptions, SqlServerSingletonOptions>()
.TryAddSingleton<ISqlServerValueGeneratorCache, SqlServerValueGeneratorCache>()
.TryAddSingleton<ISqlServerUpdateSqlGenerator, SqlServerUpdateSqlGenerator>()
.TryAddSingleton<ISqlServerSequenceValueGeneratorFactory, SqlServerSequenceValueGeneratorFactory>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;

/// <summary>
/// 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.
/// </summary>
public interface ISqlServerSingletonOptions : ISingletonOptions
{
/// <summary>
/// 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.
/// </summary>
int CompatibilityLevel { get; }

/// <summary>
/// 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.
/// </summary>
int? CompatibilityLevelWithoutDefault { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
public class SqlServerOptionsExtension : RelationalOptionsExtension
{
private DbContextOptionsExtensionInfo? _info;
private int? _compatibilityLevel;

/// <summary>
/// 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.
/// </summary>
// 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 DefaultCompatibilityLevel = 160;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -36,6 +51,7 @@ public SqlServerOptionsExtension()
protected SqlServerOptionsExtension(SqlServerOptionsExtension copyFrom)
: base(copyFrom)
{
_compatibilityLevel = copyFrom._compatibilityLevel;
}

/// <summary>
Expand All @@ -56,6 +72,39 @@ public override DbContextOptionsExtensionInfo Info
protected override RelationalOptionsExtension Clone()
=> new SqlServerOptionsExtension(this);

/// <summary>
/// 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.
/// </summary>
public virtual int CompatibilityLevel
=> _compatibilityLevel ?? DefaultCompatibilityLevel;

/// <summary>
/// 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.
/// </summary>
public virtual int? CompatibilityLevelWithoutDefault
=> _compatibilityLevel;

/// <summary>
/// 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.
/// </summary>
public virtual SqlServerOptionsExtension WithCompatibilityLevel(int? compatibilityLevel)
{
var clone = (SqlServerOptionsExtension)Clone();

clone._compatibilityLevel = compatibilityLevel;

return clone;
}

/// <summary>
/// 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
Expand All @@ -81,7 +130,8 @@ public override bool IsDatabaseProvider
=> true;

public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
=> other is ExtensionInfo;
=> other is ExtensionInfo otherInfo
&& Extension.CompatibilityLevel == otherInfo.Extension.CompatibilityLevel;

public override string LogFragment
{
Expand All @@ -93,6 +143,13 @@ public override string LogFragment

builder.Append(base.LogFragment);

if (Extension._compatibilityLevel is int compatibilityLevel)
{
builder
.Append("CompatibilityLevel=")
.Append(compatibilityLevel);
}

_logFragment = builder.ToString();
}

Expand All @@ -101,6 +158,13 @@ public override string LogFragment
}

public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
=> debugInfo["SqlServer"] = "1";
{
debugInfo["SqlServer"] = "1";

if (Extension.CompatibilityLevel is int compatibilityLevel)
{
debugInfo["CompatibilityLevel"] = compatibilityLevel.ToString();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;

/// <summary>
/// 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.
/// </summary>
public class SqlServerSingletonOptions : ISqlServerSingletonOptions
{
/// <summary>
/// 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.
/// </summary>
public virtual int CompatibilityLevel { get; private set; } = SqlServerOptionsExtension.DefaultCompatibilityLevel;

/// <summary>
/// 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.
/// </summary>
public virtual int? CompatibilityLevelWithoutDefault { get; private set; }

/// <summary>
/// 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.
/// </summary>
public virtual void Initialize(IDbContextOptions options)
{
var sqlServerOptions = options.FindExtension<SqlServerOptionsExtension>();
if (sqlServerOptions != null)
{
CompatibilityLevel = sqlServerOptions.CompatibilityLevel;
CompatibilityLevelWithoutDefault = sqlServerOptions.CompatibilityLevelWithoutDefault;
}
}

/// <summary>
/// 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.
/// </summary>
public virtual void Validate(IDbContextOptions options)
{
var sqlserverOptions = options.FindExtension<SqlServerOptionsExtension>();

if (sqlserverOptions != null &&
(CompatibilityLevelWithoutDefault != sqlserverOptions.CompatibilityLevelWithoutDefault
|| CompatibilityLevel != sqlserverOptions.CompatibilityLevel))
{
throw new InvalidOperationException(
CoreStrings.SingletonOptionChanged(
nameof(SqlServerDbContextOptionsExtensions.UseSqlServer),
nameof(DbContextOptionsBuilder.UseInternalServiceProvider)));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,18 @@ public virtual SqlServerDbContextOptionsBuilder EnableRetryOnFailure(
TimeSpan maxRetryDelay,
IEnumerable<int>? errorNumbersToAdd)
=> ExecutionStrategy(c => new SqlServerRetryingExecutionStrategy(c, maxRetryCount, maxRetryDelay, errorNumbersToAdd));

/// <summary>
/// Sets the SQL Server 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 SQL Server. Defaults to <c>150</c> (SQL Server 2019).
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-dbcontext-options">Using DbContextOptions</see>, and
/// <see href="https://learn.microsoft.com/sql/t-sql/statements/alter-database-transact-sql-compatibility-level">SQL Server
/// documentation on compatibility level</see> for more information and examples.
/// </remarks>
/// <param name="compatibilityLevel"><see langword="false" /> to have null resource</param>
// TODO: Naming; Cosmos doesn't have Use/Set, so just CompatibilityLevel? SetCompatibilityLevel?
public virtual SqlServerDbContextOptionsBuilder UseCompatibilityLevel(int compatibilityLevel)
=> WithOption(e => e.WithCompatibilityLevel(compatibilityLevel));
}
5 changes: 3 additions & 2 deletions test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Numerics;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
using Microsoft.EntityFrameworkCore.TestUtilities.Xunit;

Expand Down Expand Up @@ -826,8 +827,8 @@ private static SqlServerTypeMappingSource CreateTypeMappingSource(
params IRelationalTypeMappingSourcePlugin[] plugins)
=> new(
TestServiceFactory.Instance.Create<TypeMappingSourceDependencies>(),
new RelationalTypeMappingSourceDependencies(
plugins));
new RelationalTypeMappingSourceDependencies(plugins),
new SqlServerSingletonOptions());

private class TestTypeMappingPlugin<T> : IRelationalTypeMappingSourcePlugin
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
using NetTopologySuite;
using NetTopologySuite.Geometries;
Expand All @@ -19,7 +20,8 @@ public void Generate_separates_operations_by_a_blank_line()
new CSharpHelper(
new SqlServerTypeMappingSource(
TestServiceFactory.Instance.Create<TypeMappingSourceDependencies>(),
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>()))));
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>(),
new SqlServerSingletonOptions()))));

var builder = new IndentedStringBuilder();

Expand Down Expand Up @@ -3158,7 +3160,8 @@ private void Test<T>(T operation, string expectedCode, Action<T> assert)
new IRelationalTypeMappingSourcePlugin[]
{
new SqlServerNetTopologySuiteTypeMappingSourcePlugin(NtsGeometryServices.Instance)
})))));
}),
new SqlServerSingletonOptions()))));

var builder = new IndentedStringBuilder();
generator.Generate("mb", new[] { operation }, builder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Migrations.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;

// ReSharper disable ParameterOnlyUsedForPreconditionCheck.Local
Expand Down Expand Up @@ -346,7 +347,8 @@ private static void MissingAnnotationCheck(
{
var sqlServerTypeMappingSource = new SqlServerTypeMappingSource(
TestServiceFactory.Instance.Create<TypeMappingSourceDependencies>(),
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>());
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>(),
new SqlServerSingletonOptions());

var sqlServerAnnotationCodeGenerator = new SqlServerAnnotationCodeGenerator(
new AnnotationCodeGeneratorDependencies(sqlServerTypeMappingSource));
Expand Down Expand Up @@ -448,7 +450,8 @@ public void Snapshot_with_enum_discriminator_uses_converted_values()
{
var sqlServerTypeMappingSource = new SqlServerTypeMappingSource(
TestServiceFactory.Instance.Create<TypeMappingSourceDependencies>(),
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>());
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>(),
new SqlServerSingletonOptions());

var codeHelper = new CSharpHelper(
sqlServerTypeMappingSource);
Expand Down Expand Up @@ -505,7 +508,8 @@ private static void AssertConverter(ValueConverter valueConverter, string expect

var sqlServerTypeMappingSource = new SqlServerTypeMappingSource(
TestServiceFactory.Instance.Create<TypeMappingSourceDependencies>(),
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>());
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>(),
new SqlServerSingletonOptions());

var codeHelper = new CSharpHelper(sqlServerTypeMappingSource);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Migrations.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
using Microsoft.EntityFrameworkCore.TestUtilities.FakeProvider;
using Microsoft.EntityFrameworkCore.Update.Internal;
Expand Down Expand Up @@ -57,7 +58,8 @@ private IMigrationsScaffolder CreateMigrationScaffolder<TContext>()
var idGenerator = new MigrationsIdGenerator();
var sqlServerTypeMappingSource = new SqlServerTypeMappingSource(
TestServiceFactory.Instance.Create<TypeMappingSourceDependencies>(),
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>());
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>(),
new SqlServerSingletonOptions());
var sqlServerAnnotationCodeGenerator = new SqlServerAnnotationCodeGenerator(
new AnnotationCodeGeneratorDependencies(sqlServerTypeMappingSource));
var code = new CSharpHelper(sqlServerTypeMappingSource);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Migrations.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
using NetTopologySuite;
Expand Down Expand Up @@ -7585,7 +7586,8 @@ protected CSharpMigrationsGenerator CreateMigrationsGenerator()
new IRelationalTypeMappingSourcePlugin[]
{
new SqlServerNetTopologySuiteTypeMappingSourcePlugin(NtsGeometryServices.Instance)
}));
}),
new SqlServerSingletonOptions());

var codeHelper = new CSharpHelper(sqlServerTypeMappingSource);

Expand Down
Loading

0 comments on commit 1c4b749

Please sign in to comment.