Skip to content

Commit

Permalink
Add support for table valued functions
Browse files Browse the repository at this point in the history
  • Loading branch information
pmiddleton committed Mar 5, 2018
1 parent e67874c commit 6a58596
Show file tree
Hide file tree
Showing 30 changed files with 1,683 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Internal;
Expand All @@ -21,6 +22,7 @@
using Microsoft.EntityFrameworkCore.Update.Internal;
using Microsoft.EntityFrameworkCore.ValueGeneration;
using Microsoft.Extensions.DependencyInjection;
using Remotion.Linq.Parsing.ExpressionVisitors.Transformation;
using Remotion.Linq.Parsing.ExpressionVisitors.TreeEvaluation;

namespace Microsoft.EntityFrameworkCore.Infrastructure
Expand Down Expand Up @@ -172,6 +174,8 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
TryAdd<INamedConnectionStringResolver, NamedConnectionStringResolver>();
TryAdd<IEvaluatableExpressionFilter, RelationalEvaluatableExpressionFilter>();
TryAdd<IRelationalTransactionFactory, RelationalTransactionFactory>();
TryAdd<IExpressionTranformationProvider, RelationalIExpressionTranformationProvider>();
TryAdd<IDbFunctionSourceFactory, RelationalDbFunctionSourceFactory>();

