Skip to content

Commit dc9bc73

Browse files
committed
Implement SQL Server compatibility level
Closes #30163
1 parent bcc870a commit dc9bc73

17 files changed

+231
-24
lines changed

Diff for: src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs

+2
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,10 @@ public static IServiceCollection AddEntityFrameworkSqlServer(this IServiceCollec
127127
.TryAdd<INavigationExpansionExtensibilityHelper, SqlServerNavigationExpansionExtensibilityHelper>()
128128
.TryAdd<IQueryableMethodTranslatingExpressionVisitorFactory, SqlServerQueryableMethodTranslatingExpressionVisitorFactory>()
129129
.TryAdd<IExceptionDetector, SqlServerExceptionDetector>()
130+
.TryAdd<ISingletonOptions, ISqlServerSingletonOptions>(p => p.GetRequiredService<ISqlServerSingletonOptions>())
130131
.TryAddProviderSpecificServices(
131132
b => b
133+
.TryAddSingleton<ISqlServerSingletonOptions, SqlServerSingletonOptions>()
132134
.TryAddSingleton<ISqlServerValueGeneratorCache, SqlServerValueGeneratorCache>()
133135
.TryAddSingleton<ISqlServerUpdateSqlGenerator, SqlServerUpdateSqlGenerator>()
134136
.TryAddSingleton<ISqlServerSequenceValueGeneratorFactory, SqlServerSequenceValueGeneratorFactory>()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
5+
6+
/// <summary>
7+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
8+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
9+
/// any release. You should only use it directly in your code with extreme caution and knowing that
10+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
11+
/// </summary>
12+
public interface ISqlServerSingletonOptions : ISingletonOptions
13+
{
14+
/// <summary>
15+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
16+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
17+
/// any release. You should only use it directly in your code with extreme caution and knowing that
18+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
19+
/// </summary>
20+
int CompatibilityLevel { get; }
21+
22+
/// <summary>
23+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
24+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
25+
/// any release. You should only use it directly in your code with extreme caution and knowing that
26+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
27+
/// </summary>
28+
int? CompatibilityLevelWithoutDefault { get; }
29+
}

Diff for: src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs

+66-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
1414
public class SqlServerOptionsExtension : RelationalOptionsExtension
1515
{
1616
private DbContextOptionsExtensionInfo? _info;
17+
private int? _compatibilityLevel;
18+
19+
/// <summary>
20+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
21+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
22+
/// any release. You should only use it directly in your code with extreme caution and knowing that
23+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
24+
/// </summary>
25+
// See https://learn.microsoft.com/sql/t-sql/statements/alter-database-transact-sql-compatibility-level
26+
// 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
27+
// 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
28+
// 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
29+
// 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
30+
// 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
31+
public static readonly int DefaultCompatibilityLevel = 160;
1732

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

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

75+
/// <summary>
76+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
77+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
78+
/// any release. You should only use it directly in your code with extreme caution and knowing that
79+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
80+
/// </summary>
81+
public virtual int CompatibilityLevel
82+
=> _compatibilityLevel ?? DefaultCompatibilityLevel;
83+
84+
/// <summary>
85+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
86+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
87+
/// any release. You should only use it directly in your code with extreme caution and knowing that
88+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
89+
/// </summary>
90+
public virtual int? CompatibilityLevelWithoutDefault
91+
=> _compatibilityLevel;
92+
93+
/// <summary>
94+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
95+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
96+
/// any release. You should only use it directly in your code with extreme caution and knowing that
97+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
98+
/// </summary>
99+
public virtual SqlServerOptionsExtension WithCompatibilityLevel(int? compatibilityLevel)
100+
{
101+
var clone = (SqlServerOptionsExtension)Clone();
102+
103+
clone._compatibilityLevel = compatibilityLevel;
104+
105+
return clone;
106+
}
107+
59108
/// <summary>
60109
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
61110
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -81,7 +130,8 @@ public override bool IsDatabaseProvider
81130
=> true;
82131

83132
public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
84-
=> other is ExtensionInfo;
133+
=> other is ExtensionInfo otherInfo
134+
&& Extension.CompatibilityLevel == otherInfo.Extension.CompatibilityLevel;
85135

86136
public override string LogFragment
87137
{
@@ -93,6 +143,13 @@ public override string LogFragment
93143

94144
builder.Append(base.LogFragment);
95145

146+
if (Extension._compatibilityLevel is int compatibilityLevel)
147+
{
148+
builder
149+
.Append("CompatibilityLevel=")
150+
.Append(compatibilityLevel);
151+
}
152+
96153
_logFragment = builder.ToString();
97154
}
98155

@@ -101,6 +158,13 @@ public override string LogFragment
101158
}
102159

103160
public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
104-
=> debugInfo["SqlServer"] = "1";
161+
{
162+
debugInfo["SqlServer"] = "1";
163+
164+
if (Extension.CompatibilityLevel is int compatibilityLevel)
165+
{
166+
debugInfo["CompatibilityLevel"] = compatibilityLevel.ToString();
167+
}
168+
}
105169
}
106170
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
5+
6+
/// <summary>
7+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
8+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
9+
/// any release. You should only use it directly in your code with extreme caution and knowing that
10+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
11+
/// </summary>
12+
public class SqlServerSingletonOptions : ISqlServerSingletonOptions
13+
{
14+
/// <summary>
15+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
16+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
17+
/// any release. You should only use it directly in your code with extreme caution and knowing that
18+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
19+
/// </summary>
20+
public virtual int CompatibilityLevel { get; private set; } = SqlServerOptionsExtension.DefaultCompatibilityLevel;
21+
22+
/// <summary>
23+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
24+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
25+
/// any release. You should only use it directly in your code with extreme caution and knowing that
26+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
27+
/// </summary>
28+
public virtual int? CompatibilityLevelWithoutDefault { get; private set; }
29+
30+
/// <summary>
31+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
32+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
33+
/// any release. You should only use it directly in your code with extreme caution and knowing that
34+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
35+
/// </summary>
36+
public virtual void Initialize(IDbContextOptions options)
37+
{
38+
var sqlServerOptions = options.FindExtension<SqlServerOptionsExtension>();
39+
if (sqlServerOptions != null)
40+
{
41+
CompatibilityLevel = sqlServerOptions.CompatibilityLevel;
42+
CompatibilityLevelWithoutDefault = sqlServerOptions.CompatibilityLevelWithoutDefault;
43+
}
44+
}
45+
46+
/// <summary>
47+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
48+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
49+
/// any release. You should only use it directly in your code with extreme caution and knowing that
50+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
51+
/// </summary>
52+
public virtual void Validate(IDbContextOptions options)
53+
{
54+
var sqlserverOptions = options.FindExtension<SqlServerOptionsExtension>();
55+
56+
if (sqlserverOptions != null &&
57+
(CompatibilityLevelWithoutDefault != sqlserverOptions.CompatibilityLevelWithoutDefault
58+
|| CompatibilityLevel != sqlserverOptions.CompatibilityLevel))
59+
{
60+
throw new InvalidOperationException(
61+
CoreStrings.SingletonOptionChanged(
62+
nameof(SqlServerDbContextOptionsExtensions.UseSqlServer),
63+
nameof(DbContextOptionsBuilder.UseInternalServiceProvider)));
64+
}
65+
}
66+
}

Diff for: src/EFCore.SqlServer/Infrastructure/SqlServerDbContextOptionsBuilder.cs

+14
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,18 @@ public virtual SqlServerDbContextOptionsBuilder EnableRetryOnFailure(
103103
TimeSpan maxRetryDelay,
104104
IEnumerable<int>? errorNumbersToAdd)
105105
=> ExecutionStrategy(c => new SqlServerRetryingExecutionStrategy(c, maxRetryCount, maxRetryDelay, errorNumbersToAdd));
106+
107+
/// <summary>
108+
/// Sets the SQL Server compatibility level that EF Core will use when interacting with the database. This allows configuring EF
109+
/// Core to work with older (or newer) versions of SQL Server. Defaults to <c>150</c> (SQL Server 2019).
110+
/// </summary>
111+
/// <remarks>
112+
/// See <see href="https://aka.ms/efcore-docs-dbcontext-options">Using DbContextOptions</see>, and
113+
/// <see href="https://learn.microsoft.com/sql/t-sql/statements/alter-database-transact-sql-compatibility-level">SQL Server
114+
/// documentation on compatibility level</see> for more information and examples.
115+
/// </remarks>
116+
/// <param name="compatibilityLevel"><see langword="false" /> to have null resource</param>
117+
// TODO: Naming; Cosmos doesn't have Use/Set, so just CompatibilityLevel? SetCompatibilityLevel?
118+
public virtual SqlServerDbContextOptionsBuilder UseCompatibilityLevel(int compatibilityLevel)
119+
=> WithOption(e => e.WithCompatibilityLevel(compatibilityLevel));
106120
}

