diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
index 549982fee58..b06624a5d32 100644
--- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
+++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
@@ -75,7 +75,7 @@ protected virtual void ValidateDbFunctions(
var methodInfo = dbFunction.MethodInfo;
if (dbFunction.TypeMapping == null
- && dbFunction.QueryableEntityType == null)
+ && dbFunction.ReturnEntityType == null)
{
throw new InvalidOperationException(
RelationalStrings.DbFunctionInvalidReturnType(
diff --git a/src/EFCore.Relational/Metadata/Conventions/DbFunctionTypeMappingConvention.cs b/src/EFCore.Relational/Metadata/Conventions/DbFunctionTypeMappingConvention.cs
index 1016ec643b6..fe478edb9e7 100644
--- a/src/EFCore.Relational/Metadata/Conventions/DbFunctionTypeMappingConvention.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/DbFunctionTypeMappingConvention.cs
@@ -41,6 +41,7 @@ public virtual void ProcessModelFinalizing(
foreach (var dbFunction in modelBuilder.Metadata.GetDbFunctions())
{
+ // TODO: This check needs to be updated to skip over enumerable parameter of aggregate.
foreach (var parameter in dbFunction.Parameters)
{
parameter.Builder.HasTypeMapping(!string.IsNullOrEmpty(parameter.StoreType)
@@ -48,14 +49,12 @@ public virtual void ProcessModelFinalizing(
: _relationalTypeMappingSource.FindMapping(parameter.ClrType));
}
- if (dbFunction.IsQueryable)
+ if (dbFunction.IsScalar)
{
- continue;
+ dbFunction.Builder.HasTypeMapping(!string.IsNullOrEmpty(dbFunction.StoreType)
+ ? _relationalTypeMappingSource.FindMapping(dbFunction.StoreType)
+ : _relationalTypeMappingSource.FindMapping(dbFunction.ReturnType));
}
-
- dbFunction.Builder.HasTypeMapping(!string.IsNullOrEmpty(dbFunction.StoreType)
- ? _relationalTypeMappingSource.FindMapping(dbFunction.StoreType)
- : _relationalTypeMappingSource.FindMapping(dbFunction.ReturnType));
}
}
}
diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
index 6e785d7a19b..d1ed7791b15 100644
--- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
@@ -83,7 +83,7 @@ public override ConventionSet CreateConventionSet()
ReplaceConvention(conventionSet.ForeignKeyRemovedConventions, valueGenerationConvention);
conventionSet.PropertyFieldChangedConventions.Add(relationalColumnAttributeConvention);
- conventionSet.PropertyFieldChangedConventions.Add(relationalCommentAttributeConvention);
+ conventionSet.PropertyFieldChangedConventions.Add(relationalCommentAttributeConvention);
var storeGenerationConvention = new StoreGenerationConvention(Dependencies, RelationalDependencies);
conventionSet.PropertyAnnotationChangedConventions.Add(storeGenerationConvention);
@@ -98,10 +98,10 @@ public override ConventionSet CreateConventionSet()
conventionSet.ModelFinalizingConventions,
new TableSharingConcurrencyTokenConvention(Dependencies, RelationalDependencies),
typeof(TypeMappingConvention));
- // ModelCleanupConvention would remove the entity types added by QueryableDbFunctionConvention #15898
+ // ModelCleanupConvention would remove the entity types added by TableValuedDbFunctionConvention #15898
ConventionSet.AddAfter(
conventionSet.ModelFinalizingConventions,
- new QueryableDbFunctionConvention(Dependencies, RelationalDependencies),
+ new TableValuedDbFunctionConvention(Dependencies, RelationalDependencies),
typeof(ModelCleanupConvention));
conventionSet.ModelFinalizingConventions.Add(dbFunctionAttributeConvention);
conventionSet.ModelFinalizingConventions.Add(tableNameFromDbSetConvention);
diff --git a/src/EFCore.Relational/Metadata/Conventions/QueryableDbFunctionConvention.cs b/src/EFCore.Relational/Metadata/Conventions/QueryableDbFunctionConvention.cs
index 1dbf941e386..56a0217743a 100644
--- a/src/EFCore.Relational/Metadata/Conventions/QueryableDbFunctionConvention.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/QueryableDbFunctionConvention.cs
@@ -14,14 +14,14 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
///
/// A convention that configures the entity type to which a queryable function is mapped.
///
- public class QueryableDbFunctionConvention : IModelFinalizingConvention
+ public class TableValuedDbFunctionConvention : IModelFinalizingConvention
{
///
- /// Creates a new instance of .
+ /// Creates a new instance of .
///
/// Parameter object containing dependencies for this convention.
/// Parameter object containing relational dependencies for this convention.
- public QueryableDbFunctionConvention(
+ public TableValuedDbFunctionConvention(
[NotNull] ProviderConventionSetBuilderDependencies dependencies,
[NotNull] RelationalConventionSetBuilderDependencies relationalDependencies)
{
@@ -51,7 +51,7 @@ private void ProcessDbFunctionAdded(
[NotNull] IConventionDbFunctionBuilder dbFunctionBuilder, [NotNull] IConventionContext context)
{
var function = dbFunctionBuilder.Metadata;
- if (!function.IsQueryable)
+ if (function.IsScalar)
{
return;
}
diff --git a/src/EFCore.Relational/Metadata/IDbFunction.cs b/src/EFCore.Relational/Metadata/IDbFunction.cs
index 0ed24146fa8..c8d754cb3d8 100644
--- a/src/EFCore.Relational/Metadata/IDbFunction.cs
+++ b/src/EFCore.Relational/Metadata/IDbFunction.cs
@@ -41,19 +41,24 @@ public interface IDbFunction : IAnnotatable
MethodInfo MethodInfo { get; }
///
- /// Gets the value indicating whether this method returns IQueryable
+ /// Gets the value indicating whether this function returns scalar value.
///
- bool IsQueryable { get; }
+ bool IsScalar { get; }
///
- /// Gets the entity type returned by this queryable function
+ /// Gets the value indicating whether this function is an aggregate function.
///
- IEntityType QueryableEntityType => IsQueryable
- ? Model.FindEntityType(ReturnType.GetGenericArguments()[0])
- : null;
+ bool IsAggregate { get; }
///
- /// Gets the configured store type string
+ /// Gets the entity type returned by this table valued function.
+ ///
+ IEntityType ReturnEntityType => IsScalar
+ ? null
+ : Model.FindEntityType(ReturnType.GetGenericArguments()[0]);
+
+ ///
+ /// Gets the configured store type string.
///
string StoreType { get; }
@@ -63,12 +68,12 @@ public interface IDbFunction : IAnnotatable
Type ReturnType { get; }
///
- /// Gets the type mapping for the function's return type
+ /// Gets the type mapping for the function's return type.
///
RelationalTypeMapping TypeMapping { get; }
///
- /// Gets the parameters for this function
+ /// Gets the parameters for this function.
///
IReadOnlyList Parameters { get; }
diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs
index 63e385722d1..0c0e0444da5 100644
--- a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs
+++ b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs
@@ -98,11 +98,9 @@ public DbFunction(
name, returnType.ShortDisplayName()));
}
- if (returnType.IsGenericType
- && returnType.GetGenericTypeDefinition() == typeof(IQueryable<>))
- {
- IsQueryable = true;
- }
+ IsScalar = !returnType.IsGenericType
+ || returnType.GetGenericTypeDefinition() != typeof(IQueryable<>);
+ IsAggregate = false;
ModelName = name;
ReturnType = returnType;
@@ -257,7 +255,10 @@ public static DbFunction RemoveDbFunction(
public virtual Type ReturnType { get; }
///
- public virtual bool IsQueryable { get; }
+ public virtual bool IsScalar { get; }
+
+ ///
+ public virtual bool IsAggregate { get; }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -265,11 +266,11 @@ public static DbFunction RemoveDbFunction(
/// 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.
///
- IEntityType QueryableEntityType
+ public virtual IEntityType ReturnEntityType
{
get
{
- if (!IsQueryable)
+ if (IsScalar)
{
return null;
}
@@ -475,9 +476,15 @@ public virtual Func, SqlExpression> SetTransl
ConfigurationSource configurationSource)
{
if (translation != null
- && IsQueryable)
+ && !IsScalar)
+ {
+ throw new InvalidOperationException(RelationalStrings.DbFunctionTableValuedCustomTranslation(MethodInfo.DisplayName()));
+ }
+
+ if (translation != null
+ && !IsAggregate)
{
- throw new InvalidOperationException(RelationalStrings.DbFunctionQueryableCustomTranslation(MethodInfo.DisplayName()));
+ throw new InvalidOperationException(RelationalStrings.DbFunctionAggregateCustomTranslation(MethodInfo.DisplayName()));
}
_translation = translation;
@@ -527,7 +534,7 @@ IConventionModel IConventionDbFunction.Model
}
///
- IEntityType IDbFunction.QueryableEntityType => QueryableEntityType;
+ IEntityType IDbFunction.ReturnEntityType => ReturnEntityType;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionBuilder.cs
index a04bf81ff29..d9f0fb84df8 100644
--- a/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionBuilder.cs
@@ -169,7 +169,7 @@ public virtual IConventionDbFunctionBuilder HasTranslation(
///
public virtual bool CanSetTranslation(
[CanBeNull] Func, SqlExpression> translation, ConfigurationSource configurationSource)
- => (!Metadata.IsQueryable || configurationSource == ConfigurationSource.Explicit)
+ => ((Metadata.IsScalar && !Metadata.IsAggregate) || configurationSource == ConfigurationSource.Explicit)
&& (configurationSource.Overrides(Metadata.GetTranslationConfigurationSource())
|| Metadata.Translation == translation);
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
index 63cd2e8728e..d9e0d354759 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
+++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
@@ -300,14 +300,6 @@ public static string DuplicateColumnNameDefaultSqlMismatch([CanBeNull] object en
GetString("DuplicateColumnNameDefaultSqlMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table), nameof(value1), nameof(value2)),
entityType1, property1, entityType2, property2, columnName, table, value1, value2);
- ///
- /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured to use different collations ('{collation1}' and '{collation2}').
- ///
- public static string DuplicateColumnNameCollationMismatch([CanBeNull] object entityType1, [CanBeNull] object property1, [CanBeNull] object entityType2, [CanBeNull] object property2, [CanBeNull] object columnName, [CanBeNull] object table, [CanBeNull] object collation1, [CanBeNull] object collation2)
- => string.Format(
- GetString("DuplicateColumnNameCollationMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table), nameof(collation1), nameof(collation2)),
- entityType1, property1, entityType2, property2, columnName, table, collation1, collation2);
-
///
/// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured to use different comments ('{comment1}' and '{comment2}').
///
@@ -316,6 +308,14 @@ public static string DuplicateColumnNameCommentMismatch([CanBeNull] object entit
GetString("DuplicateColumnNameCommentMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table), nameof(comment1), nameof(comment2)),
entityType1, property1, entityType2, property2, columnName, table, comment1, comment2);
+ ///
+ /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured to use different collations ('{collation1}' and '{collation2}').
+ ///
+ public static string DuplicateColumnNameCollationMismatch([CanBeNull] object entityType1, [CanBeNull] object property1, [CanBeNull] object entityType2, [CanBeNull] object property2, [CanBeNull] object columnName, [CanBeNull] object table, [CanBeNull] object collation1, [CanBeNull] object collation2)
+ => string.Format(
+ GetString("DuplicateColumnNameCollationMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table), nameof(collation1), nameof(collation2)),
+ entityType1, property1, entityType2, property2, columnName, table, collation1, collation2);
+
///
/// {conflictingConfiguration} cannot be set for '{property}' at the same time as {existingConfiguration}. Remove one of these values.
///
@@ -633,11 +633,11 @@ public static string DatabaseModelMissing
=> GetString("DatabaseModelMissing");
///
- /// Cannot set custom translation on the DbFunction '{function}' since it returns IQueryable type.
+ /// Cannot set custom translation on the DbFunction '{function}' since it is a table valued function.
///
- public static string DbFunctionQueryableCustomTranslation([CanBeNull] object function)
+ public static string DbFunctionTableValuedCustomTranslation([CanBeNull] object function)
=> string.Format(
- GetString("DbFunctionQueryableCustomTranslation", nameof(function)),
+ GetString("DbFunctionTableValuedCustomTranslation", nameof(function)),
function);
///
@@ -800,6 +800,14 @@ public static string UnsupportedDataOperationStoreType([CanBeNull] object type,
GetString("UnsupportedDataOperationStoreType", nameof(type), nameof(column)),
type, column);
+ ///
+ /// Cannot set custom translation on the DbFunction '{function}' since it is an aggregate function.
+ ///
+ public static string DbFunctionAggregateCustomTranslation([CanBeNull] object function)
+ => string.Format(
+ GetString("DbFunctionAggregateCustomTranslation", nameof(function)),
+ function);
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx
index b0fd389ec80..188c82a9fa3 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.resx
+++ b/src/EFCore.Relational/Properties/RelationalStrings.resx
@@ -518,8 +518,8 @@
The database model hasn't been initialized. The model needs to be finalized before the database model can be accessed.
-
- Cannot set custom translation on the DbFunction '{function}' since it returns IQueryable type.
+
+ Cannot set custom translation on the DbFunction '{function}' since it is a table valued function.
The DbFunction '{function}' has an invalid return type '{type}'. Only functions that return IQueryable of entity type are supported.
@@ -581,4 +581,7 @@
The store type '{type}' used for the column '{column}' in a migration data operation is not supported by the current provider.
+
+ Cannot set custom translation on the DbFunction '{function}' since it is an aggregate function.
+
\ No newline at end of file
diff --git a/src/EFCore.Relational/Query/Internal/QueryableFunctionQueryRootExpression.cs b/src/EFCore.Relational/Query/Internal/TableValuedFunctionQueryRootExpression.cs
similarity index 86%
rename from src/EFCore.Relational/Query/Internal/QueryableFunctionQueryRootExpression.cs
rename to src/EFCore.Relational/Query/Internal/TableValuedFunctionQueryRootExpression.cs
index 54afd6c5927..133b63f7832 100644
--- a/src/EFCore.Relational/Query/Internal/QueryableFunctionQueryRootExpression.cs
+++ b/src/EFCore.Relational/Query/Internal/TableValuedFunctionQueryRootExpression.cs
@@ -12,10 +12,10 @@
namespace Microsoft.EntityFrameworkCore.Query.Internal
{
- public class QueryableFunctionQueryRootExpression : QueryRootExpression
+ public class TableValuedFunctionQueryRootExpression : QueryRootExpression
{
//Since this is always generated while compiling there is no query provider associated
- public QueryableFunctionQueryRootExpression(
+ public TableValuedFunctionQueryRootExpression(
[NotNull] IEntityType entityType, [NotNull] IDbFunction function, [NotNull] IReadOnlyCollection arguments)
: base(entityType)
{
@@ -41,7 +41,7 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
}
return changed
- ? new QueryableFunctionQueryRootExpression(EntityType, Function, arguments)
+ ? new TableValuedFunctionQueryRootExpression(EntityType, Function, arguments)
: this;
}
@@ -56,10 +56,10 @@ public override void Print(ExpressionPrinter expressionPrinter)
public override bool Equals(object obj)
=> obj != null
&& (ReferenceEquals(this, obj)
- || obj is QueryableFunctionQueryRootExpression queryRootExpression
+ || obj is TableValuedFunctionQueryRootExpression queryRootExpression
&& Equals(queryRootExpression));
- private bool Equals(QueryableFunctionQueryRootExpression queryRootExpression)
+ private bool Equals(TableValuedFunctionQueryRootExpression queryRootExpression)
=> base.Equals(queryRootExpression)
&& Equals(Function, queryRootExpression.Function)
&& Arguments.SequenceEqual(queryRootExpression.Arguments, ExpressionEqualityComparer.Instance);
diff --git a/src/EFCore.Relational/Query/Internal/QueryableFunctionToQueryRootConvertingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/TableValuedFunctionToQueryRootConvertingExpressionVisitor.cs
similarity index 63%
rename from src/EFCore.Relational/Query/Internal/QueryableFunctionToQueryRootConvertingExpressionVisitor.cs
rename to src/EFCore.Relational/Query/Internal/TableValuedFunctionToQueryRootConvertingExpressionVisitor.cs
index e1ba427b890..a16055a8db5 100644
--- a/src/EFCore.Relational/Query/Internal/QueryableFunctionToQueryRootConvertingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/Internal/TableValuedFunctionToQueryRootConvertingExpressionVisitor.cs
@@ -9,11 +9,11 @@
namespace Microsoft.EntityFrameworkCore.Query.Internal
{
- public class QueryableFunctionToQueryRootConvertingExpressionVisitor : ExpressionVisitor
+ public class TableValuedFunctionToQueryRootConvertingExpressionVisitor : ExpressionVisitor
{
private readonly IModel _model;
- public QueryableFunctionToQueryRootConvertingExpressionVisitor([NotNull] IModel model)
+ public TableValuedFunctionToQueryRootConvertingExpressionVisitor([NotNull] IModel model)
{
Check.NotNull(model, nameof(model));
@@ -24,13 +24,13 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
{
var function = _model.FindDbFunction(methodCallExpression.Method);
- return function?.IsQueryable == true
- ? CreateQueryableFunctionQueryRootExpression(function, methodCallExpression.Arguments)
+ return function?.IsScalar == false
+ ? CreateTableValuedFunctionQueryRootExpression(function, methodCallExpression.Arguments)
: base.VisitMethodCall(methodCallExpression);
}
- private Expression CreateQueryableFunctionQueryRootExpression(
+ private Expression CreateTableValuedFunctionQueryRootExpression(
IDbFunction function, IReadOnlyCollection arguments)
- => new QueryableFunctionQueryRootExpression(function.QueryableEntityType, function, arguments);
+ => new TableValuedFunctionQueryRootExpression(function.ReturnEntityType, function, arguments);
}
}
diff --git a/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs
index 907a431c79b..ce530b9a7e7 100644
--- a/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs
@@ -454,12 +454,12 @@ protected override Expression VisitProjection(ProjectionExpression projectionExp
VisitInternal(projectionExpression.Expression).ResultExpression);
}
- protected override Expression VisitQueryableFunction(QueryableFunctionExpression queryableFunctionExpression)
+ protected override Expression VisitTableValuedFunction(TableValuedFunctionExpression tableValuedFunctionExpression)
{
- Check.NotNull(queryableFunctionExpression, nameof(queryableFunctionExpression));
+ Check.NotNull(tableValuedFunctionExpression, nameof(tableValuedFunctionExpression));
// See issue#20180
- return queryableFunctionExpression;
+ return tableValuedFunctionExpression;
}
protected override Expression VisitRowNumber(RowNumberExpression rowNumberExpression)
diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs
index 26d83ec2fe8..6a5ba9752e4 100644
--- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs
+++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs
@@ -270,29 +270,29 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction
return sqlFunctionExpression;
}
- protected override Expression VisitQueryableFunction(QueryableFunctionExpression queryableFunctionExpression)
+ protected override Expression VisitTableValuedFunction(TableValuedFunctionExpression tableValuedFunctionExpression)
{
- Check.NotNull(queryableFunctionExpression, nameof(queryableFunctionExpression));
+ Check.NotNull(tableValuedFunctionExpression, nameof(tableValuedFunctionExpression));
- if (!string.IsNullOrEmpty(queryableFunctionExpression.Schema))
+ if (!string.IsNullOrEmpty(tableValuedFunctionExpression.Schema))
{
_relationalCommandBuilder
- .Append(_sqlGenerationHelper.DelimitIdentifier(queryableFunctionExpression.Schema))
+ .Append(_sqlGenerationHelper.DelimitIdentifier(tableValuedFunctionExpression.Schema))
.Append(".");
}
_relationalCommandBuilder
- .Append(_sqlGenerationHelper.DelimitIdentifier(queryableFunctionExpression.Name))
+ .Append(_sqlGenerationHelper.DelimitIdentifier(tableValuedFunctionExpression.Name))
.Append("(");
- GenerateList(queryableFunctionExpression.Arguments, e => Visit(e));
+ GenerateList(tableValuedFunctionExpression.Arguments, e => Visit(e));
_relationalCommandBuilder
.Append(")")
.Append(AliasSeparator)
- .Append(_sqlGenerationHelper.DelimitIdentifier(queryableFunctionExpression.Alias));
+ .Append(_sqlGenerationHelper.DelimitIdentifier(tableValuedFunctionExpression.Alias));
- return queryableFunctionExpression;
+ return tableValuedFunctionExpression;
}
protected override Expression VisitColumn(ColumnExpression columnExpression)
diff --git a/src/EFCore.Relational/Query/RelationalEvaluatableExpressionFilter.cs b/src/EFCore.Relational/Query/RelationalEvaluatableExpressionFilter.cs
index cd2f984f41f..0d488a5a28e 100644
--- a/src/EFCore.Relational/Query/RelationalEvaluatableExpressionFilter.cs
+++ b/src/EFCore.Relational/Query/RelationalEvaluatableExpressionFilter.cs
@@ -63,7 +63,7 @@ public override bool IsEvaluatableExpression(Expression expression, IModel model
{
// Never evaluate DbFunction
// If it is inside lambda then we will have whole method call
- // If it is outside of lambda then it will be evaluated for queryable function already.
+ // If it is outside of lambda then it will be evaluated for table valued function already.
return false;
}
diff --git a/src/EFCore.Relational/Query/RelationalQueryTranslationPreprocessor.cs b/src/EFCore.Relational/Query/RelationalQueryTranslationPreprocessor.cs
index a026b076218..9248f1b3bd0 100644
--- a/src/EFCore.Relational/Query/RelationalQueryTranslationPreprocessor.cs
+++ b/src/EFCore.Relational/Query/RelationalQueryTranslationPreprocessor.cs
@@ -26,7 +26,7 @@ public RelationalQueryTranslationPreprocessor(
public override Expression NormalizeQueryableMethod(Expression expression)
{
expression = base.NormalizeQueryableMethod(expression);
- expression = new QueryableFunctionToQueryRootConvertingExpressionVisitor(QueryCompilationContext.Model).Visit(expression);
+ expression = new TableValuedFunctionToQueryRootConvertingExpressionVisitor(QueryCompilationContext.Model).Visit(expression);
return expression;
}
diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
index 62b15aa8f4c..4cd2fc2ff52 100644
--- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
@@ -75,10 +75,10 @@ protected override Expression VisitExtension(Expression extensionExpression)
fromSqlQueryRootExpression.Sql,
fromSqlQueryRootExpression.Argument));
- case QueryableFunctionQueryRootExpression queryableFunctionQueryRootExpression:
- var function = queryableFunctionQueryRootExpression.Function;
+ case TableValuedFunctionQueryRootExpression tableValuedFunctionQueryRootExpression:
+ var function = tableValuedFunctionQueryRootExpression.Function;
var arguments = new List();
- foreach (var arg in queryableFunctionQueryRootExpression.Arguments)
+ foreach (var arg in tableValuedFunctionQueryRootExpression.Arguments)
{
var sqlArgument = _sqlTranslator.Translate(arg);
if (sqlArgument == null)
@@ -86,7 +86,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
var methodCall = Expression.Call(
Expression.Constant(null, function.MethodInfo.DeclaringType),
function.MethodInfo,
- queryableFunctionQueryRootExpression.Arguments);
+ tableValuedFunctionQueryRootExpression.Arguments);
throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCall.Print()));
}
@@ -94,12 +94,11 @@ protected override Expression VisitExtension(Expression extensionExpression)
arguments.Add(sqlArgument);
}
- // TODO: Allow translation to construct the table
- var entityType = queryableFunctionQueryRootExpression.EntityType;
+ var entityType = tableValuedFunctionQueryRootExpression.EntityType;
var alias = (entityType.GetViewOrTableMappings().SingleOrDefault()?.Table.Name
?? entityType.ShortName()).Substring(0, 1).ToLower();
- var translation = new QueryableFunctionExpression(function.Schema, function.Name, arguments, alias);
+ var translation = new TableValuedFunctionExpression(function.Schema, function.Name, arguments, alias);
var queryExpression = _sqlExpressionFactory.Select(entityType, translation);
return CreateShapedQueryExpression(entityType, queryExpression);
diff --git a/src/EFCore.Relational/Query/SqlExpressionVisitor.cs b/src/EFCore.Relational/Query/SqlExpressionVisitor.cs
index 1eba46de3ed..7b8351831cb 100644
--- a/src/EFCore.Relational/Query/SqlExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/SqlExpressionVisitor.cs
@@ -68,8 +68,8 @@ protected override Expression VisitExtension(Expression extensionExpression)
case ProjectionExpression projectionExpression:
return VisitProjection(projectionExpression);
- case QueryableFunctionExpression queryableFunctionExpression:
- return VisitQueryableFunction(queryableFunctionExpression);
+ case TableValuedFunctionExpression tableValuedFunctionExpression:
+ return VisitTableValuedFunction(tableValuedFunctionExpression);
case RowNumberExpression rowNumberExpression:
return VisitRowNumber(rowNumberExpression);
@@ -124,7 +124,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
protected abstract Expression VisitOrdering([NotNull] OrderingExpression orderingExpression);
protected abstract Expression VisitOuterApply([NotNull] OuterApplyExpression outerApplyExpression);
protected abstract Expression VisitProjection([NotNull] ProjectionExpression projectionExpression);
- protected abstract Expression VisitQueryableFunction([NotNull] QueryableFunctionExpression queryableFunctionExpression);
+ protected abstract Expression VisitTableValuedFunction([NotNull] TableValuedFunctionExpression queryableFunctionExpression);
protected abstract Expression VisitRowNumber([NotNull] RowNumberExpression rowNumberExpression);
protected abstract Expression VisitScalarSubquery([NotNull] ScalarSubqueryExpression scalarSubqueryExpression);
protected abstract Expression VisitSelect([NotNull] SelectExpression selectExpression);
diff --git a/src/EFCore.Relational/Query/SqlExpressions/QueryableFunctionExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/TableValuedFunctionExpression.cs
similarity index 84%
rename from src/EFCore.Relational/Query/SqlExpressions/QueryableFunctionExpression.cs
rename to src/EFCore.Relational/Query/SqlExpressions/TableValuedFunctionExpression.cs
index d49a9b511c7..d6e770112dd 100644
--- a/src/EFCore.Relational/Query/SqlExpressions/QueryableFunctionExpression.cs
+++ b/src/EFCore.Relational/Query/SqlExpressions/TableValuedFunctionExpression.cs
@@ -13,9 +13,9 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions
///
/// Represents a SQL Table Valued Fuction in the sql generation tree.
///
- public class QueryableFunctionExpression : TableExpressionBase
+ public class TableValuedFunctionExpression : TableExpressionBase
{
- public QueryableFunctionExpression(
+ public TableValuedFunctionExpression(
[CanBeNull] string schema, [NotNull] string name, [NotNull] IReadOnlyList arguments, [NotNull] string alias)
: base(alias)
{
@@ -45,16 +45,16 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
}
return changed
- ? new QueryableFunctionExpression(Schema, Name, arguments, Alias)
+ ? new TableValuedFunctionExpression(Schema, Name, arguments, Alias)
: this;
}
- public virtual QueryableFunctionExpression Update([NotNull] IReadOnlyList arguments)
+ public virtual TableValuedFunctionExpression Update([NotNull] IReadOnlyList arguments)
{
Check.NotNull(arguments, nameof(arguments));
return !arguments.SequenceEqual(Arguments)
- ? new QueryableFunctionExpression(Schema, Name, arguments, Alias)
+ ? new TableValuedFunctionExpression(Schema, Name, arguments, Alias)
: this;
}
@@ -75,10 +75,10 @@ public override void Print(ExpressionPrinter expressionPrinter)
public override bool Equals(object obj)
=> obj != null
&& (ReferenceEquals(this, obj)
- || obj is QueryableFunctionExpression queryableExpression
+ || obj is TableValuedFunctionExpression queryableExpression
&& Equals(queryableExpression));
- private bool Equals(QueryableFunctionExpression queryableExpression)
+ private bool Equals(TableValuedFunctionExpression queryableExpression)
=> base.Equals(queryableExpression)
&& string.Equals(Name, queryableExpression.Name)
&& string.Equals(Schema, queryableExpression.Schema)
diff --git a/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs
index ade8952c423..3c62cc0c7b3 100644
--- a/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs
+++ b/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs
@@ -449,12 +449,12 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction
/// 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 VisitQueryableFunction(QueryableFunctionExpression queryableFunctionExpression)
+ protected override Expression VisitTableValuedFunction(TableValuedFunctionExpression tableValuedFunctionExpression)
{
- Check.NotNull(queryableFunctionExpression, nameof(queryableFunctionExpression));
+ Check.NotNull(tableValuedFunctionExpression, nameof(tableValuedFunctionExpression));
// TODO: See issue#20180
- return queryableFunctionExpression;
+ return tableValuedFunctionExpression;
}
///
diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs
index c12d794fef9..9f4474539af 100644
--- a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs
+++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs
@@ -112,20 +112,20 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction
/// 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 VisitQueryableFunction(QueryableFunctionExpression queryableFunctionExpression)
+ protected override Expression VisitTableValuedFunction(TableValuedFunctionExpression tableValuedFunctionExpression)
{
- Check.NotNull(queryableFunctionExpression, nameof(queryableFunctionExpression));
+ Check.NotNull(tableValuedFunctionExpression, nameof(tableValuedFunctionExpression));
- if (string.IsNullOrEmpty(queryableFunctionExpression.Schema))
+ if (string.IsNullOrEmpty(tableValuedFunctionExpression.Schema))
{
- queryableFunctionExpression = new QueryableFunctionExpression(
+ tableValuedFunctionExpression = new TableValuedFunctionExpression(
schema: "dbo",
- queryableFunctionExpression.Name,
- queryableFunctionExpression.Arguments,
- queryableFunctionExpression.Alias);
+ tableValuedFunctionExpression.Name,
+ tableValuedFunctionExpression.Arguments,
+ tableValuedFunctionExpression.Alias);
}
- return base.VisitQueryableFunction(queryableFunctionExpression);
+ return base.VisitTableValuedFunction(tableValuedFunctionExpression);
}
}
}
diff --git a/src/EFCore/Query/EvaluatableExpressionFilter.cs b/src/EFCore/Query/EvaluatableExpressionFilter.cs
index 8e18416824d..0ac8ba8f5a8 100644
--- a/src/EFCore/Query/EvaluatableExpressionFilter.cs
+++ b/src/EFCore/Query/EvaluatableExpressionFilter.cs
@@ -21,7 +21,7 @@ namespace Microsoft.EntityFrameworkCore.Query
/// This service cannot depend on services registered as .
///
///
- public class EvaluatableExpressionFilter : EvaluatableExpressionFilterBase
+ public class EvaluatableExpressionFilter : IEvaluatableExpressionFilter
{
// This methods are non-deterministic and result varies based on time of running the query.
// Hence we don't evaluate them. See issue#2069
@@ -65,17 +65,24 @@ private static readonly MethodInfo _randomNextTwoArgs
/// The dependencies to use.
public EvaluatableExpressionFilter(
[NotNull] EvaluatableExpressionFilterDependencies dependencies)
- : base(dependencies)
{
+ Check.NotNull(dependencies, nameof(dependencies));
+
+ Dependencies = dependencies;
}
+ ///
+ /// Parameter object containing service dependencies.
+ ///
+ protected virtual EvaluatableExpressionFilterDependencies Dependencies { get; }
+
///
/// Checks whether the given expression can be evaluated.
///
/// The expression.
/// The model.
/// True if the expression can be evaluated; false otherwise.
- public override bool IsEvaluatableExpression(Expression expression, IModel model)
+ public virtual bool IsEvaluatableExpression(Expression expression, IModel model)
{
Check.NotNull(expression, nameof(expression));
Check.NotNull(model, nameof(model));
@@ -109,7 +116,15 @@ public override bool IsEvaluatableExpression(Expression expression, IModel model
break;
}
- return base.IsEvaluatableExpression(expression, model);
+ foreach (var plugin in Dependencies.Plugins)
+ {
+ if (!plugin.IsEvaluatableExpression(expression))
+ {
+ return false;
+ }
+ }
+
+ return true;
}
}
}
diff --git a/src/EFCore/Query/EvaluatableExpressionFilterBase.cs b/src/EFCore/Query/EvaluatableExpressionFilterBase.cs
deleted file mode 100644
index 242c8968cca..00000000000
--- a/src/EFCore/Query/EvaluatableExpressionFilterBase.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-// 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.Metadata;
-using Microsoft.EntityFrameworkCore.Utilities;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace Microsoft.EntityFrameworkCore.Query
-{
- ///
- ///
- /// Represents a filter for evaluatable expressions.
- ///
- ///
- /// The service lifetime is . This means a single instance
- /// is used by many instances. The implementation must be thread-safe.
- /// This service cannot depend on services registered as .
- ///
- ///
- public class EvaluatableExpressionFilterBase : IEvaluatableExpressionFilter
- {
- ///
- /// Parameter object containing dependencies for this service.
- ///
- protected virtual EvaluatableExpressionFilterDependencies Dependencies { get; }
-
- ///
- ///
- /// Creates a new instance.
- ///
- ///
- /// This type is typically used by database providers (and other extensions). It is generally
- /// not used in application code.
- ///
- ///
- /// The dependencies to use.
- public EvaluatableExpressionFilterBase(
- [NotNull] EvaluatableExpressionFilterDependencies dependencies)
- {
- Check.NotNull(dependencies, nameof(dependencies));
-
- Dependencies = dependencies;
- }
-
- ///
- /// Checks whether the given expression can be evaluated.
- ///
- /// The expression.
- /// The model.
- /// True if the expression can be evaluated; false otherwise.
- public virtual bool IsEvaluatableExpression(Expression expression, IModel model)
- {
- Check.NotNull(expression, nameof(expression));
- Check.NotNull(model, nameof(model));
-
- foreach (var plugin in Dependencies.Plugins)
- {
- if (!plugin.IsEvaluatableExpression(expression))
- {
- return false;
- }
- }
-
- return true;
- }
- }
-}
diff --git a/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs b/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs
index be75d3f9d79..c1ab90b9ae8 100644
--- a/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs
+++ b/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs
@@ -634,8 +634,9 @@ var queryableNoParams
function = model.FindDbFunction(function.ModelName);
var entityType = model.FindEntityType(typeof(Foo));
- Assert.True(function.IsQueryable);
- Assert.Same(entityType, function.QueryableEntityType);
+ Assert.False(function.IsScalar);
+ Assert.False(function.IsAggregate);
+ Assert.Same(entityType, function.ReturnEntityType);
}
[ConditionalFact]