Skip to content

Commit 124d334

Browse files
committed
Support interfaces in ExecuteUpdate setter property lambda
Fixes #29618
1 parent 3f82c2b commit 124d334

File tree

10 files changed

+204
-16
lines changed

10 files changed

+204
-16
lines changed

src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,8 +1195,8 @@ static Expression PruneOwnedIncludes(IncludeExpression includeExpression)
11951195
foreach (var (propertyExpression, _) in propertyValueLambdaExpressions)
11961196
{
11971197
var left = RemapLambdaBody(source, propertyExpression);
1198-
left = left.UnwrapTypeConversion(out _);
1199-
if (!IsValidPropertyAccess(RelationalDependencies.Model, left, out var ese))
1198+
1199+
if (!TryProcessPropertyAccess(RelationalDependencies.Model, ref left, out var ese))
12001200
{
12011201
AddTranslationErrorDetails(RelationalStrings.InvalidPropertyInSetProperty(propertyExpression.Print()));
12021202
return null;
@@ -1399,29 +1399,46 @@ when methodCallExpression.Method.IsGenericMethod
13991399
}
14001400
}
14011401

1402-
static bool IsValidPropertyAccess(
1402+
static bool TryProcessPropertyAccess(
14031403
IModel model,
1404-
Expression expression,
1404+
ref Expression expression,
14051405
[NotNullWhen(true)] out EntityShaperExpression? entityShaperExpression)
14061406
{
1407-
if (expression is MemberExpression { Expression: EntityShaperExpression ese })
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)
14081414
{
1415+
expression = memberExpression.Update(ese);
1416+
14091417
entityShaperExpression = ese;
14101418
return true;
14111419
}
14121420

14131421
if (expression is MethodCallExpression mce)
14141422
{
14151423
if (mce.TryGetEFPropertyArguments(out var source, out _)
1416-
&& source is EntityShaperExpression ese1)
1424+
&& source.UnwrapTypeConversion(out _) is EntityShaperExpression ese1)
14171425
{
1426+
if (source != ese1)
1427+
{
1428+
var rewrittenArguments = mce.Arguments.ToArray();
1429+
rewrittenArguments[0] = ese1;
1430+
expression = mce.Update(mce.Object, rewrittenArguments);
1431+
}
1432+
14181433
entityShaperExpression = ese1;
14191434
return true;
14201435
}
14211436

14221437
if (mce.TryGetIndexerArguments(model, out var source2, out _)
1423-
&& source2 is EntityShaperExpression ese2)
1438+
&& source2.UnwrapTypeConversion(out _) is EntityShaperExpression ese2)
14241439
{
1440+
expression = mce.Update(ese2, mce.Arguments);
1441+
14251442
entityShaperExpression = ese2;
14261443
return true;
14271444
}

src/Shared/ExpressionExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ public static LambdaExpression UnwrapLambdaFromQuote(this Expression expression)
2424
public static Expression? UnwrapTypeConversion(this Expression? expression, out Type? convertedType)
2525
{
2626
convertedType = null;
27-
while (expression is UnaryExpression unaryExpression
28-
&& (unaryExpression.NodeType == ExpressionType.Convert
29-
|| unaryExpression.NodeType == ExpressionType.ConvertChecked
30-
|| unaryExpression.NodeType == ExpressionType.TypeAs))
27+
while (expression is UnaryExpression
28+
{
29+
NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked or ExpressionType.TypeAs
30+
} unaryExpression)
3131
{
3232
expression = unaryExpression.Operand;
3333
if (unaryExpression.Type != typeof(object) // Ignore object conversion

test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,5 +158,26 @@ public virtual Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
158158
s => s.SetProperty(e => e.Name, "Eagle"),
159159
rowsAffectedCount: 1));
160160

161+
[ConditionalTheory]
162+
[MemberData(nameof(IsAsyncData))]
163+
public virtual Task Update_with_interface_in_property_expression(bool async)
164+
=> AssertUpdate(
165+
async,
166+
ss => ss.Set<Coke>(),
167+
e => e,
168+
s => s.SetProperty(c => ((ISugary)c).SugarGrams, 0),
169+
rowsAffectedCount: 1);
170+
171+
[ConditionalTheory]
172+
[MemberData(nameof(IsAsyncData))]
173+
public virtual Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
174+
=> AssertUpdate(
175+
async,
176+
ss => ss.Set<Coke>(),
177+
e => e,
178+
// ReSharper disable once RedundantCast
179+
s => s.SetProperty(c => EF.Property<int>((ISugary)c, nameof(ISugary.SugarGrams)), 0),
180+
rowsAffectedCount: 1);
181+
161182
protected abstract void ClearLog();
162183
}

