From 4262584865be83b9b600775b5fd7a043e48710cb Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 20 Sep 2022 08:42:02 +0200 Subject: [PATCH] Allow specifying property value without lambda in ExecuteUpdate (#29139) And make SetProperty accept a non-expression lambda directly Closes #28968 --- ...yableMethodTranslatingExpressionVisitor.cs | 34 +++-- .../Query/SetPropertyCalls.cs | 22 ++- .../FiltersInheritanceBulkUpdatesTestBase.cs | 12 +- .../InheritanceBulkUpdatesTestBase.cs | 12 +- .../NorthwindBulkUpdatesTestBase.cs | 136 ++++++++++++------ .../EntitySplittingTestBase.cs | 4 +- .../TableSplittingTestBase.cs | 4 +- .../NorthwindBulkUpdatesSqlServerTest.cs | 43 +++++- .../NorthwindBulkUpdatesSqliteTest.cs | 40 +++++- 9 files changed, 229 insertions(+), 78 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 2dc88ae1c9b..1c75ce8ef19 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -1152,7 +1152,7 @@ static Expression PruneOwnedIncludes(IncludeExpression includeExpression) /// /// The lambda expression containing /// + /// cref="SetPropertyCalls{TSource}.SetProperty{TProperty}(Func{TSource, TProperty}, Func{TSource, TProperty})" /> /// statements. /// /// The non query after translation. @@ -1160,7 +1160,7 @@ static Expression PruneOwnedIncludes(IncludeExpression includeExpression) ShapedQueryExpression source, LambdaExpression setPropertyCalls) { - var propertyValueLambdaExpressions = new List<(LambdaExpression, LambdaExpression)>(); + var propertyValueLambdaExpressions = new List<(LambdaExpression, Expression)>(); PopulateSetPropertyCalls(setPropertyCalls.Body, propertyValueLambdaExpressions, setPropertyCalls.Parameters[0]); if (TranslationErrorDetails != null) { @@ -1174,7 +1174,7 @@ static Expression PruneOwnedIncludes(IncludeExpression includeExpression) } EntityShaperExpression? entityShaperExpression = null; - var remappedUnwrappeLeftExpressions = new List(); + var remappedUnwrappedLeftExpressions = new List(); foreach (var (propertyExpression, _) in propertyValueLambdaExpressions) { var left = RemapLambdaBody(source, propertyExpression); @@ -1197,7 +1197,7 @@ static Expression PruneOwnedIncludes(IncludeExpression includeExpression) return null; } - remappedUnwrappeLeftExpressions.Add(left); + remappedUnwrappedLeftExpressions.Add(left); } Check.DebugAssert(entityShaperExpression != null, "EntityShaperExpression should have a value."); @@ -1233,7 +1233,7 @@ static Expression PruneOwnedIncludes(IncludeExpression includeExpression) { return TranslateSetPropertyExpressions( this, source, selectExpression, tableExpression, - propertyValueLambdaExpressions, remappedUnwrappeLeftExpressions); + propertyValueLambdaExpressions, remappedUnwrappedLeftExpressions); } // We need to convert to join with original query using PK @@ -1279,9 +1279,13 @@ static Expression PruneOwnedIncludes(IncludeExpression includeExpression) entitySource), propertyReplacement, propertyExpression.Body), transparentIdentifierParameter); - valueExpression = Expression.Lambda( - ReplacingExpressionVisitor.Replace(valueExpression.Parameters[0], valueReplacement, valueExpression.Body), - transparentIdentifierParameter); + + valueExpression = valueExpression is LambdaExpression lambdaExpression + ? Expression.Lambda( + ReplacingExpressionVisitor.Replace(lambdaExpression.Parameters[0], valueReplacement, lambdaExpression.Body), + transparentIdentifierParameter) + : valueExpression; + propertyValueLambdaExpressions[i] = (propertyExpression, valueExpression); } @@ -1294,7 +1298,7 @@ static Expression PruneOwnedIncludes(IncludeExpression includeExpression) ShapedQueryExpression source, SelectExpression selectExpression, TableExpression tableExpression, - List<(LambdaExpression, LambdaExpression)> propertyValueLambdaExpressions, + List<(LambdaExpression, Expression)> propertyValueLambdaExpressions, List? leftExpressions) { var columnValueSetters = new List(); @@ -1312,7 +1316,10 @@ static Expression PruneOwnedIncludes(IncludeExpression includeExpression) left = left.UnwrapTypeConversion(out _); } - var right = visitor.RemapLambdaBody(source, valueExpression); + var right = valueExpression is LambdaExpression lambdaExpression + ? visitor.RemapLambdaBody(source, lambdaExpression) + : valueExpression; + if (right.Type != left.Type) { right = Expression.Convert(right, left.Type); @@ -1348,7 +1355,7 @@ static Expression PruneOwnedIncludes(IncludeExpression includeExpression) void PopulateSetPropertyCalls( Expression expression, - List<(LambdaExpression, LambdaExpression)> list, + List<(LambdaExpression, Expression)> list, ParameterExpression parameter) { switch (expression) @@ -1363,9 +1370,8 @@ when methodCallExpression.Method.IsGenericMethod && methodCallExpression.Method.DeclaringType!.IsGenericType && methodCallExpression.Method.DeclaringType.GetGenericTypeDefinition() == typeof(SetPropertyCalls<>): - list.Add( - (methodCallExpression.Arguments[0].UnwrapLambdaFromQuote(), - methodCallExpression.Arguments[1].UnwrapLambdaFromQuote())); + list.Add(((LambdaExpression)methodCallExpression.Arguments[0], methodCallExpression.Arguments[1])); + PopulateSetPropertyCalls(methodCallExpression.Object!, list, parameter); break; diff --git a/src/EFCore.Relational/Query/SetPropertyCalls.cs b/src/EFCore.Relational/Query/SetPropertyCalls.cs index cbd60a922ef..6508452059c 100644 --- a/src/EFCore.Relational/Query/SetPropertyCalls.cs +++ b/src/EFCore.Relational/Query/SetPropertyCalls.cs @@ -34,11 +34,27 @@ private SetPropertyCalls() /// A value expression. /// /// The same instance so that multiple calls to - /// can be chained. + /// + /// can be chained. /// public SetPropertyCalls SetProperty( - Expression> propertyExpression, - Expression> valueExpression) + Func propertyExpression, + Func valueExpression) + => throw new InvalidOperationException(RelationalStrings.SetPropertyMethodInvoked); + + /// + /// Specifies a property and corresponding value it should be updated to in ExecuteUpdate method. + /// + /// The type of property. + /// A property access expression. + /// A value expression. + /// + /// The same instance so that multiple calls to + /// can be chained. + /// + public SetPropertyCalls SetProperty( + Func propertyExpression, + TProperty valueExpression) => throw new InvalidOperationException(RelationalStrings.SetPropertyMethodInvoked); #region Hidden System.Object members diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs index 0752cec0e17..686fb24805d 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs @@ -102,7 +102,7 @@ public virtual Task Update_where_hierarchy(bool async) async, ss => ss.Set().Where(e => e.Name == "Great spotted kiwi"), e => e, - s => s.SetProperty(e => e.Name, e => "Animal"), + s => s.SetProperty(e => e.Name, "Animal"), rowsAffectedCount: 1, (b, a) => a.ForEach(e => Assert.Equal("Animal", e.Name))); @@ -113,7 +113,7 @@ public virtual Task Update_where_hierarchy_subquery(bool async) async, ss => ss.Set().Where(e => e.Name == "Great spotted kiwi").OrderBy(e => e.Name).Skip(0).Take(3), e => e, - s => s.SetProperty(e => e.Name, e => "Animal"), + s => s.SetProperty(e => e.Name, "Animal"), rowsAffectedCount: 1); [ConditionalTheory] @@ -123,7 +123,7 @@ public virtual Task Update_where_hierarchy_derived(bool async) async, ss => ss.Set().Where(e => e.Name == "Great spotted kiwi"), e => e, - s => s.SetProperty(e => e.Name, e => "Kiwi"), + s => s.SetProperty(e => e.Name, "Kiwi"), rowsAffectedCount: 1); [ConditionalTheory] @@ -133,7 +133,7 @@ public virtual Task Update_where_using_hierarchy(bool async) async, ss => ss.Set().Where(e => e.Animals.Where(a => a.CountryId > 0).Count() > 0), e => e, - s => s.SetProperty(e => e.Name, e => "Monovia"), + s => s.SetProperty(e => e.Name, "Monovia"), rowsAffectedCount: 1); [ConditionalTheory] @@ -143,7 +143,7 @@ public virtual Task Update_where_using_hierarchy_derived(bool async) async, ss => ss.Set().Where(e => e.Animals.OfType().Where(a => a.CountryId > 0).Count() > 0), e => e, - s => s.SetProperty(e => e.Name, e => "Monovia"), + s => s.SetProperty(e => e.Name, "Monovia"), rowsAffectedCount: 1); [ConditionalTheory] @@ -155,7 +155,7 @@ public virtual Task Update_where_keyless_entity_mapped_to_sql_query(bool async) async, ss => ss.Set().Where(e => e.CountryId > 0), e => e, - s => s.SetProperty(e => e.Name, e => "Eagle"), + s => s.SetProperty(e => e.Name, "Eagle"), rowsAffectedCount: 1)); protected abstract void ClearLog(); diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs index f7beaa7aeed..08b871a55d8 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs @@ -102,7 +102,7 @@ public virtual Task Update_where_hierarchy(bool async) async, ss => ss.Set().Where(e => e.Name == "Great spotted kiwi"), e => e, - s => s.SetProperty(e => e.Name, e => "Animal"), + s => s.SetProperty(e => e.Name, "Animal"), rowsAffectedCount: 1, (b, a) => a.ForEach(e => Assert.Equal("Animal", e.Name))); @@ -113,7 +113,7 @@ public virtual Task Update_where_hierarchy_subquery(bool async) async, ss => ss.Set().Where(e => e.Name == "Great spotted kiwi").OrderBy(e => e.Name).Skip(0).Take(3), e => e, - s => s.SetProperty(e => e.Name, e => "Animal"), + s => s.SetProperty(e => e.Name, "Animal"), rowsAffectedCount: 1); [ConditionalTheory] @@ -123,7 +123,7 @@ public virtual Task Update_where_hierarchy_derived(bool async) async, ss => ss.Set().Where(e => e.Name == "Great spotted kiwi"), e => e, - s => s.SetProperty(e => e.Name, e => "Kiwi"), + s => s.SetProperty(e => e.Name, "Kiwi"), rowsAffectedCount: 1); [ConditionalTheory] @@ -133,7 +133,7 @@ public virtual Task Update_where_using_hierarchy(bool async) async, ss => ss.Set().Where(e => e.Animals.Where(a => a.CountryId > 0).Count() > 0), e => e, - s => s.SetProperty(e => e.Name, e => "Monovia"), + s => s.SetProperty(e => e.Name, "Monovia"), rowsAffectedCount: 2); [ConditionalTheory] @@ -143,7 +143,7 @@ public virtual Task Update_where_using_hierarchy_derived(bool async) async, ss => ss.Set().Where(e => e.Animals.OfType().Where(a => a.CountryId > 0).Count() > 0), e => e, - s => s.SetProperty(e => e.Name, e => "Monovia"), + s => s.SetProperty(e => e.Name, "Monovia"), rowsAffectedCount: 1); [ConditionalTheory] @@ -155,7 +155,7 @@ public virtual Task Update_where_keyless_entity_mapped_to_sql_query(bool async) async, ss => ss.Set().Where(e => e.CountryId > 0), e => e, - s => s.SetProperty(e => e.Name, e => "Eagle"), + s => s.SetProperty(e => e.Name, "Eagle"), rowsAffectedCount: 1)); protected abstract void ClearLog(); diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs index 1d5f6adbdb2..ff708de6c77 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs @@ -371,7 +371,7 @@ public virtual Task Update_Where_set_constant_TagWith(bool async) async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).TagWith("MyUpdate"), e => e, - s => s.SetProperty(c => c.ContactName, c => "Updated"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 8, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -382,7 +382,7 @@ public virtual Task Update_Where_set_constant(bool async) async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), e => e, - s => s.SetProperty(c => c.ContactName, c => "Updated"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 8, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -395,7 +395,7 @@ await AssertUpdate( async, ss => ss.Set().Where(c => c.CustomerID == customer), e => e, - s => s.SetProperty(c => c.ContactName, c => "Updated"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 1, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -404,7 +404,7 @@ await AssertUpdate( async, ss => ss.Set().Where(c => c.CustomerID == customer), e => e, - s => s.SetProperty(c => c.ContactName, c => "Updated"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 0, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); } @@ -418,11 +418,61 @@ public virtual Task Update_Where_set_parameter(bool async) async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), e => e, - s => s.SetProperty(c => c.ContactName, c => value), + s => s.SetProperty(c => c.ContactName, value), rowsAffectedCount: 8, (b, a) => Assert.All(a, c => Assert.Equal("Abc", c.ContactName))); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Where_set_parameter_from_closure_array(bool async) + { + var array = new[] { "Abc", "Def" }; + return AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), + e => e, + s => s.SetProperty(c => c.ContactName, array[0]), + rowsAffectedCount: 8, + (b, a) => Assert.All(a, c => Assert.Equal("Abc", c.ContactName))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Where_set_parameter_from_inline_list(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), + e => e, + s => s.SetProperty(c => c.ContactName, new List { "Abc", "Def" }[0]), + rowsAffectedCount: 8, + (b, a) => Assert.All(a, c => Assert.Equal("Abc", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Where_set_parameter_from_multilevel_property_access(bool async) + { + var container = new Container { Containee = new() { Property = "Abc" } }; + + return AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), + e => e, + s => s.SetProperty(c => c.ContactName, container.Containee.Property), + rowsAffectedCount: 8, + (b, a) => Assert.All(a, c => Assert.Equal("Abc", c.ContactName))); + } + + class Container + { + public Containee Containee { get; set; } + } + + class Containee + { + public string Property { get; set; } + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Update_Where_Skip_set_constant(bool async) @@ -430,7 +480,7 @@ public virtual Task Update_Where_Skip_set_constant(bool async) async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).Skip(4), e => e, - s => s.SetProperty(c => c.ContactName, c => "Updated"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 4, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -441,7 +491,7 @@ public virtual Task Update_Where_Take_set_constant(bool async) async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).Take(4), e => e, - s => s.SetProperty(c => c.ContactName, c => "Updated"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 4, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -452,7 +502,7 @@ public virtual Task Update_Where_Skip_Take_set_constant(bool async) async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).Skip(2).Take(4), e => e, - s => s.SetProperty(c => c.ContactName, c => "Updated"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 4, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -463,7 +513,7 @@ public virtual Task Update_Where_OrderBy_set_constant(bool async) async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).OrderBy(c => c.City), e => e, - s => s.SetProperty(c => c.ContactName, c => "Updated"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 8, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -474,7 +524,7 @@ public virtual Task Update_Where_OrderBy_Skip_set_constant(bool async) 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"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 4, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -485,7 +535,7 @@ public virtual Task Update_Where_OrderBy_Take_set_constant(bool async) 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"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 4, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -496,7 +546,7 @@ public virtual Task Update_Where_OrderBy_Skip_Take_set_constant(bool async) 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"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 4, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -507,7 +557,7 @@ public virtual Task Update_Where_OrderBy_Skip_Take_Skip_Take_set_constant(bool a 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"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 2, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -522,7 +572,7 @@ public virtual Task Update_Where_GroupBy_aggregate_set_constant(bool async) == ss.Set() .GroupBy(e => e.CustomerID).Where(g => g.Count() > 11).Select(e => e.Key).FirstOrDefault()), e => e, - s => s.SetProperty(c => c.ContactName, c => "Updated"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 1, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -537,7 +587,7 @@ public virtual Task Update_Where_GroupBy_First_set_constant(bool async) == ss.Set() .GroupBy(e => e.CustomerID).Where(g => g.Count() > 11).Select(e => e.First().CustomerID).FirstOrDefault()), e => e, - s => s.SetProperty(c => c.ContactName, c => "Updated"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 1, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -552,7 +602,7 @@ public virtual Task Update_Where_GroupBy_First_set_constant_2(bool async) == ss.Set() .GroupBy(e => e.CustomerID).Where(g => g.Count() > 11).Select(e => e.First().Customer).FirstOrDefault()), e => e, - s => s.SetProperty(c => c.ContactName, c => "Updated"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 1, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -566,7 +616,7 @@ public virtual Task Update_Where_GroupBy_First_set_constant_3(bool async) c => ss.Set() .GroupBy(e => e.CustomerID).Where(g => g.Count() > 11).Select(e => e.First().Customer).Contains(c)), e => e, - s => s.SetProperty(c => c.ContactName, c => "Updated"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 24, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -577,7 +627,7 @@ public virtual Task Update_Where_Distinct_set_constant(bool async) async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).Distinct(), e => e, - s => s.SetProperty(c => c.ContactName, c => "Updated"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 8, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -588,7 +638,7 @@ public virtual Task Update_Where_using_navigation_set_null(bool async) async, ss => ss.Set().Where(o => o.Customer.City == "Seattle"), e => e, - s => s.SetProperty(c => c.OrderDate, c => null), + s => s.SetProperty(c => c.OrderDate, (DateTime?)null), rowsAffectedCount: 14, (b, a) => Assert.All(a, c => Assert.Null(c.OrderDate))); @@ -599,7 +649,7 @@ public virtual Task Update_Where_using_navigation_2_set_constant(bool async) async, ss => ss.Set().Where(od => od.Order.Customer.City == "Seattle"), e => e, - s => s.SetProperty(c => c.Quantity, c => 1), + s => s.SetProperty(c => c.Quantity, 1), rowsAffectedCount: 40, (b, a) => Assert.All(a, c => Assert.Equal(1, c.Quantity))); @@ -610,7 +660,7 @@ public virtual Task Update_Where_SelectMany_set_null(bool async) async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).SelectMany(c => c.Orders), e => e, - s => s.SetProperty(c => c.OrderDate, c => null), + s => s.SetProperty(c => c.OrderDate, (DateTime?)null), rowsAffectedCount: 63, (b, a) => Assert.All(a, c => Assert.Null(c.OrderDate))); @@ -657,7 +707,7 @@ public virtual Task Update_Where_set_constant_using_ef_property(bool async) async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), e => e, - s => s.SetProperty(c => EF.Property(c, "ContactName"), c => "Updated"), + s => s.SetProperty(c => EF.Property(c, "ContactName"), "Updated"), rowsAffectedCount: 8, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -668,7 +718,7 @@ public virtual Task Update_Where_set_null(bool async) async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), e => e, - s => s.SetProperty(c => c.ContactName, c => null), + s => s.SetProperty(c => c.ContactName, (string)null), rowsAffectedCount: 8, (b, a) => Assert.All(a, c => Assert.Null(c.ContactName))); @@ -705,7 +755,7 @@ public virtual Task Update_Where_multiple_set(bool async) async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), e => e, - s => s.SetProperty(c => c.ContactName, c => value).SetProperty(c => c.City, c => "Seattle"), + s => s.SetProperty(c => c.ContactName, c => value).SetProperty(c => c.City, "Seattle"), rowsAffectedCount: 8, (b, a) => Assert.All( a, c => @@ -724,7 +774,7 @@ public virtual Task Update_with_invalid_lambda_in_set_property_throws(bool async async, ss => ss.Set().Where(od => od.OrderID < 10250), e => e, - s => s.SetProperty(e => e.MaybeScalar(e => e.OrderID), e => 10300), + s => s.SetProperty(e => e.MaybeScalar(e => e.OrderID), 10300), rowsAffectedCount: 0)); [ConditionalTheory] @@ -737,7 +787,7 @@ public virtual Task Update_multiple_entity_throws(bool async) ss => ss.Set().Where(o => o.CustomerID.StartsWith("F")) .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)), + s => s.SetProperty(c => c.Customer.ContactName, "Name").SetProperty(c => c.e.OrderDate, new DateTime(2020, 1, 1)), rowsAffectedCount: 0)); [ConditionalTheory] @@ -745,13 +795,13 @@ public virtual Task Update_multiple_entity_throws(bool async) public virtual Task Update_unmapped_property_throws(bool async) => AssertTranslationFailed( RelationalStrings.UnableToTranslateSetProperty( - "c => c.IsLondon", "c => True", + "c => c.IsLondon", "True", CoreStrings.QueryUnableToTranslateMember("IsLondon", "Customer")), () => AssertUpdate( async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), e => e, - s => s.SetProperty(c => c.IsLondon, c => true), + s => s.SetProperty(c => c.IsLondon, true), rowsAffectedCount: 0)); [ConditionalTheory] @@ -762,7 +812,7 @@ public virtual Task Update_Union_set_constant(bool 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"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 12, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -774,7 +824,7 @@ public virtual Task Update_Concat_set_constant(bool 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"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 12, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -786,7 +836,7 @@ public virtual Task Update_Except_set_constant(bool 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"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 8, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -798,7 +848,7 @@ public virtual Task Update_Intersect_set_constant(bool 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"), + s => s.SetProperty(c => c.ContactName, "Updated"), rowsAffectedCount: 0, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -812,7 +862,7 @@ 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"), + s => s.SetProperty(c => c.c.ContactName, "Updated"), rowsAffectedCount: 2, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -827,7 +877,7 @@ 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"), + s => s.SetProperty(c => c.c.ContactName, "Updated"), rowsAffectedCount: 8, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -840,7 +890,7 @@ public virtual Task Update_with_cross_join_set_constant(bool async) 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"), + s => s.SetProperty(c => c.c.ContactName, "Updated"), rowsAffectedCount: 8, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -853,7 +903,7 @@ public virtual Task Update_with_cross_apply_set_constant(bool async) 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"), + s => s.SetProperty(c => c.c.ContactName, "Updated"), rowsAffectedCount: 0, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -866,7 +916,7 @@ public virtual Task Update_with_outer_apply_set_constant(bool async) 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"), + s => s.SetProperty(c => c.c.ContactName, "Updated"), rowsAffectedCount: 8, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -887,7 +937,7 @@ from o in grouping.DefaultIfEmpty() o }, e => e.c, - s => s.SetProperty(c => c.c.ContactName, c => "Updated"), + s => s.SetProperty(c => c.c.ContactName, "Updated"), rowsAffectedCount: 8, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -901,7 +951,7 @@ from c2 in ss.Set().Where(c => c.City.StartsWith("S")) 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"), + s => s.SetProperty(c => c.c.ContactName, "Updated"), rowsAffectedCount: 0, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -920,7 +970,7 @@ from o in ss.Set().Where(o => o.OrderID < 10300 && o.OrderDate.Value.Year o }, e => e.c, - s => s.SetProperty(c => c.c.ContactName, c => "Updated"), + s => s.SetProperty(c => c.c.ContactName, "Updated"), rowsAffectedCount: 8, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); @@ -938,7 +988,7 @@ await TestHelpers.ExecuteWithStrategyInTransactionAsync( @"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"))); + .ExecuteUpdateAsync(s => s.SetProperty(c => c.ContactName, "Updated"))); } else { @@ -950,7 +1000,7 @@ FROM [Customers] @"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"))); + .ExecuteUpdate(s => s.SetProperty(c => c.ContactName, "Updated"))); } } @@ -962,7 +1012,7 @@ public virtual Task Update_Where_SelectMany_subquery_set_null(bool 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), + s => s.SetProperty(c => c.OrderDate, (DateTime?)null), rowsAffectedCount: 35, (b, a) => Assert.All(a, c => Assert.Null(c.OrderDate))); diff --git a/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs index 26d71232400..7c25241fa52 100644 --- a/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs @@ -82,7 +82,7 @@ await TestHelpers.ExecuteWithStrategyInTransactionAsync( RelationalStrings.NonQueryTranslationFailedWithDetails( "", RelationalStrings.ExecuteOperationOnEntitySplitting("ExecuteUpdate", "MeterReading"))[21..], (await Assert.ThrowsAsync( - () => context.MeterReadings.ExecuteUpdateAsync(s => s.SetProperty(m => m.CurrentRead, m => "Value")))).Message)); + () => context.MeterReadings.ExecuteUpdateAsync(s => s.SetProperty(m => m.CurrentRead, "Value")))).Message)); } else { @@ -93,7 +93,7 @@ await TestHelpers.ExecuteWithStrategyInTransactionAsync( RelationalStrings.NonQueryTranslationFailedWithDetails( "", RelationalStrings.ExecuteOperationOnEntitySplitting("ExecuteUpdate", "MeterReading"))[21..], Assert.Throws( - () => context.MeterReadings.ExecuteUpdate(s => s.SetProperty(m => m.CurrentRead, m => "Value"))).Message)); + () => context.MeterReadings.ExecuteUpdate(s => s.SetProperty(m => m.CurrentRead, "Value"))).Message)); } } diff --git a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs index 6724ca7aa3c..1d027b23dde 100644 --- a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs @@ -657,7 +657,7 @@ public virtual async Task ExecuteUpdate_works_for_table_sharing(bool async) await TestHelpers.ExecuteWithStrategyInTransactionAsync( CreateContext, UseTransaction, - async context => await context.Set().ExecuteUpdateAsync(s => s.SetProperty(e => e.SeatingCapacity, e => 1)), + async context => await context.Set().ExecuteUpdateAsync(s => s.SetProperty(e => e.SeatingCapacity, 1)), context => { Assert.True(context.Set().All(e => e.SeatingCapacity == 1)); @@ -670,7 +670,7 @@ await TestHelpers.ExecuteWithStrategyInTransactionAsync( TestHelpers.ExecuteWithStrategyInTransaction( CreateContext, UseTransaction, - context => context.Set().ExecuteUpdate(s => s.SetProperty(e => e.SeatingCapacity, e => 1)), + context => context.Set().ExecuteUpdate(s => s.SetProperty(e => e.SeatingCapacity, 1)), context => Assert.True(context.Set().All(e => e.SeatingCapacity == 1))); } } diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs index 503e5eaf819..3efcc5a797e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs @@ -5,9 +5,13 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; public class NorthwindBulkUpdatesSqlServerTest : NorthwindBulkUpdatesTestBase> { - public NorthwindBulkUpdatesSqlServerTest(NorthwindBulkUpdatesSqlServerFixture fixture) + public NorthwindBulkUpdatesSqlServerTest( + NorthwindBulkUpdatesSqlServerFixture fixture, + ITestOutputHelper testOutputHelper) : base(fixture) { + ClearLog(); + // Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } [ConditionalFact] @@ -609,6 +613,43 @@ FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'F%'"); } + public override async Task Update_Where_set_parameter_from_closure_array(bool async) + { + await base.Update_Where_set_parameter_from_closure_array(async); + + AssertExecuteUpdateSql( + @"@__p_0='Abc' (Size = 4000) + +UPDATE [c] +SET [c].[ContactName] = @__p_0 +FROM [Customers] AS [c] +WHERE [c].[CustomerID] LIKE N'F%'"); + } + + public override async Task Update_Where_set_parameter_from_inline_list(bool async) + { + await base.Update_Where_set_parameter_from_inline_list(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] +SET [c].[ContactName] = N'Abc' +FROM [Customers] AS [c] +WHERE [c].[CustomerID] LIKE N'F%'"); + } + + public override async Task Update_Where_set_parameter_from_multilevel_property_access(bool async) + { + await base.Update_Where_set_parameter_from_multilevel_property_access(async); + + AssertExecuteUpdateSql( + @"@__container_Containee_Property_0='Abc' (Size = 4000) + +UPDATE [c] +SET [c].[ContactName] = @__container_Containee_Property_0 +FROM [Customers] AS [c] +WHERE [c].[CustomerID] LIKE N'F%'"); + } + public override async Task Update_Where_Skip_set_constant(bool async) { await base.Update_Where_Skip_set_constant(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs index fbd9b9c4572..1795ff9fa90 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs @@ -7,9 +7,13 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; public class NorthwindBulkUpdatesSqliteTest : NorthwindBulkUpdatesTestBase> { - public NorthwindBulkUpdatesSqliteTest(NorthwindBulkUpdatesSqliteFixture fixture) + public NorthwindBulkUpdatesSqliteTest( + NorthwindBulkUpdatesSqliteFixture fixture, + ITestOutputHelper testOutputHelper) : base(fixture) { + ClearLog(); + // Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } [ConditionalFact] @@ -587,6 +591,40 @@ public override async Task Update_Where_set_parameter(bool async) WHERE ""c"".""CustomerID"" LIKE 'F%'"); } + public override async Task Update_Where_set_parameter_from_closure_array(bool async) + { + await base.Update_Where_set_parameter_from_closure_array(async); + + AssertExecuteUpdateSql( + @"@__p_0='Abc' (Size = 3) + +UPDATE ""Customers"" AS ""c"" +SET ""ContactName"" = @__p_0 +WHERE ""c"".""CustomerID"" LIKE 'F%'"); + } + + public override async Task Update_Where_set_parameter_from_inline_list(bool async) + { + await base.Update_Where_set_parameter_from_inline_list(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Customers"" AS ""c"" +SET ""ContactName"" = 'Abc' +WHERE ""c"".""CustomerID"" LIKE 'F%'"); + } + + public override async Task Update_Where_set_parameter_from_multilevel_property_access(bool async) + { + await base.Update_Where_set_parameter_from_multilevel_property_access(async); + + AssertExecuteUpdateSql( + @"@__container_Containee_Property_0='Abc' (Size = 3) + +UPDATE ""Customers"" AS ""c"" +SET ""ContactName"" = @__container_Containee_Property_0 +WHERE ""c"".""CustomerID"" LIKE 'F%'"); + } + public override async Task Update_Where_Skip_set_constant(bool async) { await base.Update_Where_Skip_set_constant(async);