diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs index bae6307727a..0d7ddc80370 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs @@ -17,10 +17,7 @@ public partial class RelationalQueryableMethodTranslatingExpressionVisitor /// The non query after translation. protected virtual NonQueryExpression? TranslateExecuteDelete(ShapedQueryExpression source) { - if (source.ShaperExpression is IncludeExpression includeExpression) - { - source = source.UpdateShaperExpression(PruneIncludes(includeExpression)); - } + source = source.UpdateShaperExpression(new IncludePruner().Visit(source.ShaperExpression)); if (source.ShaperExpression is not StructuralTypeShaperExpression { StructuralType: IEntityType entityType } shaper) { diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs index 9473fb1ab9a..9b2cc06f5f3 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs @@ -33,10 +33,7 @@ public partial class RelationalQueryableMethodTranslatingExpressionVisitor { // Our source may have IncludeExpressions because of owned entities or auto-include; unwrap these, as they're meaningless for // ExecuteUpdate's lambdas. Note that we don't currently support updates across tables. - if (source.ShaperExpression is IncludeExpression includeExpression) - { - source = source.UpdateShaperExpression(PruneIncludes(includeExpression)); - } + source = source.UpdateShaperExpression(new IncludePruner().Visit(source.ShaperExpression)); var setters = new List<(LambdaExpression PropertySelector, Expression ValueExpression)>(); PopulateSetPropertyCalls(setPropertyCalls.Body, setters, setPropertyCalls.Parameters[0]); diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 4b9c1b147e3..08cfbfcd9d0 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -1342,16 +1342,15 @@ private Expression RemapLambdaBody(ShapedQueryExpression shapedQueryExpression, private Expression ExpandSharedTypeEntities(SelectExpression selectExpression, Expression lambdaBody) => _sharedTypeEntityExpandingExpressionVisitor.Expand(selectExpression, lambdaBody); - private static Expression PruneIncludes(IncludeExpression includeExpression) + private sealed class IncludePruner : ExpressionVisitor { - if (includeExpression.Navigation is ISkipNavigation or not INavigation) - { - return includeExpression; - } - - return includeExpression.EntityExpression is IncludeExpression innerIncludeExpression - ? PruneIncludes(innerIncludeExpression) - : includeExpression.EntityExpression; + protected override Expression VisitExtension(Expression node) + => node switch + { + IncludeExpression { Navigation: ISkipNavigation or not INavigation } i => i, + IncludeExpression i => Visit(i.EntityExpression), + _ => base.VisitExtension(node) + }; } private sealed class SharedTypeEntityExpandingExpressionVisitor : ExpressionVisitor diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs index b07fc573589..f4b7723e9d9 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs @@ -122,6 +122,24 @@ await AssertUpdate( rowsAffectedCount: 0); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Update_non_owned_property_on_entity_with_owned_in_join(bool async) + { + var contextFactory = await InitializeAsync( + onModelCreating: mb => + { + mb.Entity().OwnsOne(o => o.OwnedReference); + }); + + await AssertUpdate( + async, + contextFactory.CreateContext, + ss => ss.Set().Join(ss.Set(), o => o.Id, i => i.Id, (o, i) => new { Outer = o, Inner = i}), + s => s.SetProperty(t => t.Outer.Title, "NewValue"), + rowsAffectedCount: 0); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual async Task Update_owned_and_non_owned_properties_with_table_sharing(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs index 112109b057c..d399fc5bf0a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs @@ -65,6 +65,19 @@ FROM [Owner] AS [o] """); } + public override async Task Update_non_owned_property_on_entity_with_owned_in_join(bool async) + { + await base.Update_non_owned_property_on_entity_with_owned_in_join(async); + + AssertSql( + """ +UPDATE [o] +SET [o].[Title] = N'NewValue' +FROM [Owner] AS [o] +INNER JOIN [Owner] AS [o0] ON [o].[Id] = [o0].[Id] +"""); + } + public override async Task Update_owned_and_non_owned_properties_with_table_sharing(bool async) { await base.Update_owned_and_non_owned_properties_with_table_sharing(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs index 3064635eaa4..8925722d997 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs @@ -61,6 +61,19 @@ public override async Task Update_non_owned_property_on_entity_with_owned2(bool """); } + public override async Task Update_non_owned_property_on_entity_with_owned_in_join(bool async) + { + await base.Update_non_owned_property_on_entity_with_owned_in_join(async); + + AssertSql( + """ +UPDATE "Owner" AS "o" +SET "Title" = 'NewValue' +FROM "Owner" AS "o0" +WHERE "o"."Id" = "o0"."Id" +"""); + } + public override async Task Update_owned_and_non_owned_properties_with_table_sharing(bool async) { await base.Update_owned_and_non_owned_properties_with_table_sharing(async);