Skip to content

Commit 4b7b6e4

Browse files
committed
Merge branch 'release/10.0'
2 parents 15af73b + 39531c8 commit 4b7b6e4

6 files changed

Lines changed: 133 additions & 29 deletions

File tree

src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2084,36 +2084,29 @@ bool PreserveConvertNode(Expression expression)
20842084
return value;
20852085
}
20862086

2087-
if (tempParameterName is null)
2087+
parameterName = string.IsNullOrWhiteSpace(tempParameterName) ? "p" : tempParameterName;
2088+
2089+
// The VB compiler prefixes closure member names with $VB$Local_, remove that (#33150)
2090+
if (parameterName.StartsWith("$VB$Local_", StringComparison.Ordinal))
20882091
{
2089-
parameterName = "p";
2092+
parameterName = parameterName["$VB$Local_".Length..];
20902093
}
2091-
else
2092-
{
2093-
parameterName = tempParameterName;
20942094

2095-
// The VB compiler prefixes closure member names with $VB$Local_, remove that (#33150)
2096-
if (parameterName.StartsWith("$VB$Local_", StringComparison.Ordinal))
2097-
{
2098-
parameterName = parameterName["$VB$Local_".Length..];
2099-
}
2100-
2101-
// In many databases, parameter names must start with a letter or underscore.
2102-
// The same is true for C# variable names, from which we derive the parameter name, so in principle we shouldn't see an issue;
2103-
// but just in case, prepend an underscore if the parameter name doesn't start with a letter or underscore.
2104-
if (!char.IsLetter(parameterName[0]) && parameterName[0] != '_')
2105-
{
2106-
parameterName = "_" + parameterName;
2107-
}
2095+
// In many databases, parameter names must start with a letter or underscore.
2096+
// The same is true for C# variable names, from which we derive the parameter name, so in principle we shouldn't see an issue;
2097+
// but just in case, prepend an underscore if the parameter name doesn't start with a letter or underscore.
2098+
if (parameterName.Length > 0 && !char.IsLetter(parameterName[0]) && parameterName[0] != '_')
2099+
{
2100+
parameterName = "_" + parameterName;
2101+
}
21082102

2109-
// Just as a safety guard, if there's any problematic character in the name for any reason, fall back to "p".
2110-
foreach (var c in parameterName)
2103+
// Just as a safety guard, if there's any problematic character in the name for any reason, fall back to "p".
2104+
foreach (var c in parameterName)
2105+
{
2106+
if (!char.IsLetterOrDigit(c) && c != '_')
21112107
{
2112-
if (!char.IsLetterOrDigit(c) && c != '_')
2113-
{
2114-
parameterName = "p";
2115-
break;
2116-
}
2108+
parameterName = "p";
2109+
break;
21172110
}
21182111
}
21192112

src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,11 +1082,18 @@ private Expression ProcessExecuteUpdate(NavigationExpansionExpression source, Me
10821082

10831083
// Apply any pending selector before processing the ExecuteUpdate setters; this adds a Select() (if necessary) before
10841084
// ExecuteUpdate, to avoid the pending selector flowing into each setter lambda and making it more complicated.
1085+
// However, only do this when the pending selector produces entity/structural type references (i.e. the snapshot is not just
1086+
// a DefaultExpression). When the pending selector projects only scalar values (e.g. select new { p.Used, n.Qty }),
1087+
// applying it would lose the connection between the projected scalar and the original entity property, breaking
1088+
// ExecuteUpdate's property selector recognition (#37771).
10851089
var newStructure = SnapshotExpression(source.PendingSelector);
1086-
var queryable = Reduce(source);
1087-
var navigationTree = new NavigationTreeExpression(newStructure);
1088-
var parameterName = source.CurrentParameter.Name ?? GetParameterName("e");
1089-
source = new NavigationExpansionExpression(queryable, navigationTree, navigationTree, parameterName);
1090+
if (newStructure is not DefaultExpression)
1091+
{
1092+
var queryable = Reduce(source);
1093+
var navigationTree = new NavigationTreeExpression(newStructure);
1094+
var parameterName = source.CurrentParameter.Name ?? GetParameterName("e");
1095+
source = new NavigationExpansionExpression(queryable, navigationTree, navigationTree, parameterName);
1096+
}
10901097

10911098
NewArrayExpression settersArray;
10921099
switch (setters)

test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4529,6 +4529,18 @@ public override async Task Parameter_extraction_can_throw_exception_from_user_co
45294529
AssertSql();
45304530
}
45314531

4532+
public override Task Captured_variable_from_switch_case_pattern_matching(bool async)
4533+
=> Fixture.NoSyncTest(
4534+
async, async a =>
4535+
{
4536+
await base.Captured_variable_from_switch_case_pattern_matching(a);
4537+
4538+
AssertSql(
4539+
"""
4540+
ReadItem(None, ALFKI)
4541+
""");
4542+
});
4543+
45324544
public override async Task Where_query_composition5(bool async)
45334545
{
45344546
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => AssertQuery(

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,44 @@ FROM [Customers]
9999
}
100100
});
101101