test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesTestBase.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,14 @@ public override Task Update_where_hierarchy_derived(bool async)
5656
=> AssertTranslationFailed(
5757
RelationalStrings.ExecuteOperationOnTPT("ExecuteUpdate", "Kiwi"),
5858
() => base.Update_where_hierarchy_derived(async));
59+
60+
public override Task Update_with_interface_in_property_expression(bool async)
61+
=> AssertTranslationFailed(
62+
RelationalStrings.ExecuteOperationOnTPT("ExecuteUpdate", "Coke"),
63+
() => base.Update_with_interface_in_property_expression(async));
64+
65+
public override Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
66+
=> AssertTranslationFailed(
67+
RelationalStrings.ExecuteOperationOnTPT("ExecuteUpdate", "Coke"),
68+
() => base.Update_with_interface_in_EF_Property_in_property_expression(async));
5969
}

test/EFCore.SqlServer.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqlServerTest.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;
55

66
public class InheritanceBulkUpdatesSqlServerTest : InheritanceBulkUpdatesTestBase<InheritanceBulkUpdatesSqlServerFixture>
77
{
8-
public InheritanceBulkUpdatesSqlServerTest(InheritanceBulkUpdatesSqlServerFixture fixture)
8+
public InheritanceBulkUpdatesSqlServerTest(
9+
InheritanceBulkUpdatesSqlServerFixture fixture,
10+
ITestOutputHelper testOutputHelper)
11+
912
: base(fixture)
1013
{
1114
ClearLog();
15+
// Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
1216
}
1317

1418
[ConditionalFact]
@@ -205,6 +209,32 @@ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool
205209
AssertExecuteUpdateSql();
206210
}
207211

212+
public override async Task Update_with_interface_in_property_expression(bool async)
213+
{
214+
await base.Update_with_interface_in_property_expression(async);
215+
216+
AssertExecuteUpdateSql(
217+
"""
218+
UPDATE [d]
219+
SET [d].[SugarGrams] = 0
220+
FROM [Drinks] AS [d]
221+
WHERE [d].[Discriminator] = N'Coke'
222+
""");
223+
}
224+
225+
public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
226+
{
227+
await base.Update_with_interface_in_EF_Property_in_property_expression(async);
228+
229+
AssertExecuteUpdateSql(
230+
"""
231+
UPDATE [d]
232+
SET [d].[SugarGrams] = 0
233+
FROM [Drinks] AS [d]
234+
WHERE [d].[Discriminator] = N'Coke'
235+
""");
236+
}
237+
208238
protected override void ClearLog()
209239
=> Fixture.TestSqlLoggerFactory.Clear();
210240

test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqlServerTest.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;
55

