From bd7d0aaf1ba59771b29de0ae6e3da4099403c913 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 14 Nov 2024 17:31:47 +0100 Subject: [PATCH 01/16] Add missing awaits in MigrationsInfrastructureTestBase (#35107) (cherry picked from commit a3c56773adea670d1672e5ec9c05f608744b861d) --- .../Migrations/MigrationsInfrastructureTestBase.cs | 8 ++++---- .../Migrations/MigrationsInfrastructureSqlServerTest.cs | 8 ++++---- .../Migrations/MigrationsInfrastructureSqliteTest.cs | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs index 20ca2928a65..25844386efa 100644 --- a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs @@ -286,21 +286,21 @@ await history.GetAppliedMigrationsAsync(), } [ConditionalFact] - public virtual void Can_generate_no_migration_script() + public virtual async Task Can_generate_no_migration_script() { using var db = Fixture.CreateEmptyContext(); var migrator = db.GetService(); - SetAndExecuteSqlAsync(migrator.GenerateScript()); + await SetAndExecuteSqlAsync(migrator.GenerateScript()); } [ConditionalFact] - public virtual void Can_generate_migration_from_initial_database_to_initial() + public virtual async Task Can_generate_migration_from_initial_database_to_initial() { using var db = Fixture.CreateContext(); var migrator = db.GetService(); - SetAndExecuteSqlAsync(migrator.GenerateScript(fromMigration: Migration.InitialDatabase, toMigration: Migration.InitialDatabase)); + await SetAndExecuteSqlAsync(migrator.GenerateScript(fromMigration: Migration.InitialDatabase, toMigration: Migration.InitialDatabase)); } [ConditionalFact] diff --git a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsInfrastructureSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsInfrastructureSqlServerTest.cs index 5c1986442c5..c4b8eb03ac8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsInfrastructureSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsInfrastructureSqlServerTest.cs @@ -35,9 +35,9 @@ public override void Can_apply_range_of_migrations() Fixture.TestSqlLoggerFactory.Log.Single(l => l.Id == RelationalEventId.NonTransactionalMigrationOperationWarning).Message); } - public override void Can_generate_migration_from_initial_database_to_initial() + public override async Task Can_generate_migration_from_initial_database_to_initial() { - base.Can_generate_migration_from_initial_database_to_initial(); + await base.Can_generate_migration_from_initial_database_to_initial(); Assert.Equal( """ @@ -57,9 +57,9 @@ CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) ignoreLineEndingDifferences: true); } - public override void Can_generate_no_migration_script() + public override async Task Can_generate_no_migration_script() { - base.Can_generate_no_migration_script(); + await base.Can_generate_no_migration_script(); Assert.Equal( """ diff --git a/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsInfrastructureSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsInfrastructureSqliteTest.cs index 05b25da32f2..54031150080 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsInfrastructureSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsInfrastructureSqliteTest.cs @@ -12,9 +12,9 @@ namespace Microsoft.EntityFrameworkCore.Migrations public class MigrationsInfrastructureSqliteTest(MigrationsInfrastructureSqliteTest.MigrationsInfrastructureSqliteFixture fixture) : MigrationsInfrastructureTestBase(fixture) { - public override void Can_generate_migration_from_initial_database_to_initial() + public override async Task Can_generate_migration_from_initial_database_to_initial() { - base.Can_generate_migration_from_initial_database_to_initial(); + await base.Can_generate_migration_from_initial_database_to_initial(); Assert.Equal( """ @@ -29,9 +29,9 @@ public override void Can_generate_migration_from_initial_database_to_initial() ignoreLineEndingDifferences: true); } - public override void Can_generate_no_migration_script() + public override async Task Can_generate_no_migration_script() { - base.Can_generate_no_migration_script(); + await base.Can_generate_no_migration_script(); Assert.Equal( """ From a8c7b9d3f97748c35a1fc41d011f5a88f42c05f5 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 26 Nov 2024 20:41:43 +0100 Subject: [PATCH 02/16] Fix TPC equality check inside subquery predicate (#35120) (#35201) Fixes #35118 (cherry picked from commit 3d0b86d07b3f1350e95422865b81ee3c7829a719) --- .../Query/SqlExpressions/SelectExpression.cs | 31 +++++++++++++++++-- .../NorthwindMiscellaneousQueryCosmosTest.cs | 8 +++++ .../NorthwindMiscellaneousQueryTestBase.cs | 7 +++++ ...orthwindMiscellaneousQuerySqlServerTest.cs | 15 +++++++++ 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index b466aba6cb4..a482de4adb0 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; @@ -52,6 +53,9 @@ public sealed partial class SelectExpression : TableExpressionBase private static ConstructorInfo? _quotingConstructor; + private static readonly bool UseOldBehavior35118 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35118", out var enabled35118) && enabled35118; + /// /// 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 @@ -1550,7 +1554,8 @@ public void ApplyPredicate(SqlExpression sqlExpression) Left: ColumnExpression leftColumn, Right: SqlConstantExpression { Value: string s1 } } - when GetTable(leftColumn) is TpcTablesExpression + when (UseOldBehavior35118 ? GetTable(leftColumn) : TryGetTable(leftColumn, out var table, out _) ? table : null) + is TpcTablesExpression { DiscriminatorColumn: var discriminatorColumn, DiscriminatorValues: var discriminatorValues @@ -1573,7 +1578,8 @@ when GetTable(leftColumn) is TpcTablesExpression Left: SqlConstantExpression { Value: string s2 }, Right: ColumnExpression rightColumn } - when GetTable(rightColumn) is TpcTablesExpression + when (UseOldBehavior35118 ? GetTable(rightColumn) : TryGetTable(rightColumn, out var table, out _) ? table : null) + is TpcTablesExpression { DiscriminatorColumn: var discriminatorColumn, DiscriminatorValues: var discriminatorValues @@ -1597,7 +1603,8 @@ when GetTable(rightColumn) is TpcTablesExpression Item: ColumnExpression itemColumn, Values: IReadOnlyList valueExpressions } - when GetTable(itemColumn) is TpcTablesExpression + when (UseOldBehavior35118 ? GetTable(itemColumn) : TryGetTable(itemColumn, out var table, out _) ? table : null) + is TpcTablesExpression { DiscriminatorColumn: var discriminatorColumn, DiscriminatorValues: var discriminatorValues @@ -2724,6 +2731,24 @@ private bool ContainsReferencedTable(ColumnExpression column) return false; } + private bool TryGetTable(ColumnExpression column, [NotNullWhen(true)] out TableExpressionBase? table, out int tableIndex) + { + for (var i = 0; i < _tables.Count; i++) + { + var t = _tables[i]; + if (t.UnwrapJoin().Alias == column.TableAlias) + { + table = t; + tableIndex = i; + return true; + } + } + + table = null; + tableIndex = 0; + return false; + } + private enum JoinType { InnerJoin, diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs index 2b0278d4aa2..7884621121e 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs @@ -5276,6 +5276,14 @@ public virtual async Task ToPageAsync_in_subquery_throws() #endregion ToPageAsync + public override async Task Column_access_inside_subquery_predicate(bool async) + { + // Uncorrelated subquery, not supported by Cosmos + await AssertTranslationFailed(() => base.Column_access_inside_subquery_predicate(async)); + + AssertSql(); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs index b04c427891e..3a8b3d94496 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs @@ -5849,4 +5849,11 @@ public virtual Task Static_member_access_gets_parameterized_within_larger_evalua private static string StaticProperty => "ALF"; + + [ConditionalTheory] // #35118 + [MemberData(nameof(IsAsyncData))] + public virtual Task Column_access_inside_subquery_predicate(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => ss.Set().Where(o => c.CustomerID == "ALFKI").Any())); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index ddad65e8400..76dfebfc240 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -7461,6 +7461,21 @@ FROM [Orders] AS [o] """); } + public override async Task Column_access_inside_subquery_predicate(bool async) + { + await base.Column_access_inside_subquery_predicate(async); + + AssertSql( + """ +SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE EXISTS ( + SELECT 1 + FROM [Orders] AS [o] + WHERE [c].[CustomerID] = N'ALFKI') +"""); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); From fa4ce99ee686e67f7c438716ebbe00888f5e8276 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 26 Nov 2024 20:42:20 +0100 Subject: [PATCH 03/16] Add missing Converts when simplifying in funcletizer (#35122) (#35202) Fixes #35095 (cherry picked from commit 3cae7a805033a120a0188aa8a7b7953962cb790a) --- .../Internal/ExpressionTreeFuncletizer.cs | 21 +++++++++++++++---- .../Query/NorthwindWhereQueryCosmosTest.cs | 12 +++++++++++ .../Query/NorthwindWhereQueryTestBase.cs | 15 +++++++++++-- .../Query/NorthwindWhereQuerySqlServerTest.cs | 18 ++++++++++++++-- 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs index d301f12df10..7f48c98ca91 100644 --- a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs +++ b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs @@ -100,6 +100,9 @@ public class ExpressionTreeFuncletizer : ExpressionVisitor private static readonly IReadOnlySet EmptyStringSet = new HashSet(); + private static readonly bool UseOldBehavior35095 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35095", out var enabled35095) && enabled35095; + private static readonly MethodInfo ReadOnlyCollectionIndexerGetter = typeof(ReadOnlyCollection).GetProperties() .Single(p => p.GetIndexParameters() is { Length: 1 } indexParameters && indexParameters[0].ParameterType == typeof(int)).GetMethod!; @@ -379,17 +382,23 @@ protected override Expression VisitBinary(BinaryExpression binary) case ExpressionType.Coalesce: leftValue = Evaluate(left); + Expression returnValue; switch (leftValue) { case null: - return Visit(binary.Right, out _state); + returnValue = Visit(binary.Right, out _state); + break; case bool b: _state = leftState with { StateType = StateType.EvaluatableWithoutCapturedVariable }; - return Constant(b); + returnValue = Constant(b); + break; default: - return left; + returnValue = left; + break; } + return UseOldBehavior35095 ? returnValue : ConvertIfNeeded(returnValue, binary.Type); + case ExpressionType.OrElse or ExpressionType.AndAlso when Evaluate(left) is bool leftBoolValue: { left = Constant(leftBoolValue); @@ -511,9 +520,10 @@ protected override Expression VisitConditional(ConditionalExpression conditional // If the test evaluates, simplify the conditional away by bubbling up the leg that remains if (testState.IsEvaluatable && Evaluate(test) is bool testBoolValue) { - return testBoolValue + var returnValue = testBoolValue ? Visit(conditional.IfTrue, out _state) : Visit(conditional.IfFalse, out _state); + return UseOldBehavior35095 ? returnValue : ConvertIfNeeded(returnValue, conditional.Type); } var ifTrue = Visit(conditional.IfTrue, out var ifTrueState); @@ -2101,6 +2111,9 @@ static Expression RemoveConvert(Expression expression) } } + private static Expression ConvertIfNeeded(Expression expression, Type type) + => expression.Type == type ? expression : Convert(expression, type); + private bool IsGenerallyEvaluatable(Expression expression) => _evaluatableExpressionFilter.IsEvaluatableExpression(expression, _model) && (_parameterize diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs index 81298094b74..93ac1555485 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs @@ -3303,6 +3303,18 @@ FROM root c SELECT VALUE c["OrderID"] FROM root c WHERE ((c["$type"] = "Order") AND (c["OrderID"] = 10252)) +"""); + }); + + public override Task Simplifiable_coalesce_over_nullable(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Simplifiable_coalesce_over_nullable(a); + + AssertSql( + """ +ReadItem(None, Order|10248) """); }); diff --git a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs index c1de5a6b834..dbf562f66e0 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs @@ -2536,7 +2536,18 @@ await AssertQuery( elementAsserter: (e, a) => AssertEqual(e.Id, a.Id)); } - #region Evaluation order of predicates + [ConditionalTheory] // #35095 + [MemberData(nameof(IsAsyncData))] + public virtual Task Simplifiable_coalesce_over_nullable(bool async) + { + int? orderId = 10248; + + return AssertQuery( + async, + ss => ss.Set().Where(o => o.OrderID == (orderId ?? 0))); + } + + #region Evaluation order of operators [ConditionalTheory] [MemberData(nameof(IsAsyncData))] @@ -2559,5 +2570,5 @@ public virtual Task Take_and_Distinct_evaluation_order(bool async) async, ss => ss.Set().Select(c => c.ContactTitle).OrderBy(t => t).Take(3).Distinct()); - #endregion Evaluation order of predicates + #endregion Evaluation order of operators } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs index 132b41e85bb..437ba3f5b4f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs @@ -3425,7 +3425,21 @@ FROM [Orders] AS [o] """); } - #region Evaluation order of predicates + public override async Task Simplifiable_coalesce_over_nullable(bool async) + { + await base.Simplifiable_coalesce_over_nullable(async); + + AssertSql( + """ +@__p_0='10248' + +SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +FROM [Orders] AS [o] +WHERE [o].[OrderID] = @__p_0 +"""); + } + + #region Evaluation order of operators public override async Task Take_and_Where_evaluation_order(bool async) { @@ -3483,7 +3497,7 @@ ORDER BY [c].[ContactTitle] """); } - #endregion Evaluation order of predicates + #endregion Evaluation order of operators private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); From 3695bcab8373683e41cc7e5391669d6114f96dc3 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 26 Nov 2024 21:27:34 +0100 Subject: [PATCH 04/16] Correct VisitUnary operand evaluation in funcletizer (#35172) (#35203) Fixes #35152 (cherry picked from commit 3ba88c418276c5ba8264a29be42e0d5a39e92c94) --- .../Internal/ExpressionTreeFuncletizer.cs | 18 ++++++++++++++++-- .../NorthwindMiscellaneousQueryCosmosTest.cs | 11 +++++++++++ .../NorthwindMiscellaneousQueryTestBase.cs | 11 +++++++++++ ...NorthwindMiscellaneousQuerySqlServerTest.cs | 11 +++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs index 7f48c98ca91..a04ef3b8888 100644 --- a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs +++ b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs @@ -103,6 +103,9 @@ public class ExpressionTreeFuncletizer : ExpressionVisitor private static readonly bool UseOldBehavior35095 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35095", out var enabled35095) && enabled35095; + private static readonly bool UseOldBehavior35152 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35152", out var enabled35152) && enabled35152; + private static readonly MethodInfo ReadOnlyCollectionIndexerGetter = typeof(ReadOnlyCollection).GetProperties() .Single(p => p.GetIndexParameters() is { Length: 1 } indexParameters && indexParameters[0].ParameterType == typeof(int)).GetMethod!; @@ -1558,9 +1561,20 @@ UnaryExpression EvaluateOperand(UnaryExpression unary, Expression operand, State operand = ProcessEvaluatableRoot(operand, ref operandState); } - if (_state.ContainsEvaluatable) + if (UseOldBehavior35152) + { + if (_state.ContainsEvaluatable) + { + _state = _calculatingPath + ? State.CreateContainsEvaluatable( + typeof(UnaryExpression), + [_state.Path! with { PathFromParent = static e => Property(e, nameof(UnaryExpression.Operand)) }]) + : State.NoEvaluatability; + } + } + else { - _state = _calculatingPath + _state = operandState.ContainsEvaluatable && _calculatingPath ? State.CreateContainsEvaluatable( typeof(UnaryExpression), [_state.Path! with { PathFromParent = static e => Property(e, nameof(UnaryExpression.Operand)) }]) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs index 7884621121e..9d74bed0e7a 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs @@ -5284,6 +5284,17 @@ public override async Task Column_access_inside_subquery_predicate(bool async) AssertSql(); } + public override async Task Cast_to_object_over_parameter_directly_in_lambda(bool async) + { + // Sync always throws before getting to exception being tested. + if (async) + { + // Cosmos doesn't support ORDER BY over parameter/constant: + // Unsupported ORDER BY clause. ORDER BY item expression could not be mapped to a document path. + await Assert.ThrowsAsync(() => base.Cast_to_object_over_parameter_directly_in_lambda(async: true)); + } + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs index 3a8b3d94496..f0b8346fcb4 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs @@ -5856,4 +5856,15 @@ public virtual Task Column_access_inside_subquery_predicate(bool async) => AssertQuery( async, ss => ss.Set().Where(c => ss.Set().Where(o => c.CustomerID == "ALFKI").Any())); + + [ConditionalTheory] // #35152 + [MemberData(nameof(IsAsyncData))] + public virtual Task Cast_to_object_over_parameter_directly_in_lambda(bool async) + { + var i = 8; + + return AssertQuery( + async, + ss => ss.Set().OrderBy(o => (object)i).Select(o => o)); + } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index 76dfebfc240..839f136e9fc 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -7476,6 +7476,17 @@ FROM [Orders] AS [o] """); } + public override async Task Cast_to_object_over_parameter_directly_in_lambda(bool async) + { + await base.Cast_to_object_over_parameter_directly_in_lambda(async); + + AssertSql( + """ +SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +FROM [Orders] AS [o] +"""); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); From 1a69d23869a0e16d8075ec89afa1c2932ee75774 Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Tue, 26 Nov 2024 16:18:46 -0800 Subject: [PATCH 05/16] Fix to #35206 - Query/Perf: don't use Invoke in value comparer lambdas when constructing shaper with PopulateCollection (#35207) In EF9 we changed the way we generate shapers in preparation for AOT scenarios. We no longer can embed arbitrary objects into the shaper, instead we need to provide a way to construct that object in code, or simulate the functionality it used to provide. One of the examples was use of ValueComparers in PopulateIncludeCollection. Now instead of passing list of ValueComparer objects to use (which we can't reliably generate in code), we pass the delegate which is used to compare two values: ``` (left, right) => left == null ? right == null : right != null && Invoke((v1, v2) => v1 == v2, (int)left, (int)right) ``` This incurs a performance hit on some scenarios with collections, but can be improved by simplifying the delegate we use. Instead of having nested lambdas and using Invoke, we can inline the body of the nested lambda directly into the outer lambda, like so: ``` (left, right) => left == null ? right == null : right != null && (int)left == (int)right ``` This one change yields significant improvement in the affected scenarios (reducing both time spent and allocations): ef 9 before the Invoke fix | Method | Async | Mean | Error | StdDev | Op/s | Gen0 | Gen1 | Allocated | |-------------------------- |------ |---------:|--------:|--------:|------:|-----------:|----------:|----------:| | PredicateMultipleIncludes | False | 322.6 ms | 0.97 ms | 0.86 ms | 3.099 | 13000.0000 | 6000.0000 | 79.48 MB | | PredicateMultipleIncludes | True | 344.9 ms | 6.79 ms | 6.67 ms | 2.899 | 14000.0000 | 7000.0000 | 87.72 MB | ef 9 after the invoke fix | Method | Async | Mean | Error | StdDev | Op/s | Gen0 | Gen1 | Allocated | |-------------------------- |------ |---------:|--------:|--------:|------:|-----------:|----------:|----------:| | PredicateMultipleIncludes | False | 242.8 ms | 2.39 ms | 2.12 ms | 4.119 | 8000.0000 | 5000.0000 | 51.69 MB | | PredicateMultipleIncludes | True | 263.4 ms | 2.21 ms | 2.06 ms | 3.797 | 10000.0000 | 9000.0000 | 59.93 MB | --- src/EFCore/ChangeTracking/ValueComparer`.cs | 47 +++++++++++++++------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/EFCore/ChangeTracking/ValueComparer`.cs b/src/EFCore/ChangeTracking/ValueComparer`.cs index d6eb5da40d8..4deec681b6f 100644 --- a/src/EFCore/ChangeTracking/ValueComparer`.cs +++ b/src/EFCore/ChangeTracking/ValueComparer`.cs @@ -44,6 +44,9 @@ public class ValueComparer private static readonly PropertyInfo StructuralComparisonsStructuralEqualityComparerProperty = typeof(StructuralComparisons).GetProperty(nameof(StructuralComparisons.StructuralEqualityComparer))!; + private static readonly bool UseOldBehavior35206 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35206", out var enabled35206) && enabled35206; + /// /// Creates a new with a default comparison /// expression and a shallow copy for the snapshot. @@ -263,18 +266,38 @@ public override LambdaExpression ObjectEqualsExpression var left = Parameter(typeof(object), "left"); var right = Parameter(typeof(object), "right"); - _objectEqualsExpression = Lambda>( - Condition( - Equal(left, Constant(null)), - Equal(right, Constant(null)), - AndAlso( - NotEqual(right, Constant(null)), - Invoke( - EqualsExpression, - Convert(left, typeof(T)), - Convert(right, typeof(T))))), - left, - right); + if (!UseOldBehavior35206) + { + var remap = ReplacingExpressionVisitor.Replace( + [EqualsExpression.Parameters[0], EqualsExpression.Parameters[1]], + [Convert(left, typeof(T)), Convert(right, typeof(T))], + EqualsExpression.Body); + + _objectEqualsExpression = Lambda>( + Condition( + Equal(left, Constant(null)), + Equal(right, Constant(null)), + AndAlso( + NotEqual(right, Constant(null)), + remap)), + left, + right); + } + else + { + _objectEqualsExpression = Lambda>( + Condition( + Equal(left, Constant(null)), + Equal(right, Constant(null)), + AndAlso( + NotEqual(right, Constant(null)), + Invoke( + EqualsExpression, + Convert(left, typeof(T)), + Convert(right, typeof(T))))), + left, + right); + } } return _objectEqualsExpression; From b50581ad639f6602f0c537c568b0fe8eaae94812 Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Tue, 26 Nov 2024 16:20:06 -0800 Subject: [PATCH 06/16] Fix to #35208 (#35211) Port of #35209 **Description** In EF9 we changed the way we generate shapers in preparation for AOT scenarios. We no longer can embed arbitrary objects into the shaper, instead we need to provide a way to construct that object in code (using LiftableConstant mechanism), or simulate the functionality it used to provide. At the end of our processing, we find all liftable constants and for the non-AOT case we compile their resolver lambdas and invoke the result with liftable context object to produce the resulting constant object we initially wanted. (in AOT case we generate code from the resolver lambda). Problem is that we are compiling the resolver lambda in the interpretation mode - if the final product is itself a delegate, that delegate will itself be in the interpreter mode and therefore less efficient when invoked multiple times when the query runs. Fix is to use regular compilation rather than interpretation. **Customer impact** Queries using collection navigation with significant amount of data suffer large performance degradation when compared with EF8. No good workaround. **How found** Multiple customer reports on 9.0.0 **Regression** Yes, from 8.0. **Testing** Ad-hoc perf testing with BenchmarkDotNet. Functional change already covered by numerous tests. **Risk** Low, quirk added. --- .../Query/RelationalLiftableConstantProcessor.cs | 2 +- src/EFCore/Query/LiftableConstantProcessor.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalLiftableConstantProcessor.cs b/src/EFCore.Relational/Query/RelationalLiftableConstantProcessor.cs index 2d0b9fa8c0c..32445d46a71 100644 --- a/src/EFCore.Relational/Query/RelationalLiftableConstantProcessor.cs +++ b/src/EFCore.Relational/Query/RelationalLiftableConstantProcessor.cs @@ -36,7 +36,7 @@ protected override ConstantExpression InlineConstant(LiftableConstantExpression if (liftableConstant.ResolverExpression is Expression> resolverExpression) { - var resolver = resolverExpression.Compile(preferInterpretation: true); + var resolver = resolverExpression.Compile(preferInterpretation: UseOldBehavior35208); var value = resolver(_relationalMaterializerLiftableConstantContext); return Expression.Constant(value, liftableConstant.Type); } diff --git a/src/EFCore/Query/LiftableConstantProcessor.cs b/src/EFCore/Query/LiftableConstantProcessor.cs index 5e384638cd8..973601369bc 100644 --- a/src/EFCore/Query/LiftableConstantProcessor.cs +++ b/src/EFCore/Query/LiftableConstantProcessor.cs @@ -31,6 +31,9 @@ private sealed record LiftedConstant( private readonly LiftedConstantOptimizer _liftedConstantOptimizer = new(); private ParameterExpression? _contextParameter; + protected static readonly bool UseOldBehavior35208 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35208", out var enabled35208) && enabled35208; + /// /// Exposes all constants that have been lifted during the last invocation of . /// @@ -198,7 +201,7 @@ protected virtual ConstantExpression InlineConstant(LiftableConstantExpression l // Make sure there aren't any problematic un-lifted constants within the resolver expression. _unsupportedConstantChecker.Check(resolverExpression); - var resolver = resolverExpression.Compile(preferInterpretation: true); + var resolver = resolverExpression.Compile(preferInterpretation: UseOldBehavior35208); var value = resolver(_materializerLiftableConstantContext); return Expression.Constant(value, liftableConstant.Type); From 08b4d430d49b93c095d433a6d4d66b259aca20a2 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Wed, 27 Nov 2024 15:22:13 +0000 Subject: [PATCH 07/16] [release/9.0] Enable Cosmos vector search on EF9 (#35183) Replaces #35002 --- Directory.Packages.props | 8 ++-- .../Extensions/DistanceFunction.cs | 36 ----------------- src/EFCore.Cosmos/Extensions/Embedding.cs | 19 --------- .../Extensions/VectorDataType.cs | 40 ------------------- .../Extensions/VectorIndexPath.cs | 14 ------- .../Extensions/VectorIndexType.cs | 35 ---------------- .../Properties/CosmosStrings.Designer.cs | 6 --- .../Properties/CosmosStrings.resx | 3 -- .../Storage/Internal/CosmosClientWrapper.cs | 7 +--- .../VectorSearchCosmosTest.cs | 3 +- 10 files changed, 7 insertions(+), 164 deletions(-) delete mode 100644 src/EFCore.Cosmos/Extensions/DistanceFunction.cs delete mode 100644 src/EFCore.Cosmos/Extensions/Embedding.cs delete mode 100644 src/EFCore.Cosmos/Extensions/VectorDataType.cs delete mode 100644 src/EFCore.Cosmos/Extensions/VectorIndexPath.cs delete mode 100644 src/EFCore.Cosmos/Extensions/VectorIndexType.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 2a88b69da3e..5975e2b9a33 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -36,7 +36,7 @@ - + @@ -51,14 +51,14 @@ - + - - + + diff --git a/src/EFCore.Cosmos/Extensions/DistanceFunction.cs b/src/EFCore.Cosmos/Extensions/DistanceFunction.cs deleted file mode 100644 index 2df582e5ca1..00000000000 --- a/src/EFCore.Cosmos/Extensions/DistanceFunction.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.Serialization; - -// ReSharper disable once CheckNamespace -namespace Microsoft.Azure.Cosmos; - -/// -/// Defines the distance function for a vector index specification in the Azure Cosmos DB service. -/// Warning: this type will be replaced by the type from the Cosmos SDK, when it is available. -/// -/// -/// for usage. -[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)] -public enum DistanceFunction -{ - /// - /// Represents the Euclidean distance function. - /// - [EnumMember(Value = "euclidean")] - Euclidean, - - /// - /// Represents the cosine distance function. - /// - [EnumMember(Value = "cosine")] - Cosine, - - /// - /// Represents the dot product distance function. - /// - [EnumMember(Value = "dotproduct")] - DotProduct, -} diff --git a/src/EFCore.Cosmos/Extensions/Embedding.cs b/src/EFCore.Cosmos/Extensions/Embedding.cs deleted file mode 100644 index e46c274d887..00000000000 --- a/src/EFCore.Cosmos/Extensions/Embedding.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.CodeAnalysis; - -// ReSharper disable once CheckNamespace -namespace Microsoft.Azure.Cosmos; - -[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)] -internal class Embedding : IEquatable -{ - public string? Path { get; set; } - public VectorDataType DataType { get; set; } - public int Dimensions { get; set; } - public DistanceFunction DistanceFunction { get; set; } - - public bool Equals(Embedding? that) - => Equals(Path, that?.Path) && Equals(DataType, that?.DataType) && Equals(Dimensions, that.Dimensions); -} diff --git a/src/EFCore.Cosmos/Extensions/VectorDataType.cs b/src/EFCore.Cosmos/Extensions/VectorDataType.cs deleted file mode 100644 index cb547fd1b3a..00000000000 --- a/src/EFCore.Cosmos/Extensions/VectorDataType.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.Serialization; - -// ReSharper disable once CheckNamespace -namespace Microsoft.Azure.Cosmos; - -/// -/// Defines the target data type of a vector index specification in the Azure Cosmos DB service. -/// Warning: this type will be replaced by the type from the Cosmos SDK, when it is available. -/// -[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)] -public enum VectorDataType -{ - /// - /// Represents a 16-bit floating point data type. - /// - [EnumMember(Value = "float16")] - Float16, - - /// - /// Represents a 32-bit floating point data type. - /// - [EnumMember(Value = "float32")] - Float32, - - /// - /// Represents an unsigned 8-bit binary data type. - /// - [EnumMember(Value = "uint8")] - Uint8, - - /// - /// Represents a signed 8-bit binary data type. - /// - [EnumMember(Value = "int8")] - Int8, -} diff --git a/src/EFCore.Cosmos/Extensions/VectorIndexPath.cs b/src/EFCore.Cosmos/Extensions/VectorIndexPath.cs deleted file mode 100644 index 8a575c2c40f..00000000000 --- a/src/EFCore.Cosmos/Extensions/VectorIndexPath.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.CodeAnalysis; - -// ReSharper disable once CheckNamespace -namespace Microsoft.Azure.Cosmos; - -[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)] -internal sealed class VectorIndexPath -{ - public string? Path { get; set; } - public VectorIndexType Type { get; set; } -} diff --git a/src/EFCore.Cosmos/Extensions/VectorIndexType.cs b/src/EFCore.Cosmos/Extensions/VectorIndexType.cs deleted file mode 100644 index 88f71d32a5d..00000000000 --- a/src/EFCore.Cosmos/Extensions/VectorIndexType.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.Serialization; - -// ReSharper disable once CheckNamespace -namespace Microsoft.Azure.Cosmos; - -/// -/// Defines the target index type of the vector index path specification in the Azure Cosmos DB service. -/// Warning: this type will be replaced by the type from the Cosmos SDK, when it is available. -/// -[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)] -public enum VectorIndexType -{ - /// - /// Represents a flat vector index type. - /// - [EnumMember(Value = "flat")] - Flat, - - /// - /// Represents a Disk ANN vector index type. - /// - [EnumMember(Value = "diskANN")] - // ReSharper disable once InconsistentNaming - DiskANN, - - /// - /// Represents a quantized flat vector index type. - /// - [EnumMember(Value = "quantizedFlat")] - QuantizedFlat, -} diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs index d9571fc2d05..6c63a1c718e 100644 --- a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs +++ b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs @@ -305,12 +305,6 @@ public static string NoReadItemQueryString(object? resourceId, object? partition public static string NoSubqueryPushdown => GetString("NoSubqueryPushdown"); - /// - /// Container configuration for embeddings is not yet supported by the Cosmos SDK. Instead, configure the container manually. See https://aka.ms/ef-cosmos-vectors for more information. - /// - public static string NoVectorContainerConfig - => GetString("NoVectorContainerConfig"); - /// /// The expression '{sqlExpression}' in the SQL tree does not have a type mapping assigned. /// diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.resx b/src/EFCore.Cosmos/Properties/CosmosStrings.resx index 171d85f852c..8f9a875524b 100644 --- a/src/EFCore.Cosmos/Properties/CosmosStrings.resx +++ b/src/EFCore.Cosmos/Properties/CosmosStrings.resx @@ -271,9 +271,6 @@ Azure Cosmos DB does not have an appropriate subquery for this translation. - - Container configuration for embeddings is not yet supported by the Cosmos SDK. Instead, configure the container manually. See https://aka.ms/ef-cosmos-vectors for more information. - The expression '{sqlExpression}' in the SQL tree does not have a type mapping assigned. diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs index a5c89b6b37d..9f2793deeaa 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs @@ -272,17 +272,14 @@ private static async Task CreateContainerIfNotExistsOnceAsync( AnalyticalStoreTimeToLiveInSeconds = parameters.AnalyticalStoreTimeToLiveInSeconds, }; - // TODO: Enable these once they are available in the Cosmos SDK. See #33783. if (embeddings.Any()) { - throw new InvalidOperationException(CosmosStrings.NoVectorContainerConfig); - //containerProperties.VectorEmbeddingPolicy = new VectorEmbeddingPolicy(embeddings); + containerProperties.VectorEmbeddingPolicy = new VectorEmbeddingPolicy(embeddings); } if (vectorIndexes.Any()) { - throw new InvalidOperationException(CosmosStrings.NoVectorContainerConfig); - //containerProperties.IndexingPolicy = new IndexingPolicy { VectorIndexes = vectorIndexes }; + containerProperties.IndexingPolicy = new IndexingPolicy { VectorIndexes = vectorIndexes }; } var response = await wrapper.Client.GetDatabase(wrapper._databaseId).CreateContainerIfNotExistsAsync( diff --git a/test/EFCore.Cosmos.FunctionalTests/VectorSearchCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/VectorSearchCosmosTest.cs index 04da9fd280c..92d4aacb0f9 100644 --- a/test/EFCore.Cosmos.FunctionalTests/VectorSearchCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/VectorSearchCosmosTest.cs @@ -9,8 +9,7 @@ namespace Microsoft.EntityFrameworkCore; #pragma warning disable EF9103 -// These tests are skipped for now because we cannot create a compatible test container until the SDK supports it. -internal class VectorSearchCosmosTest : IClassFixture +public class VectorSearchCosmosTest : IClassFixture { public VectorSearchCosmosTest(VectorSearchFixture fixture, ITestOutputHelper testOutputHelper) { From 59e92ae6e3aa6eb610e06aae09814f2413e4278b Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Wed, 27 Nov 2024 15:22:45 +0000 Subject: [PATCH 08/16] [release/9.0] Return null when the type is nullable for Cosmos Max/Min/Average (#35216) * Return null when the type is nullable for Cosmos Max/Min/Average (#35173) * Return null when the type is nullable for Cosmos Max/Min/Average Fixes #35094 This was a regression resulting from the major Cosmos query refactoring that happened in EF9. In EF8, the functions Min, Max, and Average would return null if the return type was nullable or was cast to a nullable when the collection is empty. In EF9, this started throwing, which is correct for non-nullable types, but a regression for nullable types. * Added notes * Added quirks * Fix tests. --- ...yableMethodTranslatingExpressionVisitor.cs | 126 ++++++++---- .../AdHocMiscellaneousQueryCosmosTest.cs | 111 +++++++++++ ...thwindAggregateOperatorsQueryCosmosTest.cs | 182 ++++++------------ 3 files changed, 262 insertions(+), 157 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs index 9b446a78152..7ad49bd00c4 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs @@ -16,6 +16,9 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal; /// public class CosmosQueryableMethodTranslatingExpressionVisitor : QueryableMethodTranslatingExpressionVisitor { + private static readonly bool UseOldBehavior35094 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35094", out var enabled) && enabled; + private readonly CosmosQueryCompilationContext _queryCompilationContext; private readonly ISqlExpressionFactory _sqlExpressionFactory; private readonly ITypeMappingSource _typeMappingSource; @@ -445,23 +448,29 @@ private ShapedQueryExpression CreateShapedQueryExpression(SelectExpression selec /// protected override ShapedQueryExpression? TranslateAverage(ShapedQueryExpression source, LambdaExpression? selector, Type resultType) { - var selectExpression = (SelectExpression)source.QueryExpression; - if (selectExpression.IsDistinct - || selectExpression.Limit != null - || selectExpression.Offset != null) + if (UseOldBehavior35094) { - return null; - } + var selectExpression = (SelectExpression)source.QueryExpression; + if (selectExpression.IsDistinct + || selectExpression.Limit != null + || selectExpression.Offset != null) + { + return null; + } - if (selector != null) - { - source = TranslateSelect(source, selector); - } + if (selector != null) + { + source = TranslateSelect(source, selector); + } - var projection = (SqlExpression)selectExpression.GetMappedProjection(new ProjectionMember()); - projection = _sqlExpressionFactory.Function("AVG", new[] { projection }, projection.Type, projection.TypeMapping); + var projection = (SqlExpression)selectExpression.GetMappedProjection(new ProjectionMember()); + projection = _sqlExpressionFactory.Function("AVG", new[] { projection }, projection.Type, projection.TypeMapping); - return AggregateResultShaper(source, projection, throwOnNullResult: true, resultType); + return AggregateResultShaper(source, projection, throwOnNullResult: true, resultType); + + } + + return TranslateAggregate(source, selector, resultType, "AVG"); } /// @@ -843,24 +852,29 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou /// protected override ShapedQueryExpression? TranslateMax(ShapedQueryExpression source, LambdaExpression? selector, Type resultType) { - var selectExpression = (SelectExpression)source.QueryExpression; - if (selectExpression.IsDistinct - || selectExpression.Limit != null - || selectExpression.Offset != null) + if (UseOldBehavior35094) { - return null; - } + var selectExpression = (SelectExpression)source.QueryExpression; + if (selectExpression.IsDistinct + || selectExpression.Limit != null + || selectExpression.Offset != null) + { + return null; + } - if (selector != null) - { - source = TranslateSelect(source, selector); - } + if (selector != null) + { + source = TranslateSelect(source, selector); + } - var projection = (SqlExpression)selectExpression.GetMappedProjection(new ProjectionMember()); + var projection = (SqlExpression)selectExpression.GetMappedProjection(new ProjectionMember()); - projection = _sqlExpressionFactory.Function("MAX", new[] { projection }, resultType, projection.TypeMapping); + projection = _sqlExpressionFactory.Function("MAX", new[] { projection }, resultType, projection.TypeMapping); - return AggregateResultShaper(source, projection, throwOnNullResult: true, resultType); + return AggregateResultShaper(source, projection, throwOnNullResult: true, resultType); + } + + return TranslateAggregate(source, selector, resultType, "MAX"); } /// @@ -871,24 +885,29 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou /// protected override ShapedQueryExpression? TranslateMin(ShapedQueryExpression source, LambdaExpression? selector, Type resultType) { - var selectExpression = (SelectExpression)source.QueryExpression; - if (selectExpression.IsDistinct - || selectExpression.Limit != null - || selectExpression.Offset != null) + if (UseOldBehavior35094) { - return null; - } + var selectExpression = (SelectExpression)source.QueryExpression; + if (selectExpression.IsDistinct + || selectExpression.Limit != null + || selectExpression.Offset != null) + { + return null; + } - if (selector != null) - { - source = TranslateSelect(source, selector); - } + if (selector != null) + { + source = TranslateSelect(source, selector); + } - var projection = (SqlExpression)selectExpression.GetMappedProjection(new ProjectionMember()); + var projection = (SqlExpression)selectExpression.GetMappedProjection(new ProjectionMember()); - projection = _sqlExpressionFactory.Function("MIN", new[] { projection }, resultType, projection.TypeMapping); + projection = _sqlExpressionFactory.Function("MIN", new[] { projection }, resultType, projection.TypeMapping); - return AggregateResultShaper(source, projection, throwOnNullResult: true, resultType); + return AggregateResultShaper(source, projection, throwOnNullResult: true, resultType); + } + + return TranslateAggregate(source, selector, resultType, "MIN"); } /// @@ -1520,6 +1539,35 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s #endregion Queryable collection support + private ShapedQueryExpression? TranslateAggregate(ShapedQueryExpression source, LambdaExpression? selector, Type resultType, string functionName) + { + var selectExpression = (SelectExpression)source.QueryExpression; + if (selectExpression.IsDistinct + || selectExpression.Limit != null + || selectExpression.Offset != null) + { + return null; + } + + if (selector != null) + { + source = TranslateSelect(source, selector); + } + + if (!_subquery && resultType.IsNullableType()) + { + // For nullable types, we want to return null from Max, Min, and Average, rather than throwing. See Issue #35094. + // Note that relational databases typically return null, which propagates. Cosmos will instead return no elements, + // and hence for Cosmos only we need to change no elements into null. + source = source.UpdateResultCardinality(ResultCardinality.SingleOrDefault); + } + + var projection = (SqlExpression)selectExpression.GetMappedProjection(new ProjectionMember()); + projection = _sqlExpressionFactory.Function(functionName, [projection], resultType, _typeMappingSource.FindMapping(resultType)); + + return AggregateResultShaper(source, projection, throwOnNullResult: true, resultType); + } + private bool TryApplyPredicate(ShapedQueryExpression source, LambdaExpression predicate) { var select = (SelectExpression)source.QueryExpression; diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/AdHocMiscellaneousQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/AdHocMiscellaneousQueryCosmosTest.cs index 7fc328c16fd..e2dd2b90904 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/AdHocMiscellaneousQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/AdHocMiscellaneousQueryCosmosTest.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.ComponentModel.DataAnnotations.Schema; + namespace Microsoft.EntityFrameworkCore.Query; #nullable disable @@ -50,6 +52,115 @@ public enum MemberType #endregion 34911 + #region 35094 + + // TODO: Move these tests to a better location. They require nullable properties with nulls in the database. + + [ConditionalFact] + public virtual async Task Min_over_value_type_containing_nulls() + { + await using var context = (await InitializeAsync()).CreateContext(); + Assert.Null(await context.Set().MinAsync(p => p.NullableVal)); + } + + [ConditionalFact] + public virtual async Task Min_over_value_type_containing_all_nulls() + { + await using var context = (await InitializeAsync()).CreateContext(); + Assert.Null(await context.Set().Where(e => e.NullableVal == null).MinAsync(p => p.NullableVal)); + } + + [ConditionalFact] + public virtual async Task Min_over_reference_type_containing_nulls() + { + await using var context = (await InitializeAsync()).CreateContext(); + Assert.Null(await context.Set().MinAsync(p => p.NullableRef)); + } + + [ConditionalFact] + public virtual async Task Min_over_reference_type_containing_all_nulls() + { + await using var context = (await InitializeAsync()).CreateContext(); + Assert.Null(await context.Set().Where(e => e.NullableRef == null).MinAsync(p => p.NullableRef)); + } + + [ConditionalFact] + public virtual async Task Min_over_reference_type_containing_no_data() + { + await using var context = (await InitializeAsync()).CreateContext(); + Assert.Null(await context.Set().Where(e => e.Id < 0).MinAsync(p => p.NullableRef)); + } + + [ConditionalFact] + public virtual async Task Max_over_value_type_containing_nulls() + { + await using var context = (await InitializeAsync()).CreateContext(); + Assert.Equal(3.14, await context.Set().MaxAsync(p => p.NullableVal)); + } + + [ConditionalFact] + public virtual async Task Max_over_value_type_containing_all_nulls() + { + await using var context = (await InitializeAsync()).CreateContext(); + Assert.Null(await context.Set().Where(e => e.NullableVal == null).MaxAsync(p => p.NullableVal)); + } + + [ConditionalFact] + public virtual async Task Max_over_reference_type_containing_nulls() + { + await using var context = (await InitializeAsync()).CreateContext(); + Assert.Equal("Value", await context.Set().MaxAsync(p => p.NullableRef)); + } + + [ConditionalFact] + public virtual async Task Max_over_reference_type_containing_all_nulls() + { + await using var context = (await InitializeAsync()).CreateContext(); + Assert.Null(await context.Set().Where(e => e.NullableRef == null).MaxAsync(p => p.NullableRef)); + } + + [ConditionalFact] + public virtual async Task Max_over_reference_type_containing_no_data() + { + await using var context = (await InitializeAsync()).CreateContext(); + Assert.Null(await context.Set().Where(e => e.Id < 0).MaxAsync(p => p.NullableRef)); + } + + [ConditionalFact] + public virtual async Task Average_over_value_type_containing_nulls() + { + await using var context = (await InitializeAsync()).CreateContext(); + Assert.Null(await context.Set().AverageAsync(p => p.NullableVal)); + } + + [ConditionalFact] + public virtual async Task Average_over_value_type_containing_all_nulls() + { + await using var context = (await InitializeAsync()).CreateContext(); + Assert.Null(await context.Set().Where(e => e.NullableVal == null).AverageAsync(p => p.NullableVal)); + } + + protected class Context35094(DbContextOptions options) : DbContext(options) + { + public DbSet Products { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + => modelBuilder.Entity().HasData( + new Product { Id = 1, NullableRef = "Value", NullableVal = 3.14 }, + new Product { Id = 2, NullableVal = 3.14 }, + new Product { Id = 3, NullableRef = "Value" }); + + public class Product + { + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public int Id { get; set; } + public double? NullableVal { get; set; } + public string NullableRef { get; set; } + } + } + + #endregion 35094 + protected override string StoreName => "AdHocMiscellaneousQueryTests"; diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs index 2d30fc87c90..6f1291dbecd 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs @@ -555,49 +555,33 @@ FROM root c } } - public override async Task Average_no_data_nullable(bool async) - { - // Sync always throws before getting to exception being tested. - if (async) - { - await Fixture.NoSyncTest( - async, async a => - { - Assert.Equal( - CoreStrings.SequenceContainsNoElements, - (await Assert.ThrowsAsync(() => base.Average_no_data_nullable(a))).Message); + public override Task Average_no_data_nullable(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Average_no_data_nullable(a); - AssertSql( - """ + AssertSql( + """ SELECT VALUE AVG(c["SupplierID"]) FROM root c WHERE ((c["$type"] = "Product") AND (c["SupplierID"] = -1)) """); - }); - } - } + }); - public override async Task Average_no_data_cast_to_nullable(bool async) - { - // Sync always throws before getting to exception being tested. - if (async) - { - await Fixture.NoSyncTest( - async, async a => - { - Assert.Equal( - CoreStrings.SequenceContainsNoElements, - (await Assert.ThrowsAsync(() => base.Average_no_data_cast_to_nullable(a))).Message); + public override Task Average_no_data_cast_to_nullable(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Average_no_data_cast_to_nullable(a); - AssertSql( - """ + AssertSql( + """ SELECT VALUE AVG(c["OrderID"]) FROM root c WHERE ((c["$type"] = "Order") AND (c["OrderID"] = -1)) """); - }); - } - } + }); public override async Task Min_no_data(bool async) { @@ -647,49 +631,33 @@ public override async Task Max_no_data_subquery(bool async) AssertSql(); } - public override async Task Max_no_data_nullable(bool async) - { - // Sync always throws before getting to exception being tested. - if (async) - { - await Fixture.NoSyncTest( - async, async a => - { - Assert.Equal( - CoreStrings.SequenceContainsNoElements, - (await Assert.ThrowsAsync(() => base.Max_no_data_nullable(a))).Message); + public override Task Max_no_data_nullable(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Max_no_data_nullable(a); - AssertSql( - """ + AssertSql( + """ SELECT VALUE MAX(c["SupplierID"]) FROM root c WHERE ((c["$type"] = "Product") AND (c["SupplierID"] = -1)) """); - }); - } - } + }); - public override async Task Max_no_data_cast_to_nullable(bool async) - { - // Sync always throws before getting to exception being tested. - if (async) - { - await Fixture.NoSyncTest( - async, async a => - { - Assert.Equal( - CoreStrings.SequenceContainsNoElements, - (await Assert.ThrowsAsync(() => base.Max_no_data_cast_to_nullable(a))).Message); + public override Task Max_no_data_cast_to_nullable(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Max_no_data_cast_to_nullable(a); - AssertSql( - """ + AssertSql( + """ SELECT VALUE MAX(c["OrderID"]) FROM root c WHERE ((c["$type"] = "Order") AND (c["OrderID"] = -1)) """); - }); - } - } + }); public override async Task Min_no_data_subquery(bool async) { @@ -699,22 +667,19 @@ public override async Task Min_no_data_subquery(bool async) AssertSql(); } - public override async Task Average_with_no_arg(bool async) - { - // Always throws for sync. - if (async) - { - // Average truncates. Issue #26378. - await Assert.ThrowsAsync(async () => await base.Average_with_no_arg(async)); + public override Task Average_with_no_arg(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Average_with_no_arg(a); - AssertSql( - """ + AssertSql( + """ SELECT VALUE AVG(c["OrderID"]) FROM root c WHERE (c["$type"] = "Order") """); - } - } + }); public override Task Average_with_binary_expression(bool async) => Fixture.NoSyncTest( @@ -730,22 +695,19 @@ FROM root c """); }); - public override async Task Average_with_arg(bool async) - { - // Always throws for sync. - if (async) - { - // Average truncates. Issue #26378. - await Assert.ThrowsAsync(async () => await base.Average_with_arg(async)); + public override Task Average_with_arg(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Average_with_arg(a); - AssertSql( - """ + AssertSql( + """ SELECT VALUE AVG(c["OrderID"]) FROM root c WHERE (c["$type"] = "Order") """); - } - } + }); public override Task Average_with_arg_expression(bool async) => Fixture.NoSyncTest( @@ -874,49 +836,33 @@ FROM root c """); }); - public override async Task Min_no_data_nullable(bool async) - { - // Sync always throws before getting to exception being tested. - if (async) - { - await Fixture.NoSyncTest( - async, async a => - { - Assert.Equal( - CoreStrings.SequenceContainsNoElements, - (await Assert.ThrowsAsync(() => base.Min_no_data_nullable(a))).Message); + public override Task Min_no_data_nullable(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Min_no_data_nullable(a); - AssertSql( - """ + AssertSql( + """ SELECT VALUE MIN(c["SupplierID"]) FROM root c WHERE ((c["$type"] = "Product") AND (c["SupplierID"] = -1)) """); - }); - } - } + }); - public override async Task Min_no_data_cast_to_nullable(bool async) - { - // Sync always throws before getting to exception being tested. - if (async) - { - await Fixture.NoSyncTest( - async, async a => - { - Assert.Equal( - CoreStrings.SequenceContainsNoElements, - (await Assert.ThrowsAsync(() => base.Min_no_data_cast_to_nullable(a))).Message); + public override Task Min_no_data_cast_to_nullable(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Min_no_data_cast_to_nullable(a); - AssertSql( - """ + AssertSql( + """ SELECT VALUE MIN(c["OrderID"]) FROM root c WHERE ((c["$type"] = "Order") AND (c["OrderID"] = -1)) """); - }); - } - } + }); public override Task Min_with_coalesce(bool async) => Fixture.NoSyncTest( From b7a436f4afcd37fda348ebd1e27443c2c86ad37e Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Wed, 27 Nov 2024 10:29:18 -0800 Subject: [PATCH 09/16] Fix to #35212 - Query/Perf: Compile identifier lambdas passed to PopulateIncludeCollection rather than inline (#35217) Fixes #35212 Port of #35213 **Description** In EF9 we changed the way we generate shapers in preparation for AOT scenarios. As part of these changes we started inlining some delegates passed to PopulateIncludeCollection (as well as couple other methods), rather than compiling them. For scenarios with large number of entities we see significant perf degradation when these delegates are inlined, as opposed to compiled (like we used to do in EF8). This change reverts to EF8 behavior. **Customer impact** Queries using collection navigation with significant amount of data suffer large performance degradation when compared with EF8. No good workaround. **How found** Multiple customer reports on 9.0.0. **Regression** Yes, from 8.0. Perf regression only, no functional regression here. **Testing** Ad-hoc perf testing with BenchmarkDotNet. Functional change already covered by numerous tests. **Risk** Low - essentially reverting back to EF8 behavior, quirk added. **Benchmarks** before the fix (but already with invoke fix and no interpretation) | Method | Async | Mean | Error | StdDev | Op/s | Gen0 | Gen1 | Allocated | |------------ |------ |---------:|--------:|---------:|------:|-----------:|----------:|----------:| | MultiInclue | False | 455.1 ms | 8.94 ms | 10.29 ms | 2.197 | 11000.0000 | 6000.0000 | 67.92 MB | | MultiInclue | True | 435.4 ms | 1.77 ms | 1.66 ms | 2.297 | 11000.0000 | 6000.0000 | 67.92 MB | after the fix: | Method | Async | Mean | Error | StdDev | Op/s | Gen0 | Gen1 | Allocated | |------------ |------ |---------:|--------:|--------:|------:|----------:|----------:|----------:| | MultiInclue | False | 363.3 ms | 6.72 ms | 6.29 ms | 2.752 | 9000.0000 | 3000.0000 | 58.51 MB | | MultiInclue | True | 351.9 ms | 2.08 ms | 1.74 ms | 2.842 | 9000.0000 | 3000.0000 | 58.51 MB | This gets us pretty close to the EF8 numbers which were: | Method | Async | Mean | Error | StdDev | Op/s | Gen0 | Gen1 | Allocated | |------------ |------ |---------:|--------:|--------:|------:|----------:|----------:|----------:| | MultiInclue | False | 297.1 ms | 1.47 ms | 1.30 ms | 3.365 | 8000.0000 | 6000.0000 | 52.4 MB | | MultiInclue | True | 290.2 ms | 3.76 ms | 3.52 ms | 3.446 | 8500.0000 | 6000.0000 | 52.4 MB | --- ...sitor.ShaperProcessingExpressionVisitor.cs | 131 ++++++++++++++++-- 1 file changed, 117 insertions(+), 14 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index 60b910af18a..1f8da845072 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -24,6 +24,9 @@ public partial class RelationalShapedQueryCompilingExpressionVisitor /// public sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisitor { + private static readonly bool UseOldBehavior35212 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35212", out var enabled35212) && enabled35212; + /// /// Reading database values /// @@ -858,16 +861,46 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) QueryCompilationContext.QueryContextParameter, _dataReaderParameter); + var parentIdentifierExpression = UseOldBehavior35212 + ? parentIdentifierLambda + : _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + parentIdentifierLambda.Compile(), + Lambda>( + parentIdentifierLambda, + Parameter(typeof(MaterializerLiftableConstantContext), "_")), + "parentIdentifierLambda", + typeof(Func)); + var outerIdentifierLambda = Lambda( Visit(relationalCollectionShaperExpression.OuterIdentifier), QueryCompilationContext.QueryContextParameter, _dataReaderParameter); + var outerIdentifierExpression = UseOldBehavior35212 + ? outerIdentifierLambda + : _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + outerIdentifierLambda.Compile(), + Lambda>( + outerIdentifierLambda, + Parameter(typeof(MaterializerLiftableConstantContext), "_")), + "outerIdentifierLambda", + typeof(Func)); + var selfIdentifierLambda = Lambda( Visit(relationalCollectionShaperExpression.SelfIdentifier), QueryCompilationContext.QueryContextParameter, _dataReaderParameter); + var selfIdentifierExpression = UseOldBehavior35212 + ? selfIdentifierLambda + : _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + selfIdentifierLambda.Compile(), + Lambda>( + selfIdentifierLambda, + Parameter(typeof(MaterializerLiftableConstantContext), "_")), + "selfIdentifierLambda", + typeof(Func)); + _inline = false; _includeExpressions.Add( @@ -878,8 +911,8 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) _dataReaderParameter, _resultCoordinatorParameter, entity, - parentIdentifierLambda, - outerIdentifierLambda, + parentIdentifierExpression, + outerIdentifierExpression, _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( navigation, LiftableConstantExpressionHelpers.BuildNavigationAccessLambda(navigation), @@ -907,9 +940,9 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) QueryCompilationContext.QueryContextParameter, _dataReaderParameter, _resultCoordinatorParameter, - parentIdentifierLambda, - outerIdentifierLambda, - selfIdentifierLambda, + parentIdentifierExpression, + outerIdentifierExpression, + selfIdentifierExpression, _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( relationalCollectionShaperExpression.ParentIdentifierValueComparers .Select(x => (Func)x.Equals).ToArray(), @@ -982,6 +1015,16 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) QueryCompilationContext.QueryContextParameter, _dataReaderParameter); + var parentIdentifierExpression = UseOldBehavior35212 + ? parentIdentifierLambda + : _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + parentIdentifierLambda.Compile(), + Lambda>( + parentIdentifierLambda, + Parameter(typeof(MaterializerLiftableConstantContext), "_")), + "parentIdentifierLambda", + typeof(Func)); + _inline = false; innerProcessor._inline = true; @@ -991,6 +1034,16 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) QueryCompilationContext.QueryContextParameter, innerProcessor._dataReaderParameter); + var childIdentifierExpression = UseOldBehavior35212 + ? childIdentifierLambda + : _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + childIdentifierLambda.Compile(), + Lambda>( + childIdentifierLambda, + Parameter(typeof(MaterializerLiftableConstantContext), "_")), + "childIdentifierLambda", + typeof(Func)); + innerProcessor._inline = false; _includeExpressions.Add( @@ -1001,7 +1054,7 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) _dataReaderParameter, _resultCoordinatorParameter, entity, - parentIdentifierLambda, + parentIdentifierExpression, _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( navigation, LiftableConstantExpressionHelpers.BuildNavigationAccessLambda(navigation), @@ -1031,7 +1084,7 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) CreateReaderColumnsExpression(readerColumns, _parentVisitor.Dependencies.LiftableConstantFactory), Constant(_detailedErrorsEnabled), _resultCoordinatorParameter, - childIdentifierLambda, + childIdentifierExpression, _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( relationalSplitCollectionShaperExpression.IdentifierValueComparers .Select(x => (Func)x.Equals).ToArray(), @@ -1150,16 +1203,46 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) QueryCompilationContext.QueryContextParameter, _dataReaderParameter); + var parentIdentifierExpression = UseOldBehavior35212 + ? parentIdentifierLambda + : _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + parentIdentifierLambda.Compile(), + Lambda>( + parentIdentifierLambda, + Parameter(typeof(MaterializerLiftableConstantContext), "_")), + "parentIdentifierLambda", + typeof(Func)); + var outerIdentifierLambda = Lambda( Visit(relationalCollectionShaperExpression.OuterIdentifier), QueryCompilationContext.QueryContextParameter, _dataReaderParameter); + var outerIdentifierExpression = UseOldBehavior35212 + ? outerIdentifierLambda + : _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + outerIdentifierLambda.Compile(), + Lambda>( + outerIdentifierLambda, + Parameter(typeof(MaterializerLiftableConstantContext), "_")), + "outerIdentifierLambda", + typeof(Func)); + var selfIdentifierLambda = Lambda( Visit(relationalCollectionShaperExpression.SelfIdentifier), QueryCompilationContext.QueryContextParameter, _dataReaderParameter); + var selfIdentifierExpression = UseOldBehavior35212 + ? selfIdentifierLambda + : _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + selfIdentifierLambda.Compile(), + Lambda>( + selfIdentifierLambda, + Parameter(typeof(MaterializerLiftableConstantContext), "_")), + "selfIdentifierLambda", + typeof(Func)); + _inline = false; var collectionParameter = Parameter(relationalCollectionShaperExpression.Type); @@ -1173,8 +1256,8 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) QueryCompilationContext.QueryContextParameter, _dataReaderParameter, _resultCoordinatorParameter, - parentIdentifierLambda, - outerIdentifierLambda, + parentIdentifierExpression, + outerIdentifierExpression, _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( collectionAccessor, LiftableConstantExpressionHelpers.BuildClrCollectionAccessorLambda(navigation), @@ -1195,9 +1278,9 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) QueryCompilationContext.QueryContextParameter, _dataReaderParameter, _resultCoordinatorParameter, - parentIdentifierLambda, - outerIdentifierLambda, - selfIdentifierLambda, + parentIdentifierExpression, + outerIdentifierExpression, + selfIdentifierExpression, _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( relationalCollectionShaperExpression.ParentIdentifierValueComparers .Select(x => (Func)x.Equals).ToArray(), @@ -1267,6 +1350,16 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) QueryCompilationContext.QueryContextParameter, _dataReaderParameter); + var parentIdentifierExpression = UseOldBehavior35212 + ? parentIdentifierLambda + : _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + parentIdentifierLambda.Compile(), + Lambda>( + parentIdentifierLambda, + Parameter(typeof(MaterializerLiftableConstantContext), "_")), + "parentIdentifierLambda", + typeof(Func)); + _inline = false; innerProcessor._inline = true; @@ -1276,6 +1369,16 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) QueryCompilationContext.QueryContextParameter, innerProcessor._dataReaderParameter); + var childIdentifierExpression = UseOldBehavior35212 + ? childIdentifierLambda + : _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + childIdentifierLambda.Compile(), + Lambda>( + childIdentifierLambda, + Parameter(typeof(MaterializerLiftableConstantContext), "_")), + "childIdentifierLambda", + typeof(Func)); + innerProcessor._inline = false; var collectionParameter = Parameter(collectionType); @@ -1290,7 +1393,7 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) QueryCompilationContext.QueryContextParameter, _dataReaderParameter, _resultCoordinatorParameter, - parentIdentifierLambda, + parentIdentifierExpression, _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( collectionAccessor, LiftableConstantExpressionHelpers.BuildClrCollectionAccessorLambda(navigation), @@ -1315,7 +1418,7 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) CreateReaderColumnsExpression(readerColumns, _parentVisitor.Dependencies.LiftableConstantFactory), Constant(_detailedErrorsEnabled), _resultCoordinatorParameter, - childIdentifierLambda, + childIdentifierExpression, _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( relationalSplitCollectionShaperExpression.IdentifierValueComparers .Select(x => (Func)x.Equals).ToArray(), From 1c0ef32fbb495f4b5fdd0fc4830a8130a9cce143 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Sun, 1 Dec 2024 21:20:44 -0800 Subject: [PATCH 10/16] [release/9.0] Uniquify all variables used in SQL Server migration scripts (#35228) Fixes #35132 --- .../SqlServerMigrationsSqlGenerator.cs | 98 ++- .../Migrations/MigrationsSqlServerTest.cs | 830 +++++++++--------- .../SqlServerMigrationsSqlGeneratorTest.cs | 46 +- 3 files changed, 494 insertions(+), 480 deletions(-) diff --git a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs index a46d20ce58c..28362b77761 100644 --- a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs +++ b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs @@ -29,7 +29,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations; public class SqlServerMigrationsSqlGenerator : MigrationsSqlGenerator { private IReadOnlyList _operations = null!; - private int _variableCounter; + private int _variableCounter = -1; private readonly ICommandBatchPreparer _commandBatchPreparer; @@ -643,6 +643,8 @@ protected override void Generate( subBuilder.Append(")"); + var historyTableName = operation[SqlServerAnnotationNames.TemporalHistoryTableName] as string; + string historyTable; if (needsExec) { subBuilder @@ -650,18 +652,14 @@ protected override void Generate( var execBody = subBuilder.GetCommandList().Single().CommandText.Replace("'", "''"); + var schemaVariable = Uniquify("@historyTableSchema"); builder - .AppendLine("DECLARE @historyTableSchema sysname = SCHEMA_NAME()") + .AppendLine($"DECLARE {schemaVariable} sysname = SCHEMA_NAME()") .Append("EXEC(N'") .Append(execBody); - } - var historyTableName = operation[SqlServerAnnotationNames.TemporalHistoryTableName] as string; - string historyTable; - if (needsExec) - { historyTable = Dependencies.SqlGenerationHelper.DelimitIdentifier(historyTableName!); - tableCreationOptions.Add($"SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + N'].{historyTable})"); + tableCreationOptions.Add($"SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + {schemaVariable} + N'].{historyTable})"); } else { @@ -1116,10 +1114,11 @@ protected override void Generate( { if (operation[SqlServerAnnotationNames.EditionOptions] is string editionOptions) { + var dbVariable = Uniquify("@db_name"); builder .AppendLine("BEGIN") - .AppendLine("DECLARE @db_name nvarchar(max) = DB_NAME();") - .AppendLine("EXEC(N'ALTER DATABASE [' + @db_name + '] MODIFY ( ") + .AppendLine($"DECLARE {dbVariable} nvarchar(max) = DB_NAME();") + .AppendLine($"EXEC(N'ALTER DATABASE [' + {dbVariable} + '] MODIFY ( ") .Append(editionOptions.Replace("'", "''")) .AppendLine(" );');") .AppendLine("END") @@ -1128,19 +1127,21 @@ protected override void Generate( if (operation.Collation != operation.OldDatabase.Collation) { + var dbVariable = Uniquify("@db_name"); builder .AppendLine("BEGIN") - .AppendLine("DECLARE @db_name nvarchar(max) = DB_NAME();"); + .AppendLine($"DECLARE {dbVariable} nvarchar(max) = DB_NAME();"); + var collation = operation.Collation; if (operation.Collation == null) { - builder.AppendLine("DECLARE @defaultCollation nvarchar(max) = CAST(SERVERPROPERTY('Collation') AS nvarchar(max));"); + var collationVariable = Uniquify("@defaultCollation"); + builder.AppendLine($"DECLARE {collationVariable} nvarchar(max) = CAST(SERVERPROPERTY('Collation') AS nvarchar(max));"); + collation = "' + " + collationVariable + " + N'"; } builder - .Append("EXEC(N'ALTER DATABASE [' + @db_name + '] COLLATE ") - .Append(operation.Collation ?? "' + @defaultCollation + N'") - .AppendLine(";');") + .AppendLine($"EXEC(N'ALTER DATABASE [' + {dbVariable} + '] COLLATE {collation};');") .AppendLine("END") .AppendLine(); } @@ -1167,10 +1168,11 @@ protected override void Generate( using (builder.Indent()) { + var dbVariable = Uniquify("@db_name"); builder .AppendLine("BEGIN") .AppendLine("ALTER DATABASE CURRENT SET AUTO_CLOSE OFF;") - .AppendLine("DECLARE @db_name nvarchar(max) = DB_NAME();") + .AppendLine($"DECLARE {dbVariable} nvarchar(max) = DB_NAME();") .AppendLine("DECLARE @fg_name nvarchar(max);") .AppendLine("SELECT TOP(1) @fg_name = [name] FROM [sys].[filegroups] WHERE [type] = N'FX';") .AppendLine() @@ -1180,20 +1182,21 @@ protected override void Generate( { builder .AppendLine("BEGIN") - .AppendLine("SET @fg_name = @db_name + N'_MODFG';") + .AppendLine($"SET @fg_name = {dbVariable} + N'_MODFG';") .AppendLine("EXEC(N'ALTER DATABASE CURRENT ADD FILEGROUP [' + @fg_name + '] CONTAINS MEMORY_OPTIMIZED_DATA;');") .AppendLine("END"); } + var pathVariable = Uniquify("@path"); builder .AppendLine() - .AppendLine("DECLARE @path nvarchar(max);") - .Append("SELECT TOP(1) @path = [physical_name] FROM [sys].[database_files] ") + .AppendLine($"DECLARE {pathVariable} nvarchar(max);") + .Append($"SELECT TOP(1) {pathVariable} = [physical_name] FROM [sys].[database_files] ") .AppendLine("WHERE charindex('\\', [physical_name]) > 0 ORDER BY [file_id];") - .AppendLine("IF (@path IS NULL)") - .IncrementIndent().AppendLine("SET @path = '\\' + @db_name;").DecrementIndent() + .AppendLine($"IF ({pathVariable} IS NULL)") + .IncrementIndent().AppendLine($"SET {pathVariable} = '\\' + {dbVariable};").DecrementIndent() .AppendLine() - .AppendLine("DECLARE @filename nvarchar(max) = right(@path, charindex('\\', reverse(@path)) - 1);") + .AppendLine($"DECLARE @filename nvarchar(max) = right({pathVariable}, charindex('\\', reverse({pathVariable})) - 1);") .AppendLine( "SET @filename = REPLACE(left(@filename, len(@filename) - charindex('.', reverse(@filename))), '''', '''''') + N'_MOD';") .AppendLine( @@ -1739,10 +1742,11 @@ protected virtual void Transfer( { var stringTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string)); + var schemaVariable = Uniquify("@defaultSchema"); builder - .AppendLine("DECLARE @defaultSchema sysname = SCHEMA_NAME();") + .AppendLine($"DECLARE {schemaVariable} sysname = SCHEMA_NAME();") .Append("EXEC(") - .Append("N'ALTER SCHEMA [' + @defaultSchema + ") + .Append($"N'ALTER SCHEMA [' + {schemaVariable} + ") .Append( stringTypeMapping.GenerateSqlLiteral( "] TRANSFER " + Dependencies.SqlGenerationHelper.DelimitIdentifier(name, schema) + ";")) @@ -1908,7 +1912,7 @@ protected virtual void DropDefaultConstraint( { var stringTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string)); - var variable = "@var" + _variableCounter++; + var variable = Uniquify("@var"); builder .Append("DECLARE ") @@ -2039,18 +2043,18 @@ protected virtual void AddDescription( string? column = null, bool omitVariableDeclarations = false) { - string schemaLiteral; + var schemaLiteral = Uniquify("@defaultSchema", increase: !omitVariableDeclarations); + var descriptionVariable = Uniquify("@description", increase: false); + if (schema == null) { if (!omitVariableDeclarations) { - builder.Append("DECLARE @defaultSchema AS sysname") + builder.Append($"DECLARE {schemaLiteral} AS sysname") .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); - builder.Append("SET @defaultSchema = SCHEMA_NAME()") + builder.Append($"SET {schemaLiteral} = SCHEMA_NAME()") .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); } - - schemaLiteral = "@defaultSchema"; } else { @@ -2059,16 +2063,15 @@ protected virtual void AddDescription( if (!omitVariableDeclarations) { - builder.Append("DECLARE @description AS sql_variant") + builder.Append($"DECLARE {descriptionVariable} AS sql_variant") .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); } - builder.Append("SET @description = ") - .Append(Literal(description)) + builder.Append($"SET {descriptionVariable} = {Literal(description)}") .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); builder .Append("EXEC sp_addextendedproperty 'MS_Description', ") - .Append("@description") + .Append(descriptionVariable) .Append(", 'SCHEMA', ") .Append(schemaLiteral) .Append(", 'TABLE', ") @@ -2221,18 +2224,17 @@ protected virtual void DropDescription( { var stringTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string)); - string schemaLiteral; + var schemaLiteral = Uniquify("@defaultSchema", increase: !omitVariableDeclarations); + var descriptionVariable = Uniquify("@description", increase: false); if (schema == null) { if (!omitVariableDeclarations) { - builder.Append("DECLARE @defaultSchema AS sysname") + builder.Append($"DECLARE {schemaLiteral} AS sysname") .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); - builder.Append("SET @defaultSchema = SCHEMA_NAME()") + builder.Append($"SET {schemaLiteral} = SCHEMA_NAME()") .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); } - - schemaLiteral = "@defaultSchema"; } else { @@ -2241,7 +2243,7 @@ protected virtual void DropDescription( if (!omitVariableDeclarations) { - builder.Append("DECLARE @description AS sql_variant") + builder.Append($"DECLARE {descriptionVariable} AS sql_variant") .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); } @@ -2351,6 +2353,16 @@ private static bool HasDifferences(IEnumerable source, IEnumerable< return count != targetAnnotations.Count; } + private string Uniquify(string variableName, bool increase = true) + { + if (increase) + { + _variableCounter++; + } + + return _variableCounter == 0 ? variableName : variableName + _variableCounter; + } + private IReadOnlyList RewriteOperations( IReadOnlyList migrationOperations, IModel? model, @@ -3043,10 +3055,12 @@ void EnableVersioning(string table, string? schema, string historyTableName, str { var stringBuilder = new StringBuilder(); + string? schemaVariable = null; if (historyTableSchema == null) { + schemaVariable = Uniquify("@historyTableSchema"); // need to run command using EXEC to inject default schema - stringBuilder.AppendLine("DECLARE @historyTableSchema sysname = SCHEMA_NAME()"); + stringBuilder.AppendLine($"DECLARE {schemaVariable} sysname = SCHEMA_NAME()"); stringBuilder.Append("EXEC(N'"); } @@ -3065,7 +3079,7 @@ void EnableVersioning(string table, string? schema, string historyTableName, str else { stringBuilder.AppendLine( - $" SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].{historyTable}))')"); + $" SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + {schemaVariable} + '].{historyTable}))')"); } operations.Add( diff --git a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs index 65a90ea1f54..bce6e263c9a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs @@ -432,12 +432,12 @@ public override async Task Alter_table_change_comment() AssertSql( """ -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE', N'People'; -SET @description = N'Table comment2'; -EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'People'; +DECLARE @defaultSchema1 AS sysname; +SET @defaultSchema1 = SCHEMA_NAME(); +DECLARE @description1 AS sql_variant; +EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema1, 'TABLE', N'People'; +SET @description1 = N'Table comment2'; +EXEC sp_addextendedproperty 'MS_Description', @description1, 'SCHEMA', @defaultSchema1, 'TABLE', N'People'; """); } @@ -447,10 +447,10 @@ public override async Task Alter_table_remove_comment() AssertSql( """ -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE', N'People'; +DECLARE @defaultSchema1 AS sysname; +SET @defaultSchema1 = SCHEMA_NAME(); +DECLARE @description1 AS sql_variant; +EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema1, 'TABLE', N'People'; """); } @@ -1110,12 +1110,12 @@ public override async Task Alter_column_change_type() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SomeColumn'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [SomeColumn] bigint NOT NULL; """); } @@ -1126,12 +1126,12 @@ public override async Task Alter_column_make_required() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SomeColumn'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); UPDATE [People] SET [SomeColumn] = N'' WHERE [SomeColumn] IS NULL; ALTER TABLE [People] ALTER COLUMN [SomeColumn] nvarchar(max) NOT NULL; ALTER TABLE [People] ADD DEFAULT N'' FOR [SomeColumn]; @@ -1144,12 +1144,12 @@ public override async Task Alter_column_make_required_with_null_data() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SomeColumn'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); UPDATE [People] SET [SomeColumn] = N'' WHERE [SomeColumn] IS NULL; ALTER TABLE [People] ALTER COLUMN [SomeColumn] nvarchar(max) NOT NULL; ALTER TABLE [People] ADD DEFAULT N'' FOR [SomeColumn]; @@ -1164,12 +1164,12 @@ public override async Task Alter_column_make_required_with_index() AssertSql( """ DROP INDEX [IX_People_SomeColumn] ON [People]; -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SomeColumn'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); UPDATE [People] SET [SomeColumn] = N'' WHERE [SomeColumn] IS NULL; ALTER TABLE [People] ALTER COLUMN [SomeColumn] nvarchar(450) NOT NULL; ALTER TABLE [People] ADD DEFAULT N'' FOR [SomeColumn]; @@ -1185,12 +1185,12 @@ public override async Task Alter_column_make_required_with_composite_index() AssertSql( """ DROP INDEX [IX_People_FirstName_LastName] ON [People]; -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'FirstName'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); UPDATE [People] SET [FirstName] = N'' WHERE [FirstName] IS NULL; ALTER TABLE [People] ALTER COLUMN [FirstName] nvarchar(450) NOT NULL; ALTER TABLE [People] ADD DEFAULT N'' FOR [FirstName]; @@ -1206,12 +1206,12 @@ public override async Task Alter_column_make_computed(bool? stored) AssertSql( $""" -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Sum'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] DROP COLUMN [Sum]; ALTER TABLE [People] ADD [Sum] AS [X] + [Y]{computedColumnTypeSql}; """); @@ -1223,12 +1223,12 @@ public override async Task Alter_column_change_computed() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Sum'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] DROP COLUMN [Sum]; ALTER TABLE [People] ADD [Sum] AS [X] - [Y]; """); @@ -1241,12 +1241,12 @@ public override async Task Alter_column_change_computed_recreates_indexes() AssertSql( """ DROP INDEX [IX_People_Sum] ON [People]; -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Sum'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] DROP COLUMN [Sum]; ALTER TABLE [People] ADD [Sum] AS [X] - [Y]; """, @@ -1262,12 +1262,12 @@ public override async Task Alter_column_change_computed_type() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Sum'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] DROP COLUMN [Sum]; ALTER TABLE [People] ADD [Sum] AS [X] + [Y] PERSISTED; """); @@ -1279,12 +1279,12 @@ public override async Task Alter_column_make_non_computed() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Sum'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] DROP COLUMN [Sum]; ALTER TABLE [People] ADD [Sum] int NOT NULL; """); @@ -1327,12 +1327,12 @@ public override async Task Alter_column_change_comment() AssertSql( """ -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Id'; -SET @description = N'Some comment2'; -EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Id'; +DECLARE @defaultSchema1 AS sysname; +SET @defaultSchema1 = SCHEMA_NAME(); +DECLARE @description1 AS sql_variant; +EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema1, 'TABLE', N'People', 'COLUMN', N'Id'; +SET @description1 = N'Some comment2'; +EXEC sp_addextendedproperty 'MS_Description', @description1, 'SCHEMA', @defaultSchema1, 'TABLE', N'People', 'COLUMN', N'Id'; """); } @@ -1343,10 +1343,10 @@ public override async Task Alter_column_remove_comment() AssertSql( """ -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Id'; +DECLARE @defaultSchema1 AS sysname; +SET @defaultSchema1 = SCHEMA_NAME(); +DECLARE @description1 AS sql_variant; +EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema1, 'TABLE', N'People', 'COLUMN', N'Id'; """); } @@ -1357,12 +1357,12 @@ public override async Task Alter_column_set_collation() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(max) COLLATE German_PhoneBook_CI_AS NULL; """); } @@ -1389,12 +1389,12 @@ await Test( AssertSql( """ DROP INDEX [IX_People_Name] ON [People]; -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) COLLATE German_PhoneBook_CI_AS NULL; CREATE INDEX [IX_People_Name] ON [People] ([Name]); """); @@ -1407,12 +1407,12 @@ public override async Task Alter_column_reset_collation() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(max) NULL; """); } @@ -1423,12 +1423,12 @@ public override async Task Convert_json_entities_to_regular_owned() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Entity]') AND [c].[name] = N'OwnedCollection'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [Entity] DROP COLUMN [OwnedCollection]; """, // @@ -1501,12 +1501,12 @@ public override async Task Convert_regular_owned_entities_to_json() """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Entity]') AND [c].[name] = N'OwnedReference_Date'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [Entity] DROP COLUMN [OwnedReference_Date]; """, // @@ -1542,12 +1542,12 @@ public override async Task Convert_string_column_to_a_json_column_containing_req AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Entity]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var + '];'); UPDATE [Entity] SET [Name] = N'{}' WHERE [Name] IS NULL; ALTER TABLE [Entity] ALTER COLUMN [Name] nvarchar(max) NOT NULL; ALTER TABLE [Entity] ADD DEFAULT N'{}' FOR [Name]; @@ -1590,12 +1590,12 @@ await Test( AssertSql( """ DROP INDEX [IX_People_SomeColumn] ON [People]; -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SomeColumn'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); UPDATE [People] SET [SomeColumn] = N'' WHERE [SomeColumn] IS NULL; ALTER TABLE [People] ALTER COLUMN [SomeColumn] nvarchar(450) NOT NULL; ALTER TABLE [People] ADD DEFAULT N'' FOR [SomeColumn]; @@ -1629,12 +1629,12 @@ await Test( AssertSql( """ ALTER TABLE [People] DROP INDEX [IX_People_Name]; -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(30) NULL; ALTER TABLE [People] ADD INDEX [IX_People_Name] NONCLUSTERED ([Name]); """); @@ -1662,12 +1662,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL; """); } @@ -1701,12 +1701,12 @@ await Test( AssertSql( """ DROP INDEX [IX_People_FirstName_LastName] ON [People]; -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(30) NULL; CREATE INDEX [IX_People_FirstName_LastName] ON [People] ([FirstName], [LastName]) INCLUDE ([Name]); """); @@ -1758,12 +1758,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'IdentityColumn'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [IdentityColumn] bigint NOT NULL; """); } @@ -1803,12 +1803,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ADD DEFAULT N'Doe' FOR [Name]; """); } @@ -1854,12 +1854,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SomeProperty'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [SomeProperty] nvarchar(max) SPARSE NULL; """); } @@ -1870,12 +1870,12 @@ public override async Task Drop_column() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SomeColumn'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] DROP COLUMN [SomeColumn]; """); } @@ -1890,12 +1890,12 @@ public override async Task Drop_column_primary_key() """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Id'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] DROP COLUMN [Id]; """); } @@ -1906,12 +1906,12 @@ public override async Task Drop_column_computed_and_non_computed_with_dependency AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Y'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] DROP COLUMN [Y]; """, // @@ -1932,12 +1932,12 @@ public override async Task Drop_json_columns_from_existing_table() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Entity]') AND [c].[name] = N'OwnedCollection'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [Entity] DROP COLUMN [OwnedCollection]; """, // @@ -1982,12 +1982,12 @@ public override async Task Create_index() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'FirstName'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [FirstName] nvarchar(450) NULL; """, // @@ -2002,12 +2002,12 @@ public override async Task Create_index_unique() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'LastName'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [LastName] nvarchar(450) NULL; """, // @@ -2080,12 +2080,12 @@ public override async Task Create_index_with_filter() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL; """, // @@ -2117,12 +2117,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL; """, // @@ -2137,12 +2137,12 @@ public override async Task Create_unique_index_with_filter() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL; """, // @@ -2168,12 +2168,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'FirstName'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [FirstName] nvarchar(450) NULL; """, // @@ -2201,12 +2201,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'FirstName'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [FirstName] nvarchar(450) NULL; """, // @@ -2242,12 +2242,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL; """, // @@ -2285,12 +2285,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL; """, // @@ -2328,12 +2328,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NOT NULL; """, // @@ -2373,12 +2373,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NOT NULL; """, // @@ -2421,12 +2421,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NOT NULL; """, // @@ -2470,12 +2470,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NOT NULL; """, // @@ -2517,12 +2517,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NOT NULL; """, // @@ -2565,12 +2565,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NOT NULL; """, // @@ -2618,12 +2618,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NOT NULL; """, // @@ -2657,12 +2657,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL; """, // @@ -2697,12 +2697,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL; """, // @@ -2773,12 +2773,12 @@ public override async Task Add_primary_key_string() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SomeField'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [SomeField] nvarchar(450) NOT NULL; """, // @@ -2793,12 +2793,12 @@ public override async Task Add_primary_key_with_name() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SomeField'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); UPDATE [People] SET [SomeField] = N'' WHERE [SomeField] IS NULL; ALTER TABLE [People] ALTER COLUMN [SomeField] nvarchar(450) NOT NULL; ALTER TABLE [People] ADD DEFAULT N'' FOR [SomeField]; @@ -2903,12 +2903,12 @@ public override async Task Drop_primary_key_string() """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SomeField'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [SomeField] nvarchar(max) NOT NULL; """); } @@ -3255,12 +3255,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SeqProp'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] DROP COLUMN [SeqProp]; """, // @@ -4437,8 +4437,8 @@ ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [RenamedCustomers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[HistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [RenamedCustomers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[HistoryTable]))') """); } @@ -4519,19 +4519,19 @@ ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -SET @description = N'for VIP only'; -EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'RenamedCustomers', 'COLUMN', N'Discount'; +DECLARE @defaultSchema2 AS sysname; +SET @defaultSchema2 = SCHEMA_NAME(); +DECLARE @description2 AS sql_variant; +SET @description2 = N'for VIP only'; +EXEC sp_addextendedproperty 'MS_Description', @description2, 'SCHEMA', @defaultSchema2, 'TABLE', N'RenamedCustomers', 'COLUMN', N'Discount'; """, // """ -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -SET @description = N'for VIP only'; -EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'HistoryTable', 'COLUMN', N'Discount'; +DECLARE @defaultSchema3 AS sysname; +SET @defaultSchema3 = SCHEMA_NAME(); +DECLARE @description3 AS sql_variant; +SET @description3 = N'for VIP only'; +EXEC sp_addextendedproperty 'MS_Description', @description3, 'SCHEMA', @defaultSchema3, 'TABLE', N'HistoryTable', 'COLUMN', N'Discount'; """, // """ @@ -4539,8 +4539,8 @@ ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [RenamedCustomers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[HistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [RenamedCustomers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[HistoryTable]))') """); } @@ -5356,48 +5356,48 @@ ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customers] DROP COLUMN [Name]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var3 sysname; +SELECT @var3 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[HistoryTable]') AND [c].[name] = N'Name'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var1 + '];'); +IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var3 + '];'); ALTER TABLE [HistoryTable] DROP COLUMN [Name]; """, // """ -DECLARE @var2 sysname; -SELECT @var2 = [d].[name] +DECLARE @var4 sysname; +SELECT @var4 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Number'); -IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var2 + '];'); +IF @var4 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var4 + '];'); ALTER TABLE [Customers] DROP COLUMN [Number]; """, // """ -DECLARE @var3 sysname; -SELECT @var3 = [d].[name] +DECLARE @var5 sysname; +SELECT @var5 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[HistoryTable]') AND [c].[name] = N'Number'); -IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var3 + '];'); +IF @var5 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var5 + '];'); ALTER TABLE [HistoryTable] DROP COLUMN [Number]; """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[HistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[HistoryTable]))') """); } @@ -5454,12 +5454,12 @@ ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [Customers] DROP COLUMN [Name]; """, // @@ -5551,12 +5551,12 @@ ALTER TABLE [mySchema].[Customers] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[mySchema].[Customers]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [mySchema].[Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [mySchema].[Customers] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [mySchema].[Customers] DROP COLUMN [Name]; """, // @@ -5652,12 +5652,12 @@ ALTER TABLE [mySchema].[Customers] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[mySchema].[Customers]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [mySchema].[Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [mySchema].[Customers] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [mySchema].[Customers] DROP COLUMN [Name]; """, // @@ -5753,12 +5753,12 @@ ALTER TABLE [mySchema].[Customers] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[mySchema].[Customers]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [mySchema].[Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [mySchema].[Customers] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [mySchema].[Customers] DROP COLUMN [Name]; """, // @@ -5972,22 +5972,22 @@ ALTER TABLE [Customer] DROP PERIOD FOR SYSTEM_TIME """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var1 sysname; +SELECT @var1 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customer]') AND [c].[name] = N'PeriodEnd'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var0 + '];'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [Customer] DROP COLUMN [PeriodEnd]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customer]') AND [c].[name] = N'PeriodStart'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var1 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customer] DROP COLUMN [PeriodStart]; """, // @@ -6051,22 +6051,22 @@ ALTER TABLE [Customer] DROP PERIOD FOR SYSTEM_TIME """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var1 sysname; +SELECT @var1 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customer]') AND [c].[name] = N'PeriodEnd'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var0 + '];'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [Customer] DROP COLUMN [PeriodEnd]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customer]') AND [c].[name] = N'PeriodStart'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var1 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customer] DROP COLUMN [PeriodStart]; """, // @@ -6926,11 +6926,11 @@ await Test( AssertSql( """ -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -SET @description = N'My comment'; -EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'Customers', 'COLUMN', N'End'; +DECLARE @defaultSchema1 AS sysname; +SET @defaultSchema1 = SCHEMA_NAME(); +DECLARE @description1 AS sql_variant; +SET @description1 = N'My comment'; +EXEC sp_addextendedproperty 'MS_Description', @description1, 'SCHEMA', @defaultSchema1, 'TABLE', N'Customers', 'COLUMN', N'End'; """); } @@ -7050,32 +7050,32 @@ ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customer]') AND [c].[name] = N'IsVip'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var0 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var2 + '];'); UPDATE [Customer] SET [IsVip] = CAST(0 AS bit) WHERE [IsVip] IS NULL; ALTER TABLE [Customer] ALTER COLUMN [IsVip] bit NOT NULL; ALTER TABLE [Customer] ADD DEFAULT CAST(0 AS bit) FOR [IsVip]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var3 sysname; +SELECT @var3 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[HistoryTable]') AND [c].[name] = N'IsVip'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var1 + '];'); +IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var3 + '];'); UPDATE [HistoryTable] SET [IsVip] = CAST(0 AS bit) WHERE [IsVip] IS NULL; ALTER TABLE [HistoryTable] ALTER COLUMN [IsVip] bit NOT NULL; ALTER TABLE [HistoryTable] ADD DEFAULT CAST(0 AS bit) FOR [IsVip]; """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[HistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[HistoryTable]))') """); } @@ -7196,8 +7196,8 @@ ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[HistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[HistoryTable]))') """); } @@ -7260,8 +7260,8 @@ ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[HistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[HistoryTable]))') """); } @@ -7315,28 +7315,28 @@ ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customer]') AND [c].[name] = N'IdPlusFive'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var0 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customer] DROP COLUMN [IdPlusFive]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var3 sysname; +SELECT @var3 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[HistoryTable]') AND [c].[name] = N'IdPlusFive'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var1 + '];'); +IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var3 + '];'); ALTER TABLE [HistoryTable] DROP COLUMN [IdPlusFive]; """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[HistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[HistoryTable]))') """); } @@ -7506,28 +7506,28 @@ ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customer]') AND [c].[name] = N'Number'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var0 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customer] DROP COLUMN [Number]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var3 sysname; +SELECT @var3 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[HistoryTable]') AND [c].[name] = N'Number'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var1 + '];'); +IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var3 + '];'); ALTER TABLE [HistoryTable] DROP COLUMN [Number]; """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[HistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[HistoryTable]))') """); } @@ -7652,8 +7652,8 @@ IF EXISTS (SELECT 1 FROM [sys].[tables] [t] INNER JOIN [sys].[partitions] [p] ON """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[HistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[HistoryTable]))') """); } @@ -7793,28 +7793,28 @@ IF EXISTS (SELECT 1 FROM [sys].[tables] [t] INNER JOIN [sys].[partitions] [p] ON """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customer]') AND [c].[name] = N'MyColumn'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var0 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customer] ALTER COLUMN [MyColumn] int SPARSE NULL; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var3 sysname; +SELECT @var3 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[HistoryTable]') AND [c].[name] = N'MyColumn'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var1 + '];'); +IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var3 + '];'); ALTER TABLE [HistoryTable] ALTER COLUMN [MyColumn] int SPARSE NULL; """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[HistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [Customer] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[HistoryTable]))') """); } @@ -7874,12 +7874,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var1 sysname; +SELECT @var1 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customer]') AND [c].[name] = N'MyColumn'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var0 + '];'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [Customer] ALTER COLUMN [MyColumn] int NULL; """); } @@ -8017,13 +8017,13 @@ [Name] nvarchar(max) NULL, CONSTRAINT [PK_Customer] PRIMARY KEY ([Id]), PERIOD FOR SYSTEM_TIME([SystemTimeStart], [SystemTimeEnd]) ) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + N'].[CustomerHistory]))'); -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -SET @description = N'Table comment'; -EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'Customer'; -SET @description = N'Column comment'; -EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'Customer', 'COLUMN', N'Name'; +DECLARE @defaultSchema1 AS sysname; +SET @defaultSchema1 = SCHEMA_NAME(); +DECLARE @description1 AS sql_variant; +SET @description1 = N'Table comment'; +EXEC sp_addextendedproperty 'MS_Description', @description1, 'SCHEMA', @defaultSchema1, 'TABLE', N'Customer'; +SET @description1 = N'Column comment'; +EXEC sp_addextendedproperty 'MS_Description', @description1, 'SCHEMA', @defaultSchema1, 'TABLE', N'Customer', 'COLUMN', N'Name'; """); } @@ -8077,18 +8077,18 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var1 sysname; +SELECT @var1 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customer]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var0 + '];'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customer] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [Customer] ALTER COLUMN [Name] nvarchar(450) NULL; -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -SET @description = N'Column comment'; -EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'Customer', 'COLUMN', N'Name'; +DECLARE @defaultSchema2 AS sysname; +SET @defaultSchema2 = SCHEMA_NAME(); +DECLARE @description2 AS sql_variant; +SET @description2 = N'Column comment'; +EXEC sp_addextendedproperty 'MS_Description', @description2, 'SCHEMA', @defaultSchema2, 'TABLE', N'Customer', 'COLUMN', N'Name'; """, // """ @@ -8174,21 +8174,21 @@ await Test( AssertSql( """ -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE', N'Customer'; -SET @description = N'Modified table comment'; -EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'Customer'; +DECLARE @defaultSchema2 AS sysname; +SET @defaultSchema2 = SCHEMA_NAME(); +DECLARE @description2 AS sql_variant; +EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema2, 'TABLE', N'Customer'; +SET @description2 = N'Modified table comment'; +EXEC sp_addextendedproperty 'MS_Description', @description2, 'SCHEMA', @defaultSchema2, 'TABLE', N'Customer'; """, // """ -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE', N'Customer', 'COLUMN', N'Name'; -SET @description = N'Modified column comment'; -EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'Customer', 'COLUMN', N'Name'; +DECLARE @defaultSchema3 AS sysname; +SET @defaultSchema3 = SCHEMA_NAME(); +DECLARE @description3 AS sql_variant; +EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema3, 'TABLE', N'Customer', 'COLUMN', N'Name'; +SET @description3 = N'Modified column comment'; +EXEC sp_addextendedproperty 'MS_Description', @description3, 'SCHEMA', @defaultSchema3, 'TABLE', N'Customer', 'COLUMN', N'Name'; """); } @@ -8244,12 +8244,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var1 sysname; +SELECT @var1 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [Customers] ALTER COLUMN [Name] nvarchar(450) NULL; """, // @@ -8316,12 +8316,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var1 sysname; +SELECT @var1 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [Customers] ALTER COLUMN [Name] nvarchar(450) NULL; """, // @@ -8804,22 +8804,22 @@ ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Number'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customers] DROP COLUMN [Number]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var3 sysname; +SELECT @var3 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[HistoryTable]') AND [c].[name] = N'Number'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var1 + '];'); +IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var3 + '];'); ALTER TABLE [HistoryTable] DROP COLUMN [Number]; """, // @@ -8840,8 +8840,8 @@ FROM [sys].[default_constraints] [d] """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[HistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[HistoryTable]))') """); } @@ -8916,22 +8916,22 @@ ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Number'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customers] DROP COLUMN [Number]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var3 sysname; +SELECT @var3 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[HistoryTable]') AND [c].[name] = N'Number'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var1 + '];'); +IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var3 + '];'); ALTER TABLE [HistoryTable] DROP COLUMN [Number]; """, // @@ -8952,8 +8952,8 @@ FROM [sys].[default_constraints] [d] """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [ModifiedCustomers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[HistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [ModifiedCustomers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[HistoryTable]))') """); } @@ -9024,22 +9024,22 @@ ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Number'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customers] DROP COLUMN [Number]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var3 sysname; +SELECT @var3 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[HistoryTable]') AND [c].[name] = N'Number'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var1 + '];'); +IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var3 + '];'); ALTER TABLE [HistoryTable] DROP COLUMN [Number]; """, // @@ -9056,8 +9056,8 @@ FROM [sys].[default_constraints] [d] """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[ModifiedHistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[ModifiedHistoryTable]))') """); } @@ -9119,22 +9119,22 @@ ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Number'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customers] DROP COLUMN [Number]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var3 sysname; +SELECT @var3 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[HistoryTable]') AND [c].[name] = N'Number'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var1 + '];'); +IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var3 + '];'); ALTER TABLE [HistoryTable] DROP COLUMN [Number]; """, // @@ -9147,8 +9147,8 @@ FROM [sys].[default_constraints] [d] """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[HistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[HistoryTable]))') """); } @@ -9211,44 +9211,44 @@ ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Number'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customers] DROP COLUMN [Number]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var3 sysname; +SELECT @var3 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[HistoryTable]') AND [c].[name] = N'Number'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var1 + '];'); +IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var3 + '];'); ALTER TABLE [HistoryTable] DROP COLUMN [Number]; """, // """ -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -SET @description = N'My comment'; -EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'Customers', 'COLUMN', N'Name'; +DECLARE @defaultSchema4 AS sysname; +SET @defaultSchema4 = SCHEMA_NAME(); +DECLARE @description4 AS sql_variant; +SET @description4 = N'My comment'; +EXEC sp_addextendedproperty 'MS_Description', @description4, 'SCHEMA', @defaultSchema4, 'TABLE', N'Customers', 'COLUMN', N'Name'; """, // """ -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -SET @description = N'My comment'; -EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'HistoryTable', 'COLUMN', N'Name'; +DECLARE @defaultSchema5 AS sysname; +SET @defaultSchema5 = SCHEMA_NAME(); +DECLARE @description5 AS sql_variant; +SET @description5 = N'My comment'; +EXEC sp_addextendedproperty 'MS_Description', @description5, 'SCHEMA', @defaultSchema5, 'TABLE', N'HistoryTable', 'COLUMN', N'Name'; """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[HistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[HistoryTable]))') """); } @@ -9314,11 +9314,11 @@ await Test( """, // """ -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -SET @description = N'My comment'; -EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'Customers', 'COLUMN', N'End'; +DECLARE @defaultSchema1 AS sysname; +SET @defaultSchema1 = SCHEMA_NAME(); +DECLARE @description1 AS sql_variant; +SET @description1 = N'My comment'; +EXEC sp_addextendedproperty 'MS_Description', @description1, 'SCHEMA', @defaultSchema1, 'TABLE', N'Customers', 'COLUMN', N'End'; """); } @@ -9385,22 +9385,22 @@ ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'DateOfBirth'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customers] DROP COLUMN [DateOfBirth]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var3 sysname; +SELECT @var3 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[HistoryTable]') AND [c].[name] = N'DateOfBirth'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var1 + '];'); +IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var3 + '];'); ALTER TABLE [HistoryTable] DROP COLUMN [DateOfBirth]; """, // @@ -9413,24 +9413,24 @@ FROM [sys].[default_constraints] [d] """, // """ -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -SET @description = N'My comment'; -EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'Customers', 'COLUMN', N'End'; +DECLARE @defaultSchema4 AS sysname; +SET @defaultSchema4 = SCHEMA_NAME(); +DECLARE @description4 AS sql_variant; +SET @description4 = N'My comment'; +EXEC sp_addextendedproperty 'MS_Description', @description4, 'SCHEMA', @defaultSchema4, 'TABLE', N'Customers', 'COLUMN', N'End'; """, // """ -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -SET @description = N'My comment'; -EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'HistoryTable', 'COLUMN', N'End'; +DECLARE @defaultSchema5 AS sysname; +SET @defaultSchema5 = SCHEMA_NAME(); +DECLARE @description5 AS sql_variant; +SET @description5 = N'My comment'; +EXEC sp_addextendedproperty 'MS_Description', @description5, 'SCHEMA', @defaultSchema5, 'TABLE', N'HistoryTable', 'COLUMN', N'End'; """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[HistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[HistoryTable]))') """); } @@ -9797,12 +9797,12 @@ await Test( AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var1 sysname; +SELECT @var1 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Number'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [Customers] DROP COLUMN [Number]; """, // @@ -9968,22 +9968,22 @@ ALTER TABLE [Customers] DROP PERIOD FOR SYSTEM_TIME """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var1 sysname; +SELECT @var1 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'End'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [Customers] DROP COLUMN [End]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Start'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var1 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customers] DROP COLUMN [Start]; """, // @@ -10050,42 +10050,42 @@ ALTER TABLE [Customers] DROP PERIOD FOR SYSTEM_TIME """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var1 sysname; +SELECT @var1 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'End'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [Customers] DROP COLUMN [End]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Number'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var1 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customers] DROP COLUMN [Number]; """, // """ -DECLARE @var2 sysname; -SELECT @var2 = [d].[name] +DECLARE @var3 sysname; +SELECT @var3 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[HistoryTable]') AND [c].[name] = N'Number'); -IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var2 + '];'); +IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [HistoryTable] DROP CONSTRAINT [' + @var3 + '];'); ALTER TABLE [HistoryTable] DROP COLUMN [Number]; """, // """ -DECLARE @var3 sysname; -SELECT @var3 = [d].[name] +DECLARE @var4 sysname; +SELECT @var4 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Start'); -IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var3 + '];'); +IF @var4 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var4 + '];'); ALTER TABLE [Customers] DROP COLUMN [Start]; """, // @@ -10150,22 +10150,22 @@ ALTER TABLE [Customers] DROP PERIOD FOR SYSTEM_TIME """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var1 sysname; +SELECT @var1 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'End'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [Customers] DROP COLUMN [End]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Start'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var1 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customers] DROP COLUMN [Start]; """, // @@ -10324,12 +10324,12 @@ await Test( """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var1 sysname; +SELECT @var1 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Number'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [Customers] DROP COLUMN [Number]; """, // @@ -10519,22 +10519,22 @@ ALTER TABLE [Customers] DROP PERIOD FOR SYSTEM_TIME """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var1 sysname; +SELECT @var1 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'End'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [Customers] DROP COLUMN [End]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Start'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var1 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customers] DROP COLUMN [Start]; """, // @@ -10641,8 +10641,8 @@ ALTER TABLE [Customers] SET (SYSTEM_VERSIONING = OFF) """, // """ -DECLARE @historyTableSchema sysname = SCHEMA_NAME() -EXEC(N'ALTER TABLE [NewCustomers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].[NewHistoryTable]))') +DECLARE @historyTableSchema1 sysname = SCHEMA_NAME() +EXEC(N'ALTER TABLE [NewCustomers] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema1 + '].[NewHistoryTable]))') """); } @@ -10724,22 +10724,22 @@ ALTER TABLE [Customers] DROP PERIOD FOR SYSTEM_TIME """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var1 sysname; +SELECT @var1 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'End'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var0 + '];'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [Customers] DROP COLUMN [End]; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] +DECLARE @var2 sysname; +SELECT @var2 = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Customers]') AND [c].[name] = N'Start'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var1 + '];'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [Customers] DROP CONSTRAINT [' + @var2 + '];'); ALTER TABLE [Customers] DROP COLUMN [Start]; """, // diff --git a/test/EFCore.SqlServer.FunctionalTests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs b/test/EFCore.SqlServer.FunctionalTests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs index 9e8f89ad5f6..0b4f87a279e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs @@ -227,12 +227,12 @@ public override void AlterColumnOperation_without_column_type() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'LuckyNumber'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [LuckyNumber] int NOT NULL; """); } @@ -262,12 +262,12 @@ public virtual void AlterColumnOperation_with_identity_legacy() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Id'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [People] ALTER COLUMN [Id] int NOT NULL; """); } @@ -296,12 +296,12 @@ public virtual void AlterColumnOperation_with_index_no_oldColumn() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Person]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [Person] ALTER COLUMN [Name] nvarchar(30) NULL; """); } @@ -336,12 +336,12 @@ public virtual void AlterColumnOperation_with_added_index() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Person]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [Person] ALTER COLUMN [Name] nvarchar(30) NULL; GO @@ -378,12 +378,12 @@ public virtual void AlterColumnOperation_with_added_index_no_oldType() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Person]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [Person] ALTER COLUMN [Name] nvarchar(450) NULL; GO @@ -411,12 +411,12 @@ public virtual void AlterColumnOperation_identity_legacy() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Person]') AND [c].[name] = N'Id'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var + '];'); ALTER TABLE [Person] ALTER COLUMN [Id] bigint NOT NULL; """); } @@ -595,8 +595,8 @@ public virtual void AlterDatabaseOperation_collation_to_default() """ BEGIN DECLARE @db_name nvarchar(max) = DB_NAME(); -DECLARE @defaultCollation nvarchar(max) = CAST(SERVERPROPERTY('Collation') AS nvarchar(max)); -EXEC(N'ALTER DATABASE [' + @db_name + '] COLLATE ' + @defaultCollation + N';'); +DECLARE @defaultCollation1 nvarchar(max) = CAST(SERVERPROPERTY('Collation') AS nvarchar(max)); +EXEC(N'ALTER DATABASE [' + @db_name + '] COLLATE ' + @defaultCollation1 + N';'); END """); @@ -1264,12 +1264,12 @@ public virtual void AlterColumn_make_required_with_idempotent() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] +DECLARE @var sysname; +SELECT @var = [d].[name] FROM [sys].[default_constraints] [d] INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Person]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var + '];'); EXEC(N'UPDATE [Person] SET [Name] = N'''' WHERE [Name] IS NULL'); ALTER TABLE [Person] ALTER COLUMN [Name] nvarchar(max) NOT NULL; ALTER TABLE [Person] ADD DEFAULT N'' FOR [Name]; From fba8789b9fa60aa3e4efc297c72926562f2b79cb Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 2 Dec 2024 09:45:38 +0100 Subject: [PATCH 11/16] Avoid using `^` and `~` when invalid because of value converters (#35124) (#35241) The transformation of equality/in-equality in a (negated) XOR is only possible when the expressions are BIT or integer types on the SQL side (i.e. taking value conversion into account). Similarly, the Boolean negation `NOT` can be implemented as `~` only if the underlying expression is a BIT. Fixes #35093. (cherry picked from commit e6abfdd937df81bd9863cbfb9f8b3f8df41c2008) Co-authored-by: Andrea Canciani --- .../SearchConditionConvertingExpressionVisitor.cs | 15 ++++++++++++--- .../Query/GearsOfWarQueryTestBase.cs | 7 +++++++ .../Query/GearsOfWarQuerySqlServerTest.cs | 14 ++++++++++++++ .../Query/TPCGearsOfWarQuerySqlServerTest.cs | 14 ++++++++++++++ .../Query/TPTGearsOfWarQuerySqlServerTest.cs | 14 ++++++++++++++ .../Query/TemporalGearsOfWarQuerySqlServerTest.cs | 14 ++++++++++++++ .../Query/GearsOfWarQuerySqliteTest.cs | 11 +++++++++++ 7 files changed, 86 insertions(+), 3 deletions(-) diff --git a/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs index 16073c7e512..1ad9fefde8b 100644 --- a/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs @@ -16,6 +16,9 @@ public class SearchConditionConvertingExpressionVisitor : SqlExpressionVisitor private bool _isSearchCondition; private readonly ISqlExpressionFactory _sqlExpressionFactory; + private static readonly bool UseOldBehavior35093 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35093", out var enabled35093) && enabled35093; + /// /// 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 @@ -344,9 +347,12 @@ protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpres _isSearchCondition = parentIsSearchCondition; + var leftType = UseOldBehavior35093 ? newLeft.Type : newLeft.TypeMapping?.Converter?.ProviderClrType ?? newLeft.Type; + var rightType = UseOldBehavior35093 ? newRight.Type : newRight.TypeMapping?.Converter?.ProviderClrType ?? newRight.Type; + if (!parentIsSearchCondition - && (newLeft.Type == typeof(bool) || newLeft.Type.IsEnum || newLeft.Type.IsInteger()) - && (newRight.Type == typeof(bool) || newRight.Type.IsEnum || newRight.Type.IsInteger()) + && (leftType == typeof(bool) || leftType.IsEnum || leftType.IsInteger()) + && (rightType == typeof(bool) || rightType.IsEnum || rightType.IsInteger()) && sqlBinaryExpression.OperatorType is ExpressionType.NotEqual or ExpressionType.Equal) { // "lhs != rhs" is the same as "CAST(lhs ^ rhs AS BIT)", except that @@ -410,7 +416,10 @@ protected override Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpressio switch (sqlUnaryExpression.OperatorType) { case ExpressionType.Not - when sqlUnaryExpression.Type == typeof(bool): + when (UseOldBehavior35093 + ? sqlUnaryExpression.Type + : (sqlUnaryExpression.TypeMapping?.Converter?.ProviderClrType ?? sqlUnaryExpression.Type)) + == typeof(bool): { // when possible, avoid converting to/from predicate form if (!_isSearchCondition && sqlUnaryExpression.Operand is not (ExistsExpression or InExpression or LikeExpression)) diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index ec8180be84c..93ffe9f0cfb 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -8047,6 +8047,13 @@ public virtual Task Comparison_with_value_converted_subclass(bool async) async, ss => ss.Set().Where(f => f.ServerAddress == IPAddress.Loopback)); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Project_equality_with_value_converted_property(bool async) + => AssertQuery( + async, + ss => ss.Set().Select(m => m.Difficulty == MissionDifficulty.Unknown)); + private static readonly IEnumerable _weaponTypes = new AmmunitionType?[] { AmmunitionType.Cartridge }; [ConditionalTheory] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 14db73c59cb..f7569611bb0 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -9380,6 +9380,20 @@ FROM [Factions] AS [f] """); } + public override async Task Project_equality_with_value_converted_property(bool async) + { + await base.Project_equality_with_value_converted_property(async); + + AssertSql( + """ +SELECT CASE + WHEN [m].[Difficulty] = N'Unknown' THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END +FROM [Missions] AS [m] +"""); + } + public override async Task Contains_on_readonly_enumerable(bool async) { await base.Contains_on_readonly_enumerable(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs index f8d41c87f63..0eca14b01d9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs @@ -12341,6 +12341,20 @@ FROM [LocustHordes] AS [l] """); } + public override async Task Project_equality_with_value_converted_property(bool async) + { + await base.Project_equality_with_value_converted_property(async); + + AssertSql( + """ +SELECT CASE + WHEN [m].[Difficulty] = N'Unknown' THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END +FROM [Missions] AS [m] +"""); + } + public override async Task FirstOrDefault_on_empty_collection_of_DateTime_in_subquery(bool async) { await base.FirstOrDefault_on_empty_collection_of_DateTime_in_subquery(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs index e2260d6cc08..fb8d0803c80 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs @@ -10507,6 +10507,20 @@ FROM [Factions] AS [f] """); } + public override async Task Project_equality_with_value_converted_property(bool async) + { + await base.Project_equality_with_value_converted_property(async); + + AssertSql( + """ +SELECT CASE + WHEN [m].[Difficulty] = N'Unknown' THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END +FROM [Missions] AS [m] +"""); + } + public override async Task FirstOrDefault_on_empty_collection_of_DateTime_in_subquery(bool async) { await base.FirstOrDefault_on_empty_collection_of_DateTime_in_subquery(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs index 6895ee561b1..01ccc04db5a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs @@ -6999,6 +6999,20 @@ public override async Task Comparison_with_value_converted_subclass(bool async) """); } + public override async Task Project_equality_with_value_converted_property(bool async) + { + await base.Project_equality_with_value_converted_property(async); + + AssertSql( + """ +SELECT CASE + WHEN [m].[Difficulty] = N'Unknown' THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END +FROM [Missions] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [m] +"""); + } + public override async Task Navigation_access_on_derived_materialized_entity_using_cast(bool async) { await base.Navigation_access_on_derived_materialized_entity_using_cast(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index 6fff187cf93..aec1ce784fe 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -3076,6 +3076,17 @@ public override async Task Comparison_with_value_converted_subclass(bool async) """); } + public override async Task Project_equality_with_value_converted_property(bool async) + { + await base.Project_equality_with_value_converted_property(async); + + AssertSql( + """ +SELECT "m"."Difficulty" = 'Unknown' +FROM "Missions" AS "m" +"""); + } + public override async Task GetValueOrDefault_in_filter_non_nullable_column(bool async) { await base.GetValueOrDefault_in_filter_non_nullable_column(async); From cc53f41b229d89358e27e5bd86bab237e1aff940 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Mon, 2 Dec 2024 09:51:23 -0800 Subject: [PATCH 12/16] [release/9.0] Prevent owner entity from becoming optional (#35222) Fixes #35110 --- .../ForeignKeyPropertyDiscoveryConvention.cs | 17 ++++-- .../Internal/InternalEntityTypeBuilder.cs | 20 +++---- .../Internal/MigrationsModelDifferTest.cs | 56 +++++++++++++++++++ 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs index 45194d0a2a1..beb97f2b1b1 100644 --- a/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs @@ -50,6 +50,9 @@ public class ForeignKeyPropertyDiscoveryConvention : IPropertyFieldChangedConvention, IModelFinalizingConvention { + private static readonly bool UseOldBehavior35110 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35110", out var enabled) && enabled; + /// /// Creates a new instance of . /// @@ -81,13 +84,17 @@ private IConventionForeignKeyBuilder ProcessForeignKey( IConventionContext context) { var shouldBeRequired = true; - foreach (var property in relationshipBuilder.Metadata.Properties) + if (!relationshipBuilder.Metadata.IsOwnership + || UseOldBehavior35110) { - if (property.IsNullable) + foreach (var property in relationshipBuilder.Metadata.Properties) { - shouldBeRequired = false; - relationshipBuilder = relationshipBuilder.IsRequired(false) ?? relationshipBuilder; - break; + if (property.IsNullable) + { + shouldBeRequired = false; + relationshipBuilder = relationshipBuilder.IsRequired(false) ?? relationshipBuilder; + break; + } } } diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index dfeeb09c785..53d96998377 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -15,6 +15,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// public class InternalEntityTypeBuilder : InternalTypeBaseBuilder, IConventionEntityTypeBuilder { + private static readonly bool UseOldBehavior35110 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35110", out var enabled) && enabled; + /// /// 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 @@ -3161,17 +3164,9 @@ public static InternalIndexBuilder DetachIndex(Index indexToDetach) && targetEntityType.Name == existingTargetType.ClrType.DisplayName()))) { relationship = existingNavigation.ForeignKey.Builder; - if (existingNavigation.ForeignKey.IsOwnership) - { - relationship = relationship.IsOwnership(true, configurationSource) - ?.HasNavigations(inverse, navigation, configurationSource); - - relationship?.Metadata.UpdateConfigurationSource(configurationSource); - return relationship; - } - Check.DebugAssert( - !existingTargetType.IsOwned() + existingNavigation.ForeignKey.IsOwnership + || !existingTargetType.IsOwned() || existingNavigation.DeclaringEntityType.IsInOwnershipPath(existingTargetType) || (existingTargetType.IsInOwnershipPath(existingNavigation.DeclaringEntityType) && existingTargetType.FindOwnership()!.PrincipalEntityType != existingNavigation.DeclaringEntityType), @@ -3181,6 +3176,11 @@ public static InternalIndexBuilder DetachIndex(Index indexToDetach) relationship = relationship.IsOwnership(true, configurationSource) ?.HasNavigations(inverse, navigation, configurationSource); + if (!UseOldBehavior35110) + { + relationship = relationship?.IsRequired(true, configurationSource); + } + relationship?.Metadata.UpdateConfigurationSource(configurationSource); return relationship; } diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index 3c00a36a40b..5ca1a781feb 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -9763,6 +9763,62 @@ public void Update_AK_seed_value_with_a_referencing_foreign_key() v => Assert.Equal(4242, v)); })); + [ConditionalFact] + public void Owned_collection_with_explicit_id() + => Execute( + modelBuilder => + { + }, + source => + { + source.Entity("Microsoft.EntityFrameworkCore.Migrations.Internal.Account", b => + { + b.Property("Id"); + b.HasKey("Id"); + b.ToTable("account"); + }); + + source.Entity("Microsoft.EntityFrameworkCore.Migrations.Internal.Account", b => + { + b.OwnsMany("Microsoft.EntityFrameworkCore.Migrations.Internal.AccountHolder", "AccountHolders", b1 => + { + b1.Property("Id"); + b1.Property("account_id"); + b1.HasKey("Id"); + b1.HasIndex("account_id"); + b1.ToTable("account_holder"); + b1.WithOwner().HasForeignKey("account_id"); + }); + }); + }, + target => + { + target.Entity(builder => + { + builder.ToTable("account"); + builder.HasKey("Id"); + builder.OwnsMany(a => a.AccountHolders, navigationBuilder => + { + navigationBuilder.ToTable("account_holder"); + navigationBuilder.Property("Id"); + navigationBuilder.HasKey("Id"); + navigationBuilder.Property("account_id"); + navigationBuilder.WithOwner().HasForeignKey("account_id"); + }); + }); + }, + Assert.Empty); + + public class Account + { + public string Id { get; set; } + public IEnumerable AccountHolders { get; set; } = []; + } + + public class AccountHolder + { + } + [ConditionalFact] public void SeedData_with_guid_AK_and_multiple_owned_types() => Execute( From 017e8dc53a7d523796386e107e46909a7d245cc6 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Mon, 2 Dec 2024 09:51:53 -0800 Subject: [PATCH 13/16] [release/9.0] Set environment variables to "Development" when creating DbContext using IDesignTimeDbContextFactory (#35230) Fixes #35174 --- .../Internal/AppServiceProviderFactory.cs | 17 ----------------- .../Design/Internal/DbContextOperations.cs | 17 +++++++++++++++++ .../AppServiceProviderFactoryTest.cs | 8 -------- .../Design/Internal/DbContextOperationsTest.cs | 4 ++++ 4 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/EFCore.Design/Design/Internal/AppServiceProviderFactory.cs b/src/EFCore.Design/Design/Internal/AppServiceProviderFactory.cs index 2015337b4e6..b797f8a4add 100644 --- a/src/EFCore.Design/Design/Internal/AppServiceProviderFactory.cs +++ b/src/EFCore.Design/Design/Internal/AppServiceProviderFactory.cs @@ -55,23 +55,6 @@ public virtual IServiceProvider Create(string[] args) return null; } - var aspnetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); - var dotnetEnvironment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT"); - var environment = aspnetCoreEnvironment - ?? dotnetEnvironment - ?? "Development"; - if (aspnetCoreEnvironment == null) - { - Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", environment); - } - - if (dotnetEnvironment == null) - { - Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", environment); - } - - _reporter.WriteVerbose(DesignStrings.UsingEnvironment(environment)); - try { var services = serviceProviderFactory(args); diff --git a/src/EFCore.Design/Design/Internal/DbContextOperations.cs b/src/EFCore.Design/Design/Internal/DbContextOperations.cs index da44b15b283..761579c8bdb 100644 --- a/src/EFCore.Design/Design/Internal/DbContextOperations.cs +++ b/src/EFCore.Design/Design/Internal/DbContextOperations.cs @@ -503,6 +503,23 @@ private IDictionary> FindContextTypes(string? name = null, { _reporter.WriteVerbose(DesignStrings.FindingContexts); + var aspnetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); + var dotnetEnvironment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT"); + var environment = aspnetCoreEnvironment + ?? dotnetEnvironment + ?? "Development"; + if (aspnetCoreEnvironment == null) + { + Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", environment); + } + + if (dotnetEnvironment == null) + { + Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", environment); + } + + _reporter.WriteVerbose(DesignStrings.UsingEnvironment(environment)); + var contexts = new Dictionary?>(); try diff --git a/test/EFCore.AspNet.InMemory.FunctionalTests/AppServiceProviderFactoryTest.cs b/test/EFCore.AspNet.InMemory.FunctionalTests/AppServiceProviderFactoryTest.cs index 84b3f4c2c46..02bf4c65e98 100644 --- a/test/EFCore.AspNet.InMemory.FunctionalTests/AppServiceProviderFactoryTest.cs +++ b/test/EFCore.AspNet.InMemory.FunctionalTests/AppServiceProviderFactoryTest.cs @@ -21,8 +21,6 @@ private static void TestCreateServices(Type programType) var factory = new TestAppServiceProviderFactory( MockAssembly.Create(programType)); - Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", null); - Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", null); var services = factory.Create(["arg1"]); Assert.NotNull(services.GetRequiredService()); @@ -66,8 +64,6 @@ public void Create_with_no_builder_method() [typeof(ProgramWithNoHostBuilder)], new MockMethodInfo(typeof(ProgramWithNoHostBuilder), InjectHostIntoDiagnostics))); - Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", null); - Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", null); var services = factory.Create(["arg1"]); Assert.NotNull(services.GetRequiredService()); @@ -75,8 +71,6 @@ public void Create_with_no_builder_method() private static void InjectHostIntoDiagnostics(object[] args) { - Assert.Equal("Development", Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")); - Assert.Equal("Development", Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")); Assert.Single(args); Assert.Equal((string[])args[0], new[] { "arg1", "--applicationName", "MockAssembly" }); @@ -91,8 +85,6 @@ private class ProgramWithNoHostBuilder; private static void ValidateEnvironmentAndArgs(string[] args) { - Assert.Equal("Development", Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")); - Assert.Equal("Development", Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")); Assert.Equal(args, new[] { "arg1" }); } diff --git a/test/EFCore.Design.Tests/Design/Internal/DbContextOperationsTest.cs b/test/EFCore.Design.Tests/Design/Internal/DbContextOperationsTest.cs index 990345659b5..4120e9ad05f 100644 --- a/test/EFCore.Design.Tests/Design/Internal/DbContextOperationsTest.cs +++ b/test/EFCore.Design.Tests/Design/Internal/DbContextOperationsTest.cs @@ -414,6 +414,8 @@ public TestContext() public TestContext(DbContextOptions options) : base(options) { + Assert.Equal("Development", Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")); + Assert.Equal("Development", Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")); } } @@ -425,6 +427,8 @@ private TestContextFromFactory() public TestContextFromFactory(DbContextOptions options) : base(options) { + Assert.Equal("Development", Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")); + Assert.Equal("Development", Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")); } } From d89ec3f71e41ea68d6cce145a6caa3d6c5a9885c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Cincura=20=E2=86=B9?= Date: Mon, 2 Dec 2024 20:55:02 +0100 Subject: [PATCH 14/16] [release/9.0] Fix query filters with context accessors (#35246) --- .../Internal/ExpressionTreeFuncletizer.cs | 9 ++++- .../Query/AdHocQueryFiltersQueryTestBase.cs | 38 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs index a04ef3b8888..3d70a71491f 100644 --- a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs +++ b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs @@ -106,6 +106,9 @@ public class ExpressionTreeFuncletizer : ExpressionVisitor private static readonly bool UseOldBehavior35152 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35152", out var enabled35152) && enabled35152; + private static readonly bool UseOldBehavior35111 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35111", out var enabled35111) && enabled35111; + private static readonly MethodInfo ReadOnlyCollectionIndexerGetter = typeof(ReadOnlyCollection).GetProperties() .Single(p => p.GetIndexParameters() is { Length: 1 } indexParameters && indexParameters[0].ParameterType == typeof(int)).GetMethod!; @@ -552,7 +555,11 @@ protected override Expression VisitConditional(ConditionalExpression conditional goto case StateType.ContainsEvaluatable; case StateType.ContainsEvaluatable: - // The case where the test is evaluatable has been handled above + if (testState.IsEvaluatable) + { + test = UseOldBehavior35111 ? test : ProcessEvaluatableRoot(test, ref testState); + } + if (ifTrueState.IsEvaluatable) { ifTrue = ProcessEvaluatableRoot(ifTrue, ref ifTrueState); diff --git a/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs index c9cd6ab37f2..6754eb275f3 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs @@ -730,4 +730,42 @@ public class ChildFilter2 } #endregion + + #region 35111 + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Query_filter_with_context_accessor_with_constant(bool async) + { + var contextFactory = await InitializeAsync(); + using var context = contextFactory.CreateContext(); + + var data = async + ? await context.Set().ToListAsync() + : context.Set().ToList(); + } + + protected class Context35111(DbContextOptions options) : DbContext(options) + { + public int Foo { get; set; } + public long? Bar { get; set; } + public List Baz { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasQueryFilter(e => + Foo == 1 + ? Baz.Contains(e.Bar) + : e.Bar == Bar); + } + } + + public class FooBar35111 + { + public long Id { get; set; } + public long Bar { get; set; } + } + + #endregion } From 507152b9574b5e654662c3b033215626632ce524 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Mon, 2 Dec 2024 12:20:56 -0800 Subject: [PATCH 15/16] [release/9.0] Add more specific messages when pending model changes are detected (#35221) Fixes #35133 --- .../Diagnostics/RelationalEventId.cs | 14 ++ .../Diagnostics/RelationalLoggerExtensions.cs | 71 +++++++ .../RelationalLoggingDefinitions.cs | 20 +- .../Migrations/Internal/Migrator.cs | 85 +++++---- .../Properties/RelationalStrings.Designer.cs | 62 ++++++- .../Properties/RelationalStrings.resx | 16 +- src/EFCore/Infrastructure/ModelSource.cs | 23 ++- ...rpMigrationsGeneratorTest.ModelSnapshot.cs | 5 +- .../MigrationsInfrastructureTestBase.cs | 18 +- .../MigrationsInfrastructureSqlServerTest.cs | 175 +++++++++++++++++- 10 files changed, 424 insertions(+), 65 deletions(-) diff --git a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs index c05c73c46a3..0b1f48c5275 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs @@ -81,6 +81,7 @@ private enum Id NonTransactionalMigrationOperationWarning, AcquiringMigrationLock, MigrationsUserTransactionWarning, + ModelSnapshotNotFound, // Query events QueryClientEvaluationWarning = CoreEventId.RelationalBaseId + 500, @@ -778,6 +779,19 @@ private static EventId MakeMigrationsId(Id id) /// public static readonly EventId MigrationsUserTransactionWarning = MakeMigrationsId(Id.MigrationsUserTransactionWarning); + /// + /// Model snapshot was not found. + /// + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + public static readonly EventId ModelSnapshotNotFound = MakeMigrationsId(Id.ModelSnapshotNotFound); + private static readonly string _queryPrefix = DbLoggerCategory.Query.Name + "."; private static EventId MakeQueryId(Id id) diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs index 177b7e90bba..acecc79ca01 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs @@ -2343,6 +2343,77 @@ private static string PendingModelChanges(EventDefinitionBase definition, EventD return d.GenerateMessage(p.ContextType.ShortDisplayName()); } + /// + /// Logs for the event. + /// + /// The diagnostics logger to use. + /// The type being used. + public static void NonDeterministicModel( + this IDiagnosticsLogger diagnostics, + Type contextType) + { + var definition = RelationalResources.LogNonDeterministicModel(diagnostics); + + if (diagnostics.ShouldLog(definition)) + { + definition.Log(diagnostics, contextType.ShortDisplayName()); + } + + if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = new DbContextTypeEventData( + definition, + NonDeterministicModel, + contextType); + + diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); + } + } + + private static string NonDeterministicModel(EventDefinitionBase definition, EventData payload) + { + var d = (EventDefinition)definition; + var p = (DbContextTypeEventData)payload; + return d.GenerateMessage(p.ContextType.ShortDisplayName()); + } + + /// + /// Logs for the event. + /// + /// The diagnostics logger to use. + /// The migrator. + /// The assembly in which migrations are stored. + public static void ModelSnapshotNotFound( + this IDiagnosticsLogger diagnostics, + IMigrator migrator, + IMigrationsAssembly migrationsAssembly) + { + var definition = RelationalResources.LogNoModelSnapshotFound(diagnostics); + + if (diagnostics.ShouldLog(definition)) + { + definition.Log(diagnostics, migrationsAssembly.Assembly.GetName().Name!); + } + + if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = new MigrationAssemblyEventData( + definition, + ModelSnapshotNotFound, + migrator, + migrationsAssembly); + + diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); + } + } + + private static string ModelSnapshotNotFound(EventDefinitionBase definition, EventData payload) + { + var d = (EventDefinition)definition; + var p = (MigrationAssemblyEventData)payload; + return d.GenerateMessage(p.MigrationsAssembly.Assembly.GetName().Name!); + } + /// /// Logs for the event. /// diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs index 762172c8944..9634f027bf7 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs @@ -374,7 +374,7 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public EventDefinitionBase? LogMigrationsUserTransactionWarning; + public EventDefinitionBase? LogMigrationsUserTransaction; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -673,6 +673,24 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions [EntityFrameworkInternal] public EventDefinitionBase? LogPendingModelChanges; + /// + /// 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. + /// + [EntityFrameworkInternal] + public EventDefinitionBase? LogNonDeterministicModel; + + /// + /// 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. + /// + [EntityFrameworkInternal] + public EventDefinitionBase? LogNoModelSnapshotFound; + /// /// 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.Relational/Migrations/Internal/Migrator.cs b/src/EFCore.Relational/Migrations/Internal/Migrator.cs index 4380d89fd82..ea758d2c634 100644 --- a/src/EFCore.Relational/Migrations/Internal/Migrator.cs +++ b/src/EFCore.Relational/Migrations/Internal/Migrator.cs @@ -3,6 +3,7 @@ using System.Transactions; using Microsoft.EntityFrameworkCore.Diagnostics.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Migrations.Internal; @@ -93,24 +94,7 @@ public Migrator( public virtual void Migrate(string? targetMigration) { var useTransaction = _connection.CurrentTransaction is null; - if (!useTransaction - && _executionStrategy.RetriesOnFailure) - { - throw new NotSupportedException(RelationalStrings.TransactionSuppressedMigrationInUserTransaction); - } - - if (RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore - && HasPendingModelChanges()) - { - _logger.PendingModelChangesWarning(_currentContext.Context.GetType()); - } - - if (!useTransaction) - { - _logger.MigrationsUserTransactionWarning(); - } - - _logger.MigrateUsingConnection(this, _connection); + ValidateMigrations(useTransaction); using var transactionScope = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled); @@ -235,24 +219,7 @@ public virtual async Task MigrateAsync( CancellationToken cancellationToken = default) { var useTransaction = _connection.CurrentTransaction is null; - if (!useTransaction - && _executionStrategy.RetriesOnFailure) - { - throw new NotSupportedException(RelationalStrings.TransactionSuppressedMigrationInUserTransaction); - } - - if (RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore - && HasPendingModelChanges()) - { - _logger.PendingModelChangesWarning(_currentContext.Context.GetType()); - } - - if (!useTransaction) - { - _logger.MigrationsUserTransactionWarning(); - } - - _logger.MigrateUsingConnection(this, _connection); + ValidateMigrations(useTransaction); using var transactionScope = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled); @@ -382,6 +349,48 @@ await _migrationCommandExecutor.ExecuteNonQueryAsync( } } + private void ValidateMigrations(bool useTransaction) + { + if (!useTransaction + && _executionStrategy.RetriesOnFailure) + { + throw new NotSupportedException(RelationalStrings.TransactionSuppressedMigrationInUserTransaction); + } + + if (_migrationsAssembly.Migrations.Count == 0) + { + _logger.MigrationsNotFound(this, _migrationsAssembly); + } + else if (_migrationsAssembly.ModelSnapshot == null) + { + _logger.ModelSnapshotNotFound(this, _migrationsAssembly); + } + else if (RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore + && HasPendingModelChanges()) + { + var modelSource = (ModelSource)_currentContext.Context.GetService(); +#pragma warning disable EF1001 // Internal EF Core API usage. + var newDesignTimeModel = modelSource.CreateModel( + _currentContext.Context, _currentContext.Context.GetService(), designTime: true); +#pragma warning restore EF1001 // Internal EF Core API usage. + if (_migrationsModelDiffer.HasDifferences(newDesignTimeModel.GetRelationalModel(), _designTimeModel.Model.GetRelationalModel())) + { + _logger.NonDeterministicModel(_currentContext.Context.GetType()); + } + else + { + _logger.PendingModelChangesWarning(_currentContext.Context.GetType()); + } + } + + if (!useTransaction) + { + _logger.MigrationsUserTransactionWarning(); + } + + _logger.MigrateUsingConnection(this, _connection); + } + private IEnumerable<(string, Func>)> GetMigrationCommandLists(MigratorData parameters) { var migrationsToApply = parameters.AppliedMigrations; @@ -449,10 +458,6 @@ protected virtual void PopulateMigrations( var appliedMigrations = new Dictionary(); var unappliedMigrations = new Dictionary(); var appliedMigrationEntrySet = new HashSet(appliedMigrationEntries, StringComparer.OrdinalIgnoreCase); - if (_migrationsAssembly.Migrations.Count == 0) - { - _logger.MigrationsNotFound(this, _migrationsAssembly); - } foreach (var (key, typeInfo) in _migrationsAssembly.Migrations) { diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 731c62c4ec2..5bbbd42c9d0 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -3429,11 +3429,11 @@ public static EventDefinition LogMigrationAttributeMissingWarning(IDiagn /// public static EventDefinition LogMigrationsUserTransaction(IDiagnosticsLogger logger) { - var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogMigrationsUserTransactionWarning; + var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogMigrationsUserTransaction; if (definition == null) { definition = NonCapturingLazyInitializer.EnsureInitialized( - ref ((RelationalLoggingDefinitions)logger.Definitions).LogMigrationsUserTransactionWarning, + ref ((RelationalLoggingDefinitions)logger.Definitions).LogMigrationsUserTransaction, logger, static logger => new EventDefinition( logger.Options, @@ -3572,7 +3572,7 @@ public static EventDefinition LogNoMigrationsApplied(IDiagnosticsLogger logger) } /// - /// No migrations were found in assembly '{migrationsAssembly}'. + /// No migrations were found in assembly '{migrationsAssembly}'. A migration needs to be added before the database can be updated. /// public static EventDefinition LogNoMigrationsFound(IDiagnosticsLogger logger) { @@ -3585,7 +3585,7 @@ public static EventDefinition LogNoMigrationsFound(IDiagnosticsLogger lo static logger => new EventDefinition( logger.Options, RelationalEventId.MigrationsNotFound, - LogLevel.Debug, + LogLevel.Information, "RelationalEventId.MigrationsNotFound", level => LoggerMessage.Define( level, @@ -3596,6 +3596,56 @@ public static EventDefinition LogNoMigrationsFound(IDiagnosticsLogger lo return (EventDefinition)definition; } + /// + /// Model snapshot was not found in assembly '{migrationsAssembly}'. Skipping pending model changes check. + /// + public static EventDefinition LogNoModelSnapshotFound(IDiagnosticsLogger logger) + { + var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogNoModelSnapshotFound; + if (definition == null) + { + definition = NonCapturingLazyInitializer.EnsureInitialized( + ref ((RelationalLoggingDefinitions)logger.Definitions).LogNoModelSnapshotFound, + logger, + static logger => new EventDefinition( + logger.Options, + RelationalEventId.ModelSnapshotNotFound, + LogLevel.Information, + "RelationalEventId.ModelSnapshotNotFound", + level => LoggerMessage.Define( + level, + RelationalEventId.ModelSnapshotNotFound, + _resourceManager.GetString("LogNoModelSnapshotFound")!))); + } + + return (EventDefinition)definition; + } + + /// + /// The model for context '{contextType}' changes each time it is built. This is usually caused by dynamic values used in a 'HasData' call (e.g. `new DateTime()`, `Guid.NewGuid()`). Add a new migration and examine its contents to locate the cause, and replace the dynamic call with a static, hardcoded value. See https://aka.ms/efcore-docs-pending-changes. + /// + public static EventDefinition LogNonDeterministicModel(IDiagnosticsLogger logger) + { + var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogNonDeterministicModel; + if (definition == null) + { + definition = NonCapturingLazyInitializer.EnsureInitialized( + ref ((RelationalLoggingDefinitions)logger.Definitions).LogNonDeterministicModel, + logger, + static logger => new EventDefinition( + logger.Options, + RelationalEventId.PendingModelChangesWarning, + LogLevel.Error, + "RelationalEventId.PendingModelChangesWarning", + level => LoggerMessage.Define( + level, + RelationalEventId.PendingModelChangesWarning, + _resourceManager.GetString("LogNonDeterministicModel")!))); + } + + return (EventDefinition)definition; + } + /// /// The migration operation '{operation}' from migration '{migration}' cannot be executed in a transaction. If the app is terminated or an unrecoverable error occurs while this operation is being executed then the migration will be left in a partially applied state and would need to be reverted manually before it can be applied again. Create a separate migration that contains just this operation. /// @@ -3610,7 +3660,7 @@ public static EventDefinition LogNonTransactionalMigrationOperat static logger => new EventDefinition( logger.Options, RelationalEventId.NonTransactionalMigrationOperationWarning, - LogLevel.Error, + LogLevel.Warning, "RelationalEventId.NonTransactionalMigrationOperationWarning", level => LoggerMessage.Define( level, @@ -3747,7 +3797,7 @@ public static EventDefinition LogOptionalDependentWithoutIdentifyingProp } /// - /// The model for context '{contextType}' has pending changes. Add a new migration before updating the database. + /// The model for context '{contextType}' has pending changes. Add a new migration before updating the database. See https://aka.ms/efcore-docs-pending-changes. /// public static EventDefinition LogPendingModelChanges(IDiagnosticsLogger logger) { diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 104daf9dafb..2382f648428 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -811,12 +811,20 @@ Information RelationalEventId.MigrationsNotApplied - No migrations were found in assembly '{migrationsAssembly}'. - Debug RelationalEventId.MigrationsNotFound string + No migrations were found in assembly '{migrationsAssembly}'. A migration needs to be added before the database can be updated. + Information RelationalEventId.MigrationsNotFound string + + + Model snapshot was not found in assembly '{migrationsAssembly}'. Skipping pending model changes check. + Information RelationalEventId.ModelSnapshotNotFound string + + + The model for context '{contextType}' changes each time it is built. This is usually caused by dynamic values used in a 'HasData' call (e.g. `new DateTime()`, `Guid.NewGuid()`). Add a new migration and examine its contents to locate the cause, and replace the dynamic call with a static, hardcoded value. See https://aka.ms/efcore-docs-pending-changes. + Error RelationalEventId.PendingModelChangesWarning string The migration operation '{operation}' from migration '{migration}' cannot be executed in a transaction. If the app is terminated or an unrecoverable error occurs while this operation is being executed then the migration will be left in a partially applied state and would need to be reverted manually before it can be applied again. Create a separate migration that contains just this operation. - Error RelationalEventId.NonTransactionalMigrationOperationWarning string string + Warning RelationalEventId.NonTransactionalMigrationOperationWarning string string Opened connection to database '{database}' on server '{server}'. @@ -839,7 +847,7 @@ Warning RelationalEventId.OptionalDependentWithoutIdentifyingPropertyWarning string - The model for context '{contextType}' has pending changes. Add a new migration before updating the database. + The model for context '{contextType}' has pending changes. Add a new migration before updating the database. See https://aka.ms/efcore-docs-pending-changes. Error RelationalEventId.PendingModelChangesWarning string diff --git a/src/EFCore/Infrastructure/ModelSource.cs b/src/EFCore/Infrastructure/ModelSource.cs index d1113d8bd27..e69a0e42e3a 100644 --- a/src/EFCore/Infrastructure/ModelSource.cs +++ b/src/EFCore/Infrastructure/ModelSource.cs @@ -65,11 +65,7 @@ public virtual IModel GetModel( { if (!cache.TryGetValue(cacheKey, out model)) { - model = CreateModel( - context, modelCreationDependencies.ConventionSetBuilder, modelCreationDependencies.ModelDependencies); - - var designTimeModel = modelCreationDependencies.ModelRuntimeInitializer.Initialize( - model, designTime: true, modelCreationDependencies.ValidationLogger); + var designTimeModel = CreateModel(context, modelCreationDependencies, designTime: true); var runtimeModel = (IModel)designTimeModel.FindRuntimeAnnotationValue(CoreAnnotationNames.ReadOnlyModel)!; @@ -88,6 +84,23 @@ public virtual IModel GetModel( return model!; } + /// + /// 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. + /// + [EntityFrameworkInternal] + public virtual IModel CreateModel( + DbContext context, + ModelCreationDependencies modelCreationDependencies, + bool designTime) + { + var model = CreateModel(context, modelCreationDependencies.ConventionSetBuilder, modelCreationDependencies.ModelDependencies); + return modelCreationDependencies.ModelRuntimeInitializer.Initialize( + model, designTime, modelCreationDependencies.ValidationLogger); + } + /// /// Creates the model. This method is called when the model was not found in the cache. /// diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs index d9216f9dfe5..ce667239197 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs @@ -8570,10 +8570,7 @@ protected CSharpMigrationsGenerator CreateMigrationsGenerator() var sqlServerTypeMappingSource = new SqlServerTypeMappingSource( TestServiceFactory.Instance.Create(), new RelationalTypeMappingSourceDependencies( - new IRelationalTypeMappingSourcePlugin[] - { - new SqlServerNetTopologySuiteTypeMappingSourcePlugin(NtsGeometryServices.Instance) - })); + [new SqlServerNetTopologySuiteTypeMappingSourcePlugin(NtsGeometryServices.Instance)])); var codeHelper = new CSharpHelper(sqlServerTypeMappingSource); diff --git a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs index 25844386efa..a00f1c67989 100644 --- a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs @@ -154,8 +154,8 @@ public virtual void Can_apply_one_migration() x => Assert.Equal("00000000000001_Migration1", x.MigrationId)); Assert.Equal( - LogLevel.Error, - Fixture.TestSqlLoggerFactory.Log.Single(l => l.Id == RelationalEventId.PendingModelChangesWarning).Level); + LogLevel.Information, + Fixture.TestSqlLoggerFactory.Log.Single(l => l.Id == RelationalEventId.ModelSnapshotNotFound).Level); } [ConditionalFact] @@ -291,6 +291,10 @@ public virtual async Task Can_generate_no_migration_script() using var db = Fixture.CreateEmptyContext(); var migrator = db.GetService(); + await db.Database.EnsureDeletedAsync(); + await GiveMeSomeTimeAsync(db); + await db.GetService().CreateAsync(); + await SetAndExecuteSqlAsync(migrator.GenerateScript()); } @@ -300,6 +304,10 @@ public virtual async Task Can_generate_migration_from_initial_database_to_initia using var db = Fixture.CreateContext(); var migrator = db.GetService(); + await db.Database.EnsureDeletedAsync(); + await GiveMeSomeTimeAsync(db); + await db.GetService().CreateAsync(); + await SetAndExecuteSqlAsync(migrator.GenerateScript(fromMigration: Migration.InitialDatabase, toMigration: Migration.InitialDatabase)); } @@ -310,6 +318,7 @@ public virtual async Task Can_generate_up_and_down_scripts() var migrator = db.GetService(); await db.Database.EnsureDeletedAsync(); + await GiveMeSomeTimeAsync(db); await db.GetService().CreateAsync(); await SetAndExecuteSqlAsync(migrator.GenerateScript()); @@ -327,6 +336,7 @@ public virtual async Task Can_generate_up_and_down_scripts_noTransactions() var migrator = db.GetService(); await db.Database.EnsureDeletedAsync(); + await GiveMeSomeTimeAsync(db); await db.GetService().CreateAsync(); await SetAndExecuteSqlAsync(migrator.GenerateScript(options: MigrationsSqlGenerationOptions.NoTransactions)); @@ -345,6 +355,7 @@ public virtual async Task Can_generate_one_up_and_down_script() var migrator = db.GetService(); await db.Database.EnsureDeletedAsync(); + await GiveMeSomeTimeAsync(db); await db.GetService().CreateAsync(); await ExecuteSqlAsync(migrator.GenerateScript( @@ -367,6 +378,7 @@ public virtual async Task Can_generate_up_and_down_script_using_names() var migrator = db.GetService(); await db.Database.EnsureDeletedAsync(); + await GiveMeSomeTimeAsync(db); await db.GetService().CreateAsync(); await ExecuteSqlAsync(migrator.GenerateScript( @@ -389,6 +401,7 @@ public virtual async Task Can_generate_idempotent_up_and_down_scripts() var migrator = db.GetService(); await db.Database.EnsureDeletedAsync(); + await GiveMeSomeTimeAsync(db); await db.GetService().CreateAsync(); await SetAndExecuteSqlAsync(migrator.GenerateScript( @@ -409,6 +422,7 @@ public virtual async Task Can_generate_idempotent_up_and_down_scripts_noTransact var migrator = db.GetService(); await db.Database.EnsureDeletedAsync(); + await GiveMeSomeTimeAsync(db); await db.GetService().CreateAsync(); await SetAndExecuteSqlAsync(migrator.GenerateScript( diff --git a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsInfrastructureSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsInfrastructureSqlServerTest.cs index c4b8eb03ac8..491a5e86ab6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsInfrastructureSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsInfrastructureSqlServerTest.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; using Microsoft.EntityFrameworkCore.TestModels.AspNetIdentity; +using static Microsoft.EntityFrameworkCore.Migrations.MigrationsInfrastructureFixtureBase; // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.Migrations @@ -650,12 +651,136 @@ public override void Can_get_active_provider() Assert.Equal("Microsoft.EntityFrameworkCore.SqlServer", ActiveProvider); } + [ConditionalFact] + public void Throws_when_no_migrations() + { + using var context = new DbContext( + Fixture.TestStore.AddProviderOptions( + new DbContextOptionsBuilder().EnableServiceProviderCaching(false) + .ConfigureWarnings(e => e.Throw(RelationalEventId.MigrationsNotFound))).Options); + + context.Database.EnsureDeleted(); + GiveMeSomeTime(context); + + Assert.Equal( + CoreStrings.WarningAsErrorTemplate( + RelationalEventId.MigrationsNotFound.ToString(), + RelationalResources.LogNoMigrationsFound(new TestLogger()) + .GenerateMessage(typeof(DbContext).Assembly.GetName().Name), + "RelationalEventId.MigrationsNotFound"), + (Assert.Throws(context.Database.Migrate)).Message); + } + + [ConditionalFact] + public async Task Throws_when_no_migrations_async() + { + using var context = new DbContext( + Fixture.TestStore.AddProviderOptions( + new DbContextOptionsBuilder().EnableServiceProviderCaching(false) + .ConfigureWarnings(e => e.Throw(RelationalEventId.MigrationsNotFound))).Options); + + await context.Database.EnsureDeletedAsync(); + await GiveMeSomeTimeAsync(context); + + Assert.Equal( + CoreStrings.WarningAsErrorTemplate( + RelationalEventId.MigrationsNotFound.ToString(), + RelationalResources.LogNoMigrationsFound(new TestLogger()) + .GenerateMessage(typeof(DbContext).Assembly.GetName().Name), + "RelationalEventId.MigrationsNotFound"), + (await Assert.ThrowsAsync(() => context.Database.MigrateAsync())).Message); + } + + [ConditionalFact] + public void Throws_when_no_snapshot() + { + using var context = new MigrationsContext( + Fixture.TestStore.AddProviderOptions( + new DbContextOptionsBuilder().EnableServiceProviderCaching(false) + .ConfigureWarnings(e => e.Throw(RelationalEventId.ModelSnapshotNotFound))).Options); + + context.Database.EnsureDeleted(); + GiveMeSomeTime(context); + + Assert.Equal( + CoreStrings.WarningAsErrorTemplate( + RelationalEventId.ModelSnapshotNotFound.ToString(), + RelationalResources.LogNoModelSnapshotFound(new TestLogger()) + .GenerateMessage(typeof(MigrationsContext).Assembly.GetName().Name), + "RelationalEventId.ModelSnapshotNotFound"), + (Assert.Throws(context.Database.Migrate)).Message); + } + + [ConditionalFact] + public async Task Throws_when_no_snapshot_async() + { + using var context = new MigrationsContext( + Fixture.TestStore.AddProviderOptions( + new DbContextOptionsBuilder().EnableServiceProviderCaching(false) + .ConfigureWarnings(e => e.Throw(RelationalEventId.ModelSnapshotNotFound))).Options); + + await context.Database.EnsureDeletedAsync(); + await GiveMeSomeTimeAsync(context); + + Assert.Equal( + CoreStrings.WarningAsErrorTemplate( + RelationalEventId.ModelSnapshotNotFound.ToString(), + RelationalResources.LogNoModelSnapshotFound(new TestLogger()) + .GenerateMessage(typeof(MigrationsContext).Assembly.GetName().Name), + "RelationalEventId.ModelSnapshotNotFound"), + (await Assert.ThrowsAsync(() => context.Database.MigrateAsync())).Message); + } + + [ConditionalFact] + public void Throws_for_nondeterministic_HasData() + { + using var context = new BloggingContext( + Fixture.TestStore.AddProviderOptions( + new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options, + randomData: true); + + context.Database.EnsureDeleted(); + GiveMeSomeTime(context); + + Assert.Equal( + CoreStrings.WarningAsErrorTemplate( + RelationalEventId.PendingModelChangesWarning.ToString(), + RelationalResources.LogNonDeterministicModel(new TestLogger()) + .GenerateMessage(nameof(BloggingContext)), + "RelationalEventId.PendingModelChangesWarning"), + (Assert.Throws(context.Database.Migrate)).Message); + } + + [ConditionalFact] + public async Task Throws_for_nondeterministic_HasData_async() + { + using var context = new BloggingContext( + Fixture.TestStore.AddProviderOptions( + new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options, + randomData: true); + + await context.Database.EnsureDeletedAsync(); + await GiveMeSomeTimeAsync(context); + + Assert.Equal( + CoreStrings.WarningAsErrorTemplate( + RelationalEventId.PendingModelChangesWarning.ToString(), + RelationalResources.LogNonDeterministicModel(new TestLogger()) + .GenerateMessage(nameof(BloggingContext)), + "RelationalEventId.PendingModelChangesWarning"), + (await Assert.ThrowsAsync(() => context.Database.MigrateAsync())).Message); + } + [ConditionalFact] public void Throws_for_pending_model_changes() { using var context = new BloggingContext( Fixture.TestStore.AddProviderOptions( - new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options); + new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options, + randomData: false); + + context.Database.EnsureDeleted(); + GiveMeSomeTime(context); Assert.Equal( CoreStrings.WarningAsErrorTemplate( @@ -671,7 +796,11 @@ public async Task Throws_for_pending_model_changes_async() { using var context = new BloggingContext( Fixture.TestStore.AddProviderOptions( - new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options); + new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options, + randomData: false); + + await context.Database.EnsureDeletedAsync(); + await GiveMeSomeTimeAsync(context); Assert.Equal( CoreStrings.WarningAsErrorTemplate( @@ -925,7 +1054,7 @@ SELECT @result ignoreLineEndingDifferences: true); } - private class BloggingContext(DbContextOptions options) : DbContext(options) + private class BloggingContext(DbContextOptions options, bool? randomData = null) : DbContext(options) { // ReSharper disable once UnusedMember.Local public DbSet Blogs { get; set; } @@ -939,6 +1068,46 @@ public class Blog public string Name { get; set; } // ReSharper restore UnusedMember.Local } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + if (randomData != null) + { + modelBuilder.Entity().HasData( + new Blog { Id = randomData.Value ? (int)new Random().NextInt64(int.MaxValue) : 1, Name = "HalfADonkey" }); + } + } + } + + [DbContext(typeof(BloggingContext))] + partial class BloggingContextSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.MigrationsInfrastructureSqlServerTest+BloggingContext+Blog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Blogs"); + }); +#pragma warning restore 612, 618 + } } [DbContext(typeof(BloggingContext))] From 6489581a192778ab4846291248f062f2b7ebd8be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Cincura=20=E2=86=B9?= Date: Mon, 2 Dec 2024 21:36:40 +0100 Subject: [PATCH 16/16] [release/9.0] Fix Contains on ImmutableArray (#35251) --- ...yableMethodNormalizingExpressionVisitor.cs | 9 +++++- src/EFCore/Query/QueryRootProcessor.cs | 19 ++++++++++++- .../PrimitiveCollectionsQueryCosmosTest.cs | 24 ++++++++++++++++ .../PrimitiveCollectionsQueryTestBase.cs | 16 +++++++++++ ...imitiveCollectionsQueryOldSqlServerTest.cs | 18 ++++++++++++ ...imitiveCollectionsQuerySqlServer160Test.cs | 28 +++++++++++++++++++ ...veCollectionsQuerySqlServerJsonTypeTest.cs | 28 +++++++++++++++++++ .../PrimitiveCollectionsQuerySqlServerTest.cs | 28 +++++++++++++++++++ .../PrimitiveCollectionsQuerySqliteTest.cs | 28 +++++++++++++++++++ 9 files changed, 196 insertions(+), 2 deletions(-) diff --git a/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs b/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs index ad9d12fc3ae..7a43eab9d98 100644 --- a/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs @@ -20,6 +20,9 @@ public class QueryableMethodNormalizingExpressionVisitor : ExpressionVisitor private readonly SelectManyVerifyingExpressionVisitor _selectManyVerifyingExpressionVisitor = new(); private readonly GroupJoinConvertingExpressionVisitor _groupJoinConvertingExpressionVisitor = new(); + private static readonly bool UseOldBehavior35102 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35102", out var enabled35102) && enabled35102; + /// /// 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 @@ -489,12 +492,16 @@ private Expression TryConvertCollectionContainsToQueryableContains(MethodCallExp var sourceType = methodCallExpression.Method.DeclaringType!.GetGenericArguments()[0]; + var objectExpression = methodCallExpression.Object!.Type.IsValueType && !UseOldBehavior35102 + ? Expression.Convert(methodCallExpression.Object!, typeof(IEnumerable<>).MakeGenericType(sourceType)) + : methodCallExpression.Object!; + return VisitMethodCall( Expression.Call( QueryableMethods.Contains.MakeGenericMethod(sourceType), Expression.Call( QueryableMethods.AsQueryable.MakeGenericMethod(sourceType), - methodCallExpression.Object!), + objectExpression), methodCallExpression.Arguments[0])); } diff --git a/src/EFCore/Query/QueryRootProcessor.cs b/src/EFCore/Query/QueryRootProcessor.cs index ec87a57f35d..79b6597476c 100644 --- a/src/EFCore/Query/QueryRootProcessor.cs +++ b/src/EFCore/Query/QueryRootProcessor.cs @@ -13,6 +13,9 @@ public class QueryRootProcessor : ExpressionVisitor { private readonly QueryCompilationContext _queryCompilationContext; + private static readonly bool UseOldBehavior35102 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35102", out var enabled35102) && enabled35102; + /// /// Creates a new instance of the class with associated query provider. /// @@ -85,7 +88,21 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp private Expression VisitQueryRootCandidate(Expression expression, Type elementClrType) { - switch (expression) + var candidateExpression = expression; + + if (!UseOldBehavior35102) + { + // In case the collection was value type, in order to call methods like AsQueryable, + // we need to convert it to IEnumerable which requires boxing. + // We do that with Convert expression which we need to unwrap here. + if (expression is UnaryExpression { NodeType: ExpressionType.Convert } convertExpression + && convertExpression.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + candidateExpression = convertExpression.Operand; + } + } + + switch (candidateExpression) { // An array containing only constants is represented as a ConstantExpression with the array as the value. // Convert that into a NewArrayExpression for use with InlineQueryRootExpression diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs index 78dd12d771c..ea203a234b2 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs @@ -613,6 +613,30 @@ WHERE ARRAY_CONTAINS(@__ints_0, c["Int"]) """ @__ints_0='[10,999]' +SELECT VALUE c +FROM root c +WHERE NOT(ARRAY_CONTAINS(@__ints_0, c["Int"])) +"""); + }); + + public override Task Parameter_collection_ImmutableArray_of_ints_Contains_int(bool async) + => CosmosTestHelpers.Instance.NoSyncTest( + async, async a => + { + await base.Parameter_collection_ImmutableArray_of_ints_Contains_int(a); + + AssertSql( + """ +@__ints_0='[10,999]' + +SELECT VALUE c +FROM root c +WHERE ARRAY_CONTAINS(@__ints_0, c["Int"]) +""", + // + """ +@__ints_0='[10,999]' + SELECT VALUE c FROM root c WHERE NOT(ARRAY_CONTAINS(@__ints_0, c["Int"])) diff --git a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs index 2304ac3db4c..60e7cd24697 100644 --- a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Immutable; + namespace Microsoft.EntityFrameworkCore.Query; public abstract class PrimitiveCollectionsQueryTestBase(TFixture fixture) : QueryTestBase(fixture) @@ -363,6 +365,20 @@ await AssertQuery( ss => ss.Set().Where(c => !ints.Contains(c.Int))); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Parameter_collection_ImmutableArray_of_ints_Contains_int(bool async) + { + var ints = ImmutableArray.Create([10, 999]); + + await AssertQuery( + async, + ss => ss.Set().Where(c => ints.Contains(c.Int))); + await AssertQuery( + async, + ss => ss.Set().Where(c => !ints.Contains(c.Int))); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual async Task Parameter_collection_of_ints_Contains_nullable_int(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs index d23ea73a1ce..1041c1e10c1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs @@ -498,6 +498,24 @@ WHERE [p].[Int] NOT IN (10, 999) """); } + public override async Task Parameter_collection_ImmutableArray_of_ints_Contains_int(bool async) + { + await base.Parameter_collection_ImmutableArray_of_ints_Contains_int(async); + + AssertSql( + """ +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +""", + // + """ +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] NOT IN (10, 999) +"""); + } + public override async Task Parameter_collection_of_ints_Contains_nullable_int(bool async) { await base.Parameter_collection_of_ints_Contains_nullable_int(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs index a42b4ac42f7..9aea6357ba3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs @@ -516,6 +516,34 @@ FROM OPENJSON(@__ints_0) WITH ([value] int '$') AS [i] """ @__ints_0='[10,999]' (Size = 4000) +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] NOT IN ( + SELECT [i].[value] + FROM OPENJSON(@__ints_0) WITH ([value] int '$') AS [i] +) +"""); + } + + public override async Task Parameter_collection_ImmutableArray_of_ints_Contains_int(bool async) + { + await base.Parameter_collection_ImmutableArray_of_ints_Contains_int(async); + + AssertSql( + """ +@__ints_0='[10,999]' (Nullable = false) (Size = 4000) + +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN ( + SELECT [i].[value] + FROM OPENJSON(@__ints_0) WITH ([value] int '$') AS [i] +) +""", + // + """ +@__ints_0='[10,999]' (Nullable = false) (Size = 4000) + SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings] FROM [PrimitiveCollectionsEntity] AS [p] WHERE [p].[Int] NOT IN ( diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs index d382ca0642d..29656d04fe6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs @@ -532,6 +532,34 @@ FROM OPENJSON(@__ints_0) WITH ([value] int '$') AS [i] """ @__ints_0='[10,999]' (Size = 4000) +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] NOT IN ( + SELECT [i].[value] + FROM OPENJSON(@__ints_0) WITH ([value] int '$') AS [i] +) +"""); + } + + public override async Task Parameter_collection_ImmutableArray_of_ints_Contains_int(bool async) + { + await base.Parameter_collection_ImmutableArray_of_ints_Contains_int(async); + + AssertSql( + """ +@__ints_0='[10,999]' (Nullable = false) (Size = 4000) + +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN ( + SELECT [i].[value] + FROM OPENJSON(@__ints_0) WITH ([value] int '$') AS [i] +) +""", + // + """ +@__ints_0='[10,999]' (Nullable = false) (Size = 4000) + SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings] FROM [PrimitiveCollectionsEntity] AS [p] WHERE [p].[Int] NOT IN ( diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs index de05c4c3797..0708539aaa5 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs @@ -539,6 +539,34 @@ FROM OPENJSON(@__ints_0) WITH ([value] int '$') AS [i] """ @__ints_0='[10,999]' (Size = 4000) +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] NOT IN ( + SELECT [i].[value] + FROM OPENJSON(@__ints_0) WITH ([value] int '$') AS [i] +) +"""); + } + + public override async Task Parameter_collection_ImmutableArray_of_ints_Contains_int(bool async) + { + await base.Parameter_collection_ImmutableArray_of_ints_Contains_int(async); + + AssertSql( + """ +@__ints_0='[10,999]' (Nullable = false) (Size = 4000) + +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN ( + SELECT [i].[value] + FROM OPENJSON(@__ints_0) WITH ([value] int '$') AS [i] +) +""", + // + """ +@__ints_0='[10,999]' (Nullable = false) (Size = 4000) + SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings] FROM [PrimitiveCollectionsEntity] AS [p] WHERE [p].[Int] NOT IN ( diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs index ce27814d118..58b6bb73a75 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs @@ -529,6 +529,34 @@ FROM json_each(@__ints_0) AS "i" """ @__ints_0='[10,999]' (Size = 8) +SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."NullableString", "p"."NullableStrings", "p"."String", "p"."Strings" +FROM "PrimitiveCollectionsEntity" AS "p" +WHERE "p"."Int" NOT IN ( + SELECT "i"."value" + FROM json_each(@__ints_0) AS "i" +) +"""); + } + + public override async Task Parameter_collection_ImmutableArray_of_ints_Contains_int(bool async) + { + await base.Parameter_collection_ImmutableArray_of_ints_Contains_int(async); + + AssertSql( + """ +@__ints_0='[10,999]' (Nullable = false) (Size = 8) + +SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."NullableString", "p"."NullableStrings", "p"."String", "p"."Strings" +FROM "PrimitiveCollectionsEntity" AS "p" +WHERE "p"."Int" IN ( + SELECT "i"."value" + FROM json_each(@__ints_0) AS "i" +) +""", + // + """ +@__ints_0='[10,999]' (Nullable = false) (Size = 8) + SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."NullableString", "p"."NullableStrings", "p"."String", "p"."Strings" FROM "PrimitiveCollectionsEntity" AS "p" WHERE "p"."Int" NOT IN (