Skip to content

Commit

Permalink
Query: Introduce custom query root for TVF
Browse files Browse the repository at this point in the history
- Avoids having to do special processing in other visitors
- Visit's method's argument correctly in other visitor (this means that if there is a navigation expansion required for a parameter it will be done automatically)
- Remove Nav expansion factory not needed anymore
- Rename ExpressionPrinter.VisitList to VisitCollection and take IReadOnlyCollection, that is what most expression uses.
- Remove unnecessary exception message. They are part of a larger work item.

Resolves #20146
  • Loading branch information
smitpatel committed Mar 4, 2020
1 parent 1303423 commit 363d37c
Show file tree
Hide file tree
Showing 25 changed files with 253 additions and 222 deletions.
2 changes: 1 addition & 1 deletion src/EFCore.Cosmos/Query/Internal/SqlFunctionExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public override void Print(ExpressionPrinter expressionPrinter)

expressionPrinter.Append(Name);
expressionPrinter.Append("(");
expressionPrinter.VisitList(Arguments);
expressionPrinter.VisitCollection(Arguments);
expressionPrinter.Append(")");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
TryAdd<IQueryTranslationPreprocessorFactory, RelationalQueryTranslationPreprocessorFactory>();
TryAdd<IRelationalParameterBasedQueryTranslationPostprocessorFactory, RelationalParameterBasedQueryTranslationPostprocessorFactory>();
TryAdd<IRelationalQueryStringFactory, RelationalQueryStringFactory>();
TryAdd<INavigationExpandingExpressionVisitorFactory, RelationalNavigationExpandingExpressionVisitorFactory>();

