Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ private readonly IDictionary<FromSqlExpression, Expression> _visitedFromSqlExpre

private readonly Dictionary<string, SqlParameterExpression> _sqlParameters = new();

private CacheSafeParameterFacade _parametersFacade;
private ParametersCacheDecorator _parametersDecorator;
private ParameterNameGenerator _parameterNameGenerator;

/// <summary>
Expand All @@ -53,7 +53,7 @@ public RelationalParameterProcessor(
_typeMappingSource = dependencies.TypeMappingSource;
_parameterNameGeneratorFactory = dependencies.ParameterNameGeneratorFactory;
_sqlGenerationHelper = dependencies.SqlGenerationHelper;
_parametersFacade = default!;
_parametersDecorator = default!;
_parameterNameGenerator = default!;
}

Expand All @@ -68,13 +68,13 @@ public RelationalParameterProcessor(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual Expression Expand(Expression queryExpression, CacheSafeParameterFacade parametersFacade)
public virtual Expression Expand(Expression queryExpression, ParametersCacheDecorator parametersDecorator)
{
_visitedFromSqlExpressions.Clear();
_prefixedParameterNames.Clear();
_sqlParameters.Clear();
_parameterNameGenerator = _parameterNameGeneratorFactory.Create();
_parametersFacade = parametersFacade;
_parametersDecorator = parametersDecorator;

var result = Visit(queryExpression);

Expand Down Expand Up @@ -140,7 +140,7 @@ private FromSqlExpression VisitFromSql(FromSqlExpression fromSql)
{
case QueryParameterExpression queryParameter:
// parameter value will never be null. It could be empty object?[]
var parameters = _parametersFacade.GetParametersAndDisableSqlCaching();
var parameters = _parametersDecorator.GetAndDisableCaching();
var parameterValues = (object?[])parameters[queryParameter.Name]!;

var subParameters = new List<IRelationalParameter>(parameterValues.Length);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,47 @@
namespace Microsoft.EntityFrameworkCore.Query;

/// <summary>
/// A facade over <see cref="QueryContext.Parameters" /> which provides cache-safe way to access parameters after the SQL cache.
/// A decorator over <see cref="QueryContext.Parameters" /> which provides a cache-safe way to access parameters after the SQL cache.
/// </summary>
/// <remarks>
/// The SQL cache only includes then nullability of parameters in its cache key. Accordingly, this type exposes an API for checking
/// the nullability of a parameter. It also allows retrieving the full parameter dictionary for arbitrary checks, but when this
/// API is called, the facade records this fact, and the resulting SQL will not get cached.
/// API is called, the decorator records this fact, and the resulting SQL will not get cached.
/// </remarks>
public sealed class CacheSafeParameterFacade(Dictionary<string, object?> parameters)
public sealed class ParametersCacheDecorator(Dictionary<string, object?> parameters)
{
/// <summary>
/// Returns whether the parameter with the given name is null.
/// Returns whether the parameter with the given name is <see langword="null" />.
/// </summary>
/// <remarks>
/// The method assumes that the parameter with the given name exists in the dictionary,
/// and otherwise throws <see cref="UnreachableException" />.
/// </remarks>
public bool IsParameterNull(string parameterName)
public bool IsNull(string parameterName)
=> parameters.TryGetValue(parameterName, out var value)
? value is null
: throw new UnreachableException($"Parameter with name '{parameterName}' does not exist.");

/// <summary>
/// Returns the full dictionary of parameters, and disables caching for the generated SQL.
/// </summary>
public Dictionary<string, object?> GetParametersAndDisableSqlCaching()
public Dictionary<string, object?> GetAndDisableCaching()
{
CanCache = false;

return parameters;
}

/// <summary>
/// Whether the SQL generated using this facade can be cached, i.e. whether the full dictionary of parameters
/// Whether the SQL generated using this decorator can be cached, i.e. whether the full dictionary of parameters
/// has been accessed.
/// </summary>
/// <remarks>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </remarks>
[EntityFrameworkInternal]
public bool CanCache { get; private set; } = true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public RelationalParameterBasedSqlProcessor(
[EntityFrameworkInternal]
public virtual Expression Process(Expression queryExpression, Dictionary<string, object?> parameters, out bool canCache)
{
var parametersFacade = new CacheSafeParameterFacade(parameters);
var parametersFacade = new ParametersCacheDecorator(parameters);
var result = Process(queryExpression, parametersFacade);
canCache = parametersFacade.CanCache;

Expand All @@ -60,11 +60,11 @@ public virtual Expression Process(Expression queryExpression, Dictionary<string,
/// Performs final query processing that takes parameter values into account.
/// </summary>
/// <param name="queryExpression">A query expression to process.</param>
/// <param name="parametersFacade">A facade allowing access to parameters in a cache-safe way.</param>
public virtual Expression Process(Expression queryExpression, CacheSafeParameterFacade parametersFacade)
/// <param name="parametersDecorator">A decorator allowing access to parameters in a cache-safe way.</param>
public virtual Expression Process(Expression queryExpression, ParametersCacheDecorator parametersDecorator)
{
queryExpression = ProcessSqlNullability(queryExpression, parametersFacade);
queryExpression = ExpandFromSqlParameter(queryExpression, parametersFacade);
queryExpression = ProcessSqlNullability(queryExpression, parametersDecorator);
queryExpression = ExpandFromSqlParameter(queryExpression, parametersDecorator);

return queryExpression;
}
Expand All @@ -74,19 +74,19 @@ public virtual Expression Process(Expression queryExpression, CacheSafeParameter
/// optimize it for given parameter values.
/// </summary>
/// <param name="queryExpression">A query expression to optimize.</param>
/// <param name="parametersFacade">A facade allowing access to parameters in a cache-safe way.</param>
/// <param name="Decorator">A decorator allowing access to parameters in a cache-safe way.</param>
/// <returns>A processed query expression.</returns>
protected virtual Expression ProcessSqlNullability(Expression queryExpression, CacheSafeParameterFacade parametersFacade)
=> new SqlNullabilityProcessor(Dependencies, Parameters).Process(queryExpression, parametersFacade);
protected virtual Expression ProcessSqlNullability(Expression queryExpression, ParametersCacheDecorator Decorator)
=> new SqlNullabilityProcessor(Dependencies, Parameters).Process(queryExpression, Decorator);

/// <summary>
/// Expands the parameters to <see cref="FromSqlExpression" /> inside the query expression for given parameter values.
/// </summary>
/// <param name="queryExpression">A query expression to optimize.</param>
/// <param name="parametersFacade">A facade allowing access to parameters in a cache-safe way.</param>
/// <param name="Decorator">A decorator allowing access to parameters in a cache-safe way.</param>
/// <returns>A processed query expression.</returns>
protected virtual Expression ExpandFromSqlParameter(Expression queryExpression, CacheSafeParameterFacade parametersFacade)
=> new RelationalParameterProcessor(Dependencies).Expand(queryExpression, parametersFacade);
protected virtual Expression ExpandFromSqlParameter(Expression queryExpression, ParametersCacheDecorator Decorator)
=> new RelationalParameterProcessor(Dependencies).Expand(queryExpression, Decorator);

/// <summary>
/// Optimizes the query expression for given parameter values.
Expand Down
24 changes: 12 additions & 12 deletions src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public SqlNullabilityProcessor(
_nonNullableColumns = [];
_nullValueColumns = [];
_collectionParameterExpansionMap = [];
ParametersFacade = null!;
ParametersDecorator = null!;
}

/// <summary>
Expand All @@ -66,20 +66,20 @@ public SqlNullabilityProcessor(
/// <summary>
/// Dictionary of current parameter values in use.
/// </summary>
protected virtual CacheSafeParameterFacade ParametersFacade { get; private set; }
protected virtual ParametersCacheDecorator ParametersDecorator { get; private set; }

/// <summary>
/// Processes a query expression to apply null semantics and optimize it.
/// </summary>
/// <param name="queryExpression">A query expression to process.</param>
/// <param name="parametersFacade">A facade allowing access to parameters in a cache-safe way.</param>
/// <param name="parametersDecorator">A decorator allowing access to parameters in a cache-safe way.</param>
/// <returns>An optimized query expression.</returns>
public virtual Expression Process(Expression queryExpression, CacheSafeParameterFacade parametersFacade)
public virtual Expression Process(Expression queryExpression, ParametersCacheDecorator parametersDecorator)
{
_nonNullableColumns.Clear();
_nullValueColumns.Clear();
_collectionParameterExpansionMap.Clear();
ParametersFacade = parametersFacade;
ParametersDecorator = parametersDecorator;

var result = Visit(queryExpression);

Expand Down Expand Up @@ -117,7 +117,7 @@ protected override Expression VisitExtension(Expression node)
Check.DebugAssert(valuesParameter.TypeMapping is not null);
Check.DebugAssert(valuesParameter.TypeMapping.ElementTypeMapping is not null);
var elementTypeMapping = (RelationalTypeMapping)valuesParameter.TypeMapping.ElementTypeMapping;
var queryParameters = ParametersFacade.GetParametersAndDisableSqlCaching();
var queryParameters = ParametersDecorator.GetAndDisableCaching();
var values = ((IEnumerable?)queryParameters[valuesParameter.Name])?.Cast<object>().ToList() ?? [];

var intTypeMapping = (IntTypeMapping?)Dependencies.TypeMappingSource.FindMapping(typeof(int));
Expand Down Expand Up @@ -817,7 +817,7 @@ InExpression ProcessInExpressionValues(
// The InExpression has a values parameter. Expand it out, embedding its values as constants into the SQL; disable SQL
// caching.
var elementTypeMapping = (RelationalTypeMapping)inExpression.ValuesParameter.TypeMapping!.ElementTypeMapping!;
var parameters = ParametersFacade.GetParametersAndDisableSqlCaching();
var parameters = ParametersDecorator.GetAndDisableCaching();
var values = ((IEnumerable?)parameters[valuesParameter.Name])?.Cast<object>().ToList() ?? [];

processedValues = [];
Expand Down Expand Up @@ -1430,7 +1430,7 @@ protected virtual SqlExpression VisitSqlParameter(
bool allowOptimizedExpansion,
out bool nullable)
{
if (ParametersFacade.IsParameterNull(sqlParameterExpression.Name))
if (ParametersDecorator.IsNull(sqlParameterExpression.Name))
{
nullable = true;

Expand All @@ -1444,7 +1444,7 @@ protected virtual SqlExpression VisitSqlParameter(

if (sqlParameterExpression.TranslationMode is ParameterTranslationMode.Constant)
{
var parameters = ParametersFacade.GetParametersAndDisableSqlCaching();
var parameters = ParametersDecorator.GetAndDisableCaching();

return _sqlExpressionFactory.Constant(
parameters[sqlParameterExpression.Name],
Expand Down Expand Up @@ -1544,7 +1544,7 @@ protected virtual int CalculateParameterBucketSize(int count, RelationalTypeMapp
// Note that we can check parameter values for null since we cache by the parameter nullability; but we cannot do the same for bool.
private bool IsNull(SqlExpression? expression)
=> expression is SqlConstantExpression { Value: null }
|| expression is SqlParameterExpression { Name: string parameterName } && ParametersFacade.IsParameterNull(parameterName);
|| expression is SqlParameterExpression { Name: string parameterName } && ParametersDecorator.IsNull(parameterName);

private bool IsTrue(SqlExpression? expression)
=> expression is SqlConstantExpression { Value: true };
Expand Down Expand Up @@ -1845,7 +1845,7 @@ protected virtual bool TryMakeNonNullable(
&& collection is SqlParameterExpression collectionParameter)
{
// We're looking at a parameter beyond its simple nullability, so we can't use the SQL cache for this query.
var parameters = ParametersFacade.GetParametersAndDisableSqlCaching();
var parameters = ParametersDecorator.GetAndDisableCaching();
if (parameters[collectionParameter.Name] is not IList values)
{
throw new UnreachableException($"Parameter '{collectionParameter.Name}' is not an IList.");
Expand Down Expand Up @@ -1971,7 +1971,7 @@ private SqlExpression ProcessNullNotNull(SqlExpression sqlExpression, bool opera
// not_null_value_parameter is null -> false
// not_null_value_parameter is not null -> true
return _sqlExpressionFactory.Constant(
ParametersFacade.IsParameterNull(sqlParameterOperand.Name) ^ sqlUnaryExpression.OperatorType == ExpressionType.NotEqual,
ParametersDecorator.IsNull(sqlParameterOperand.Name) ^ sqlUnaryExpression.OperatorType == ExpressionType.NotEqual,
sqlUnaryExpression.TypeMapping);

case ColumnExpression columnOperand
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,26 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class SqlServerParameterBasedSqlProcessor : RelationalParameterBasedSqlProcessor
public class SqlServerParameterBasedSqlProcessor(
RelationalParameterBasedSqlProcessorDependencies dependencies,
RelationalParameterBasedSqlProcessorParameters parameters,
ISqlServerSingletonOptions sqlServerSingletonOptions)
: RelationalParameterBasedSqlProcessor(dependencies, parameters)
{
private readonly ISqlServerSingletonOptions _sqlServerSingletonOptions;
private readonly ISqlServerSingletonOptions _sqlServerSingletonOptions = sqlServerSingletonOptions;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public SqlServerParameterBasedSqlProcessor(
RelationalParameterBasedSqlProcessorDependencies dependencies,
RelationalParameterBasedSqlProcessorParameters parameters,
ISqlServerSingletonOptions sqlServerSingletonOptions)
: base(dependencies, parameters)
{
_sqlServerSingletonOptions = sqlServerSingletonOptions;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public override Expression Process(Expression queryExpression, CacheSafeParameterFacade parametersFacade)
public override Expression Process(Expression queryExpression, ParametersCacheDecorator parametersDecorator)
{
var afterZeroLimitConversion = new SqlServerZeroLimitConverter(Dependencies.SqlExpressionFactory)
.Process(queryExpression, parametersFacade);
.Process(queryExpression, parametersDecorator);

var afterBaseProcessing = base.Process(afterZeroLimitConversion, parametersFacade);
var afterBaseProcessing = base.Process(afterZeroLimitConversion, parametersDecorator);

var afterSearchConditionConversion = new SearchConditionConverter(Dependencies.SqlExpressionFactory)
.Visit(afterBaseProcessing);
Expand All @@ -50,7 +39,7 @@ public override Expression Process(Expression queryExpression, CacheSafeParamete
}

/// <inheritdoc />
protected override Expression ProcessSqlNullability(Expression selectExpression, CacheSafeParameterFacade parametersFacade)
=> new SqlServerSqlNullabilityProcessor(Dependencies, Parameters, _sqlServerSingletonOptions).Process(
selectExpression, parametersFacade);
protected override Expression ProcessSqlNullability(Expression selectExpression, ParametersCacheDecorator Decorator)
=> new SqlServerSqlNullabilityProcessor(Dependencies, Parameters, _sqlServerSingletonOptions)
.Process(selectExpression, Decorator);
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ public SqlServerSqlNullabilityProcessor(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public override Expression Process(Expression queryExpression, CacheSafeParameterFacade parametersFacade)
public override Expression Process(Expression queryExpression, ParametersCacheDecorator parametersDecorator)
{
var result = base.Process(queryExpression, parametersFacade);
var result = base.Process(queryExpression, parametersDecorator);
_openJsonAliasCounter = 0;
return result;
}
Expand Down Expand Up @@ -303,7 +303,7 @@ private bool TryHandleOverLimitParameters(
out List<SqlExpression>? constantsResult,
out bool? containsNulls)
{
var parameters = ParametersFacade.GetParametersAndDisableSqlCaching();
var parameters = ParametersDecorator.GetAndDisableCaching();
var values = ((IEnumerable?)parameters[valuesParameter.Name])?.Cast<object>().ToList() ?? [];

// SQL Server has limit on number of parameters in a query.
Expand Down
Loading
Loading