Skip to content

Commit

Permalink
Query: Change RelationalCommandCache to allow non-SelectExpression as…
Browse files Browse the repository at this point in the history
… query expression (#28048)

Part of #795
  • Loading branch information
smitpatel authored May 18, 2022
1 parent 1f27c57 commit 6de40fe
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ private readonly IDictionary<FromSqlExpression, Expression> _visitedFromSqlExpre
private IReadOnlyDictionary<string, object?> _parametersValues;
private ParameterNameGenerator _parameterNameGenerator;
private bool _canCache;
private SelectExpression _selectExpression;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -44,7 +43,6 @@ public FromSqlParameterExpandingExpressionVisitor(
_parameterNameGeneratorFactory = dependencies.ParameterNameGeneratorFactory;
_parametersValues = default!;
_parameterNameGenerator = default!;
_selectExpression = default!;
}

/// <summary>
Expand All @@ -58,18 +56,17 @@ 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.
/// </summary>
public virtual SelectExpression Expand(
SelectExpression selectExpression,
public virtual Expression Expand(
Expression queryExpression,
IReadOnlyDictionary<string, object?> parameterValues,
out bool canCache)
{
_visitedFromSqlExpressions.Clear();
_parameterNameGenerator = _parameterNameGeneratorFactory.Create();
_parametersValues = parameterValues;
_canCache = true;
_selectExpression = selectExpression;

var result = (SelectExpression)Visit(selectExpression);
var result = Visit(queryExpression);
canCache = _canCache;

return result;
Expand Down
27 changes: 14 additions & 13 deletions src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ private static readonly ConcurrentDictionary<object, object> Locks

private readonly IMemoryCache _memoryCache;
private readonly IQuerySqlGeneratorFactory _querySqlGeneratorFactory;
private readonly SelectExpression _selectExpression;
private readonly Expression _queryExpression;
private readonly RelationalParameterBasedSqlProcessor _relationalParameterBasedSqlProcessor;

/// <summary>
Expand All @@ -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);
}

Expand All @@ -51,7 +51,7 @@ public RelationalCommandCache(
/// </summary>
public virtual IRelationalCommandTemplate GetRelationalCommandTemplate(IReadOnlyDictionary<string, object?> parameters)
{
var cacheKey = new CommandCacheKey(_selectExpression, parameters);
var cacheKey = new CommandCacheKey(_queryExpression, parameters);

if (_memoryCache.TryGetValue(cacheKey, out IRelationalCommandTemplate? relationalCommandTemplate))
{
Expand All @@ -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)
{
Expand All @@ -96,22 +96,22 @@ public virtual IRelationalCommandTemplate GetRelationalCommandTemplate(IReadOnly
/// </summary>
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<CommandCacheKey>
{
private readonly SelectExpression _selectExpression;
private readonly Expression _queryExpression;
private readonly IReadOnlyDictionary<string, object?> _parameterValues;

public CommandCacheKey(SelectExpression selectExpression, IReadOnlyDictionary<string, object?> parameterValues)
public CommandCacheKey(Expression queryExpression, IReadOnlyDictionary<string, object?> parameterValues)
{
_selectExpression = selectExpression;
_queryExpression = queryExpression;
_parameterValues = parameterValues;
}

Expand All @@ -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;
}
Expand Down
34 changes: 22 additions & 12 deletions src/EFCore.Relational/Query/QuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,33 @@ public QuerySqlGenerator(QuerySqlGeneratorDependencies dependencies)
protected virtual QuerySqlGeneratorDependencies Dependencies { get; }

/// <summary>
/// Gets a relational command for a <see cref="SelectExpression" />.
/// Gets a relational command for a query expression.
/// </summary>
/// <param name="selectExpression">A select expression to print in command text.</param>
/// <returns>A relational command with a SQL represented by the select expression.</returns>
public virtual IRelationalCommand GetCommand(SelectExpression selectExpression)
/// <param name="queryExpression">A query expression to print in command text.</param>
/// <returns>A relational command with a SQL represented by the query expression.</returns>
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();
Expand Down
50 changes: 25 additions & 25 deletions src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Microsoft.EntityFrameworkCore.Query;

/// <summary>
/// <para>
/// A class that processes the <see cref="SelectExpression" /> after parementer values are known.
/// A class that processes the query expression after parementer values are known.
/// </para>
/// <para>
/// This type is typically used by database providers (and other extensions). It is generally
Expand All @@ -18,7 +18,7 @@ namespace Microsoft.EntityFrameworkCore.Query;
public class RelationalParameterBasedSqlProcessor
{
/// <summary>
/// Creates a new instance of the <see cref="QueryTranslationPostprocessor" /> class.
/// Creates a new instance of the <see cref="RelationalParameterBasedSqlProcessor" /> class.
/// </summary>
/// <param name="dependencies">Parameter object containing dependencies for this class.</param>
/// <param name="useRelationalNulls">A bool value indicating if relational nulls should be used.</param>
Expand All @@ -41,51 +41,51 @@ public RelationalParameterBasedSqlProcessor(
protected virtual bool UseRelationalNulls { get; }

/// <summary>
/// Optimizes the <see cref="SelectExpression" /> for given parameter values.
/// Optimizes the query expression for given parameter values.
/// </summary>
/// <param name="selectExpression">A select expression to optimize.</param>
/// <param name="queryExpression">A query expression to optimize.</param>
/// <param name="parametersValues">A dictionary of parameter values to use.</param>
/// <param name="canCache">A bool value indicating if the select expression can be cached.</param>
/// <returns>An optimized select expression.</returns>
public virtual SelectExpression Optimize(
SelectExpression selectExpression,
/// <param name="canCache">A bool value indicating if the query expression can be cached.</param>
/// <returns>An optimized query expression.</returns>
public virtual Expression Optimize(
Expression queryExpression,
IReadOnlyDictionary<string, object?> 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;
}

/// <summary>
/// Processes the <see cref="SelectExpression" /> 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.
/// </summary>
/// <param name="selectExpression">A select expression to optimize.</param>
/// <param name="queryExpression">A query expression to optimize.</param>
/// <param name="parametersValues">A dictionary of parameter values to use.</param>
/// <param name="canCache">A bool value indicating if the select expression can be cached.</param>
/// <returns>A processed select expression.</returns>
protected virtual SelectExpression ProcessSqlNullability(
SelectExpression selectExpression,
/// <param name="canCache">A bool value indicating if the query expression can be cached.</param>
/// <returns>A processed query expression.</returns>
protected virtual Expression ProcessSqlNullability(
Expression queryExpression,
IReadOnlyDictionary<string, object?> parametersValues,
out bool canCache)
=> new SqlNullabilityProcessor(Dependencies, UseRelationalNulls).Process(selectExpression, parametersValues, out canCache);
=> new SqlNullabilityProcessor(Dependencies, UseRelationalNulls).Process(queryExpression, parametersValues, out canCache);

/// <summary>
/// Expands the parameters to <see cref="FromSqlExpression" /> inside the <see cref="SelectExpression" /> for given parameter values.
/// Expands the parameters to <see cref="FromSqlExpression" /> inside the query expression for given parameter values.
/// </summary>
/// <param name="selectExpression">A select expression to optimize.</param>
/// <param name="queryExpression">A query expression to optimize.</param>
/// <param name="parametersValues">A dictionary of parameter values to use.</param>
/// <param name="canCache">A bool value indicating if the select expression can be cached.</param>
/// <returns>A processed select expression.</returns>
protected virtual SelectExpression ExpandFromSqlParameter(
SelectExpression selectExpression,
/// <param name="canCache">A bool value indicating if the query expression can be cached.</param>
/// <returns>A processed query expression.</returns>
protected virtual Expression ExpandFromSqlParameter(
Expression queryExpression,
IReadOnlyDictionary<string, object?> parametersValues,
out bool canCache)
=> new FromSqlParameterExpandingExpressionVisitor(Dependencies).Expand(selectExpression, parametersValues, out canCache);
=> new FromSqlParameterExpandingExpressionVisitor(Dependencies).Expand(queryExpression, parametersValues, out canCache);
}
18 changes: 11 additions & 7 deletions src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@ public SqlNullabilityProcessor(
protected virtual IReadOnlyDictionary<string, object?> ParameterValues { get; private set; }

/// <summary>
/// Processes a <see cref="SelectExpression" /> to apply null semantics and optimize it.
/// Processes a query expression to apply null semantics and optimize it.
/// </summary>
/// <param name="selectExpression">A select expression to process.</param>
/// <param name="queryExpression">A query expression to process.</param>
/// <param name="parameterValues">A dictionary of parameter values in use.</param>
/// <param name="canCache">A bool value indicating whether the select expression can be cached.</param>
/// <returns>An optimized select expression.</returns>
public virtual SelectExpression Process(
SelectExpression selectExpression,
/// <param name="canCache">A bool value indicating whether the query expression can be cached.</param>
/// <returns>An optimized query expression.</returns>
public virtual Expression Process(
Expression queryExpression,
IReadOnlyDictionary<string, object?> parameterValues,
out bool canCache)
{
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
public virtual SelectExpression Process(
SelectExpression selectExpression,
public virtual Expression Process(
Expression queryExpression,
IReadOnlyDictionary<string, object?> parametersValues,
out bool canCache)
{
_parameterValues = parametersValues;
_canCache = true;

var result = (SelectExpression)Visit(selectExpression);
var result = Visit(queryExpression);

canCache = _canCache;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
public override SelectExpression Optimize(
SelectExpression selectExpression,
public override Expression Optimize(
Expression queryExpression,
IReadOnlyDictionary<string, object?> 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);
}
}

0 comments on commit 6de40fe

Please sign in to comment.