Skip to content

Commit

Permalink
Support interfaces in ExecuteUpdate setter property lambda
Browse files Browse the repository at this point in the history
  • Loading branch information
roji committed Nov 24, 2022
1 parent 3f82c2b commit 124d334
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1195,8 +1195,8 @@ static Expression PruneOwnedIncludes(IncludeExpression includeExpression)
foreach (var (propertyExpression, _) in propertyValueLambdaExpressions)
{
var left = RemapLambdaBody(source, propertyExpression);
left = left.UnwrapTypeConversion(out _);
if (!IsValidPropertyAccess(RelationalDependencies.Model, left, out var ese))

if (!TryProcessPropertyAccess(RelationalDependencies.Model, ref left, out var ese))
{
AddTranslationErrorDetails(RelationalStrings.InvalidPropertyInSetProperty(propertyExpression.Print()));
return null;
Expand Down Expand Up @@ -1399,29 +1399,46 @@ when methodCallExpression.Method.IsGenericMethod
}
}

static bool IsValidPropertyAccess(
static bool TryProcessPropertyAccess(
IModel model,
Expression expression,
ref Expression expression,
[NotNullWhen(true)] out EntityShaperExpression? entityShaperExpression)
{
if (expression is MemberExpression { Expression: EntityShaperExpression ese })
// Unwrap any object/base-type Convert nodes around the property access expression
expression = expression.UnwrapTypeConversion(out _);

// Identify property access (direct, EF.Property...), while also unwrapping object/base-type Convert nodes on the expression
// being accessed.
if (expression is MemberExpression memberExpression
&& memberExpression.Expression.UnwrapTypeConversion(out _) is EntityShaperExpression ese)
{
expression = memberExpression.Update(ese);

entityShaperExpression = ese;
return true;
}

if (expression is MethodCallExpression mce)
{
if (mce.TryGetEFPropertyArguments(out var source, out _)
&& source is EntityShaperExpression ese1)
&& source.UnwrapTypeConversion(out _) is EntityShaperExpression ese1)
{
if (source != ese1)
{
var rewrittenArguments = mce.Arguments.ToArray();
rewrittenArguments[0] = ese1;
expression = mce.Update(mce.Object, rewrittenArguments);
}

entityShaperExpression = ese1;
return true;
}

if (mce.TryGetIndexerArguments(model, out var source2, out _)
&& source2 is EntityShaperExpression ese2)
&& source2.UnwrapTypeConversion(out _) is EntityShaperExpression ese2)
{
expression = mce.Update(ese2, mce.Arguments);

entityShaperExpression = ese2;
return true;
}
Expand Down
8 changes: 4 additions & 4 deletions src/Shared/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ public static LambdaExpression UnwrapLambdaFromQuote(this Expression expression)
public static Expression? UnwrapTypeConversion(this Expression? expression, out Type? convertedType)
{
convertedType = null;
while (expression is UnaryExpression unaryExpression
&& (unaryExpression.NodeType == ExpressionType.Convert
|| unaryExpression.NodeType == ExpressionType.ConvertChecked
|| unaryExpression.NodeType == ExpressionType.TypeAs))
while (expression is UnaryExpression
{
NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked or ExpressionType.TypeAs
} unaryExpression)
{
expression = unaryExpression.Operand;
if (unaryExpression.Type != typeof(object) // Ignore object conversion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,26 @@ public virtual Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
s => s.SetProperty(e => e.Name, "Eagle"),
rowsAffectedCount: 1));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Update_with_interface_in_property_expression(bool async)
=> AssertUpdate(
async,
ss => ss.Set<Coke>(),
e => e,
s => s.SetProperty(c => ((ISugary)c).SugarGrams, 0),
rowsAffectedCount: 1);

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
=> AssertUpdate(
async,
ss => ss.Set<Coke>(),
e => e,
// ReSharper disable once RedundantCast
s => s.SetProperty(c => EF.Property<int>((ISugary)c, nameof(ISugary.SugarGrams)), 0),
rowsAffectedCount: 1);

protected abstract void ClearLog();
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,14 @@ public override Task Update_where_hierarchy_derived(bool async)
=> AssertTranslationFailed(
RelationalStrings.ExecuteOperationOnTPT("ExecuteUpdate", "Kiwi"),
() => base.Update_where_hierarchy_derived(async));

public override Task Update_with_interface_in_property_expression(bool async)
=> AssertTranslationFailed(
RelationalStrings.ExecuteOperationOnTPT("ExecuteUpdate", "Coke"),
() => base.Update_with_interface_in_property_expression(async));

public override Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
=> AssertTranslationFailed(
RelationalStrings.ExecuteOperationOnTPT("ExecuteUpdate", "Coke"),
() => base.Update_with_interface_in_EF_Property_in_property_expression(async));
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;

public class InheritanceBulkUpdatesSqlServerTest : InheritanceBulkUpdatesTestBase<InheritanceBulkUpdatesSqlServerFixture>
{
public InheritanceBulkUpdatesSqlServerTest(InheritanceBulkUpdatesSqlServerFixture fixture)
public InheritanceBulkUpdatesSqlServerTest(
InheritanceBulkUpdatesSqlServerFixture fixture,
ITestOutputHelper testOutputHelper)

: base(fixture)
{
ClearLog();
// Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}

[ConditionalFact]
Expand Down Expand Up @@ -205,6 +209,32 @@ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool
AssertExecuteUpdateSql();
}

public override async Task Update_with_interface_in_property_expression(bool async)
{
await base.Update_with_interface_in_property_expression(async);

AssertExecuteUpdateSql(
"""
UPDATE [d]
SET [d].[SugarGrams] = 0
FROM [Drinks] AS [d]
WHERE [d].[Discriminator] = N'Coke'
""");
}

public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
{
await base.Update_with_interface_in_EF_Property_in_property_expression(async);

AssertExecuteUpdateSql(
"""
UPDATE [d]
SET [d].[SugarGrams] = 0
FROM [Drinks] AS [d]
WHERE [d].[Discriminator] = N'Coke'
""");
}

protected override void ClearLog()
=> Fixture.TestSqlLoggerFactory.Clear();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;

public class TPCInheritanceBulkUpdatesSqlServerTest : TPCInheritanceBulkUpdatesTestBase<TPCInheritanceBulkUpdatesSqlServerFixture>
{
public TPCInheritanceBulkUpdatesSqlServerTest(TPCInheritanceBulkUpdatesSqlServerFixture fixture)
public TPCInheritanceBulkUpdatesSqlServerTest(
TPCInheritanceBulkUpdatesSqlServerFixture fixture,
ITestOutputHelper testOutputHelper)
: base(fixture)
{
ClearLog();
// Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}

[ConditionalFact]
Expand Down Expand Up @@ -176,6 +179,30 @@ FROM [Kiwi] AS [k]
""");
}

public override async Task Update_with_interface_in_property_expression(bool async)
{
await base.Update_with_interface_in_property_expression(async);

AssertExecuteUpdateSql(
"""
UPDATE [c]
SET [c].[SugarGrams] = 0
FROM [Coke] AS [c]
""");
}

public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
{
await base.Update_with_interface_in_EF_Property_in_property_expression(async);

AssertExecuteUpdateSql(
"""
UPDATE [c]
SET [c].[SugarGrams] = 0
FROM [Coke] AS [c]
""");
}

public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
{
await base.Update_where_keyless_entity_mapped_to_sql_query(async);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,20 @@ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool
AssertExecuteUpdateSql();
}

public override async Task Update_with_interface_in_property_expression(bool async)
{
await base.Update_with_interface_in_property_expression(async);

AssertExecuteUpdateSql();
}

public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
{
await base.Update_with_interface_in_EF_Property_in_property_expression(async);

AssertExecuteUpdateSql();
}

protected override void ClearLog()
=> Fixture.TestSqlLoggerFactory.Clear();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;

public class InheritanceBulkUpdatesSqliteTest : InheritanceBulkUpdatesTestBase<InheritanceBulkUpdatesSqliteFixture>
{
public InheritanceBulkUpdatesSqliteTest(InheritanceBulkUpdatesSqliteFixture fixture)
public InheritanceBulkUpdatesSqliteTest(
InheritanceBulkUpdatesSqliteFixture fixture,
ITestOutputHelper testOutputHelper)
: base(fixture)
{
ClearLog();
// Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}

[ConditionalFact]
Expand Down Expand Up @@ -196,6 +199,30 @@ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool
AssertExecuteUpdateSql();
}

public override async Task Update_with_interface_in_property_expression(bool async)
{
await base.Update_with_interface_in_property_expression(async);

AssertExecuteUpdateSql(
"""
UPDATE "Drinks" AS "d"
SET "SugarGrams" = 0
WHERE "d"."Discriminator" = 'Coke'
""");
}

public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
{
await base.Update_with_interface_in_EF_Property_in_property_expression(async);

AssertExecuteUpdateSql(
"""
UPDATE "Drinks" AS "d"
SET "SugarGrams" = 0
WHERE "d"."Discriminator" = 'Coke'
""");
}

protected override void ClearLog()
=> Fixture.TestSqlLoggerFactory.Clear();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;

public class TPCInheritanceBulkUpdatesSqliteTest : TPCInheritanceBulkUpdatesTestBase<TPCInheritanceBulkUpdatesSqliteFixture>
{
public TPCInheritanceBulkUpdatesSqliteTest(TPCInheritanceBulkUpdatesSqliteFixture fixture)
public TPCInheritanceBulkUpdatesSqliteTest(
TPCInheritanceBulkUpdatesSqliteFixture fixture,
ITestOutputHelper testOutputHelper)
: base(fixture)
{
ClearLog();
// Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}

[ConditionalFact]
Expand Down Expand Up @@ -177,6 +180,28 @@ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool
AssertExecuteUpdateSql();
}

public override async Task Update_with_interface_in_property_expression(bool async)
{
await base.Update_with_interface_in_property_expression(async);

AssertExecuteUpdateSql(
"""
UPDATE "Coke" AS "c"
SET "SugarGrams" = 0
""");
}

public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
{
await base.Update_with_interface_in_EF_Property_in_property_expression(async);

AssertExecuteUpdateSql(
"""
UPDATE "Coke" AS "c"
SET "SugarGrams" = 0
""");
}

protected override void ClearLog()
=> Fixture.TestSqlLoggerFactory.Clear();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;

public class TPTInheritanceBulkUpdatesSqliteTest : TPTInheritanceBulkUpdatesTestBase<TPTInheritanceBulkUpdatesSqliteFixture>
{
public TPTInheritanceBulkUpdatesSqliteTest(TPTInheritanceBulkUpdatesSqliteFixture fixture)
public TPTInheritanceBulkUpdatesSqliteTest(
TPTInheritanceBulkUpdatesSqliteFixture fixture,
ITestOutputHelper testOutputHelper)
: base(fixture)
{
ClearLog();
// Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}

[ConditionalFact]
Expand Down Expand Up @@ -142,6 +145,20 @@ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool
AssertExecuteUpdateSql();
}

public override async Task Update_with_interface_in_property_expression(bool async)
{
await base.Update_with_interface_in_property_expression(async);

AssertExecuteUpdateSql();
}

public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
{
await base.Update_with_interface_in_EF_Property_in_property_expression(async);

AssertExecuteUpdateSql();
}

protected override void ClearLog()
=> Fixture.TestSqlLoggerFactory.Clear();

Expand Down

0 comments on commit 124d334

Please sign in to comment.