Skip to content

Commit e006093

Browse files
[automated] Merge branch 'release/10.0' => 'main' (#37811)
Co-authored-by: Shay Rojansky <roji@roji.org>
1 parent 39c16b2 commit e006093

5 files changed

Lines changed: 106 additions & 6 deletions

File tree

src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2101,7 +2101,7 @@ bool PreserveConvertNode(Expression expression)
21012101
// In many databases, parameter names must start with a letter or underscore.
21022102
// The same is true for C# variable names, from which we derive the parameter name, so in principle we shouldn't see an issue;
21032103
// 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] != '_')
2104+
if (parameterName.Length > 0 && !char.IsLetter(parameterName[0]) && parameterName[0] != '_')
21052105
{
21062106
parameterName = "_" + parameterName;
21072107
}
@@ -2215,7 +2215,7 @@ static string SanitizeCompilerGeneratedName(string s)
22152215
{
22162216
// Compiler-generated field names intentionally contain illegal characters, specifically angle brackets <>.
22172217
// In cases where there's something within the angle brackets, that tends to be the original user-provided variable name
2218-
// (e.g. <PropertyName>k__BackingField). If we see angle brackets, extract that out, or it the angle brackets contain no
2218+
// (e.g. <PropertyName>k__BackingField). If we see angle brackets, extract that out, or if the angle brackets contain no
22192219
// content, strip them out entirely and take what comes after.
22202220
var closingBracket = s.IndexOf('>');
22212221
if (closingBracket == -1)

src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ private static readonly PropertyInfo QueryContextContextPropertyInfo
5858

5959
private readonly Dictionary<string, object?> _parameters = new();
6060

61+
6162
/// <summary>
6263
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
6364
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -1082,11 +1083,18 @@ private Expression ProcessExecuteUpdate(NavigationExpansionExpression source, Me
10821083

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

10911099
NewArrayExpression settersArray;
10921100
switch (setters)

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)