diff --git a/src/EFCore.Relational/Query/Internal/RelationalParameterProcessor.cs b/src/EFCore.Relational/Query/Internal/RelationalParameterProcessor.cs index f1ed4b087b1..e78101c5de6 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalParameterProcessor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalParameterProcessor.cs @@ -35,7 +35,7 @@ private readonly IDictionary _visitedFromSqlExpre private readonly Dictionary _sqlParameters = new(); - private CacheSafeParameterFacade _parametersFacade; + private ParametersCacheDecorator _parametersDecorator; private ParameterNameGenerator _parameterNameGenerator; /// @@ -53,7 +53,7 @@ public RelationalParameterProcessor( _typeMappingSource = dependencies.TypeMappingSource; _parameterNameGeneratorFactory = dependencies.ParameterNameGeneratorFactory; _sqlGenerationHelper = dependencies.SqlGenerationHelper; - _parametersFacade = default!; + _parametersDecorator = default!; _parameterNameGenerator = default!; } @@ -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. /// - 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); @@ -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(parameterValues.Length); diff --git a/src/EFCore.Relational/Query/CacheSafeParameterFacade.cs b/src/EFCore.Relational/Query/ParametersCacheDecorator.cs similarity index 56% rename from src/EFCore.Relational/Query/CacheSafeParameterFacade.cs rename to src/EFCore.Relational/Query/ParametersCacheDecorator.cs index 0a54459d278..474550a5ce1 100644 --- a/src/EFCore.Relational/Query/CacheSafeParameterFacade.cs +++ b/src/EFCore.Relational/Query/ParametersCacheDecorator.cs @@ -4,23 +4,23 @@ namespace Microsoft.EntityFrameworkCore.Query; /// -/// A facade over which provides cache-safe way to access parameters after the SQL cache. +/// A decorator over which provides a cache-safe way to access parameters after the SQL cache. /// /// /// 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. /// -public sealed class CacheSafeParameterFacade(Dictionary parameters) +public sealed class ParametersCacheDecorator(Dictionary parameters) { /// - /// Returns whether the parameter with the given name is null. + /// Returns whether the parameter with the given name is . /// /// /// The method assumes that the parameter with the given name exists in the dictionary, /// and otherwise throws . /// - 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."); @@ -28,7 +28,7 @@ public bool IsParameterNull(string parameterName) /// /// Returns the full dictionary of parameters, and disables caching for the generated SQL. /// - public Dictionary GetParametersAndDisableSqlCaching() + public Dictionary GetAndDisableCaching() { CanCache = false; @@ -36,8 +36,15 @@ public bool IsParameterNull(string parameterName) } /// - /// 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. /// + /// + /// 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. + /// + [EntityFrameworkInternal] public bool CanCache { get; private set; } = true; } diff --git a/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessor.cs b/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessor.cs index 990e718f952..a58ba65d325 100644 --- a/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessor.cs +++ b/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessor.cs @@ -49,7 +49,7 @@ public RelationalParameterBasedSqlProcessor( [EntityFrameworkInternal] public virtual Expression Process(Expression queryExpression, Dictionary parameters, out bool canCache) { - var parametersFacade = new CacheSafeParameterFacade(parameters); + var parametersFacade = new ParametersCacheDecorator(parameters); var result = Process(queryExpression, parametersFacade); canCache = parametersFacade.CanCache; @@ -60,11 +60,11 @@ public virtual Expression Process(Expression queryExpression, Dictionary /// A query expression to process. - /// A facade allowing access to parameters in a cache-safe way. - public virtual Expression Process(Expression queryExpression, CacheSafeParameterFacade parametersFacade) + /// A decorator allowing access to parameters in a cache-safe way. + 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; } @@ -74,19 +74,19 @@ public virtual Expression Process(Expression queryExpression, CacheSafeParameter /// optimize it for given parameter values. /// /// A query expression to optimize. - /// A facade allowing access to parameters in a cache-safe way. + /// A decorator allowing access to parameters in a cache-safe way. /// A processed query expression. - 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); /// /// Expands the parameters to inside the query expression for given parameter values. /// /// A query expression to optimize. - /// A facade allowing access to parameters in a cache-safe way. + /// A decorator allowing access to parameters in a cache-safe way. /// A processed query expression. - 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); /// /// Optimizes the query expression for given parameter values. diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index 644ebeb3438..f5543360c7c 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -45,7 +45,7 @@ public SqlNullabilityProcessor( _nonNullableColumns = []; _nullValueColumns = []; _collectionParameterExpansionMap = []; - ParametersFacade = null!; + ParametersDecorator = null!; } /// @@ -66,20 +66,20 @@ public SqlNullabilityProcessor( /// /// Dictionary of current parameter values in use. /// - protected virtual CacheSafeParameterFacade ParametersFacade { get; private set; } + protected virtual ParametersCacheDecorator ParametersDecorator { get; private set; } /// /// Processes a query expression to apply null semantics and optimize it. /// /// A query expression to process. - /// A facade allowing access to parameters in a cache-safe way. + /// A decorator allowing access to parameters in a cache-safe way. /// An optimized query expression. - 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); @@ -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().ToList() ?? []; var intTypeMapping = (IntTypeMapping?)Dependencies.TypeMappingSource.FindMapping(typeof(int)); @@ -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().ToList() ?? []; processedValues = []; @@ -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; @@ -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], @@ -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 }; @@ -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."); @@ -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 diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedSqlProcessor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedSqlProcessor.cs index ed57e0fee82..d2618a41d12 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedSqlProcessor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedSqlProcessor.cs @@ -11,9 +11,13 @@ 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. /// -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; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -21,27 +25,12 @@ public class SqlServerParameterBasedSqlProcessor : RelationalParameterBasedSqlPr /// 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. /// - public SqlServerParameterBasedSqlProcessor( - RelationalParameterBasedSqlProcessorDependencies dependencies, - RelationalParameterBasedSqlProcessorParameters parameters, - ISqlServerSingletonOptions sqlServerSingletonOptions) - : base(dependencies, parameters) - { - _sqlServerSingletonOptions = sqlServerSingletonOptions; - } - - /// - /// 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. - /// - 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); @@ -50,7 +39,7 @@ public override Expression Process(Expression queryExpression, CacheSafeParamete } /// - 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); } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlNullabilityProcessor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlNullabilityProcessor.cs index 921429a1dbc..ce7cbbd75fc 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlNullabilityProcessor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlNullabilityProcessor.cs @@ -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. /// - 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; } @@ -303,7 +303,7 @@ private bool TryHandleOverLimitParameters( out List? constantsResult, out bool? containsNulls) { - var parameters = ParametersFacade.GetParametersAndDisableSqlCaching(); + var parameters = ParametersDecorator.GetAndDisableCaching(); var values = ((IEnumerable?)parameters[valuesParameter.Name])?.Cast().ToList() ?? []; // SQL Server has limit on number of parameters in a query. diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerZeroLimitConverter.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerZeroLimitConverter.cs index f4eea232d21..86bad814019 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerZeroLimitConverter.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerZeroLimitConverter.cs @@ -15,7 +15,7 @@ public class SqlServerZeroLimitConverter : ExpressionVisitor { private readonly ISqlExpressionFactory _sqlExpressionFactory; - private CacheSafeParameterFacade _parametersFacade; + private ParametersCacheDecorator _parametersDecorator; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -26,7 +26,7 @@ public class SqlServerZeroLimitConverter : ExpressionVisitor public SqlServerZeroLimitConverter(ISqlExpressionFactory sqlExpressionFactory) { _sqlExpressionFactory = sqlExpressionFactory; - _parametersFacade = null!; + _parametersDecorator = null!; } /// @@ -35,9 +35,9 @@ public SqlServerZeroLimitConverter(ISqlExpressionFactory sqlExpressionFactory) /// 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. /// - public virtual Expression Process(Expression queryExpression, CacheSafeParameterFacade parametersFacade) + public virtual Expression Process(Expression queryExpression, ParametersCacheDecorator parametersDecorator) { - _parametersFacade = parametersFacade; + _parametersDecorator = parametersDecorator; return Visit(queryExpression); } @@ -72,7 +72,7 @@ bool IsZero(SqlExpression? sqlExpression) => sqlExpression switch { SqlConstantExpression { Value: int i } => i == 0, - SqlParameterExpression p => _parametersFacade.GetParametersAndDisableSqlCaching()[p.Name] is 0, + SqlParameterExpression p => _parametersDecorator.GetAndDisableCaching()[p.Name] is 0, _ => false }; } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteParameterBasedSqlProcessor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteParameterBasedSqlProcessor.cs index 5833215b9c0..042e6ed41af 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteParameterBasedSqlProcessor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteParameterBasedSqlProcessor.cs @@ -9,20 +9,11 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.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. /// -public class SqliteParameterBasedSqlProcessor : RelationalParameterBasedSqlProcessor +public class SqliteParameterBasedSqlProcessor( + RelationalParameterBasedSqlProcessorDependencies dependencies, + RelationalParameterBasedSqlProcessorParameters parameters) + : RelationalParameterBasedSqlProcessor(dependencies, parameters) { - /// - /// 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. - /// - public SqliteParameterBasedSqlProcessor( - RelationalParameterBasedSqlProcessorDependencies dependencies, - RelationalParameterBasedSqlProcessorParameters parameters) - : base(dependencies, parameters) - { - } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -30,6 +21,6 @@ public SqliteParameterBasedSqlProcessor( /// 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. /// - protected override Expression ProcessSqlNullability(Expression queryExpression, CacheSafeParameterFacade parametersFacade) - => new SqliteSqlNullabilityProcessor(Dependencies, Parameters).Process(queryExpression, parametersFacade); + protected override Expression ProcessSqlNullability(Expression queryExpression, ParametersCacheDecorator parametersDecorator) + => new SqliteSqlNullabilityProcessor(Dependencies, Parameters).Process(queryExpression, parametersDecorator); }