Diff for: test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Numerics;
55
using Microsoft.EntityFrameworkCore.Internal;
6+
using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
67
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
78
using Microsoft.EntityFrameworkCore.TestUtilities.Xunit;
89

@@ -826,8 +827,8 @@ private static SqlServerTypeMappingSource CreateTypeMappingSource(
826827
params IRelationalTypeMappingSourcePlugin[] plugins)
827828
=> new(
828829
TestServiceFactory.Instance.Create<TypeMappingSourceDependencies>(),
829-
new RelationalTypeMappingSourceDependencies(
830-
plugins));
830+
new RelationalTypeMappingSourceDependencies(plugins),
831+
new SqlServerSingletonOptions());
831832

832833
private class TestTypeMappingPlugin<T> : IRelationalTypeMappingSourcePlugin
833834
{

Diff for: test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using Microsoft.EntityFrameworkCore.Design.Internal;
5+
using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
56
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
67
using NetTopologySuite;
78
using NetTopologySuite.Geometries;
@@ -19,7 +20,8 @@ public void Generate_separates_operations_by_a_blank_line()
1920
new CSharpHelper(
2021
new SqlServerTypeMappingSource(
2122
TestServiceFactory.Instance.Create<TypeMappingSourceDependencies>(),
22-
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>()))));
23+
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>(),
24+
new SqlServerSingletonOptions()))));
2325

