Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query: Change RelationalCommandCache to allow non-SelectExpression as query expression #28048

Merged
merged 1 commit into from
May 18, 2022
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 @@ -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);
}
}