66
public class TPCInheritanceBulkUpdatesSqlServerTest : TPCInheritanceBulkUpdatesTestBase<TPCInheritanceBulkUpdatesSqlServerFixture>
77
{
8-
public TPCInheritanceBulkUpdatesSqlServerTest(TPCInheritanceBulkUpdatesSqlServerFixture fixture)
8+
public TPCInheritanceBulkUpdatesSqlServerTest(
9+
TPCInheritanceBulkUpdatesSqlServerFixture fixture,
10+
ITestOutputHelper testOutputHelper)
911
: base(fixture)
1012
{
1113
ClearLog();
14+
// Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
1215
}
1316

1417
[ConditionalFact]
@@ -176,6 +179,30 @@ FROM [Kiwi] AS [k]
176179
""");
177180
}
178181

182+
public override async Task Update_with_interface_in_property_expression(bool async)
183+
{
184+
await base.Update_with_interface_in_property_expression(async);
185+
186+
AssertExecuteUpdateSql(
187+
"""
188+
UPDATE [c]
189+
SET [c].[SugarGrams] = 0
190+
FROM [Coke] AS [c]
191+
""");
192+
}
193+
194+
public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
195+
{
196+
await base.Update_with_interface_in_EF_Property_in_property_expression(async);
197+
198+
AssertExecuteUpdateSql(
199+
"""
200+
UPDATE [c]
201+
SET [c].[SugarGrams] = 0
202+
FROM [Coke] AS [c]
203+
""");
204+
}
205+
179206
public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
180207
{
181208
await base.Update_where_keyless_entity_mapped_to_sql_query(async);

test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqlServerTest.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,20 @@ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool
144144
AssertExecuteUpdateSql();
145145
}
146146

147+
public override async Task Update_with_interface_in_property_expression(bool async)
148+
{
149+
await base.Update_with_interface_in_property_expression(async);
150+
151+
AssertExecuteUpdateSql();
152+
}
153+
154+
public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
155+
{
156+
await base.Update_with_interface_in_EF_Property_in_property_expression(async);
157+
158+
AssertExecuteUpdateSql();
159+
}
160+
147161
protected override void ClearLog()
148162
=> Fixture.TestSqlLoggerFactory.Clear();
149163

test/EFCore.Sqlite.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqliteTest.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;
55

66
public class InheritanceBulkUpdatesSqliteTest : InheritanceBulkUpdatesTestBase<InheritanceBulkUpdatesSqliteFixture>
77
{
8-
public InheritanceBulkUpdatesSqliteTest(InheritanceBulkUpdatesSqliteFixture fixture)
8+
public InheritanceBulkUpdatesSqliteTest(
9+
InheritanceBulkUpdatesSqliteFixture fixture,
10+
ITestOutputHelper testOutputHelper)
911
: base(fixture)
1012
{
1113
ClearLog();
14+
// Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
1215
}
1316

1417
[ConditionalFact]
@@ -196,6 +199,30 @@ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool
196199
AssertExecuteUpdateSql();
197200
}
198201

202+
public override async Task Update_with_interface_in_property_expression(bool async)
203+
{
204+
await base.Update_with_interface_in_property_expression(async);
205+
206+
AssertExecuteUpdateSql(
207+
"""
208+
UPDATE "Drinks" AS "d"
209+
SET "SugarGrams" = 0
210+
WHERE "d"."Discriminator" = 'Coke'
211+
""");
212+
}
213+
214+
public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
215+
{
216+
await base.Update_with_interface_in_EF_Property_in_property_expression(async);
217+
218+
AssertExecuteUpdateSql(
219+
"""
220+
UPDATE "Drinks" AS "d"
221+
SET "SugarGrams" = 0
222+
WHERE "d"."Discriminator" = 'Coke'
223+
""");
224+
}
225+
199226
protected override void ClearLog()
200227
=> Fixture.TestSqlLoggerFactory.Clear();
201228

test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqliteTest.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;
55

66
public class TPCInheritanceBulkUpdatesSqliteTest : TPCInheritanceBulkUpdatesTestBase<TPCInheritanceBulkUpdatesSqliteFixture>
77
{
8-
public TPCInheritanceBulkUpdatesSqliteTest(TPCInheritanceBulkUpdatesSqliteFixture fixture)
8+
public TPCInheritanceBulkUpdatesSqliteTest(
9+
TPCInheritanceBulkUpdatesSqliteFixture fixture,
10+
ITestOutputHelper testOutputHelper)
911
: base(fixture)
1012
{
1113
ClearLog();
14+
// Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
1215
}
1316

1417
[ConditionalFact]
@@ -177,6 +180,28 @@ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool
177180
AssertExecuteUpdateSql();
178181
}
179182

183+
public override async Task Update_with_interface_in_property_expression(bool async)
184+
{
185+
await base.Update_with_interface_in_property_expression(async);
186+
187+
AssertExecuteUpdateSql(
188+
"""
189+
UPDATE "Coke" AS "c"
190+
SET "SugarGrams" = 0
191+
""");
192+
}
193+
194+
public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
195+
{
196+
await base.Update_with_interface_in_EF_Property_in_property_expression(async);
197+
198+
AssertExecuteUpdateSql(
199+
"""
200+
UPDATE "Coke" AS "c"
201+
SET "SugarGrams" = 0
202+
""");
203+
}
204+
180205
protected override void ClearLog()
181206
=> Fixture.TestSqlLoggerFactory.Clear();
182207

test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqliteTest.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;
55

66
public class TPTInheritanceBulkUpdatesSqliteTest : TPTInheritanceBulkUpdatesTestBase<TPTInheritanceBulkUpdatesSqliteFixture>
77
{
8-
public TPTInheritanceBulkUpdatesSqliteTest(TPTInheritanceBulkUpdatesSqliteFixture fixture)
8+
public TPTInheritanceBulkUpdatesSqliteTest(
9+
TPTInheritanceBulkUpdatesSqliteFixture fixture,
10+
ITestOutputHelper testOutputHelper)
911
: base(fixture)
1012
{
1113
ClearLog();
14+
// Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
1215
}
1316

1417
[ConditionalFact]
@@ -142,6 +145,20 @@ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool
142145
AssertExecuteUpdateSql();
143146
}
144147

148+
public override async Task Update_with_interface_in_property_expression(bool async)
149+
{
150+
await base.Update_with_interface_in_property_expression(async);
151+
152+
AssertExecuteUpdateSql();
153+
}
154+
155+
public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
156+
{
157+
await base.Update_with_interface_in_EF_Property_in_property_expression(async);
158+
159+
AssertExecuteUpdateSql();
160+
}
161+
145162
protected override void ClearLog()
146163
=> Fixture.TestSqlLoggerFactory.Clear();
147164

0 commit comments

Comments
 (0)