102+
[ConditionalTheory, MemberData(nameof(IsAsyncData))] // #37771
103+
public virtual Task Update_with_select_mixed_entity_scalar_anonymous_projection(bool async)
104+
=> TestHelpers.ExecuteWithStrategyInTransactionAsync(
105+
() => Fixture.CreateContext(),
106+
(facade, transaction) => Fixture.UseTransaction(facade, transaction),
107+
async context =>
108+
{
109+
var queryable = context.Set<Customer>().Select(c => new { Entity = c, c.ContactName });
110+
111+
if (async)
112+
{
113+
await queryable.ExecuteUpdateAsync(s => s.SetProperty(c => c.Entity.ContactName, "Updated"));
114+
}
115+
else
116+
{
117+
queryable.ExecuteUpdate(s => s.SetProperty(c => c.Entity.ContactName, "Updated"));
118+
}
119+
});
120+
121+
[ConditionalTheory, MemberData(nameof(IsAsyncData))] // #37771
122+
public virtual Task Update_with_select_scalar_anonymous_projection(bool async)
123+
=> TestHelpers.ExecuteWithStrategyInTransactionAsync(
124+
() => Fixture.CreateContext(),
125+
(facade, transaction) => Fixture.UseTransaction(facade, transaction),
126+
async context =>
127+
{
128+
var queryable = context.Set<Customer>().Select(c => new { c.ContactName, c.City });
129+
130+
if (async)
131+
{
132+
await queryable.ExecuteUpdateAsync(s => s.SetProperty(c => c.ContactName, "Updated"));
133+
}
134+
else
135+
{
136+
queryable.ExecuteUpdate(s => s.SetProperty(c => c.ContactName, "Updated"));
137+
}
138+
});
139+
102140
protected static async Task AssertTranslationFailed(string details, Func<Task> query)
103141
{
104142
var exception = await Assert.ThrowsAsync<InvalidOperationException>(query);

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,6 +1665,34 @@ OFFSET @p ROWS
16651665
""");
16661666
}
16671667

1668+
public override async Task Update_with_select_mixed_entity_scalar_anonymous_projection(bool async)
1669+
{
1670+
await base.Update_with_select_mixed_entity_scalar_anonymous_projection(async);
1671+
1672+
AssertSql(
1673+
"""
1674+
@p='Updated' (Size = 30)
1675+
1676+
UPDATE [c]
1677+
SET [c].[ContactName] = @p
1678+
FROM [Customers] AS [c]
1679+
""");
1680+
}
1681+
1682+
public override async Task Update_with_select_scalar_anonymous_projection(bool async)
1683+
{
1684+
await base.Update_with_select_scalar_anonymous_projection(async);
1685+
1686+
AssertSql(
1687+
"""
1688+
@p='Updated' (Size = 30)
1689+
1690+
UPDATE [c]
1691+
SET [c].[ContactName] = @p
1692+
FROM [Customers] AS [c]
1693+
""");
1694+
}
1695+
16681696
private void AssertSql(params string[] expected)
16691697
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
16701698

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,6 +1571,32 @@ ORDER BY "o"."OrderID"
15711571
""");
15721572
}
15731573

1574+
public override async Task Update_with_select_mixed_entity_scalar_anonymous_projection(bool async)
1575+
{
1576+
await base.Update_with_select_mixed_entity_scalar_anonymous_projection(async);
1577+
1578+
AssertSql(
1579+
"""
1580+
@p='Updated' (Size = 7)
1581+
1582+
UPDATE "Customers" AS "c"
1583+
SET "ContactName" = @p
1584+
""");
1585+
}
1586+
1587+
public override async Task Update_with_select_scalar_anonymous_projection(bool async)
1588+
{
1589+
await base.Update_with_select_scalar_anonymous_projection(async);
1590+
1591+
AssertSql(
1592+
"""
1593+
@p='Updated' (Size = 7)
1594+
1595+
UPDATE "Customers" AS "c"
1596+
SET "ContactName" = @p
1597+
""");
1598+
}
1599+
15741600
private void AssertSql(params string[] expected)
15751601
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
15761602

0 commit comments

Comments
 (0)