Skip to content

Commit 6b70079

Browse files
authored
Mechanism for accessing parameters and disabling SQL caching (#36267)
Closes #36266
1 parent 21b0ff2 commit 6b70079

File tree

37 files changed

+258
-307
lines changed

37 files changed

+258
-307
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -745,5 +745,5 @@ private static Expression MatchTypes(Expression expression, Type targetType)
745745

746746
[UsedImplicitly]
747747
private static T GetParameterValue<T>(QueryContext queryContext, string parameterName)
748-
=> (T)queryContext.ParameterValues[parameterName]!;
748+
=> (T)queryContext.Parameters[parameterName]!;
749749
}

src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.PagingQueryingEnumerable.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public PagingQueryingEnumerable(
6767
_cosmosContainer = rootEntityType.GetContainer()
6868
?? throw new UnreachableException("Root entity type without a Cosmos container.");
6969
_cosmosPartitionKey = GeneratePartitionKey(
70-
rootEntityType, partitionKeyPropertyValues, _cosmosQueryContext.ParameterValues);
70+
rootEntityType, partitionKeyPropertyValues, _cosmosQueryContext.Parameters);
7171
}
7272

7373
public IAsyncEnumerator<CosmosPage<T>> GetAsyncEnumerator(CancellationToken cancellationToken = default)
@@ -77,9 +77,9 @@ private CosmosSqlQuery GenerateQuery()
7777
=> _querySqlGeneratorFactory.Create().GetSqlQuery(
7878
(SelectExpression)new ParameterInliner(
7979
_sqlExpressionFactory,
80-
_cosmosQueryContext.ParameterValues)
80+
_cosmosQueryContext.Parameters)
8181
.Visit(_selectExpression),
82-
_cosmosQueryContext.ParameterValues);
82+
_cosmosQueryContext.Parameters);
8383

8484
private sealed class AsyncEnumerator : IAsyncEnumerator<CosmosPage<T>>
8585
{
@@ -135,11 +135,11 @@ public async ValueTask<bool> MoveNextAsync()
135135

136136
_hasExecuted = true;
137137

138-
var maxItemCount = (int)_cosmosQueryContext.ParameterValues[_queryingEnumerable._maxItemCountParameterName];
138+
var maxItemCount = (int)_cosmosQueryContext.Parameters[_queryingEnumerable._maxItemCountParameterName];
139139
var continuationToken =
140-
(string)_cosmosQueryContext.ParameterValues[_queryingEnumerable._continuationTokenParameterName];
140+
(string)_cosmosQueryContext.Parameters[_queryingEnumerable._continuationTokenParameterName];
141141
var responseContinuationTokenLimitInKb = (int?)
142-
_cosmosQueryContext.ParameterValues[_queryingEnumerable._responseContinuationTokenLimitInKbParameterName];
142+
_cosmosQueryContext.Parameters[_queryingEnumerable._responseContinuationTokenLimitInKbParameterName];
143143

144144
var sqlQuery = _queryingEnumerable.GenerateQuery();
145145

src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public QueryingEnumerable(
5757
_cosmosContainer = rootEntityType.GetContainer()
5858
?? throw new UnreachableException("Root entity type without a Cosmos container.");
5959
_cosmosPartitionKey = GeneratePartitionKey(
60-
rootEntityType, partitionKeyPropertyValues, _cosmosQueryContext.ParameterValues);
60+
rootEntityType, partitionKeyPropertyValues, _cosmosQueryContext.Parameters);
6161
}
6262

6363
public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
@@ -73,9 +73,9 @@ private CosmosSqlQuery GenerateQuery()
7373
=> _querySqlGeneratorFactory.Create().GetSqlQuery(
7474
(SelectExpression)new ParameterInliner(
7575
_sqlExpressionFactory,
76-
_cosmosQueryContext.ParameterValues)
76+
_cosmosQueryContext.Parameters)
7777
.Visit(_selectExpression),
78-
_cosmosQueryContext.ParameterValues);
78+
_cosmosQueryContext.Parameters);
7979

8080
public string ToQueryString()
8181
{

src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ReadItemQueryingEnumerable.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public ReadItemQueryingEnumerable(
5353
_cosmosContainer = rootEntityType.GetContainer()
5454
?? throw new UnreachableException("Root entity type without a Cosmos container.");
5555
_cosmosPartitionKey = GeneratePartitionKey(
56-
rootEntityType, partitionKeyPropertyValues, _cosmosQueryContext.ParameterValues);
56+
rootEntityType, partitionKeyPropertyValues, _cosmosQueryContext.Parameters);
5757
}
5858