2426
var builder = new IndentedStringBuilder();
2527

@@ -3158,7 +3160,8 @@ private void Test<T>(T operation, string expectedCode, Action<T> assert)
31583160
new IRelationalTypeMappingSourcePlugin[]
31593161
{
31603162
new SqlServerNetTopologySuiteTypeMappingSourcePlugin(NtsGeometryServices.Instance)
3161-
})))));
3163+
}),
3164+
new SqlServerSingletonOptions()))));
31623165

31633166
var builder = new IndentedStringBuilder();
31643167
generator.Generate("mb", new[] { operation }, builder);

Diff for: test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs

+7-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Microsoft.EntityFrameworkCore.Metadata.Internal;
77
using Microsoft.EntityFrameworkCore.Migrations.Internal;
88
using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal;
9+
using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
910
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
1011

1112
// ReSharper disable ParameterOnlyUsedForPreconditionCheck.Local
@@ -346,7 +347,8 @@ private static void MissingAnnotationCheck(
346347
{
347348
var sqlServerTypeMappingSource = new SqlServerTypeMappingSource(
348349
TestServiceFactory.Instance.Create<TypeMappingSourceDependencies>(),
349-
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>());
350+
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>(),
351+
new SqlServerSingletonOptions());
350352

351353
var sqlServerAnnotationCodeGenerator = new SqlServerAnnotationCodeGenerator(
352354
new AnnotationCodeGeneratorDependencies(sqlServerTypeMappingSource));
@@ -448,7 +450,8 @@ public void Snapshot_with_enum_discriminator_uses_converted_values()
448450
{
449451
var sqlServerTypeMappingSource = new SqlServerTypeMappingSource(
450452
TestServiceFactory.Instance.Create<TypeMappingSourceDependencies>(),
451-
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>());
453+
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>(),
454+
new SqlServerSingletonOptions());
452455

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

506509
var sqlServerTypeMappingSource = new SqlServerTypeMappingSource(
507510
TestServiceFactory.Instance.Create<TypeMappingSourceDependencies>(),
508-
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>());
511+
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>(),
512+
new SqlServerSingletonOptions());
509513

510514
var codeHelper = new CSharpHelper(sqlServerTypeMappingSource);
511515

Diff for: test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Microsoft.EntityFrameworkCore.Metadata.Internal;
77
using Microsoft.EntityFrameworkCore.Migrations.Internal;
88
using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal;
9+
using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
910
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
1011
using Microsoft.EntityFrameworkCore.TestUtilities.FakeProvider;
1112
using Microsoft.EntityFrameworkCore.Update.Internal;
@@ -57,7 +58,8 @@ private IMigrationsScaffolder CreateMigrationScaffolder<TContext>()
5758
var idGenerator = new MigrationsIdGenerator();
5859
var sqlServerTypeMappingSource = new SqlServerTypeMappingSource(
5960
TestServiceFactory.Instance.Create<TypeMappingSourceDependencies>(),
60-
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>());
61+
TestServiceFactory.Instance.Create<RelationalTypeMappingSourceDependencies>(),
62+
new SqlServerSingletonOptions());
6163
var sqlServerAnnotationCodeGenerator = new SqlServerAnnotationCodeGenerator(
6264
new AnnotationCodeGeneratorDependencies(sqlServerTypeMappingSource));
6365
var code = new CSharpHelper(sqlServerTypeMappingSource);

Diff for: test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.EntityFrameworkCore.Metadata.Internal;
88
using Microsoft.EntityFrameworkCore.Migrations.Internal;
99
using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal;
10+
using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
1011
using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal;
1112
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
1213
using NetTopologySuite;
@@ -7585,7 +7586,8 @@ protected CSharpMigrationsGenerator CreateMigrationsGenerator()
75857586
new IRelationalTypeMappingSourcePlugin[]
75867587
{
75877588
new SqlServerNetTopologySuiteTypeMappingSourcePlugin(NtsGeometryServices.Instance)
7588-
}));
7589+
}),
7590+
new SqlServerSingletonOptions());
75897591

75907592
var codeHelper = new CSharpHelper(sqlServerTypeMappingSource);
75917593

0 commit comments

Comments
 (0)