Skip to content

Commit d1e1dfa

Browse files
authored
Simplify parameter names (#35200)
Closes #35113 Fixes #35196
1 parent 8d18b1c commit d1e1dfa

File tree

159 files changed

+6297
-6136
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

159 files changed

+6297
-6136
lines changed

src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class CosmosSqlTranslatingExpressionVisitor(
2424
QueryableMethodTranslatingExpressionVisitor queryableMethodTranslatingExpressionVisitor)
2525
: ExpressionVisitor
2626
{
27-
private const string RuntimeParameterPrefix = QueryCompilationContext.QueryParameterPrefix + "entity_equality_";
27+
private const string RuntimeParameterPrefix = "entity_equality_";
2828

2929
private static readonly MethodInfo ParameterValueExtractorMethod =
3030
typeof(CosmosSqlTranslatingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(ParameterValueExtractor))!;
@@ -1040,9 +1040,7 @@ private bool TryRewriteContainsEntity(Expression source, Expression item, [NotNu
10401040
QueryCompilationContext.QueryContextParameter
10411041
);
10421042

1043-
var newParameterName =
1044-
$"{RuntimeParameterPrefix}"
1045-
+ $"{sqlParameterExpression.Name[QueryCompilationContext.QueryParameterPrefix.Length..]}_{property.Name}";
1043+
var newParameterName = $"{RuntimeParameterPrefix}{sqlParameterExpression.Name}_{property.Name}";
10461044

10471045
rewrittenSource = queryCompilationContext.RegisterRuntimeParameter(newParameterName, lambda);
10481046
break;
@@ -1165,9 +1163,7 @@ private Expression CreatePropertyAccessExpression(Expression target, IProperty p
11651163
Expression.Constant(property, typeof(IProperty))),
11661164
QueryCompilationContext.QueryContextParameter);
11671165

1168-
var newParameterName =
1169-
$"{RuntimeParameterPrefix}"
1170-
+ $"{sqlParameterExpression.Name[QueryCompilationContext.QueryParameterPrefix.Length..]}_{property.Name}";
1166+
var newParameterName = $"{RuntimeParameterPrefix}{sqlParameterExpression.Name}_{property.Name}";
11711167

11721168
return queryCompilationContext.RegisterRuntimeParameter(newParameterName, lambda);
11731169

src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal;
1818
/// </summary>
1919
public class InMemoryExpressionTranslatingExpressionVisitor : ExpressionVisitor
2020
{
21-
private const string RuntimeParameterPrefix = QueryCompilationContext.QueryParameterPrefix + "entity_equality_";
21+
private const string RuntimeParameterPrefix = "entity_equality_";
2222

2323
private static readonly List<MethodInfo> SingleResultMethodInfos =
2424
[
@@ -1298,9 +1298,7 @@ when methodCallExpression.Method.GetGenericMethodDefinition() == GetParameterVal
12981298
QueryCompilationContext.QueryContextParameter
12991299
);
13001300

1301-
var newParameterName =
1302-
$"{RuntimeParameterPrefix}"
1303-
+ $"{parameterName[QueryCompilationContext.QueryParameterPrefix.Length..]}_{property.Name}";
1301+
var newParameterName = $"{RuntimeParameterPrefix}{parameterName}_{property.Name}";
13041302

13051303
rewrittenSource = _queryCompilationContext.RegisterRuntimeParameter(newParameterName, lambda);
13061304
break;
@@ -1442,9 +1440,7 @@ when methodCallExpression.Method.GetGenericMethodDefinition() == GetParameterVal
14421440
Expression.Constant(property, typeof(IProperty))),
14431441
QueryCompilationContext.QueryContextParameter);
14441442

1445-
var newParameterName =
1446-
$"{RuntimeParameterPrefix}"
1447-
+ $"{parameterName[QueryCompilationContext.QueryParameterPrefix.Length..]}_{property.Name}";
1443+
var newParameterName = $"{RuntimeParameterPrefix}{parameterName}_{property.Name}";
14481444

14491445
return _queryCompilationContext.RegisterRuntimeParameter(newParameterName, lambda);
14501446

src/EFCore.Relational/Query/Internal/FromSqlParameterExpandingExpressionVisitor.cs renamed to src/EFCore.Relational/Query/Internal/RelationalParameterProcessor.cs

Lines changed: 109 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,40 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Diagnostics.CodeAnalysis;
54
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
65
using Microsoft.EntityFrameworkCore.Storage.Internal;
76

87
namespace Microsoft.EntityFrameworkCore.Query.Internal;
98

109
/// <summary>
10+
/// This visitor processes the parameters of <see cref="FromSqlExpression" />, expanding them and creating the appropriate
11+
/// <see cref="IRelationalParameter" /> for them, and ensures parameter names are unique across the SQL tree.
12+
/// </summary>
13+
/// <remarks>
1114
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
1215
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
1316
/// any release. You should only use it directly in your code with extreme caution and knowing that
1417
/// doing so can result in application failures when updating to a new Entity Framework Core release.
15-
/// </summary>
16-
public class FromSqlParameterExpandingExpressionVisitor : ExpressionVisitor
18+
/// </remarks>
19+
public class RelationalParameterProcessor : ExpressionVisitor
1720
{
1821
private readonly IDictionary<FromSqlExpression, Expression> _visitedFromSqlExpressions
1922
= new Dictionary<FromSqlExpression, Expression>(ReferenceEqualityComparer.Instance);
2023

2124
private readonly ISqlExpressionFactory _sqlExpressionFactory;
2225
private readonly IRelationalTypeMappingSource _typeMappingSource;
26+
private readonly ISqlGenerationHelper _sqlGenerationHelper;
2327
private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory;
2428

29+
/// <summary>
30+
/// Contains parameter names seen so far, for uniquification. These parameter names have already gone through
31+
/// <see cref="ISqlGenerationHelper.GenerateParameterName(string)"/> (i.e. they're prefixed), since
32+
/// <see cref="DbParameter.ParameterName" /> can be prefixed or not.
33+
/// </summary>
34+
private readonly HashSet<string> _prefixedParameterNames = new();
35+
36+
private readonly Dictionary<string, SqlParameterExpression> _sqlParameters = new();
37+
2538
private IReadOnlyDictionary<string, object?> _parametersValues;
2639
private ParameterNameGenerator _parameterNameGenerator;
2740
private bool _canCache;
@@ -32,14 +45,15 @@ private readonly IDictionary<FromSqlExpression, Expression> _visitedFromSqlExpre
3245
/// any release. You should only use it directly in your code with extreme caution and knowing that
3346
/// doing so can result in application failures when updating to a new Entity Framework Core release.
3447
/// </summary>
35-
public FromSqlParameterExpandingExpressionVisitor(
48+
public RelationalParameterProcessor(
3649
RelationalParameterBasedSqlProcessorDependencies dependencies)
3750
{
3851
Dependencies = dependencies;
3952

4053
_sqlExpressionFactory = dependencies.SqlExpressionFactory;
4154
_typeMappingSource = dependencies.TypeMappingSource;
4255
_parameterNameGeneratorFactory = dependencies.ParameterNameGeneratorFactory;
56+
_sqlGenerationHelper = dependencies.SqlGenerationHelper;
4357
_parametersValues = default!;
4458
_parameterNameGenerator = default!;
4559
}
@@ -61,6 +75,8 @@ public virtual Expression Expand(
6175
out bool canCache)
6276
{
6377
_visitedFromSqlExpressions.Clear();
78+
_prefixedParameterNames.Clear();
79+
_sqlParameters.Clear();
6480
_parameterNameGenerator = _parameterNameGeneratorFactory.Create();
6581
_parametersValues = parameterValues;
6682
_canCache = true;
@@ -77,19 +93,55 @@ public virtual Expression Expand(
7793
/// any release. You should only use it directly in your code with extreme caution and knowing that
7894
/// doing so can result in application failures when updating to a new Entity Framework Core release.
7995
/// </summary>
80-
[return: NotNullIfNotNull(nameof(expression))]
81-
public override Expression? Visit(Expression? expression)
82-
{
83-
if (expression is not FromSqlExpression fromSql)
96+
protected override Expression VisitExtension(Expression expression)
97+
=> expression switch
8498
{
85-
return base.Visit(expression);
86-
}
99+
FromSqlExpression fromSql
100+
=> _visitedFromSqlExpressions.TryGetValue(fromSql, out var visitedFromSql)
101+
? visitedFromSql
102+
: _visitedFromSqlExpressions[fromSql] = VisitFromSql(fromSql),
103+
104+
SqlParameterExpression parameter => VisitSqlParameter(parameter),
105+
106+
_ => base.VisitExtension(expression)
107+
};
87108

88-
if (_visitedFromSqlExpressions.TryGetValue(fromSql, out var visitedFromSql))
109+
private SqlParameterExpression VisitSqlParameter(SqlParameterExpression parameter)
110+
{
111+
var typeMapping = parameter.TypeMapping!;
112+
113+
// Try to see if a parameter already exists - if so, just integrate the same placeholder into the SQL instead of sending the same
114+
// data twice.
115+
// Note that if the type mapping differs, we do send the same data twice (e.g. the same string may be sent once as Unicode, once as
116+
// non-Unicode).
117+
// TODO: Note that we perform Equals comparison on the value converter. We should be able to do reference comparison, but for
118+
// that we need to ensure that there's only ever one type mapping instance (i.e. no type mappings are ever instantiated out of the
119+
// type mapping source). See #30677.
120+
if (_sqlParameters.TryGetValue(parameter.InvariantName, out var existingParameter)
121+
&& existingParameter is { TypeMapping: RelationalTypeMapping existingTypeMapping }
122+
&& string.Equals(existingTypeMapping.StoreType, typeMapping.StoreType, StringComparison.OrdinalIgnoreCase)
123+
&& (existingTypeMapping.Converter is null && typeMapping.Converter is null
124+
|| existingTypeMapping.Converter is not null && existingTypeMapping.Converter.Equals(typeMapping.Converter)))
89125
{
90-
return visitedFromSql;
126+
return parameter;
91127
}
92128

129+
var uniquifiedName = UniquifyParameterName(parameter.Name);
130+
var newParameter = uniquifiedName == parameter.Name
131+
? parameter
132+
: new SqlParameterExpression(
133+
parameter.InvariantName,
134+
uniquifiedName,
135+
parameter.Type,
136+
parameter.IsNullable,
137+
parameter.ShouldBeConstantized,
138+
parameter.TypeMapping);
139+
140+
return _sqlParameters[newParameter.InvariantName] = newParameter;
141+
}
142+
143+
private FromSqlExpression VisitFromSql(FromSqlExpression fromSql)
144+
{
93145
switch (fromSql.Arguments)
94146
{
95147
case QueryParameterExpression queryParameter:
@@ -101,22 +153,14 @@ public virtual Expression Expand(
101153
// ReSharper disable once ForCanBeConvertedToForeach
102154
for (var i = 0; i < parameterValues.Length; i++)
103155
{
104-
var parameterName = _parameterNameGenerator.GenerateNext();
105156
if (parameterValues[i] is DbParameter dbParameter)
106157
{
107-
if (string.IsNullOrEmpty(dbParameter.ParameterName))
108-
{
109-
dbParameter.ParameterName = parameterName;
110-
}
111-
else
112-
{
113-
parameterName = dbParameter.ParameterName;
114-
}
115-
116-
subParameters.Add(new RawRelationalParameter(parameterName, dbParameter));
158+
ProcessDbParameter(dbParameter);
159+
subParameters.Add(new RawRelationalParameter(dbParameter.ParameterName, dbParameter));
117160
}
118161
else
119162
{
163+
var parameterName = GenerateNewParameterName();
120164
subParameters.Add(
121165
new TypeMappedRelationalParameter(
122166
parameterName,
@@ -126,18 +170,18 @@ public virtual Expression Expand(
126170
}
127171
}
128172

129-
return _visitedFromSqlExpressions[fromSql] = fromSql.Update(
130-
Expression.Constant(new CompositeRelationalParameter(queryParameter.Name!, subParameters)));
173+
return fromSql.Update(Expression.Constant(new CompositeRelationalParameter(queryParameter.Name, subParameters)));
131174

132175
case ConstantExpression { Value: object?[] existingValues }:
133176
{
134177
var constantValues = new object?[existingValues.Length];
178+
135179
for (var i = 0; i < existingValues.Length; i++)
136180
{
137181
constantValues[i] = ProcessConstantValue(existingValues[i]);
138182
}
139183

140-
return _visitedFromSqlExpressions[fromSql] = fromSql.Update(Expression.Constant(constantValues, typeof(object[])));
184+
return fromSql.Update(Expression.Constant(constantValues, typeof(object[])));
141185
}
142186

143187
case NewArrayExpression { Expressions: var expressions }:
@@ -154,35 +198,60 @@ public virtual Expression Expand(
154198
constantValues[i] = ProcessConstantValue(existingValue);
155199
}
156200

157-
return _visitedFromSqlExpressions[fromSql] = fromSql.Update(Expression.Constant(constantValues, typeof(object[])));
201+
return fromSql.Update(Expression.Constant(constantValues, typeof(object[])));
158202
}
159203

160204
default:
161-
Check.DebugFail("FromSql.Arguments must be Constant/QueryParameterExpression");
162-
return null;
205+
throw new UnreachableException("FromSql.Arguments must be Constant/QueryParameterExpression");
163206
}
164207

165208
object ProcessConstantValue(object? existingConstantValue)
166209
{
167210
if (existingConstantValue is DbParameter dbParameter)
168211
{
169-
var parameterName = _parameterNameGenerator.GenerateNext();
170-
if (string.IsNullOrEmpty(dbParameter.ParameterName))
171-
{
172-
dbParameter.ParameterName = parameterName;
173-
}
174-
else
175-
{
176-
parameterName = dbParameter.ParameterName;
177-
}
178-
179-
return new RawRelationalParameter(parameterName, dbParameter);
212+
ProcessDbParameter(dbParameter);
213+
return new RawRelationalParameter(dbParameter.ParameterName, dbParameter);
180214
}
181215

182216
return _sqlExpressionFactory.Constant(
183217
existingConstantValue,
184218
existingConstantValue?.GetType() ?? typeof(object),
185219
_typeMappingSource.GetMappingForValue(existingConstantValue));
186220
}
221+
222+
void ProcessDbParameter(DbParameter dbParameter)
223+
{
224+
dbParameter.ParameterName = string.IsNullOrEmpty(dbParameter.ParameterName)
225+
? GenerateNewParameterName()
226+
: UniquifyParameterName(dbParameter.ParameterName);
227+
}
228+
}
229+
230+
private string GenerateNewParameterName()
231+
{
232+
string name, prefixedName;
233+
do
234+
{
235+
name = _parameterNameGenerator.GenerateNext();
236+
prefixedName = _sqlGenerationHelper.GenerateParameterName(name);
237+
}
238+
while (_prefixedParameterNames.Contains(prefixedName));
239+
240+
_prefixedParameterNames.Add(prefixedName);
241+
return name;
242+
}
243+
244+
private string UniquifyParameterName(string originalName)
245+
{
246+
var parameterName = originalName;
247+
var prefixedName = _sqlGenerationHelper.GenerateParameterName(originalName);
248+
for (var j = 0; _prefixedParameterNames.Contains(prefixedName); j++)
249+
{
250+
parameterName = originalName + j;
251+
prefixedName = _sqlGenerationHelper.GenerateParameterName(parameterName);
252+
}
253+
254+
_prefixedParameterNames.Add(prefixedName);
255+
return parameterName;
187256
}
188257
}

0 commit comments

Comments
 (0)