TryAdd<ISingletonUpdateSqlGenerator>(p =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ protected virtual void ValidateDbFunctions([NotNull] IModel model)

if (dbFunction.Translation == null)
{
if (RelationalDependencies.TypeMappingSource.FindMapping(methodInfo.ReturnType) == null)
if ((dbFunction.IsIQueryable && model.FindEntityType(dbFunction.MethodInfo.ReturnType.GetGenericArguments()[0]) == null)
&& RelationalDependencies.TypeMappingSource.FindMapping(methodInfo.ReturnType) == null)
{
throw new InvalidOperationException(
RelationalStrings.DbFunctionInvalidReturnType(
Expand Down
5 changes: 5 additions & 0 deletions src/EFCore.Relational/Metadata/IDbFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public interface IDbFunction
/// </summary>
MethodInfo MethodInfo { get; }

/// <summary>
/// Does this method return IQueryable
/// </summary>
bool IsIQueryable { get; }

/// <summary>
/// A translation callback for performing custom translation of the method call into a SQL expression fragment.
/// </summary>
Expand Down
23 changes: 23 additions & 0 deletions src/EFCore.Relational/Metadata/Internal/DbFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,23 @@ private DbFunction(
RelationalStrings.DbFunctionInvalidReturnType(methodInfo.DisplayName(), methodInfo.ReturnType.ShortDisplayName()));
}

if (methodInfo.ReturnType.IsGenericType
&& methodInfo.ReturnType.GetGenericTypeDefinition() == typeof(IQueryable<>))
{
if (methodInfo.IsStatic)
{
throw new ArgumentException(
RelationalStrings.DbFunctionQueryableNotStatic(methodInfo.DisplayName()));
}

IsIQueryable = true;

if (model.FindEntityType(methodInfo.ReturnType.GetGenericArguments()[0]) == null)
{
model.AddQueryType(methodInfo.ReturnType.GetGenericArguments()[0]);
}
}

MethodInfo = methodInfo;

_model = model;
Expand Down Expand Up @@ -186,6 +203,12 @@ private void UpdateNameConfigurationSource(ConfigurationSource configurationSour
/// </summary>
public virtual Func<IReadOnlyCollection<Expression>, Expression> Translation { get; set; }

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual bool IsIQueryable { get; set; }

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
Expand Down
24 changes: 24 additions & 0 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.

9 changes: 9 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,15 @@
<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="DbFunctionTableValuedFunctionMustReturnIQueryable" xml:space="preserve">
<value>The DbFunction '{function}' must return IQueryable.</value>
</data>
<data name="DbFunctionNotFound" xml:space="preserve">
<value>The DbFunction '{function}' is not registered with the model.</value>
</data>
<data name="DbFunctionQueryableNotStatic" xml:space="preserve">
<value>IQueryable DbFunctions must be instance methods. '{function}' is static.</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
Expand Up @@ -33,6 +33,7 @@ public class RelationalEntityQueryableExpressionVisitor : EntityQueryableExpress
private readonly IMaterializerFactory _materializerFactory;
private readonly IShaperCommandContextFactory _shaperCommandContextFactory;
private readonly IQuerySource _querySource;
private readonly ISqlTranslatingExpressionVisitorFactory _sqlTranslatingExpressionVisitorFactory;

/// <summary>
/// Creates a new instance of <see cref="RelationalEntityQueryableExpressionVisitor" />.
Expand All @@ -53,6 +54,7 @@ public RelationalEntityQueryableExpressionVisitor(
_materializerFactory = dependencies.MaterializerFactory;
_shaperCommandContextFactory = dependencies.ShaperCommandContextFactory;
_querySource = querySource;
_sqlTranslatingExpressionVisitorFactory = dependencies.SqlTranslatingExpressionVisitorFactory;
}

private new RelationalQueryModelVisitor QueryModelVisitor => (RelationalQueryModelVisitor)base.QueryModelVisitor;
Expand Down Expand Up @@ -119,16 +121,40 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
{
Check.NotNull(node, nameof(node));

QueryModelVisitor
.BindMethodCallExpression(
var dbFunc = _model.Relational().FindDbFunction(node.Method);

if (dbFunc != null && dbFunc.IsIQueryable)
{
return VisitDbFunctionSourceExpression(new DbFunctionSourceExpression(node, _model));
}
else
{
QueryModelVisitor.BindMethodCallExpression(
node,
(property, querySource, selectExpression)
=> selectExpression.AddToProjection(
property,
querySource),
bindSubQueries: true);

return base.VisitMethodCall(node);
return base.VisitMethodCall(node);
}
}

/// <summary>
/// Visits Extension <see cref="Expression" /> nodes.
/// </summary>
/// <param name="node"> The node being visited. </param>
/// <returns> An expression to use in place of the node. </returns>
protected override Expression VisitExtension(Expression node)
{
switch (node)
{
case DbFunctionSourceExpression dbNode:
return VisitDbFunctionSourceExpression(dbNode);
default:
return base.VisitExtension(node);
}
}

/// <summary>
Expand Down Expand Up @@ -238,6 +264,57 @@ var useQueryComposition
Expression.Constant(shaper));
}

/// <summary>
/// todo
/// </summary>
/// <param name="dbFunctionSourceExpression">todo</param>
/// <returns>todo</returns>
protected Expression VisitDbFunctionSourceExpression([NotNull] DbFunctionSourceExpression dbFunctionSourceExpression)
{
var relationalQueryCompilationContext = QueryModelVisitor.QueryCompilationContext;
var selectExpression = _selectExpressionFactory.Create(relationalQueryCompilationContext);

QueryModelVisitor.AddQuery(_querySource, selectExpression);

var sqlTranslatingExpressionVisitor = _sqlTranslatingExpressionVisitorFactory.Create(QueryModelVisitor);

var sqlFuncExpression = (SqlFunctionExpression)sqlTranslatingExpressionVisitor.Visit(dbFunctionSourceExpression);

Func<IQuerySqlGenerator> querySqlGeneratorFunc = selectExpression.CreateDefaultQuerySqlGenerator;

Shaper shaper;

if (dbFunctionSourceExpression.IsIQueryable)
{
var tableAlias
= relationalQueryCompilationContext.CreateUniqueTableAlias(
_querySource.HasGeneratedItemName()
? dbFunctionSourceExpression.Name[0].ToString().ToLowerInvariant()
: _querySource.ItemName);

selectExpression.AddTable(new TableValuedSqlFunctionExpression(sqlFuncExpression, _querySource, tableAlias));

var entityType = _model.FindEntityType(dbFunctionSourceExpression.ReturnType);

shaper = CreateShaper(dbFunctionSourceExpression.ReturnType, entityType, selectExpression);
}
else
{
selectExpression.AddToProjection(sqlFuncExpression);

shaper = new ValueBufferShaper(_querySource);
}

return Expression.Call(
QueryModelVisitor.QueryCompilationContext.QueryMethodProvider // TODO: Don't use ShapedQuery when projecting
.ShapedQueryMethod
.MakeGenericMethod(shaper.Type),
EntityQueryModelVisitor.QueryContextParameter,
Expression.Constant(_shaperCommandContextFactory.Create(querySqlGeneratorFunc)),
Expression.Constant(shaper));
}


private Shaper CreateShaper(Type elementType, IEntityType entityType, SelectExpression selectExpression)
{
Shaper shaper;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,25 @@ public sealed class RelationalEntityQueryableExpressionVisitorDependencies
/// <param name="selectExpressionFactory"> The select expression factory. </param>
/// <param name="materializerFactory"> The materializer factory. </param>
/// <param name="shaperCommandContextFactory"> The shaper command context factory. </param>
/// <param name="sqlTranslatingExpressionVisitorFactory"> TODO. </param>
public RelationalEntityQueryableExpressionVisitorDependencies(
[NotNull] IModel model,
[NotNull] ISelectExpressionFactory selectExpressionFactory,
[NotNull] IMaterializerFactory materializerFactory,
[NotNull] IShaperCommandContextFactory shaperCommandContextFactory)
[NotNull] IShaperCommandContextFactory shaperCommandContextFactory,
[NotNull] ISqlTranslatingExpressionVisitorFactory sqlTranslatingExpressionVisitorFactory)
{
Check.NotNull(model, nameof(model));
Check.NotNull(selectExpressionFactory, nameof(selectExpressionFactory));
Check.NotNull(materializerFactory, nameof(materializerFactory));
Check.NotNull(shaperCommandContextFactory, nameof(shaperCommandContextFactory));
Check.NotNull(sqlTranslatingExpressionVisitorFactory, nameof(sqlTranslatingExpressionVisitorFactory));

Model = model;
SelectExpressionFactory = selectExpressionFactory;
MaterializerFactory = materializerFactory;
ShaperCommandContextFactory = shaperCommandContextFactory;
SqlTranslatingExpressionVisitorFactory = sqlTranslatingExpressionVisitorFactory;
}

/// <summary>
Expand All @@ -84,6 +88,13 @@ public RelationalEntityQueryableExpressionVisitorDependencies(
/// </summary>
public IShaperCommandContextFactory ShaperCommandContextFactory { get; }

/// <summary>
/// todo
/// </summary>
public ISqlTranslatingExpressionVisitorFactory SqlTranslatingExpressionVisitorFactory { get; }



/// <summary>
/// Clones this dependency parameter object with one service replaced.
/// </summary>
Expand All @@ -94,7 +105,8 @@ public RelationalEntityQueryableExpressionVisitorDependencies With([NotNull] IMo
model,
SelectExpressionFactory,
MaterializerFactory,
ShaperCommandContextFactory);
ShaperCommandContextFactory,
SqlTranslatingExpressionVisitorFactory);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
Expand All @@ -106,7 +118,8 @@ public RelationalEntityQueryableExpressionVisitorDependencies With([NotNull] ISe
Model,
selectExpressionFactory,
MaterializerFactory,
ShaperCommandContextFactory);
ShaperCommandContextFactory,
SqlTranslatingExpressionVisitorFactory);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
Expand All @@ -118,7 +131,8 @@ public RelationalEntityQueryableExpressionVisitorDependencies With([NotNull] IMa
Model,
SelectExpressionFactory,
materializerFactory,
ShaperCommandContextFactory);
ShaperCommandContextFactory,
SqlTranslatingExpressionVisitorFactory);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
Expand All @@ -130,6 +144,20 @@ public RelationalEntityQueryableExpressionVisitorDependencies With([NotNull] ISh
Model,
SelectExpressionFactory,
MaterializerFactory,
shaperCommandContextFactory);
shaperCommandContextFactory,
SqlTranslatingExpressionVisitorFactory);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
/// </summary>
/// <param name="sqlTranslatingExpressionVisitorFactory"> A replacement for the current dependency of this type. </param>
/// <returns> A new parameter object with the given service replaced. </returns>
public RelationalEntityQueryableExpressionVisitorDependencies With([NotNull] ISqlTranslatingExpressionVisitorFactory sqlTranslatingExpressionVisitorFactory)
=> new RelationalEntityQueryableExpressionVisitorDependencies(
Model,
SelectExpressionFactory,
MaterializerFactory,
ShaperCommandContextFactory,
sqlTranslatingExpressionVisitorFactory);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,20 @@ var equalityExpression
? new NullCompensatedExpression(newOperand)
: nullCompensatedExpression;
}
case DbFunctionSourceExpression dbFunctionExpression:
{
var newArguments = Visit(dbFunctionExpression.Arguments);

if (newArguments.Any(a => a == null))
{
return null;
}

//TODO - can you custom translate here?
return //dbFunctionExpression.Translate(newArguments)
//??
new SqlFunctionExpression(dbFunctionExpression.Name, dbFunctionExpression.UnwrappedType, dbFunctionExpression.Schema, newArguments);
}
case DiscriminatorPredicateExpression discriminatorPredicateExpression:
return new DiscriminatorPredicateExpression(
base.VisitExtension(expression), discriminatorPredicateExpression.QuerySource);
Expand Down
Loading

0 comments on commit 6a58596

Please sign in to comment.