ServiceCollectionMap.GetInfrastructure()
.AddDependencySingleton<RelationalSqlGenerationHelperDependencies>()
Expand Down
18 changes: 2 additions & 16 deletions src/EFCore.Relational/Properties/RelationalStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 0 additions & 6 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -423,12 +423,6 @@
<data name="DbFunctionInvalidInstanceType" xml:space="preserve">
<value>The DbFunction '{function}' defined on type '{type}' must be either a static method or an instance method defined on a DbContext subclass. Instance methods on other types are not supported.</value>
</data>
<data name="DbFunctionCantProjectIQueryable" xml:space="preserve">
<value>Queryable Db Functions used in projections cannot return IQueryable. IQueryable must be converted to a collection type such as List or Array.</value>
</data>
<data name="DbFunctionProjectedCollectionMustHavePK" xml:space="preserve">
<value>Return type of a queryable function '{functionName}' which is used in a projected collection must define a primary key.</value>
</data>
<data name="ConflictingAmbientTransaction" xml:space="preserve">
<value>An ambient transaction has been detected. The ambient transaction needs to be completed before beginning a transaction on this connection.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Query.Internal
{
public class QueryableFunctionQueryRootExpression : QueryRootExpression
{
//Since this is always generated while compiling there is no query provider associated
public QueryableFunctionQueryRootExpression(
[NotNull] IEntityType entityType, [NotNull] IDbFunction function, [NotNull] IReadOnlyCollection<Expression> arguments)
: base(entityType)
{
Check.NotNull(function, nameof(function));
Check.NotNull(arguments, nameof(arguments));

Function = function;
Arguments = arguments;
}

public virtual IDbFunction Function { get; }
public virtual IReadOnlyCollection<Expression> Arguments { get; }

protected override Expression VisitChildren(ExpressionVisitor visitor)
{
var arguments = new List<Expression>();
var changed = false;
foreach (var argument in Arguments)
{
var newArgument = visitor.Visit(argument);
arguments.Add(newArgument);
changed |= argument != newArgument;
}

return changed
? new QueryableFunctionQueryRootExpression(EntityType, Function, arguments)
: this;
}

public override void Print(ExpressionPrinter expressionPrinter)
{
expressionPrinter.Append(Function.MethodInfo.DisplayName());
expressionPrinter.Append("(");
expressionPrinter.VisitCollection(Arguments);
expressionPrinter.Append(")");
}

public override bool Equals(object obj)
=> obj != null
&& (ReferenceEquals(this, obj)
|| obj is QueryableFunctionQueryRootExpression queryRootExpression
&& Equals(queryRootExpression));

private bool Equals(QueryableFunctionQueryRootExpression queryRootExpression)
=> base.Equals(queryRootExpression)
&& Equals(Function, queryRootExpression.Function)
&& Arguments.SequenceEqual(queryRootExpression.Arguments, ExpressionEqualityComparer.Instance);

public override int GetHashCode()
{
var hashCode = new HashCode();
hashCode.Add(base.GetHashCode());
hashCode.Add(Function);
foreach (var item in Arguments)
{
hashCode.Add(item);
}

return hashCode.ToHashCode();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Linq.Expressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Query.Internal
{
public class QueryableFunctionToQueryRootConvertingExpressionVisitor : ExpressionVisitor
{
private readonly IModel _model;

public QueryableFunctionToQueryRootConvertingExpressionVisitor([NotNull] IModel model)
{
Check.NotNull(model, nameof(model));

_model = model;
}

protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
{
var function = _model.FindDbFunction(methodCallExpression.Method);

return function?.IsIQueryable == true
? CreateQueryableFunctionQueryRootExpression(function, methodCallExpression.Arguments)
: base.VisitMethodCall(methodCallExpression);
}

private Expression CreateQueryableFunctionQueryRootExpression(
IDbFunction function, IReadOnlyCollection<Expression> arguments)
{
var entityType = _model.FindEntityType(function.MethodInfo.ReturnType.GetGenericArguments()[0]);

return new QueryableFunctionQueryRootExpression(entityType, function, arguments);
}
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,6 @@ protected override Expression VisitNew(NewExpression newExpression)
return null;
}

if (newExpression.Arguments.Any(arg => arg is MethodCallExpression methodCallExp && _model.FindDbFunction(methodCallExp.Method)?.IsIQueryable == true))
{
throw new InvalidOperationException(RelationalStrings.DbFunctionCantProjectIQueryable());
}

var newArguments = new Expression[newExpression.Arguments.Count];
for (var i = 0; i < newArguments.Length; i++)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,20 @@ public class RelationalQueryTranslationPreprocessorFactory : IQueryTranslationPr
{
private readonly QueryTranslationPreprocessorDependencies _dependencies;
private readonly RelationalQueryTranslationPreprocessorDependencies _relationalDependencies;
private readonly INavigationExpandingExpressionVisitorFactory _navigationExpandingExpressionVisitorFactory;

public RelationalQueryTranslationPreprocessorFactory(
[NotNull] QueryTranslationPreprocessorDependencies dependencies,
[NotNull] RelationalQueryTranslationPreprocessorDependencies relationalDependencies,
[NotNull] INavigationExpandingExpressionVisitorFactory navigationExpandingExpressionVisitorFactory)
[NotNull] RelationalQueryTranslationPreprocessorDependencies relationalDependencies)
{
_dependencies = dependencies;
_relationalDependencies = relationalDependencies;
_navigationExpandingExpressionVisitorFactory = navigationExpandingExpressionVisitorFactory;
}

public virtual QueryTranslationPreprocessor Create(QueryCompilationContext queryCompilationContext)
{
Check.NotNull(queryCompilationContext, nameof(queryCompilationContext));

return new RelationalQueryTranslationPreprocessor(_dependencies, _relationalDependencies, queryCompilationContext, _navigationExpandingExpressionVisitorFactory);
return new RelationalQueryTranslationPreprocessor(_dependencies, _relationalDependencies, queryCompilationContext);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ public RelationalEvaluatableExpressionFilter(
/// </summary>
protected virtual RelationalEvaluatableExpressionFilterDependencies RelationalDependencies { get; }

/// <summary>
/// Checks whether the given expression can be evaluated.
/// </summary>
/// <param name="expression"> The expression. </param>
/// <param name="model"> The model. </param>
/// <summary>
/// Checks whether the given expression can be evaluated.
/// </summary>
/// <param name="expression"> The expression. </param>
/// <param name="model"> The model. </param>
/// <returns> True if the expression can be evaluated; false otherwise. </returns>
public override bool IsEvaluatableExpression(Expression expression, IModel model)
{
Expand All @@ -67,7 +67,7 @@ public override bool IsEvaluatableExpression(Expression expression, IModel model
return base.IsEvaluatableExpression(expression, model);
}

public override bool IsQueryableFunction(Expression expression, IModel model) =>
public override bool IsQueryableFunction(Expression expression, IModel model) =>
expression is MethodCallExpression methodCallExpression
&& model.FindDbFunction(methodCallExpression.Method)?.IsIQueryable == true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Linq.Expressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Query
Expand All @@ -11,15 +13,22 @@ public class RelationalQueryTranslationPreprocessor : QueryTranslationPreprocess
public RelationalQueryTranslationPreprocessor(
[NotNull] QueryTranslationPreprocessorDependencies dependencies,
[NotNull] RelationalQueryTranslationPreprocessorDependencies relationalDependencies,
[NotNull] QueryCompilationContext queryCompilationContext,
[NotNull] INavigationExpandingExpressionVisitorFactory navigationExpandingExpressionVisitorFactory)
: base(dependencies, queryCompilationContext, navigationExpandingExpressionVisitorFactory)
[NotNull] QueryCompilationContext queryCompilationContext)
: base(dependencies, queryCompilationContext)
{
Check.NotNull(relationalDependencies, nameof(relationalDependencies));

RelationalDependencies = relationalDependencies;
}

protected virtual RelationalQueryTranslationPreprocessorDependencies RelationalDependencies { get; }

public override Expression NormalizeQueryableMethodCall(Expression expression)
{
expression = base.NormalizeQueryableMethodCall(expression);
expression = new QueryableFunctionToQueryRootConvertingExpressionVisitor(QueryCompilationContext.Model).Visit(expression);

return expression;
}
}
}
Loading

0 comments on commit 363d37c

Please sign in to comment.