@@ -18,6 +18,8 @@ public class RelationalQueryableMethodTranslatingExpressionVisitor : QueryableMe
1818 private readonly ISqlExpressionFactory _sqlExpressionFactory ;
1919 private readonly bool _subquery ;
2020
21+ private UpdatePropertySelectorUnwrappingExpressionVisitor ? _updatePropertySelectorUnwrappingExpressionVisitor ;
22+
2123 /// <summary>
2224 /// Creates a new instance of the <see cref="QueryableMethodTranslatingExpressionVisitor" /> class.
2325 /// </summary>
@@ -1196,7 +1198,10 @@ static Expression PruneOwnedIncludes(IncludeExpression includeExpression)
11961198 {
11971199 var left = RemapLambdaBody ( source , propertyExpression ) ;
11981200
1199- if ( ! TryProcessPropertyAccess ( RelationalDependencies . Model , ref left , out var ese ) )
1201+ _updatePropertySelectorUnwrappingExpressionVisitor ??= new UpdatePropertySelectorUnwrappingExpressionVisitor ( ) ;
1202+ left = _updatePropertySelectorUnwrappingExpressionVisitor . Visit ( left ) ;
1203+
1204+ if ( ! IsValidPropertyAccess ( RelationalDependencies . Model , left , out var ese ) )
12001205 {
12011206 AddTranslationErrorDetails ( RelationalStrings . InvalidPropertyInSetProperty ( propertyExpression . Print ( ) ) ) ;
12021207 return null ;
@@ -1399,46 +1404,29 @@ when methodCallExpression.Method.IsGenericMethod
13991404 }
14001405 }
14011406
1402- static bool TryProcessPropertyAccess (
1407+ static bool IsValidPropertyAccess (
14031408 IModel model ,
1404- ref Expression expression ,
1409+ Expression expression ,
14051410 [ NotNullWhen ( true ) ] out EntityShaperExpression ? entityShaperExpression )
14061411 {
1407- // Unwrap any object/base-type Convert nodes around the property access expression
1408- expression = expression . UnwrapTypeConversion ( out _ ) ;
1409-
1410- // Identify property access (direct, EF.Property...), while also unwrapping object/base-type Convert nodes on the expression
1411- // being accessed.
1412- if ( expression is MemberExpression memberExpression
1413- && memberExpression . Expression . UnwrapTypeConversion ( out _ ) is EntityShaperExpression ese )
1412+ if ( expression is MemberExpression { Expression : EntityShaperExpression ese } )
14141413 {
1415- expression = memberExpression . Update ( ese ) ;
1416-
14171414 entityShaperExpression = ese ;
14181415 return true ;
14191416 }
14201417
14211418 if ( expression is MethodCallExpression mce )
14221419 {
14231420 if ( mce . TryGetEFPropertyArguments ( out var source , out _ )
1424- && source . UnwrapTypeConversion ( out _ ) is EntityShaperExpression ese1 )
1421+ && source is EntityShaperExpression ese1 )
14251422 {
1426- if ( source != ese1 )
1427- {
1428- var rewrittenArguments = mce . Arguments . ToArray ( ) ;
1429- rewrittenArguments [ 0 ] = ese1 ;
1430- expression = mce . Update ( mce . Object , rewrittenArguments ) ;
1431- }
1432-
14331423 entityShaperExpression = ese1 ;
14341424 return true ;
14351425 }
14361426
14371427 if ( mce . TryGetIndexerArguments ( model , out var source2 , out _ )
1438- && source2 . UnwrapTypeConversion ( out _ ) is EntityShaperExpression ese2 )
1428+ && source2 is EntityShaperExpression ese2 )
14391429 {
1440- expression = mce . Update ( ese2 , mce . Arguments ) ;
1441-
14421430 entityShaperExpression = ese2 ;
14431431 return true ;
14441432 }
@@ -1468,6 +1456,29 @@ static Expression GetEntitySource(IModel model, Expression propertyAccessExpress
14681456 }
14691457 }
14701458
1459+ // For property setter selectors in ExecuteUpdate, this unwraps casts to interface/base class (#29618), as well as IncludeExpressions
1460+ // (which occur when the target entity has owned entities, #28727).
1461+ private class UpdatePropertySelectorUnwrappingExpressionVisitor : ExpressionVisitor
1462+ {
1463+ [ return : NotNullIfNotNull ( nameof ( node ) ) ]
1464+ public override Expression ? Visit ( Expression ? node )
1465+ {
1466+ if ( node is null )
1467+ {
1468+ return node ;
1469+ }
1470+
1471+ node = node . UnwrapTypeConversion ( out _ ) ;
1472+
1473+ if ( node is IncludeExpression includeExpression )
1474+ {
1475+ node = Visit ( includeExpression . EntityExpression ) ;
1476+ }
1477+
1478+ return base . Visit ( node ) ;
1479+ }
1480+ }
1481+
14711482 /// <summary>
14721483 /// Checks weather the current select expression can be used as-is for execute a delete operation,
14731484 /// or whether it must be pushed down into a subquery.
@@ -1662,11 +1673,9 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
16621673 }
16631674
16641675 protected override Expression VisitExtension ( Expression extensionExpression )
1665- => extensionExpression is EntityShaperExpression
1666- || extensionExpression is ShapedQueryExpression
1667- || extensionExpression is GroupByShaperExpression
1668- ? extensionExpression
1669- : base . VisitExtension ( extensionExpression ) ;
1676+ => extensionExpression is EntityShaperExpression or ShapedQueryExpression or GroupByShaperExpression
1677+ ? extensionExpression
1678+ : base . VisitExtension ( extensionExpression ) ;
16701679
16711680 private Expression ? TryExpand ( Expression ? source , MemberIdentity member )
16721681 {
0 commit comments