5959
public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
@@ -83,7 +83,7 @@ private bool TryGetResourceId(out string resourceId)
8383
{
8484
var value = _readItemInfo.PropertyValues[property] switch
8585
{
86-
SqlParameterExpression { Name: var parameterName } => _cosmosQueryContext.ParameterValues[parameterName],
86+
SqlParameterExpression { Name: var parameterName } => _cosmosQueryContext.Parameters[parameterName],
8787
SqlConstantExpression { Value: var constantValue } => constantValue,
8888
_ => throw new UnreachableException()
8989
};

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,7 +1179,7 @@ when memberInitExpression.Bindings.SingleOrDefault(
11791179

11801180
private static T? ParameterValueExtractor<T>(QueryContext context, string baseParameterName, IProperty property)
11811181
{
1182-
var baseParameter = context.ParameterValues[baseParameterName];
1182+
var baseParameter = context.Parameters[baseParameterName];
11831183
return baseParameter == null ? (T?)(object?)null : (T?)property.GetGetter().GetClrValue(baseParameter);
11841184
}
11851185

@@ -1188,7 +1188,7 @@ when memberInitExpression.Bindings.SingleOrDefault(
11881188
string baseParameterName,
11891189
IProperty property)
11901190
{
1191-
if (context.ParameterValues[baseParameterName] is not IEnumerable<TEntity> baseListParameter)
1191+
if (context.Parameters[baseParameterName] is not IEnumerable<TEntity> baseListParameter)
11921192
{
11931193
return null;
11941194
}

src/EFCore.Design/Query/Internal/PrecompiledQueryCodeGenerator.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ or nameof(EntityFrameworkQueryableExtensions.ExecuteUpdateAsync),
797797
{
798798
// Special case: this is a non-lambda argument (Skip/Take/FromSql).
799799
// Simply add the argument directly as a parameter
800-
code.AppendLine($"""queryContext.AddParameter("{evaluatableRootPaths.ParameterName}", {parameterName});""");
800+
code.AppendLine($"""queryContext.Parameters.Add("{evaluatableRootPaths.ParameterName}", {parameterName});""");
801801
continue;
802802
}
803803

@@ -849,7 +849,7 @@ void GenerateCapturedVariableExtractors(
849849
// (see ExpressionTreeFuncletizer.Evaluate()).
850850
// TODO: Basically this means that the evaluator should come from ExpressionTreeFuncletizer itself, as part of its outputs
851851
// TODO: Integrate try/catch around the evaluation?
852-
code.AppendLine("queryContext.AddParameter(");
852+
code.AppendLine("queryContext.Parameters.Add(");
853853
using (code.Indent())
854854
{
855855
code
@@ -893,7 +893,7 @@ void GenerateCapturedVariableExtractors(
893893
};
894894

895895
code.AppendLine(
896-
$"""queryContext.AddParameter("{evaluatableRootPaths.ParameterName}", {argumentsParameter});""");
896+
$"""queryContext.Parameters.Add("{evaluatableRootPaths.ParameterName}", {argumentsParameter});""");
897897

898898
break;
899899
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,7 +1216,7 @@ private static Expression ProcessSingleResultScalar(
12161216

12171217
[UsedImplicitly]
12181218
private static T GetParameterValue<T>(QueryContext queryContext, string parameterName)
1219-
=> (T)queryContext.ParameterValues[parameterName]!;
1219+
=> (T)queryContext.Parameters[parameterName]!;
12201220

12211221
private static bool IsConvertedToNullable(Expression result, Expression original)
12221222
=> result.Type.IsNullableType()
@@ -1480,7 +1480,7 @@ when CanEvaluate(memberInitExpression):
14801480

14811481
private static T? ParameterValueExtractor<T>(QueryContext context, string baseParameterName, IProperty property)
14821482
{
1483-
var baseParameter = context.ParameterValues[baseParameterName];
1483+
var baseParameter = context.Parameters[baseParameterName];
14841484
return baseParameter == null ? (T?)(object?)null : (T?)property.GetGetter().GetClrValue(baseParameter);
14851485
}
14861486

@@ -1489,7 +1489,7 @@ when CanEvaluate(memberInitExpression):
14891489
string baseParameterName,
14901490
IProperty property)
14911491
{
1492-
if (!(context.ParameterValues[baseParameterName] is IEnumerable<TEntity> baseListParameter))
1492+
if (!(context.Parameters[baseParameterName] is IEnumerable<TEntity> baseListParameter))
14931493
{
14941494
return null;
14951495
}

src/EFCore.Relational/Extensions/Internal/RelationalCommandResolverExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public static IRelationalCommand RentAndPopulateRelationalCommand(
2424
this RelationalCommandResolver relationalCommandResolver,
2525
RelationalQueryContext queryContext)
2626
{
27-
var relationalCommandTemplate = relationalCommandResolver(queryContext.ParameterValues);
27+
var relationalCommandTemplate = relationalCommandResolver(queryContext.Parameters);
2828
var relationalCommand = queryContext.Connection.RentCommand();
2929
relationalCommand.PopulateFrom(relationalCommandTemplate);
3030
return relationalCommand;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
6+
namespace Microsoft.EntityFrameworkCore.Query;
7+
8+
/// <summary>
9+
/// A facade over <see cref="QueryContext.Parameters" /> which provides cache-safe way to access parameters after the SQL cache.
10+
/// </summary>
11+
/// <remarks>
12+
/// The SQL cache only includes then nullability of parameters in its cache key. Accordingly, this type exposes an API for checking
13+
/// the nullability of a parameter. It also allows retrieving the full parameter dictionary for arbitrary checks, but when this
14+
/// API is called, the facade records this fact, and the resulting SQL will not get cached.
15+
/// </remarks>
16+
public sealed class CacheSafeParameterFacade(Dictionary<string, object?> parameters)
17+
{
18+
/// <summary>
19+
/// Returns whether the parameter with the given name is null.
20+
/// </summary>
21+
/// <remarks>
22+
/// The method assumes that the parameter with the given name exists in the dictionary,
23+
/// and otherwise throws <see cref="UnreachableException" />.
24+
/// </remarks>
25+
public bool IsParameterNull(string parameterName)
26+
=> parameters.TryGetValue(parameterName, out var value)
27+
? value is null
28+
: throw new UnreachableException($"Parameter with name '{parameterName}' does not exist.");
29+
30+
/// <summary>
31+
/// Returns the full dictionary of parameters, and disables caching for the generated SQL.
32+
/// </summary>
33+
public Dictionary<string, object?> GetParametersAndDisableSqlCaching()
34+
{
35+
CanCache = false;
36+
37+
return parameters;
38+
}
39+
40+
/// <summary>
41+
/// Whether the SQL generated using this facade can be cached, i.e. whether the full dictionary of parameters
42+
/// has been accessed.
43+
/// </summary>
44+
public bool CanCache { get; private set; } = true;
45+
}

src/EFCore.Relational/Query/Internal/FromSqlQueryingEnumerable.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,11 @@ IEnumerator IEnumerable.GetEnumerator()
128128
/// doing so can result in application failures when updating to a new Entity Framework Core release.
129129
/// </summary>
130130
public virtual DbCommand CreateDbCommand()
131-
=> _relationalCommandResolver(_relationalQueryContext.ParameterValues)
131+
=> _relationalCommandResolver(_relationalQueryContext.Parameters)
132132
.CreateDbCommand(
133133
new RelationalCommandParameterObject(
134134
_relationalQueryContext.Connection,
135-
_relationalQueryContext.ParameterValues,
135+
_relationalQueryContext.Parameters,
136136
null,
137137
null,
138138
null,
@@ -269,7 +269,7 @@ private static bool InitializeReader(Enumerator enumerator)
269269
enumerator._dataReader = relationalCommand.ExecuteReader(
270270
new RelationalCommandParameterObject(
271271
enumerator._relationalQueryContext.Connection,
272-
enumerator._relationalQueryContext.ParameterValues,
272+
enumerator._relationalQueryContext.Parameters,
273273
enumerator._readerColumns,
274274
enumerator._relationalQueryContext.Context,
275275
enumerator._relationalQueryContext.CommandLogger,
@@ -384,7 +384,7 @@ private static async Task<bool> InitializeReaderAsync(AsyncEnumerator enumerator
384384
enumerator._dataReader = await relationalCommand.ExecuteReaderAsync(
385385
new RelationalCommandParameterObject(
386386
enumerator._relationalQueryContext.Connection,
387-
enumerator._relationalQueryContext.ParameterValues,
387+
enumerator._relationalQueryContext.Parameters,
388388
enumerator._readerColumns,
389389
enumerator._relationalQueryContext.Context,
390390
enumerator._relationalQueryContext.CommandLogger,

0 commit comments

Comments
 (0)