From a511eaef270c0ef4ffebbf00957bb12428e2ef4a Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Tue, 16 Aug 2022 14:56:15 -0700 Subject: [PATCH] ExecuteUpdate: Convert to join for query with unsupported operations (#28741) Resolves #28661 Also source fix for #28738 --- .../Query/QuerySqlGenerator.cs | 3 +- ...yableMethodTranslatingExpressionVisitor.cs | 195 +++++--- ...ranslatingExpressionVisitorDependencies.cs | 9 +- .../NorthwindBulkUpdatesTestBase.cs | 362 ++++++++++++-- .../NorthwindBulkUpdatesSqlServerTest.cs | 445 +++++++++++++++-- .../NorthwindBulkUpdatesSqliteTest.cs | 457 +++++++++++++++--- 6 files changed, 1271 insertions(+), 200 deletions(-) diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs index b25242c4926..f32420c4660 100644 --- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs @@ -1237,7 +1237,8 @@ protected override Expression VisitUpdate(UpdateExpression updateExpression) && selectExpression.Having == null && selectExpression.Orderings.Count == 0 && selectExpression.GroupBy.Count == 0 - && selectExpression.Projection.Count == 0) + && selectExpression.Projection.Count == 0 + && selectExpression.Tables.All(e => !(e is LeftJoinExpression || e is OuterApplyExpression))) { _relationalCommandBuilder.Append("UPDATE "); Visit(updateExpression.Table); diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index be972081c08..918bc3b0cc5 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Infrastructure; namespace Microsoft.EntityFrameworkCore.Query; @@ -1127,12 +1128,12 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp } EntityShaperExpression? entityShaperExpression = null; - var setColumnValues = new List(); - foreach (var (propertyExpression, valueExpression) in propertyValueLambdaExpressions) + var remappedUnwrappeLeftExpressions = new List(); + foreach (var (propertyExpression, _) in propertyValueLambdaExpressions) { var left = RemapLambdaBody(source, propertyExpression); left = left.UnwrapTypeConversion(out _); - if (!IsValidPropertyAccess(left, out var ese)) + if (!IsValidPropertyAccess(RelationalDependencies.Model, left, out var ese)) { AddTranslationErrorDetails(RelationalStrings.InvalidPropertyInSetProperty(propertyExpression.Print())); return null; @@ -1148,28 +1149,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp entityShaperExpression.EntityType.DisplayName(), ese.EntityType.DisplayName())); return null; } - - var right = RemapLambdaBody(source, valueExpression); - if (right.Type != left.Type) - { - right = Expression.Convert(right, left.Type); - } - // We generate equality between property = value while translating sothat value infer tye type mapping from property correctly. - // Later we decompose it back into left/right components so that the equality is not in the tree which can get affected by - // null semantics or other visitor. - var setter = Infrastructure.ExpressionExtensions.CreateEqualsExpression(left, right); - var translation = _sqlTranslator.Translate(setter); - if (translation is SqlBinaryExpression { OperatorType: ExpressionType.Equal, Left: ColumnExpression column } sqlBinaryExpression) - { - setColumnValues.Add(new SetColumnValue(column, sqlBinaryExpression.Right)); - } - else - { - // We would reach here only if the property is unmapped or value fails to translate. - AddTranslationErrorDetails(RelationalStrings.UnableToTranslateSetProperty( - propertyExpression.Print(), valueExpression.Print(), _sqlTranslator.TranslationErrorDetails)); - return null; - } + remappedUnwrappeLeftExpressions.Add(left); } Check.DebugAssert(entityShaperExpression != null, "EntityShaperExpression should have a value."); @@ -1203,10 +1183,8 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp var selectExpression = (SelectExpression)source.QueryExpression; if (IsValidSelectExpressionForExecuteUpdate(selectExpression, entityShaperExpression, out var tableExpression)) { - selectExpression.ReplaceProjection(new List()); - selectExpression.ApplyProjection(); - - return new NonQueryExpression(new UpdateExpression(tableExpression, selectExpression, setColumnValues)); + return TranslateSetPropertyExpressions(this, source, selectExpression, tableExpression, + propertyValueLambdaExpressions, remappedUnwrappeLeftExpressions); } // We need to convert to join with original query using PK @@ -1220,31 +1198,98 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp return null; } - //var clrType = entityType.ClrType; - //var entityParameter = Expression.Parameter(clrType); - //Expression predicateBody; - //if (pk.Properties.Count == 1) - //{ - // predicateBody = Expression.Call( - // QueryableMethods.Contains.MakeGenericMethod(clrType), source, entityParameter); - //} - //else - //{ - // var innerParameter = Expression.Parameter(clrType); - // predicateBody = Expression.Call( - // QueryableMethods.AnyWithPredicate.MakeGenericMethod(clrType), - // source, - // Expression.Quote(Expression.Lambda(Expression.Equal(innerParameter, entityParameter), innerParameter))); - //} - - //var newSource = Expression.Call( - // QueryableMethods.Where.MakeGenericMethod(clrType), - // new EntityQueryRootExpression(entityType), - // Expression.Quote(Expression.Lambda(predicateBody, entityParameter))); - - //return TranslateExecuteDelete((ShapedQueryExpression)Visit(newSource)); + var outer = (ShapedQueryExpression)Visit(new EntityQueryRootExpression(entityType)); + var inner = source; + var outerParameter = Expression.Parameter(entityType.ClrType); + var outerKeySelector = Expression.Lambda(outerParameter.CreateKeyValuesExpression(pk.Properties), outerParameter); + var firstPropertyLambdaExpression = propertyValueLambdaExpressions[0].Item1; + var entitySource = GetEntitySource(RelationalDependencies.Model, firstPropertyLambdaExpression.Body); + var innerKeySelector = Expression.Lambda( + entitySource.CreateKeyValuesExpression(pk.Properties), firstPropertyLambdaExpression.Parameters); - return null; + var joinPredicate = CreateJoinPredicate(outer, outerKeySelector, inner, innerKeySelector); + + Check.DebugAssert(joinPredicate != null, "Join predicate shouldn't be null"); + + var outerSelectExpression = (SelectExpression)outer.QueryExpression; + var outerShaperExpression = outerSelectExpression.AddInnerJoin(inner, joinPredicate, outer.ShaperExpression); + outer = outer.UpdateShaperExpression(outerShaperExpression); + var transparentIdentifierType = outer.ShaperExpression.Type; + var transparentIdentifierParameter = Expression.Parameter(transparentIdentifierType); + + var propertyReplacement = AccessField(transparentIdentifierType, transparentIdentifierParameter, "Outer"); + var valueReplacement = AccessField(transparentIdentifierType, transparentIdentifierParameter, "Inner"); + for (var i = 0; i < propertyValueLambdaExpressions.Count; i++) + { + var (propertyExpression, valueExpression) = propertyValueLambdaExpressions[i]; + propertyExpression = Expression.Lambda( + ReplacingExpressionVisitor.Replace( + ReplacingExpressionVisitor.Replace( + firstPropertyLambdaExpression.Parameters[0], + propertyExpression.Parameters[0], + entitySource), + propertyReplacement, propertyExpression.Body), + transparentIdentifierParameter); + valueExpression = Expression.Lambda( + ReplacingExpressionVisitor.Replace(valueExpression.Parameters[0], valueReplacement, valueExpression.Body), + transparentIdentifierParameter); + propertyValueLambdaExpressions[i] = (propertyExpression, valueExpression); + } + + tableExpression = (TableExpression)outerSelectExpression.Tables[0]; + + return TranslateSetPropertyExpressions(this, outer, outerSelectExpression, tableExpression, propertyValueLambdaExpressions, null); + + static NonQueryExpression? TranslateSetPropertyExpressions( + RelationalQueryableMethodTranslatingExpressionVisitor visitor, + ShapedQueryExpression source, + SelectExpression selectExpression, + TableExpression tableExpression, + List<(LambdaExpression, LambdaExpression)> propertyValueLambdaExpressions, + List? leftExpressions) + { + var setColumnValues = new List(); + for (var i = 0; i < propertyValueLambdaExpressions.Count; i++) + { + var (propertyExpression, valueExpression) = propertyValueLambdaExpressions[i]; + Expression left; + if (leftExpressions != null) + { + left = leftExpressions[i]; + } + else + { + left = visitor.RemapLambdaBody(source, propertyExpression); + left = left.UnwrapTypeConversion(out _); + } + var right = visitor.RemapLambdaBody(source, valueExpression); + if (right.Type != left.Type) + { + right = Expression.Convert(right, left.Type); + } + // We generate equality between property = value while translating so that we infer the type mapping from property correctly. + // Later we decompose it back into left/right components so that the equality is not in the tree which can get affected by + // null semantics or other visitor. + var setter = Infrastructure.ExpressionExtensions.CreateEqualsExpression(left, right); + var translation = visitor._sqlTranslator.Translate(setter); + if (translation is SqlBinaryExpression { OperatorType: ExpressionType.Equal, Left: ColumnExpression column } sqlBinaryExpression) + { + setColumnValues.Add(new SetColumnValue(column, sqlBinaryExpression.Right)); + } + else + { + // We would reach here only if the property is unmapped or value fails to translate. + visitor.AddTranslationErrorDetails(RelationalStrings.UnableToTranslateSetProperty( + propertyExpression.Print(), valueExpression.Print(), visitor._sqlTranslator.TranslationErrorDetails)); + return null; + } + } + + selectExpression.ReplaceProjection(new List()); + selectExpression.ApplyProjection(); + + return new NonQueryExpression(new UpdateExpression(tableExpression, selectExpression, setColumnValues)); + } void PopulateSetPropertyStatements( Expression expression, List<(LambdaExpression, LambdaExpression)> list, ParameterExpression parameter) @@ -1273,7 +1318,8 @@ when methodCallExpression.Method.IsGenericMethod } } - static bool IsValidPropertyAccess(Expression expression, [NotNullWhen(true)] out EntityShaperExpression? entityShaperExpression) + static bool IsValidPropertyAccess( + IModel model, Expression expression, [NotNullWhen(true)] out EntityShaperExpression? entityShaperExpression) { if (expression is MemberExpression { Expression: EntityShaperExpression ese }) { @@ -1281,17 +1327,45 @@ static bool IsValidPropertyAccess(Expression expression, [NotNullWhen(true)] out return true; } - if (expression is MethodCallExpression mce - && mce.TryGetEFPropertyArguments(out var source, out _) - && source is EntityShaperExpression ese1) + if (expression is MethodCallExpression mce) { - entityShaperExpression = ese1; - return true; + if (mce.TryGetEFPropertyArguments(out var source, out _) + && source is EntityShaperExpression ese1) + { + entityShaperExpression = ese1; + return true; + } + + if (mce.TryGetIndexerArguments(model, out var source2, out _) + && source2 is EntityShaperExpression ese2) + { + entityShaperExpression = ese2; + return true; + } } entityShaperExpression = null; return false; } + + static Expression GetEntitySource(IModel model, Expression propertyAccessExpression) + { + propertyAccessExpression = propertyAccessExpression.UnwrapTypeConversion(out _); + if (propertyAccessExpression is MethodCallExpression mce) + { + if (mce.TryGetEFPropertyArguments(out var source, out _)) + { + return source; + } + + if (mce.TryGetIndexerArguments(model, out var source2, out _)) + { + return source2; + } + } + + return ((MemberExpression)propertyAccessExpression).Expression!; + } } /// @@ -1364,7 +1438,8 @@ protected virtual bool IsValidSelectExpressionForExecuteUpdate( && (!selectExpression.IsDistinct || entityShaperExpression.EntityType.FindPrimaryKey() != null) && selectExpression.GroupBy.Count == 0 && selectExpression.Having == null - && selectExpression.Orderings.Count == 0) + && selectExpression.Orderings.Count == 0 + && selectExpression.Tables.All(e => !(e is LeftJoinExpression || e is OuterApplyExpression))) { TableExpressionBase table; if (selectExpression.Tables.Count == 1) diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitorDependencies.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitorDependencies.cs index 2f0679525a2..cfa5b0a46bd 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitorDependencies.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitorDependencies.cs @@ -48,11 +48,13 @@ public sealed record RelationalQueryableMethodTranslatingExpressionVisitorDepend public RelationalQueryableMethodTranslatingExpressionVisitorDependencies( IRelationalSqlTranslatingExpressionVisitorFactory relationalSqlTranslatingExpressionVisitorFactory, ISqlExpressionFactory sqlExpressionFactory, - IRelationalTypeMappingSource typeMappingSource) + IRelationalTypeMappingSource typeMappingSource, + IModel model) { RelationalSqlTranslatingExpressionVisitorFactory = relationalSqlTranslatingExpressionVisitorFactory; SqlExpressionFactory = sqlExpressionFactory; TypeMappingSource = typeMappingSource; + Model = model; } /// @@ -69,4 +71,9 @@ public RelationalQueryableMethodTranslatingExpressionVisitorDependencies( /// The relational type mapping souce. /// public IRelationalTypeMappingSource TypeMappingSource { get; init; } + + /// + /// The model. + /// + public IModel Model { get; init; } } diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs index 0533f0bcedc..e51da687cf8 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs @@ -100,7 +100,7 @@ public virtual Task Delete_Where_Skip_Take(bool async) [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Delete_Where_predicate_with_group_by_aggregate(bool async) + public virtual Task Delete_Where_predicate_with_GroupBy_aggregate(bool async) => AssertDelete( async, ss => ss.Set() @@ -112,7 +112,7 @@ public virtual Task Delete_Where_predicate_with_group_by_aggregate(bool async) [ConditionalTheory(Skip = "Issue#28524")] [MemberData(nameof(IsAsyncData))] - public virtual Task Delete_Where_predicate_with_group_by_aggregate_2(bool async) + public virtual Task Delete_Where_predicate_with_GroupBy_aggregate_2(bool async) => AssertDelete( async, ss => ss.Set() @@ -246,7 +246,7 @@ public virtual Task Delete_non_entity_projection_3(bool async) () => AssertDelete( async, ss => ss.Set().Where(od => od.OrderID < 10250) - .Select(e => new { OrderDetail = e, ProductID = e.ProductID }), + .Select(e => new { OrderDetail = e, e.ProductID }), rowsAffectedCount: 0)); [ConditionalTheory] @@ -334,18 +334,18 @@ from o in ss.Set().Where(o => o.OrderID < od.OrderID).OrderBy(e => e.Orde [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_constant(bool async) + public virtual Task Update_Where_set_constant(bool async) => AssertUpdate( async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), e => e, s => s.SetProperty(c => c.ContactName, c => "Updated"), rowsAffectedCount: 8, - (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual async Task Update_where_parameter_in_predicate(bool async) + public virtual async Task Update_Where_parameter_set_constant(bool async) { var customer = "ALFKI"; await AssertUpdate( @@ -354,7 +354,7 @@ await AssertUpdate( e => e, s => s.SetProperty(c => c.ContactName, c => "Updated"), rowsAffectedCount: 1, - (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); customer = null; await AssertUpdate( @@ -363,12 +363,12 @@ await AssertUpdate( e => e, s => s.SetProperty(c => c.ContactName, c => "Updated"), rowsAffectedCount: 0, - (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_parameter(bool async) + public virtual Task Update_Where_set_parameter(bool async) { var value = "Abc"; return AssertUpdate( @@ -377,23 +377,100 @@ public virtual Task Update_where_parameter(bool async) e => e, s => s.SetProperty(c => c.ContactName, c => value), rowsAffectedCount: 8, - (b, a) => a.ForEach(c => Assert.Equal("Abc", c.ContactName))); + (b, a) => Assert.All(a, c => Assert.Equal("Abc", c.ContactName))); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_take_constant(bool async) + public virtual Task Update_Where_Skip_set_constant(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).Skip(4), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 4, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Where_Take_set_constant(bool async) => AssertUpdate( async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).Take(4), e => e, s => s.SetProperty(c => c.ContactName, c => "Updated"), rowsAffectedCount: 4, - (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Where_Skip_Take_set_constant(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).Skip(2).Take(4), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 4, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_group_by_aggregate_constant(bool async) + public virtual Task Update_Where_OrderBy_set_constant(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).OrderBy(c => c.City), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 8, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Where_OrderBy_Skip_set_constant(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).OrderBy(c => c.City).Skip(4), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 4, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Where_OrderBy_Take_set_constant(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).OrderBy(c => c.City).Take(4), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 4, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Where_OrderBy_Skip_Take_set_constant(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).OrderBy(c => c.City).Skip(2).Take(4), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 4, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Where_OrderBy_Skip_Take_Skip_Take_set_constant(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).OrderBy(c => c.City).Skip(2).Take(6).Skip(2).Take(2), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 2, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Where_GroupBy_aggregate_set_constant(bool async) => AssertUpdate( async, ss => ss.Set() @@ -402,11 +479,11 @@ public virtual Task Update_where_group_by_aggregate_constant(bool async) e => e, s => s.SetProperty(c => c.ContactName, c => "Updated"), rowsAffectedCount: 1, - (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_group_by_first_constant(bool async) + public virtual Task Update_Where_GroupBy_First_set_constant(bool async) => AssertUpdate( async, ss => ss.Set() @@ -415,11 +492,11 @@ public virtual Task Update_where_group_by_first_constant(bool async) e => e, s => s.SetProperty(c => c.ContactName, c => "Updated"), rowsAffectedCount: 1, - (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); [ConditionalTheory(Skip = "Issue#26753")] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_group_by_first_constant_2(bool async) + public virtual Task Update_Where_GroupBy_First_set_constant_2(bool async) => AssertUpdate( async, ss => ss.Set() @@ -428,11 +505,11 @@ public virtual Task Update_where_group_by_first_constant_2(bool async) e => e, s => s.SetProperty(c => c.ContactName, c => "Updated"), rowsAffectedCount: 1, - (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); [ConditionalTheory(Skip = "Issue#28524")] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_group_by_first_constant_3(bool async) + public virtual Task Update_Where_GroupBy_First_set_constant_3(bool async) => AssertUpdate( async, ss => ss.Set() @@ -441,55 +518,55 @@ public virtual Task Update_where_group_by_first_constant_3(bool async) e => e, s => s.SetProperty(c => c.ContactName, c => "Updated"), rowsAffectedCount: 1, - (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_distinct_constant(bool async) + public virtual Task Update_Where_Distinct_set_constant(bool async) => AssertUpdate( async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).Distinct(), e => e, s => s.SetProperty(c => c.ContactName, c => "Updated"), rowsAffectedCount: 8, - (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_using_navigation(bool async) + public virtual Task Update_Where_using_navigation_set_null(bool async) => AssertUpdate( async, ss => ss.Set().Where(o => o.Customer.City == "Seattle"), e => e, s => s.SetProperty(c => c.OrderDate, c => null), rowsAffectedCount: 14, - (b, a) => a.ForEach(c => Assert.Null(c.OrderDate))); + (b, a) => Assert.All(a, c => Assert.Null(c.OrderDate))); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_using_navigation_2(bool async) + public virtual Task Update_Where_using_navigation_2_set_constant(bool async) => AssertUpdate( async, ss => ss.Set().Where(od => od.Order.Customer.City == "Seattle"), e => e, s => s.SetProperty(c => c.Quantity, c => 1), rowsAffectedCount: 40, - (b, a) => a.ForEach(c => Assert.Equal(1, c.Quantity))); + (b, a) => Assert.All(a, c => Assert.Equal(1, c.Quantity))); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_select_many(bool async) + public virtual Task Update_Where_SelectMany_set_null(bool async) => AssertUpdate( async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).SelectMany(c => c.Orders), e => e, s => s.SetProperty(c => c.OrderDate, c => null), rowsAffectedCount: 63, - (b, a) => a.ForEach(c => Assert.Null(c.OrderDate))); + (b, a) => Assert.All(a, c => Assert.Null(c.OrderDate))); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_using_property_plus_constant(bool async) + public virtual Task Update_Where_set_property_plus_constant(bool async) => AssertUpdate( async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), @@ -500,7 +577,7 @@ public virtual Task Update_where_using_property_plus_constant(bool async) [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_using_property_plus_parameter(bool async) + public virtual Task Update_Where_set_property_plus_parameter(bool async) { var value = "Abc"; return AssertUpdate( @@ -514,7 +591,7 @@ public virtual Task Update_where_using_property_plus_parameter(bool async) [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_using_property_plus_property(bool async) + public virtual Task Update_Where_set_property_plus_property(bool async) => AssertUpdate( async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), @@ -525,25 +602,25 @@ public virtual Task Update_where_using_property_plus_property(bool async) [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_constant_using_ef_property(bool async) + public virtual Task Update_Where_set_constant_using_ef_property(bool async) => AssertUpdate( async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), e => e, s => s.SetProperty(c => EF.Property(c, "ContactName"), c => "Updated"), rowsAffectedCount: 8, - (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_null(bool async) + public virtual Task Update_Where_set_null(bool async) => AssertUpdate( async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), e => e, s => s.SetProperty(c => c.ContactName, c => null), rowsAffectedCount: 8, - (b, a) => a.ForEach(c => Assert.Null(c.ContactName))); + (b, a) => Assert.All(a, c => Assert.Null(c.ContactName))); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] @@ -571,7 +648,7 @@ public virtual Task Update_with_invalid_lambda_throws(bool async) [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_multi_property_update(bool async) + public virtual Task Update_Where_multiple_set(bool async) { var value = "Abc"; return AssertUpdate( @@ -580,7 +657,7 @@ public virtual Task Update_where_multi_property_update(bool async) e => e, s => s.SetProperty(c => c.ContactName, c => value).SetProperty(c => c.City, c => "Seattle"), rowsAffectedCount: 8, - (b, a) => a.ForEach(c => + (b, a) => Assert.All(a, c => { Assert.Equal("Abc", c.ContactName); Assert.Equal("Seattle", c.City); @@ -601,20 +678,20 @@ public virtual Task Update_with_invalid_lambda_in_set_property_throws(bool async [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_multiple_entity_update(bool async) + public virtual Task Update_multiple_entity_throws(bool async) => AssertTranslationFailed( RelationalStrings.MultipleEntityPropertiesInSetProperty("Order", "Customer"), () => AssertUpdate( async, ss => ss.Set().Where(o => o.CustomerID.StartsWith("F")) - .Select(e => new { e, Customer = e.Customer }), + .Select(e => new { e, e.Customer }), e => e.Customer, s => s.SetProperty(c => c.Customer.ContactName, c => "Name").SetProperty(c => c.e.OrderDate, e => new DateTime(2020, 1, 1)), rowsAffectedCount: 0)); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Update_unmapped_property(bool async) + public virtual Task Update_unmapped_property_throws(bool async) => AssertTranslationFailed( RelationalStrings.UnableToTranslateSetProperty("c => c.IsLondon", "c => True", CoreStrings.QueryUnableToTranslateMember("IsLondon", "Customer")), @@ -625,6 +702,211 @@ public virtual Task Update_unmapped_property(bool async) s => s.SetProperty(c => c.IsLondon, c => true), rowsAffectedCount: 0)); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Union_set_constant(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")) + .Union(ss.Set().Where(c => c.CustomerID.StartsWith("A"))), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 12, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Concat_set_constant(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")) + .Concat(ss.Set().Where(c => c.CustomerID.StartsWith("A"))), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 12, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Except_set_constant(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")) + .Except(ss.Set().Where(c => c.CustomerID.StartsWith("A"))), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 8, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Intersect_set_constant(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")) + .Intersect(ss.Set().Where(c => c.CustomerID.StartsWith("A"))), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 0, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_with_join_set_constant(bool async) + => AssertUpdate( + async, + ss => from c in ss.Set().Where(c => c.CustomerID.StartsWith("F")) + join o in ss.Set().Where(o => o.OrderID < 10300) + on c.CustomerID equals o.CustomerID + select new { c, o }, + e => e.c, + s => s.SetProperty(c => c.c.ContactName, c => "Updated"), + rowsAffectedCount: 2, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_with_left_join_set_constant(bool async) + => AssertUpdate( + async, + ss => from c in ss.Set().Where(c => c.CustomerID.StartsWith("F")) + join o in ss.Set().Where(o => o.OrderID < 10300) + on c.CustomerID equals o.CustomerID into grouping + from o in grouping.DefaultIfEmpty() + select new { c, o }, + e => e.c, + s => s.SetProperty(c => c.c.ContactName, c => "Updated"), + rowsAffectedCount: 8, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_with_cross_join_set_constant(bool async) + => AssertUpdate( + async, + ss => from c in ss.Set().Where(c => c.CustomerID.StartsWith("F")) + from o in ss.Set().Where(o => o.OrderID < 10300) + select new { c, o }, + e => e.c, + s => s.SetProperty(c => c.c.ContactName, c => "Updated"), + rowsAffectedCount: 8, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_with_cross_apply_set_constant(bool async) + => AssertUpdate( + async, + ss => from c in ss.Set().Where(c => c.CustomerID.StartsWith("F")) + from o in ss.Set().Where(o => o.OrderID < 10300 && o.OrderDate.Value.Year < c.ContactName.Length) + select new { c, o }, + e => e.c, + s => s.SetProperty(c => c.c.ContactName, c => "Updated"), + rowsAffectedCount: 0, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_with_outer_apply_set_constant(bool async) + => AssertUpdate( + async, + ss => from c in ss.Set().Where(c => c.CustomerID.StartsWith("F")) + from o in ss.Set().Where(o => o.OrderID < 10300 && o.OrderDate.Value.Year < c.ContactName.Length).DefaultIfEmpty() + select new { c, o }, + e => e.c, + s => s.SetProperty(c => c.c.ContactName, c => "Updated"), + rowsAffectedCount: 8, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Update_FromSql_set_constant(bool async) + { + if (async) + { + await TestHelpers.ExecuteWithStrategyInTransactionAsync( + () => Fixture.CreateContext(), + (DatabaseFacade facade, IDbContextTransaction transaction) => Fixture.UseTransaction(facade, transaction), + async context => await context.Set().FromSqlRaw( + NormalizeDelimitersInRawString( + @"SELECT [Region], [PostalCode], [Phone], [Fax], [CustomerID], [Country], [ContactTitle], [ContactName], [CompanyName], [City], [Address] +FROM [Customers] +WHERE [CustomerID] LIKE 'A%'")) + .ExecuteUpdateAsync(s => s.SetProperty(c => c.ContactName, c => "Updated"))); + } + else + { + TestHelpers.ExecuteWithStrategyInTransaction( + () => Fixture.CreateContext(), + (DatabaseFacade facade, IDbContextTransaction transaction) => Fixture.UseTransaction(facade, transaction), + context => context.Set().FromSqlRaw( + NormalizeDelimitersInRawString( + @"SELECT [Region], [PostalCode], [Phone], [Fax], [CustomerID], [Country], [ContactTitle], [ContactName], [CompanyName], [City], [Address] +FROM [Customers] +WHERE [CustomerID] LIKE 'A%'")) + .ExecuteUpdate(s => s.SetProperty(c => c.ContactName, c => "Updated"))); + } + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Where_SelectMany_subquery_set_null(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")) + .SelectMany(c => c.Orders.Where(o => o.OrderDate.Value.Year == 1997)), + e => e, + s => s.SetProperty(c => c.OrderDate, c => null), + rowsAffectedCount: 35, + (b, a) => Assert.All(a, c => Assert.Null(c.OrderDate))); + + [ConditionalTheory(Skip = "Issue#28752")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Where_Join_set_property_from_joined_single_result_table(bool async) + => AssertUpdate( + async, + ss => from c in ss.Set().Where(c => c.CustomerID.StartsWith("F")) + select new { c, LastOrder = c.Orders.OrderByDescending(o => o.OrderDate).FirstOrDefault() }, + e => e.c, + s => s.SetProperty(c => c.c.City, c => c.LastOrder.OrderDate.ToString()), + rowsAffectedCount: 35, + (b, a) => Assert.All(a, c => Assert.NotNull(c.City))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Where_Join_set_property_from_joined_table(bool async) + => AssertUpdate( + async, + ss => from c in ss.Set().Where(c => c.CustomerID.StartsWith("F")) + from c2 in ss.Set().Where(c => c.CustomerID == "ALFKI") + select new { c, c2 }, + e => e.c, + s => s.SetProperty(c => c.c.City, c => c.c2.City), + rowsAffectedCount: 8, + (b, a) => Assert.All(a, c => Assert.NotNull(c.City))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Where_Join_set_property_from_joined_single_result_scalar(bool async) + => AssertUpdate( + async, + ss => from c in ss.Set().Where(c => c.CustomerID.StartsWith("F")) + select new { c, LastOrderDate = c.Orders.OrderByDescending(o => o.OrderDate).FirstOrDefault().OrderDate.Value.Year }, + e => e.c, + s => s.SetProperty(c => c.c.City, c => c.LastOrderDate.ToString()), + rowsAffectedCount: 8, + (b, a) => Assert.All(a, c => + { + if (c.CustomerID == "FISSA") + { + Assert.Null(c.City); + } + else + { + Assert.NotNull(c.City); + } + })); + protected string NormalizeDelimitersInRawString(string sql) => Fixture.TestStore.NormalizeDelimitersInRawString(sql); diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs index b08ada7c255..d65a64fc885 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs @@ -169,9 +169,9 @@ OFFSET @__p_0 ROWS FETCH NEXT @__p_0 ROWS ONLY WHERE [t].[OrderID] = [o].[OrderID] AND [t].[ProductID] = [o].[ProductID])"); } - public override async Task Delete_Where_predicate_with_group_by_aggregate(bool async) + public override async Task Delete_Where_predicate_with_GroupBy_aggregate(bool async) { - await base.Delete_Where_predicate_with_group_by_aggregate(async); + await base.Delete_Where_predicate_with_GroupBy_aggregate(async); AssertSql( @"DELETE FROM [o] @@ -186,9 +186,9 @@ GROUP BY [o0].[CustomerID] HAVING COUNT(*) > 11)"); } - public override async Task Delete_Where_predicate_with_group_by_aggregate_2(bool async) + public override async Task Delete_Where_predicate_with_GroupBy_aggregate_2(bool async) { - await base.Delete_Where_predicate_with_group_by_aggregate_2(async); + await base.Delete_Where_predicate_with_GroupBy_aggregate_2(async); AssertSql(); } @@ -502,9 +502,9 @@ OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY WHERE [o].[OrderID] < 10276"); } - public override async Task Update_where_constant(bool async) + public override async Task Update_Where_set_constant(bool async) { - await base.Update_where_constant(async); + await base.Update_Where_set_constant(async); AssertExecuteUpdateSql( @"UPDATE [c] @@ -513,9 +513,9 @@ FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'F%'"); } - public override async Task Update_where_parameter_in_predicate(bool async) + public override async Task Update_Where_parameter_set_constant(bool async) { - await base.Update_where_parameter_in_predicate(async); + await base.Update_Where_parameter_set_constant(async); AssertExecuteUpdateSql( @"@__customer_0='ALFKI' (Size = 5) (DbType = StringFixedLength) @@ -541,9 +541,9 @@ FROM [Customers] AS [c] WHERE 0 = 1"); } - public override async Task Update_where_parameter(bool async) + public override async Task Update_Where_set_parameter(bool async) { - await base.Update_where_parameter(async); + await base.Update_Where_set_parameter(async); AssertExecuteUpdateSql( @"@__value_0='Abc' (Size = 4000) @@ -554,9 +554,28 @@ FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'F%'"); } - public override async Task Update_where_take_constant(bool async) + public override async Task Update_Where_Skip_set_constant(bool async) { - await base.Update_where_take_constant(async); + await base.Update_Where_Skip_set_constant(async); + + AssertExecuteUpdateSql( + @"@__p_0='4' + +UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +INNER JOIN ( + SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] + FROM [Customers] AS [c0] + WHERE [c0].[CustomerID] LIKE N'F%' + ORDER BY (SELECT 1) + OFFSET @__p_0 ROWS +) AS [t] ON [c].[CustomerID] = [t].[CustomerID]"); + } + + public override async Task Update_Where_Take_set_constant(bool async) + { + await base.Update_Where_Take_set_constant(async); AssertExecuteUpdateSql( @"@__p_0='4' @@ -567,9 +586,126 @@ FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'F%'"); } - public override async Task Update_where_group_by_aggregate_constant(bool async) + public override async Task Update_Where_Skip_Take_set_constant(bool async) { - await base.Update_where_group_by_aggregate_constant(async); + await base.Update_Where_Skip_Take_set_constant(async); + + AssertExecuteUpdateSql( + @"@__p_0='2' +@__p_1='4' + +UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +INNER JOIN ( + SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] + FROM [Customers] AS [c0] + WHERE [c0].[CustomerID] LIKE N'F%' + ORDER BY (SELECT 1) + OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY +) AS [t] ON [c].[CustomerID] = [t].[CustomerID]"); + } + + public override async Task Update_Where_OrderBy_set_constant(bool async) + { + await base.Update_Where_OrderBy_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +INNER JOIN ( + SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] + FROM [Customers] AS [c0] + WHERE [c0].[CustomerID] LIKE N'F%' +) AS [t] ON [c].[CustomerID] = [t].[CustomerID]"); + } + + public override async Task Update_Where_OrderBy_Skip_set_constant(bool async) + { + await base.Update_Where_OrderBy_Skip_set_constant(async); + + AssertExecuteUpdateSql( + @"@__p_0='4' + +UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +INNER JOIN ( + SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] + FROM [Customers] AS [c0] + WHERE [c0].[CustomerID] LIKE N'F%' + ORDER BY [c0].[City] + OFFSET @__p_0 ROWS +) AS [t] ON [c].[CustomerID] = [t].[CustomerID]"); + } + + public override async Task Update_Where_OrderBy_Take_set_constant(bool async) + { + await base.Update_Where_OrderBy_Take_set_constant(async); + + AssertExecuteUpdateSql( + @"@__p_0='4' + +UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +INNER JOIN ( + SELECT TOP(@__p_0) [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] + FROM [Customers] AS [c0] + WHERE [c0].[CustomerID] LIKE N'F%' + ORDER BY [c0].[City] +) AS [t] ON [c].[CustomerID] = [t].[CustomerID]"); + } + + public override async Task Update_Where_OrderBy_Skip_Take_set_constant(bool async) + { + await base.Update_Where_OrderBy_Skip_Take_set_constant(async); + + AssertExecuteUpdateSql( + @"@__p_0='2' +@__p_1='4' + +UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +INNER JOIN ( + SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] + FROM [Customers] AS [c0] + WHERE [c0].[CustomerID] LIKE N'F%' + ORDER BY [c0].[City] + OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY +) AS [t] ON [c].[CustomerID] = [t].[CustomerID]"); + } + + public override async Task Update_Where_OrderBy_Skip_Take_Skip_Take_set_constant(bool async) + { + await base.Update_Where_OrderBy_Skip_Take_Skip_Take_set_constant(async); + + AssertExecuteUpdateSql( + @"@__p_0='2' +@__p_1='6' + +UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +INNER JOIN ( + SELECT [t].[CustomerID], [t].[Address], [t].[City], [t].[CompanyName], [t].[ContactName], [t].[ContactTitle], [t].[Country], [t].[Fax], [t].[Phone], [t].[PostalCode], [t].[Region] + FROM ( + SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] + FROM [Customers] AS [c0] + WHERE [c0].[CustomerID] LIKE N'F%' + ORDER BY [c0].[City] + OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY + ) AS [t] + ORDER BY [t].[City] + OFFSET @__p_0 ROWS FETCH NEXT @__p_0 ROWS ONLY +) AS [t0] ON [c].[CustomerID] = [t0].[CustomerID]"); + } + + public override async Task Update_Where_GroupBy_aggregate_set_constant(bool async) + { + await base.Update_Where_GroupBy_aggregate_set_constant(async); AssertExecuteUpdateSql( @"UPDATE [c] @@ -582,9 +718,9 @@ GROUP BY [o].[CustomerID] HAVING COUNT(*) > 11)"); } - public override async Task Update_where_group_by_first_constant(bool async) + public override async Task Update_Where_GroupBy_First_set_constant(bool async) { - await base.Update_where_group_by_first_constant(async); + await base.Update_Where_GroupBy_First_set_constant(async); AssertExecuteUpdateSql( @"UPDATE [c] @@ -600,23 +736,23 @@ GROUP BY [o].[CustomerID] HAVING COUNT(*) > 11)"); } - public override async Task Update_where_group_by_first_constant_2(bool async) + public override async Task Update_Where_GroupBy_First_set_constant_2(bool async) { - await base.Update_where_group_by_first_constant_2(async); + await base.Update_Where_GroupBy_First_set_constant_2(async); AssertExecuteUpdateSql(); } - public override async Task Update_where_group_by_first_constant_3(bool async) + public override async Task Update_Where_GroupBy_First_set_constant_3(bool async) { - await base.Update_where_group_by_first_constant_3(async); + await base.Update_Where_GroupBy_First_set_constant_3(async); AssertExecuteUpdateSql(); } - public override async Task Update_where_distinct_constant(bool async) + public override async Task Update_Where_Distinct_set_constant(bool async) { - await base.Update_where_distinct_constant(async); + await base.Update_Where_Distinct_set_constant(async); AssertExecuteUpdateSql( @"UPDATE [c] @@ -625,9 +761,9 @@ FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'F%'"); } - public override async Task Update_where_using_navigation(bool async) + public override async Task Update_Where_using_navigation_set_null(bool async) { - await base.Update_where_using_navigation(async); + await base.Update_Where_using_navigation_set_null(async); AssertExecuteUpdateSql( @"UPDATE [o] @@ -637,9 +773,9 @@ FROM [Orders] AS [o] WHERE [c].[City] = N'Seattle'"); } - public override async Task Update_where_using_navigation_2(bool async) + public override async Task Update_Where_using_navigation_2_set_constant(bool async) { - await base.Update_where_using_navigation_2(async); + await base.Update_Where_using_navigation_2_set_constant(async); AssertExecuteUpdateSql( @"UPDATE [o] @@ -650,9 +786,9 @@ FROM [Order Details] AS [o] WHERE [c].[City] = N'Seattle'"); } - public override async Task Update_where_select_many(bool async) + public override async Task Update_Where_SelectMany_set_null(bool async) { - await base.Update_where_select_many(async); + await base.Update_Where_SelectMany_set_null(async); AssertExecuteUpdateSql( @"UPDATE [o] @@ -662,9 +798,9 @@ FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'F%'"); } - public override async Task Update_where_using_property_plus_constant(bool async) + public override async Task Update_Where_set_property_plus_constant(bool async) { - await base.Update_where_using_property_plus_constant(async); + await base.Update_Where_set_property_plus_constant(async); AssertExecuteUpdateSql( @"UPDATE [c] @@ -673,9 +809,9 @@ FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'F%'"); } - public override async Task Update_where_using_property_plus_parameter(bool async) + public override async Task Update_Where_set_property_plus_parameter(bool async) { - await base.Update_where_using_property_plus_parameter(async); + await base.Update_Where_set_property_plus_parameter(async); AssertExecuteUpdateSql( @"@__value_0='Abc' (Size = 4000) @@ -686,9 +822,9 @@ FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'F%'"); } - public override async Task Update_where_using_property_plus_property(bool async) + public override async Task Update_Where_set_property_plus_property(bool async) { - await base.Update_where_using_property_plus_property(async); + await base.Update_Where_set_property_plus_property(async); AssertExecuteUpdateSql( @"UPDATE [c] @@ -697,9 +833,9 @@ FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'F%'"); } - public override async Task Update_where_constant_using_ef_property(bool async) + public override async Task Update_Where_set_constant_using_ef_property(bool async) { - await base.Update_where_constant_using_ef_property(async); + await base.Update_Where_set_constant_using_ef_property(async); AssertExecuteUpdateSql( @"UPDATE [c] @@ -708,9 +844,9 @@ FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'F%'"); } - public override async Task Update_where_null(bool async) + public override async Task Update_Where_set_null(bool async) { - await base.Update_where_null(async); + await base.Update_Where_set_null(async); AssertExecuteUpdateSql( @"UPDATE [c] @@ -733,9 +869,9 @@ public override async Task Update_with_invalid_lambda_throws(bool async) AssertExecuteUpdateSql(); } - public override async Task Update_where_multi_property_update(bool async) + public override async Task Update_Where_multiple_set(bool async) { - await base.Update_where_multi_property_update(async); + await base.Update_Where_multiple_set(async); AssertExecuteUpdateSql( @"@__value_0='Abc' (Size = 4000) @@ -754,20 +890,241 @@ public override async Task Update_with_invalid_lambda_in_set_property_throws(boo AssertExecuteUpdateSql(); } - public override async Task Update_multiple_entity_update(bool async) + public override async Task Update_multiple_entity_throws(bool async) { - await base.Update_multiple_entity_update(async); + await base.Update_multiple_entity_throws(async); AssertExecuteUpdateSql(); } - public override async Task Update_unmapped_property(bool async) + public override async Task Update_unmapped_property_throws(bool async) { - await base.Update_unmapped_property(async); + await base.Update_unmapped_property_throws(async); AssertExecuteUpdateSql(); } + public override async Task Update_Union_set_constant(bool async) + { + await base.Update_Union_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +INNER JOIN ( + SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] + FROM [Customers] AS [c0] + WHERE [c0].[CustomerID] LIKE N'F%' + UNION + SELECT [c1].[CustomerID], [c1].[Address], [c1].[City], [c1].[CompanyName], [c1].[ContactName], [c1].[ContactTitle], [c1].[Country], [c1].[Fax], [c1].[Phone], [c1].[PostalCode], [c1].[Region] + FROM [Customers] AS [c1] + WHERE [c1].[CustomerID] LIKE N'A%' +) AS [t] ON [c].[CustomerID] = [t].[CustomerID]"); + } + + public override async Task Update_Concat_set_constant(bool async) + { + await base.Update_Concat_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +INNER JOIN ( + SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] + FROM [Customers] AS [c0] + WHERE [c0].[CustomerID] LIKE N'F%' + UNION ALL + SELECT [c1].[CustomerID], [c1].[Address], [c1].[City], [c1].[CompanyName], [c1].[ContactName], [c1].[ContactTitle], [c1].[Country], [c1].[Fax], [c1].[Phone], [c1].[PostalCode], [c1].[Region] + FROM [Customers] AS [c1] + WHERE [c1].[CustomerID] LIKE N'A%' +) AS [t] ON [c].[CustomerID] = [t].[CustomerID]"); + } + + public override async Task Update_Except_set_constant(bool async) + { + await base.Update_Except_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +INNER JOIN ( + SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] + FROM [Customers] AS [c0] + WHERE [c0].[CustomerID] LIKE N'F%' + EXCEPT + SELECT [c1].[CustomerID], [c1].[Address], [c1].[City], [c1].[CompanyName], [c1].[ContactName], [c1].[ContactTitle], [c1].[Country], [c1].[Fax], [c1].[Phone], [c1].[PostalCode], [c1].[Region] + FROM [Customers] AS [c1] + WHERE [c1].[CustomerID] LIKE N'A%' +) AS [t] ON [c].[CustomerID] = [t].[CustomerID]"); + } + + public override async Task Update_Intersect_set_constant(bool async) + { + await base.Update_Intersect_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +INNER JOIN ( + SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] + FROM [Customers] AS [c0] + WHERE [c0].[CustomerID] LIKE N'F%' + INTERSECT + SELECT [c1].[CustomerID], [c1].[Address], [c1].[City], [c1].[CompanyName], [c1].[ContactName], [c1].[ContactTitle], [c1].[Country], [c1].[Fax], [c1].[Phone], [c1].[PostalCode], [c1].[Region] + FROM [Customers] AS [c1] + WHERE [c1].[CustomerID] LIKE N'A%' +) AS [t] ON [c].[CustomerID] = [t].[CustomerID]"); + } + + public override async Task Update_with_join_set_constant(bool async) + { + await base.Update_with_join_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +INNER JOIN ( + SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] + FROM [Orders] AS [o] + WHERE [o].[OrderID] < 10300 +) AS [t] ON [c].[CustomerID] = [t].[CustomerID] +WHERE [c].[CustomerID] LIKE N'F%'"); + } + + public override async Task Update_with_left_join_set_constant(bool async) + { + await base.Update_with_left_join_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +LEFT JOIN ( + SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] + FROM [Orders] AS [o] + WHERE [o].[OrderID] < 10300 +) AS [t] ON [c].[CustomerID] = [t].[CustomerID] +WHERE [c].[CustomerID] LIKE N'F%'"); + } + + public override async Task Update_with_cross_join_set_constant(bool async) + { + await base.Update_with_cross_join_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +CROSS JOIN ( + SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] + FROM [Orders] AS [o] + WHERE [o].[OrderID] < 10300 +) AS [t] +WHERE [c].[CustomerID] LIKE N'F%'"); + } + + public override async Task Update_with_cross_apply_set_constant(bool async) + { + await base.Update_with_cross_apply_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +CROSS APPLY ( + SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] + FROM [Orders] AS [o] + WHERE [o].[OrderID] < 10300 AND DATEPART(year, [o].[OrderDate]) < CAST(LEN([c].[ContactName]) AS int) +) AS [t] +WHERE [c].[CustomerID] LIKE N'F%'"); + } + + public override async Task Update_with_outer_apply_set_constant(bool async) + { + await base.Update_with_outer_apply_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +OUTER APPLY ( + SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] + FROM [Orders] AS [o] + WHERE [o].[OrderID] < 10300 AND DATEPART(year, [o].[OrderDate]) < CAST(LEN([c].[ContactName]) AS int) +) AS [t] +WHERE [c].[CustomerID] LIKE N'F%'"); + } + + public override async Task Update_FromSql_set_constant(bool async) + { + await base.Update_FromSql_set_constant(async); + + AssertExecuteUpdateSql(); + } + + public override async Task Update_Where_SelectMany_subquery_set_null(bool async) + { + await base.Update_Where_SelectMany_subquery_set_null(async); + + AssertExecuteUpdateSql( + @"UPDATE [o] + SET [o].[OrderDate] = NULL +FROM [Orders] AS [o] +INNER JOIN ( + SELECT [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate], [c].[CustomerID] AS [CustomerID0] + FROM [Customers] AS [c] + INNER JOIN ( + SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate] + FROM [Orders] AS [o0] + WHERE DATEPART(year, [o0].[OrderDate]) = 1997 + ) AS [t] ON [c].[CustomerID] = [t].[CustomerID] + WHERE [c].[CustomerID] LIKE N'F%' +) AS [t0] ON [o].[OrderID] = [t0].[OrderID]"); + } + + public override async Task Update_Where_Join_set_property_from_joined_single_result_table(bool async) + { + await base.Update_Where_Join_set_property_from_joined_single_result_table(async); + + AssertExecuteUpdateSql(); + } + + public override async Task Update_Where_Join_set_property_from_joined_table(bool async) + { + await base.Update_Where_Join_set_property_from_joined_table(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[City] = [t].[City] +FROM [Customers] AS [c] +CROSS JOIN ( + SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] + FROM [Customers] AS [c0] + WHERE [c0].[CustomerID] = N'ALFKI' +) AS [t] +WHERE [c].[CustomerID] LIKE N'F%'"); + } + + public override async Task Update_Where_Join_set_property_from_joined_single_result_scalar(bool async) + { + await base.Update_Where_Join_set_property_from_joined_single_result_scalar(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[City] = CONVERT(varchar(11), DATEPART(year, ( + SELECT TOP(1) [o].[OrderDate] + FROM [Orders] AS [o] + WHERE [c].[CustomerID] = [o].[CustomerID] + ORDER BY [o].[OrderDate] DESC))) +FROM [Customers] AS [c] +WHERE [c].[CustomerID] LIKE N'F%'"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs index ae8c31f367e..05b89063b28 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs @@ -168,9 +168,9 @@ LIMIT @__p_0 OFFSET @__p_0 WHERE ""t"".""OrderID"" = ""o"".""OrderID"" AND ""t"".""ProductID"" = ""o"".""ProductID"")"); } - public override async Task Delete_Where_predicate_with_group_by_aggregate(bool async) + public override async Task Delete_Where_predicate_with_GroupBy_aggregate(bool async) { - await base.Delete_Where_predicate_with_group_by_aggregate(async); + await base.Delete_Where_predicate_with_GroupBy_aggregate(async); AssertSql( @"DELETE FROM ""Order Details"" AS ""o"" @@ -186,9 +186,9 @@ HAVING COUNT(*) > 11 LIMIT 1)"); } - public override async Task Delete_Where_predicate_with_group_by_aggregate_2(bool async) + public override async Task Delete_Where_predicate_with_GroupBy_aggregate_2(bool async) { - await base.Delete_Where_predicate_with_group_by_aggregate_2(async); + await base.Delete_Where_predicate_with_GroupBy_aggregate_2(async); AssertSql(); } @@ -481,9 +481,9 @@ public override async Task Delete_with_outer_apply(bool async) SqliteStrings.ApplyNotSupported, (await Assert.ThrowsAsync(() => base.Delete_with_outer_apply(async))).Message); - public override async Task Update_where_constant(bool async) + public override async Task Update_Where_set_constant(bool async) { - await base.Update_where_constant(async); + await base.Update_Where_set_constant(async); AssertExecuteUpdateSql( @"UPDATE ""Customers"" AS ""c"" @@ -491,9 +491,9 @@ public override async Task Update_where_constant(bool async) WHERE ""c"".""CustomerID"" LIKE 'F%'"); } - public override async Task Update_where_parameter_in_predicate(bool async) + public override async Task Update_Where_parameter_set_constant(bool async) { - await base.Update_where_parameter_in_predicate(async); + await base.Update_Where_parameter_set_constant(async); AssertExecuteUpdateSql( @"@__customer_0='ALFKI' (Size = 5) @@ -517,9 +517,9 @@ public override async Task Update_where_parameter_in_predicate(bool async) WHERE 0"); } - public override async Task Update_where_parameter(bool async) + public override async Task Update_Where_set_parameter(bool async) { - await base.Update_where_parameter(async); + await base.Update_Where_set_parameter(async); AssertExecuteUpdateSql( @"@__value_0='Abc' (Size = 3) @@ -529,23 +529,162 @@ public override async Task Update_where_parameter(bool async) WHERE ""c"".""CustomerID"" LIKE 'F%'"); } - [ConditionalTheory(Skip = "Issue#28661")] - public override async Task Update_where_take_constant(bool async) + public override async Task Update_Where_Skip_set_constant(bool async) { - await base.Update_where_take_constant(async); + await base.Update_Where_Skip_set_constant(async); AssertExecuteUpdateSql( @"@__p_0='4' -UPDATE TOP(@__p_0) [c] - SET [c].[ContactName] = N'Updated' -FROM [Customers] AS [c] -WHERE [c].[CustomerID] LIKE N'F%'"); +UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +FROM ( + SELECT ""c0"".""CustomerID"", ""c0"".""Address"", ""c0"".""City"", ""c0"".""CompanyName"", ""c0"".""ContactName"", ""c0"".""ContactTitle"", ""c0"".""Country"", ""c0"".""Fax"", ""c0"".""Phone"", ""c0"".""PostalCode"", ""c0"".""Region"" + FROM ""Customers"" AS ""c0"" + WHERE ""c0"".""CustomerID"" LIKE 'F%' + LIMIT -1 OFFSET @__p_0 +) AS ""t"" +WHERE ""c"".""CustomerID"" = ""t"".""CustomerID"""); + } + + public override async Task Update_Where_Take_set_constant(bool async) + { + await base.Update_Where_Take_set_constant(async); + + AssertExecuteUpdateSql( + @"@__p_0='4' + +UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +FROM ( + SELECT ""c0"".""CustomerID"", ""c0"".""Address"", ""c0"".""City"", ""c0"".""CompanyName"", ""c0"".""ContactName"", ""c0"".""ContactTitle"", ""c0"".""Country"", ""c0"".""Fax"", ""c0"".""Phone"", ""c0"".""PostalCode"", ""c0"".""Region"" + FROM ""Customers"" AS ""c0"" + WHERE ""c0"".""CustomerID"" LIKE 'F%' + LIMIT @__p_0 +) AS ""t"" +WHERE ""c"".""CustomerID"" = ""t"".""CustomerID"""); + } + + public override async Task Update_Where_Skip_Take_set_constant(bool async) + { + await base.Update_Where_Skip_Take_set_constant(async); + + AssertExecuteUpdateSql( + @"@__p_1='4' +@__p_0='2' + +UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +FROM ( + SELECT ""c0"".""CustomerID"", ""c0"".""Address"", ""c0"".""City"", ""c0"".""CompanyName"", ""c0"".""ContactName"", ""c0"".""ContactTitle"", ""c0"".""Country"", ""c0"".""Fax"", ""c0"".""Phone"", ""c0"".""PostalCode"", ""c0"".""Region"" + FROM ""Customers"" AS ""c0"" + WHERE ""c0"".""CustomerID"" LIKE 'F%' + LIMIT @__p_1 OFFSET @__p_0 +) AS ""t"" +WHERE ""c"".""CustomerID"" = ""t"".""CustomerID"""); + } + + public override async Task Update_Where_OrderBy_set_constant(bool async) + { + await base.Update_Where_OrderBy_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +FROM ( + SELECT ""c0"".""CustomerID"", ""c0"".""Address"", ""c0"".""City"", ""c0"".""CompanyName"", ""c0"".""ContactName"", ""c0"".""ContactTitle"", ""c0"".""Country"", ""c0"".""Fax"", ""c0"".""Phone"", ""c0"".""PostalCode"", ""c0"".""Region"" + FROM ""Customers"" AS ""c0"" + WHERE ""c0"".""CustomerID"" LIKE 'F%' +) AS ""t"" +WHERE ""c"".""CustomerID"" = ""t"".""CustomerID"""); + } + + public override async Task Update_Where_OrderBy_Skip_set_constant(bool async) + { + await base.Update_Where_OrderBy_Skip_set_constant(async); + + AssertExecuteUpdateSql( + @"@__p_0='4' + +UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +FROM ( + SELECT ""c0"".""CustomerID"", ""c0"".""Address"", ""c0"".""City"", ""c0"".""CompanyName"", ""c0"".""ContactName"", ""c0"".""ContactTitle"", ""c0"".""Country"", ""c0"".""Fax"", ""c0"".""Phone"", ""c0"".""PostalCode"", ""c0"".""Region"" + FROM ""Customers"" AS ""c0"" + WHERE ""c0"".""CustomerID"" LIKE 'F%' + ORDER BY ""c0"".""City"" + LIMIT -1 OFFSET @__p_0 +) AS ""t"" +WHERE ""c"".""CustomerID"" = ""t"".""CustomerID"""); + } + + public override async Task Update_Where_OrderBy_Take_set_constant(bool async) + { + await base.Update_Where_OrderBy_Take_set_constant(async); + + AssertExecuteUpdateSql( + @"@__p_0='4' + +UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +FROM ( + SELECT ""c0"".""CustomerID"", ""c0"".""Address"", ""c0"".""City"", ""c0"".""CompanyName"", ""c0"".""ContactName"", ""c0"".""ContactTitle"", ""c0"".""Country"", ""c0"".""Fax"", ""c0"".""Phone"", ""c0"".""PostalCode"", ""c0"".""Region"" + FROM ""Customers"" AS ""c0"" + WHERE ""c0"".""CustomerID"" LIKE 'F%' + ORDER BY ""c0"".""City"" + LIMIT @__p_0 +) AS ""t"" +WHERE ""c"".""CustomerID"" = ""t"".""CustomerID"""); + } + + public override async Task Update_Where_OrderBy_Skip_Take_set_constant(bool async) + { + await base.Update_Where_OrderBy_Skip_Take_set_constant(async); + + AssertExecuteUpdateSql( + @"@__p_1='4' +@__p_0='2' + +UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +FROM ( + SELECT ""c0"".""CustomerID"", ""c0"".""Address"", ""c0"".""City"", ""c0"".""CompanyName"", ""c0"".""ContactName"", ""c0"".""ContactTitle"", ""c0"".""Country"", ""c0"".""Fax"", ""c0"".""Phone"", ""c0"".""PostalCode"", ""c0"".""Region"" + FROM ""Customers"" AS ""c0"" + WHERE ""c0"".""CustomerID"" LIKE 'F%' + ORDER BY ""c0"".""City"" + LIMIT @__p_1 OFFSET @__p_0 +) AS ""t"" +WHERE ""c"".""CustomerID"" = ""t"".""CustomerID"""); + } + + public override async Task Update_Where_OrderBy_Skip_Take_Skip_Take_set_constant(bool async) + { + await base.Update_Where_OrderBy_Skip_Take_Skip_Take_set_constant(async); + + AssertExecuteUpdateSql( + @"@__p_1='6' +@__p_0='2' + +UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +FROM ( + SELECT ""t"".""CustomerID"", ""t"".""Address"", ""t"".""City"", ""t"".""CompanyName"", ""t"".""ContactName"", ""t"".""ContactTitle"", ""t"".""Country"", ""t"".""Fax"", ""t"".""Phone"", ""t"".""PostalCode"", ""t"".""Region"" + FROM ( + SELECT ""c0"".""CustomerID"", ""c0"".""Address"", ""c0"".""City"", ""c0"".""CompanyName"", ""c0"".""ContactName"", ""c0"".""ContactTitle"", ""c0"".""Country"", ""c0"".""Fax"", ""c0"".""Phone"", ""c0"".""PostalCode"", ""c0"".""Region"" + FROM ""Customers"" AS ""c0"" + WHERE ""c0"".""CustomerID"" LIKE 'F%' + ORDER BY ""c0"".""City"" + LIMIT @__p_1 OFFSET @__p_0 + ) AS ""t"" + ORDER BY ""t"".""City"" + LIMIT @__p_0 OFFSET @__p_0 +) AS ""t0"" +WHERE ""c"".""CustomerID"" = ""t0"".""CustomerID"""); } - public override async Task Update_where_group_by_aggregate_constant(bool async) + public override async Task Update_Where_GroupBy_aggregate_set_constant(bool async) { - await base.Update_where_group_by_aggregate_constant(async); + await base.Update_Where_GroupBy_aggregate_set_constant(async); AssertExecuteUpdateSql( @"UPDATE ""Customers"" AS ""c"" @@ -558,9 +697,9 @@ HAVING COUNT(*) > 11 LIMIT 1)"); } - public override async Task Update_where_group_by_first_constant(bool async) + public override async Task Update_Where_GroupBy_First_set_constant(bool async) { - await base.Update_where_group_by_first_constant(async); + await base.Update_Where_GroupBy_First_set_constant(async); AssertExecuteUpdateSql( @"UPDATE ""Customers"" AS ""c"" @@ -577,23 +716,23 @@ HAVING COUNT(*) > 11 LIMIT 1)"); } - public override async Task Update_where_group_by_first_constant_2(bool async) + public override async Task Update_Where_GroupBy_First_set_constant_2(bool async) { - await base.Update_where_group_by_first_constant_2(async); + await base.Update_Where_GroupBy_First_set_constant_2(async); AssertExecuteUpdateSql(); } - public override async Task Update_where_group_by_first_constant_3(bool async) + public override async Task Update_Where_GroupBy_First_set_constant_3(bool async) { - await base.Update_where_group_by_first_constant_3(async); + await base.Update_Where_GroupBy_First_set_constant_3(async); AssertExecuteUpdateSql(); } - public override async Task Update_where_distinct_constant(bool async) + public override async Task Update_Where_Distinct_set_constant(bool async) { - await base.Update_where_distinct_constant(async); + await base.Update_Where_Distinct_set_constant(async); AssertExecuteUpdateSql( @"UPDATE ""Customers"" AS ""c"" @@ -601,32 +740,42 @@ public override async Task Update_where_distinct_constant(bool async) WHERE ""c"".""CustomerID"" LIKE 'F%'"); } - public override async Task Update_where_using_navigation(bool async) + public override async Task Update_Where_using_navigation_set_null(bool async) { - await base.Update_where_using_navigation(async); + await base.Update_Where_using_navigation_set_null(async); AssertExecuteUpdateSql( @"UPDATE ""Orders"" AS ""o"" SET ""OrderDate"" = NULL -FROM ""Customers"" AS ""c"" -WHERE ""o"".""CustomerID"" = ""c"".""CustomerID"" AND ""c"".""City"" = 'Seattle'"); +FROM ( + SELECT ""o0"".""OrderID"", ""o0"".""CustomerID"", ""o0"".""EmployeeID"", ""o0"".""OrderDate"", ""c"".""CustomerID"" AS ""CustomerID0"" + FROM ""Orders"" AS ""o0"" + LEFT JOIN ""Customers"" AS ""c"" ON ""o0"".""CustomerID"" = ""c"".""CustomerID"" + WHERE ""c"".""City"" = 'Seattle' +) AS ""t"" +WHERE ""o"".""OrderID"" = ""t"".""OrderID"""); } - public override async Task Update_where_using_navigation_2(bool async) + public override async Task Update_Where_using_navigation_2_set_constant(bool async) { - await base.Update_where_using_navigation_2(async); + await base.Update_Where_using_navigation_2_set_constant(async); AssertExecuteUpdateSql( @"UPDATE ""Order Details"" AS ""o"" SET ""Quantity"" = CAST(1 AS INTEGER) -FROM ""Orders"" AS ""o0"" -LEFT JOIN ""Customers"" AS ""c"" ON ""o0"".""CustomerID"" = ""c"".""CustomerID"" -WHERE ""o"".""OrderID"" = ""o0"".""OrderID"" AND ""c"".""City"" = 'Seattle'"); +FROM ( + SELECT ""o0"".""OrderID"", ""o0"".""ProductID"", ""o0"".""Discount"", ""o0"".""Quantity"", ""o0"".""UnitPrice"", ""o1"".""OrderID"" AS ""OrderID0"", ""c"".""CustomerID"" + FROM ""Order Details"" AS ""o0"" + INNER JOIN ""Orders"" AS ""o1"" ON ""o0"".""OrderID"" = ""o1"".""OrderID"" + LEFT JOIN ""Customers"" AS ""c"" ON ""o1"".""CustomerID"" = ""c"".""CustomerID"" + WHERE ""c"".""City"" = 'Seattle' +) AS ""t"" +WHERE ""o"".""OrderID"" = ""t"".""OrderID"" AND ""o"".""ProductID"" = ""t"".""ProductID"""); } - public override async Task Update_where_select_many(bool async) + public override async Task Update_Where_SelectMany_set_null(bool async) { - await base.Update_where_select_many(async); + await base.Update_Where_SelectMany_set_null(async); AssertExecuteUpdateSql( @"UPDATE ""Orders"" AS ""o"" @@ -635,9 +784,9 @@ public override async Task Update_where_select_many(bool async) WHERE ""c"".""CustomerID"" = ""o"".""CustomerID"" AND (""c"".""CustomerID"" LIKE 'F%')"); } - public override async Task Update_where_using_property_plus_constant(bool async) + public override async Task Update_Where_set_property_plus_constant(bool async) { - await base.Update_where_using_property_plus_constant(async); + await base.Update_Where_set_property_plus_constant(async); AssertExecuteUpdateSql( @"UPDATE ""Customers"" AS ""c"" @@ -645,9 +794,9 @@ public override async Task Update_where_using_property_plus_constant(bool async) WHERE ""c"".""CustomerID"" LIKE 'F%'"); } - public override async Task Update_where_using_property_plus_parameter(bool async) + public override async Task Update_Where_set_property_plus_parameter(bool async) { - await base.Update_where_using_property_plus_parameter(async); + await base.Update_Where_set_property_plus_parameter(async); AssertExecuteUpdateSql( @"@__value_0='Abc' (Size = 3) @@ -657,9 +806,9 @@ public override async Task Update_where_using_property_plus_parameter(bool async WHERE ""c"".""CustomerID"" LIKE 'F%'"); } - public override async Task Update_where_using_property_plus_property(bool async) + public override async Task Update_Where_set_property_plus_property(bool async) { - await base.Update_where_using_property_plus_property(async); + await base.Update_Where_set_property_plus_property(async); AssertExecuteUpdateSql( @"UPDATE ""Customers"" AS ""c"" @@ -667,9 +816,9 @@ public override async Task Update_where_using_property_plus_property(bool async) WHERE ""c"".""CustomerID"" LIKE 'F%'"); } - public override async Task Update_where_constant_using_ef_property(bool async) + public override async Task Update_Where_set_constant_using_ef_property(bool async) { - await base.Update_where_constant_using_ef_property(async); + await base.Update_Where_set_constant_using_ef_property(async); AssertExecuteUpdateSql( @"UPDATE ""Customers"" AS ""c"" @@ -677,9 +826,9 @@ public override async Task Update_where_constant_using_ef_property(bool async) WHERE ""c"".""CustomerID"" LIKE 'F%'"); } - public override async Task Update_where_null(bool async) + public override async Task Update_Where_set_null(bool async) { - await base.Update_where_null(async); + await base.Update_Where_set_null(async); AssertExecuteUpdateSql( @"UPDATE ""Customers"" AS ""c"" @@ -701,9 +850,9 @@ public override async Task Update_with_invalid_lambda_throws(bool async) AssertExecuteUpdateSql(); } - public override async Task Update_where_multi_property_update(bool async) + public override async Task Update_Where_multiple_set(bool async) { - await base.Update_where_multi_property_update(async); + await base.Update_Where_multiple_set(async); AssertExecuteUpdateSql( @"@__value_0='Abc' (Size = 3) @@ -721,20 +870,220 @@ public override async Task Update_with_invalid_lambda_in_set_property_throws(boo AssertExecuteUpdateSql(); } - public override async Task Update_multiple_entity_update(bool async) + public override async Task Update_multiple_entity_throws(bool async) + { + await base.Update_multiple_entity_throws(async); + + AssertExecuteUpdateSql(); + } + + public override async Task Update_unmapped_property_throws(bool async) + { + await base.Update_unmapped_property_throws(async); + + AssertExecuteUpdateSql(); + } + + public override async Task Update_Union_set_constant(bool async) + { + await base.Update_Union_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +FROM ( + SELECT ""c0"".""CustomerID"", ""c0"".""Address"", ""c0"".""City"", ""c0"".""CompanyName"", ""c0"".""ContactName"", ""c0"".""ContactTitle"", ""c0"".""Country"", ""c0"".""Fax"", ""c0"".""Phone"", ""c0"".""PostalCode"", ""c0"".""Region"" + FROM ""Customers"" AS ""c0"" + WHERE ""c0"".""CustomerID"" LIKE 'F%' + UNION + SELECT ""c1"".""CustomerID"", ""c1"".""Address"", ""c1"".""City"", ""c1"".""CompanyName"", ""c1"".""ContactName"", ""c1"".""ContactTitle"", ""c1"".""Country"", ""c1"".""Fax"", ""c1"".""Phone"", ""c1"".""PostalCode"", ""c1"".""Region"" + FROM ""Customers"" AS ""c1"" + WHERE ""c1"".""CustomerID"" LIKE 'A%' +) AS ""t"" +WHERE ""c"".""CustomerID"" = ""t"".""CustomerID"""); + } + + public override async Task Update_Concat_set_constant(bool async) + { + await base.Update_Concat_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +FROM ( + SELECT ""c0"".""CustomerID"", ""c0"".""Address"", ""c0"".""City"", ""c0"".""CompanyName"", ""c0"".""ContactName"", ""c0"".""ContactTitle"", ""c0"".""Country"", ""c0"".""Fax"", ""c0"".""Phone"", ""c0"".""PostalCode"", ""c0"".""Region"" + FROM ""Customers"" AS ""c0"" + WHERE ""c0"".""CustomerID"" LIKE 'F%' + UNION ALL + SELECT ""c1"".""CustomerID"", ""c1"".""Address"", ""c1"".""City"", ""c1"".""CompanyName"", ""c1"".""ContactName"", ""c1"".""ContactTitle"", ""c1"".""Country"", ""c1"".""Fax"", ""c1"".""Phone"", ""c1"".""PostalCode"", ""c1"".""Region"" + FROM ""Customers"" AS ""c1"" + WHERE ""c1"".""CustomerID"" LIKE 'A%' +) AS ""t"" +WHERE ""c"".""CustomerID"" = ""t"".""CustomerID"""); + } + + public override async Task Update_Except_set_constant(bool async) + { + await base.Update_Except_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +FROM ( + SELECT ""c0"".""CustomerID"", ""c0"".""Address"", ""c0"".""City"", ""c0"".""CompanyName"", ""c0"".""ContactName"", ""c0"".""ContactTitle"", ""c0"".""Country"", ""c0"".""Fax"", ""c0"".""Phone"", ""c0"".""PostalCode"", ""c0"".""Region"" + FROM ""Customers"" AS ""c0"" + WHERE ""c0"".""CustomerID"" LIKE 'F%' + EXCEPT + SELECT ""c1"".""CustomerID"", ""c1"".""Address"", ""c1"".""City"", ""c1"".""CompanyName"", ""c1"".""ContactName"", ""c1"".""ContactTitle"", ""c1"".""Country"", ""c1"".""Fax"", ""c1"".""Phone"", ""c1"".""PostalCode"", ""c1"".""Region"" + FROM ""Customers"" AS ""c1"" + WHERE ""c1"".""CustomerID"" LIKE 'A%' +) AS ""t"" +WHERE ""c"".""CustomerID"" = ""t"".""CustomerID"""); + } + + public override async Task Update_Intersect_set_constant(bool async) + { + await base.Update_Intersect_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +FROM ( + SELECT ""c0"".""CustomerID"", ""c0"".""Address"", ""c0"".""City"", ""c0"".""CompanyName"", ""c0"".""ContactName"", ""c0"".""ContactTitle"", ""c0"".""Country"", ""c0"".""Fax"", ""c0"".""Phone"", ""c0"".""PostalCode"", ""c0"".""Region"" + FROM ""Customers"" AS ""c0"" + WHERE ""c0"".""CustomerID"" LIKE 'F%' + INTERSECT + SELECT ""c1"".""CustomerID"", ""c1"".""Address"", ""c1"".""City"", ""c1"".""CompanyName"", ""c1"".""ContactName"", ""c1"".""ContactTitle"", ""c1"".""Country"", ""c1"".""Fax"", ""c1"".""Phone"", ""c1"".""PostalCode"", ""c1"".""Region"" + FROM ""Customers"" AS ""c1"" + WHERE ""c1"".""CustomerID"" LIKE 'A%' +) AS ""t"" +WHERE ""c"".""CustomerID"" = ""t"".""CustomerID"""); + } + + public override async Task Update_with_join_set_constant(bool async) + { + await base.Update_with_join_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +FROM ( + SELECT ""o"".""OrderID"", ""o"".""CustomerID"", ""o"".""EmployeeID"", ""o"".""OrderDate"" + FROM ""Orders"" AS ""o"" + WHERE ""o"".""OrderID"" < 10300 +) AS ""t"" +WHERE ""c"".""CustomerID"" = ""t"".""CustomerID"" AND (""c"".""CustomerID"" LIKE 'F%')"); + } + + public override async Task Update_with_left_join_set_constant(bool async) + { + await base.Update_with_left_join_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +FROM ( + SELECT ""c0"".""CustomerID"", ""c0"".""Address"", ""c0"".""City"", ""c0"".""CompanyName"", ""c0"".""ContactName"", ""c0"".""ContactTitle"", ""c0"".""Country"", ""c0"".""Fax"", ""c0"".""Phone"", ""c0"".""PostalCode"", ""c0"".""Region"", ""t"".""OrderID"", ""t"".""CustomerID"" AS ""CustomerID0"", ""t"".""EmployeeID"", ""t"".""OrderDate"" + FROM ""Customers"" AS ""c0"" + LEFT JOIN ( + SELECT ""o"".""OrderID"", ""o"".""CustomerID"", ""o"".""EmployeeID"", ""o"".""OrderDate"" + FROM ""Orders"" AS ""o"" + WHERE ""o"".""OrderID"" < 10300 + ) AS ""t"" ON ""c0"".""CustomerID"" = ""t"".""CustomerID"" + WHERE ""c0"".""CustomerID"" LIKE 'F%' +) AS ""t0"" +WHERE ""c"".""CustomerID"" = ""t0"".""CustomerID"""); + } + + public override async Task Update_with_cross_join_set_constant(bool async) + { + await base.Update_with_cross_join_set_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +FROM ( + SELECT ""o"".""OrderID"", ""o"".""CustomerID"", ""o"".""EmployeeID"", ""o"".""OrderDate"" + FROM ""Orders"" AS ""o"" + WHERE ""o"".""OrderID"" < 10300 +) AS ""t"" +WHERE ""c"".""CustomerID"" LIKE 'F%'"); + } + + public override async Task Update_with_cross_apply_set_constant(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync(() => base.Update_with_cross_apply_set_constant(async))).Message); + + public override async Task Update_with_outer_apply_set_constant(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync(() => base.Update_with_outer_apply_set_constant(async))).Message); + + public override async Task Update_FromSql_set_constant(bool async) { - await base.Update_multiple_entity_update(async); + await base.Update_FromSql_set_constant(async); AssertExecuteUpdateSql(); } - public override async Task Update_unmapped_property(bool async) + public override async Task Update_Where_SelectMany_subquery_set_null(bool async) + { + await base.Update_Where_SelectMany_subquery_set_null(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Orders"" AS ""o"" + SET ""OrderDate"" = NULL +FROM ( + SELECT ""t"".""OrderID"", ""t"".""CustomerID"", ""t"".""EmployeeID"", ""t"".""OrderDate"", ""c"".""CustomerID"" AS ""CustomerID0"" + FROM ""Customers"" AS ""c"" + INNER JOIN ( + SELECT ""o0"".""OrderID"", ""o0"".""CustomerID"", ""o0"".""EmployeeID"", ""o0"".""OrderDate"" + FROM ""Orders"" AS ""o0"" + WHERE CAST(strftime('%Y', ""o0"".""OrderDate"") AS INTEGER) = 1997 + ) AS ""t"" ON ""c"".""CustomerID"" = ""t"".""CustomerID"" + WHERE ""c"".""CustomerID"" LIKE 'F%' +) AS ""t0"" +WHERE ""o"".""OrderID"" = ""t0"".""OrderID"""); + } + + public override async Task Update_Where_Join_set_property_from_joined_single_result_table(bool async) { - await base.Update_unmapped_property(async); + await base.Update_Where_Join_set_property_from_joined_single_result_table(async); AssertExecuteUpdateSql(); } + public override async Task Update_Where_Join_set_property_from_joined_table(bool async) + { + await base.Update_Where_Join_set_property_from_joined_table(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Customers"" AS ""c"" + SET ""City"" = ""t"".""City"" +FROM ( + SELECT ""c0"".""CustomerID"", ""c0"".""Address"", ""c0"".""City"", ""c0"".""CompanyName"", ""c0"".""ContactName"", ""c0"".""ContactTitle"", ""c0"".""Country"", ""c0"".""Fax"", ""c0"".""Phone"", ""c0"".""PostalCode"", ""c0"".""Region"" + FROM ""Customers"" AS ""c0"" + WHERE ""c0"".""CustomerID"" = 'ALFKI' +) AS ""t"" +WHERE ""c"".""CustomerID"" LIKE 'F%'"); + } + + public override async Task Update_Where_Join_set_property_from_joined_single_result_scalar(bool async) + { + await base.Update_Where_Join_set_property_from_joined_single_result_scalar(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Customers"" AS ""c"" + SET ""City"" = CAST(CAST(strftime('%Y', ( + SELECT ""o"".""OrderDate"" + FROM ""Orders"" AS ""o"" + WHERE ""c"".""CustomerID"" = ""o"".""CustomerID"" + ORDER BY ""o"".""OrderDate"" DESC + LIMIT 1)) AS INTEGER) AS TEXT) +WHERE ""c"".""CustomerID"" LIKE 'F%'"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);