diff --git a/src/EFCore.Relational/Query/Internal/FromSqlParameterExpandingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/FromSqlParameterExpandingExpressionVisitor.cs index b881ae64ea5..a90e59798cf 100644 --- a/src/EFCore.Relational/Query/Internal/FromSqlParameterExpandingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/FromSqlParameterExpandingExpressionVisitor.cs @@ -26,7 +26,6 @@ private readonly IDictionary _visitedFromSqlExpre private IReadOnlyDictionary _parametersValues; private ParameterNameGenerator _parameterNameGenerator; private bool _canCache; - private SelectExpression _selectExpression; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -44,7 +43,6 @@ public FromSqlParameterExpandingExpressionVisitor( _parameterNameGeneratorFactory = dependencies.ParameterNameGeneratorFactory; _parametersValues = default!; _parameterNameGenerator = default!; - _selectExpression = default!; } /// @@ -58,8 +56,8 @@ public FromSqlParameterExpandingExpressionVisitor( /// 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 SelectExpression Expand( - SelectExpression selectExpression, + public virtual Expression Expand( + Expression queryExpression, IReadOnlyDictionary parameterValues, out bool canCache) { @@ -67,9 +65,8 @@ public virtual SelectExpression Expand( _parameterNameGenerator = _parameterNameGeneratorFactory.Create(); _parametersValues = parameterValues; _canCache = true; - _selectExpression = selectExpression; - var result = (SelectExpression)Visit(selectExpression); + var result = Visit(queryExpression); canCache = _canCache; return result; diff --git a/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs b/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs index eeb20b20c8d..c92e153a0ad 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs @@ -21,7 +21,7 @@ private static readonly ConcurrentDictionary Locks private readonly IMemoryCache _memoryCache; private readonly IQuerySqlGeneratorFactory _querySqlGeneratorFactory; - private readonly SelectExpression _selectExpression; + private readonly Expression _queryExpression; private readonly RelationalParameterBasedSqlProcessor _relationalParameterBasedSqlProcessor; /// @@ -34,12 +34,12 @@ public RelationalCommandCache( IMemoryCache memoryCache, IQuerySqlGeneratorFactory querySqlGeneratorFactory, IRelationalParameterBasedSqlProcessorFactory relationalParameterBasedSqlProcessorFactory, - SelectExpression selectExpression, + Expression queryExpression, bool useRelationalNulls) { _memoryCache = memoryCache; _querySqlGeneratorFactory = querySqlGeneratorFactory; - _selectExpression = selectExpression; + _queryExpression = queryExpression; _relationalParameterBasedSqlProcessor = relationalParameterBasedSqlProcessorFactory.Create(useRelationalNulls); } @@ -51,7 +51,7 @@ public RelationalCommandCache( /// public virtual IRelationalCommandTemplate GetRelationalCommandTemplate(IReadOnlyDictionary parameters) { - var cacheKey = new CommandCacheKey(_selectExpression, parameters); + var cacheKey = new CommandCacheKey(_queryExpression, parameters); if (_memoryCache.TryGetValue(cacheKey, out IRelationalCommandTemplate? relationalCommandTemplate)) { @@ -69,9 +69,9 @@ public virtual IRelationalCommandTemplate GetRelationalCommandTemplate(IReadOnly { if (!_memoryCache.TryGetValue(cacheKey, out relationalCommandTemplate)) { - var selectExpression = _relationalParameterBasedSqlProcessor.Optimize( - _selectExpression, parameters, out var canCache); - relationalCommandTemplate = _querySqlGeneratorFactory.Create().GetCommand(selectExpression); + var queryExpression = _relationalParameterBasedSqlProcessor.Optimize( + _queryExpression, parameters, out var canCache); + relationalCommandTemplate = _querySqlGeneratorFactory.Create().GetCommand(queryExpression); if (canCache) { @@ -96,22 +96,22 @@ public virtual IRelationalCommandTemplate GetRelationalCommandTemplate(IReadOnly /// void IPrintableExpression.Print(ExpressionPrinter expressionPrinter) { - expressionPrinter.AppendLine("RelationalCommandCache.SelectExpression("); + expressionPrinter.AppendLine("RelationalCommandCache.QueryExpression("); using (expressionPrinter.Indent()) { - expressionPrinter.Visit(_selectExpression); + expressionPrinter.Visit(_queryExpression); expressionPrinter.Append(")"); } } private readonly struct CommandCacheKey : IEquatable { - private readonly SelectExpression _selectExpression; + private readonly Expression _queryExpression; private readonly IReadOnlyDictionary _parameterValues; - public CommandCacheKey(SelectExpression selectExpression, IReadOnlyDictionary parameterValues) + public CommandCacheKey(Expression queryExpression, IReadOnlyDictionary parameterValues) { - _selectExpression = selectExpression; + _queryExpression = queryExpression; _parameterValues = parameterValues; } @@ -121,7 +121,8 @@ public override bool Equals(object? obj) public bool Equals(CommandCacheKey commandCacheKey) { - if (!ReferenceEquals(_selectExpression, commandCacheKey._selectExpression)) + // Intentionally reference equals + if (!ReferenceEquals(_queryExpression, commandCacheKey._queryExpression)) { return false; } diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs index 5b884c2a846..1d8e6ae2bcc 100644 --- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs @@ -61,23 +61,33 @@ public QuerySqlGenerator(QuerySqlGeneratorDependencies dependencies) protected virtual QuerySqlGeneratorDependencies Dependencies { get; } /// - /// Gets a relational command for a . + /// Gets a relational command for a query expression. /// - /// A select expression to print in command text. - /// A relational command with a SQL represented by the select expression. - public virtual IRelationalCommand GetCommand(SelectExpression selectExpression) + /// A query expression to print in command text. + /// A relational command with a SQL represented by the query expression. + public virtual IRelationalCommand GetCommand(Expression queryExpression) { _relationalCommandBuilder = _relationalCommandBuilderFactory.Create(); - GenerateTagsHeaderComment(selectExpression); - - if (selectExpression.IsNonComposedFromSql()) - { - GenerateFromSql((FromSqlExpression)selectExpression.Tables[0]); - } - else + switch (queryExpression) { - VisitSelect(selectExpression); + case SelectExpression selectExpression: + { + GenerateTagsHeaderComment(selectExpression); + + if (selectExpression.IsNonComposedFromSql()) + { + GenerateFromSql((FromSqlExpression)selectExpression.Tables[0]); + } + else + { + VisitSelect(selectExpression); + } + } + break; + + default: + throw new InvalidOperationException(); } return _relationalCommandBuilder.Build(); diff --git a/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessor.cs b/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessor.cs index 3c67dc51f09..14f598e221f 100644 --- a/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessor.cs +++ b/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessor.cs @@ -8,7 +8,7 @@ namespace Microsoft.EntityFrameworkCore.Query; /// /// -/// A class that processes the after parementer values are known. +/// A class that processes the query expression after parementer values are known. /// /// /// This type is typically used by database providers (and other extensions). It is generally @@ -18,7 +18,7 @@ namespace Microsoft.EntityFrameworkCore.Query; public class RelationalParameterBasedSqlProcessor { /// - /// Creates a new instance of the class. + /// Creates a new instance of the class. /// /// Parameter object containing dependencies for this class. /// A bool value indicating if relational nulls should be used. @@ -41,51 +41,51 @@ public RelationalParameterBasedSqlProcessor( protected virtual bool UseRelationalNulls { get; } /// - /// Optimizes the for given parameter values. + /// Optimizes the query expression for given parameter values. /// - /// A select expression to optimize. + /// A query expression to optimize. /// A dictionary of parameter values to use. - /// A bool value indicating if the select expression can be cached. - /// An optimized select expression. - public virtual SelectExpression Optimize( - SelectExpression selectExpression, + /// A bool value indicating if the query expression can be cached. + /// An optimized query expression. + public virtual Expression Optimize( + Expression queryExpression, IReadOnlyDictionary parametersValues, out bool canCache) { canCache = true; - selectExpression = ProcessSqlNullability(selectExpression, parametersValues, out var sqlNullablityCanCache); + queryExpression = ProcessSqlNullability(queryExpression, parametersValues, out var sqlNullablityCanCache); canCache &= sqlNullablityCanCache; - selectExpression = ExpandFromSqlParameter(selectExpression, parametersValues, out var fromSqlParameterCanCache); + queryExpression = ExpandFromSqlParameter(queryExpression, parametersValues, out var fromSqlParameterCanCache); canCache &= fromSqlParameterCanCache; - return selectExpression; + return queryExpression; } /// - /// Processes the based on nullability of nodes to apply null semantics in use and + /// Processes the query expression based on nullability of nodes to apply null semantics in use and /// optimize it for given parameter values. /// - /// A select expression to optimize. + /// A query expression to optimize. /// A dictionary of parameter values to use. - /// A bool value indicating if the select expression can be cached. - /// A processed select expression. - protected virtual SelectExpression ProcessSqlNullability( - SelectExpression selectExpression, + /// A bool value indicating if the query expression can be cached. + /// A processed query expression. + protected virtual Expression ProcessSqlNullability( + Expression queryExpression, IReadOnlyDictionary parametersValues, out bool canCache) - => new SqlNullabilityProcessor(Dependencies, UseRelationalNulls).Process(selectExpression, parametersValues, out canCache); + => new SqlNullabilityProcessor(Dependencies, UseRelationalNulls).Process(queryExpression, parametersValues, out canCache); /// - /// Expands the parameters to inside the for given parameter values. + /// Expands the parameters to inside the query expression for given parameter values. /// - /// A select expression to optimize. + /// A query expression to optimize. /// A dictionary of parameter values to use. - /// A bool value indicating if the select expression can be cached. - /// A processed select expression. - protected virtual SelectExpression ExpandFromSqlParameter( - SelectExpression selectExpression, + /// A bool value indicating if the query expression can be cached. + /// A processed query expression. + protected virtual Expression ExpandFromSqlParameter( + Expression queryExpression, IReadOnlyDictionary parametersValues, out bool canCache) - => new FromSqlParameterExpandingExpressionVisitor(Dependencies).Expand(selectExpression, parametersValues, out canCache); + => new FromSqlParameterExpandingExpressionVisitor(Dependencies).Expand(queryExpression, parametersValues, out canCache); } diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index ddd086db8ac..9d03eaf7d2b 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -59,14 +59,14 @@ public SqlNullabilityProcessor( protected virtual IReadOnlyDictionary ParameterValues { get; private set; } /// - /// Processes a to apply null semantics and optimize it. + /// Processes a query expression to apply null semantics and optimize it. /// - /// A select expression to process. + /// A query expression to process. /// A dictionary of parameter values in use. - /// A bool value indicating whether the select expression can be cached. - /// An optimized select expression. - public virtual SelectExpression Process( - SelectExpression selectExpression, + /// A bool value indicating whether the query expression can be cached. + /// An optimized query expression. + public virtual Expression Process( + Expression queryExpression, IReadOnlyDictionary parameterValues, out bool canCache) { @@ -75,7 +75,11 @@ public virtual SelectExpression Process( _nullValueColumns.Clear(); ParameterValues = parameterValues; - var result = Visit(selectExpression); + var result = queryExpression switch + { + SelectExpression selectExpression => Visit(selectExpression), + _ => throw new InvalidOperationException() + }; canCache = _canCache; return result; diff --git a/src/EFCore.SqlServer/Query/Internal/SkipTakeCollapsingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SkipTakeCollapsingExpressionVisitor.cs index abcd14e315a..6d4219c78e3 100644 --- a/src/EFCore.SqlServer/Query/Internal/SkipTakeCollapsingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SkipTakeCollapsingExpressionVisitor.cs @@ -36,15 +36,15 @@ public SkipTakeCollapsingExpressionVisitor(ISqlExpressionFactory sqlExpressionFa /// 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 SelectExpression Process( - SelectExpression selectExpression, + public virtual Expression Process( + Expression queryExpression, IReadOnlyDictionary parametersValues, out bool canCache) { _parameterValues = parametersValues; _canCache = true; - var result = (SelectExpression)Visit(selectExpression); + var result = Visit(queryExpression); canCache = _canCache; diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedSqlProcessor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedSqlProcessor.cs index 4e4b2a3eec4..01a3d27c7c8 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedSqlProcessor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedSqlProcessor.cs @@ -32,19 +32,18 @@ public SqlServerParameterBasedSqlProcessor( /// 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 SelectExpression Optimize( - SelectExpression selectExpression, + public override Expression Optimize( + Expression queryExpression, IReadOnlyDictionary parametersValues, out bool canCache) { - var optimizedSelectExpression = base.Optimize(selectExpression, parametersValues, out canCache); + var optimizedQueryExpression = base.Optimize(queryExpression, parametersValues, out canCache); - optimizedSelectExpression = new SkipTakeCollapsingExpressionVisitor(Dependencies.SqlExpressionFactory) - .Process(optimizedSelectExpression, parametersValues, out var canCache2); + optimizedQueryExpression = new SkipTakeCollapsingExpressionVisitor(Dependencies.SqlExpressionFactory) + .Process(optimizedQueryExpression, parametersValues, out var canCache2); canCache &= canCache2; - return (SelectExpression)new SearchConditionConvertingExpressionVisitor(Dependencies.SqlExpressionFactory) - .Visit(optimizedSelectExpression); + return new SearchConditionConvertingExpressionVisitor(Dependencies.SqlExpressionFactory).Visit(optimizedQueryExpression); } }