Skip to content

Commit

Permalink
Query: Implement inheritance support for relational layer
Browse files Browse the repository at this point in the history
- Add discriminator predicate in SelectExpression (filed #15808 to remove redundant)
- Add TPH support in materializer. This happens at core level now
- Add translation for is operator inside lambda
- Add translation for OfType method
  • Loading branch information
smitpatel committed May 29, 2019
1 parent 3d422f9 commit 2405a18
Show file tree
Hide file tree
Showing 27 changed files with 450 additions and 191 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public InMemoryShapedQueryExpression(IEntityType entityType)
entityType,
new ProjectionBindingExpression(
QueryExpression,
new ProjectionMember(), typeof(ValueBuffer)),
new ProjectionMember(),
typeof(ValueBuffer)),
false);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ var discriminatorPredicate

private static Expression IsNotNull(IProperty property, SelectExpression selectExpression, IQuerySource querySource)
{
var column = selectExpression.BindProperty(property, querySource);
var column = selectExpression.BindProperty(property, querySource);
var nullableColumn = column.Type.IsNullableType() ? column : new NullableExpression(column);
return Expression.Not(new IsNullExpression(nullableColumn));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ private readonly IDictionary<IProperty, ColumnExpression> _propertyExpressionsCa

public EntityProjectionExpression(IEntityType entityType, TableExpressionBase innerTable, bool nullable)
{
EntityType = entityType;
EntityType = entityType.RootType();
_innerTable = innerTable;
Nullable = nullable;
}

public EntityProjectionExpression(IEntityType entityType, IDictionary<IProperty, ColumnExpression> propertyExpressions)
{
EntityType = entityType;
EntityType = entityType.RootType();
_propertyExpressionsCache = propertyExpressions;
}

Expand Down Expand Up @@ -81,6 +81,11 @@ public EntityProjectionExpression MakeNullable()

public ColumnExpression GetProperty(IProperty property)
{
if (property.DeclaringEntityType.RootType() != EntityType)
{
throw new InvalidOperationException("Called EntityProjectionExpression.GetProperty() with incorrect IProperty");
}

if (!_propertyExpressionsCache.TryGetValue(property, out var expression))
{
expression = new ColumnExpression(property, _innerTable, Nullable);
Expand Down
6 changes: 6 additions & 0 deletions src/EFCore.Relational/Query/Pipeline/ISqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;

Expand Down Expand Up @@ -76,6 +77,11 @@ SqlFunctionExpression Function(
LikeExpression Like(SqlExpression match, SqlExpression pattern, SqlExpression escapeChar = null);
SqlConstantExpression Constant(object value, RelationalTypeMapping typeMapping = null);
SqlFragmentExpression Fragment(string sql);

SelectExpression Select(SqlExpression projection);
SelectExpression Select(IEntityType entityType);
SelectExpression Select(IEntityType entityType, string sql, Expression sqlArguments);

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query.Pipeline;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;

namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline
{
public class RelationalEntityQueryableExpressionVisitor2 : EntityQueryableExpressionVisitor2
{
private readonly IModel _model;
private readonly ISqlExpressionFactory _sqlExpressionFactory;

public RelationalEntityQueryableExpressionVisitor2(IModel model)
public RelationalEntityQueryableExpressionVisitor2(IModel model, ISqlExpressionFactory sqlExpressionFactory)
{
_model = model;
_sqlExpressionFactory = sqlExpressionFactory;
}

protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
Expand All @@ -28,16 +30,39 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
// TODO: Implement parameters
var sql = (string)((ConstantExpression)methodCallExpression.Arguments[1]).Value;
var queryable = (IQueryable)((ConstantExpression)methodCallExpression.Arguments[0]).Value;
return CreateShapedQueryExpression(queryable.ElementType, sql);
return CreateShapedQueryExpression(queryable.ElementType, sql, methodCallExpression.Arguments[2]);
}

return base.VisitMethodCall(methodCallExpression);
}

protected override ShapedQueryExpression CreateShapedQueryExpression(Type elementType)
=> new RelationalShapedQueryExpression(_model.FindEntityType(elementType));
{
var entityType = _model.FindEntityType(elementType);
var queryExpression = _sqlExpressionFactory.Select(entityType);

return CreateShapedQueryExpression(entityType, queryExpression);
}

protected virtual ShapedQueryExpression CreateShapedQueryExpression(Type elementType, string sql, Expression arguments)
{
var entityType = _model.FindEntityType(elementType);
var queryExpression = _sqlExpressionFactory.Select(entityType, sql, arguments);

protected virtual ShapedQueryExpression CreateShapedQueryExpression(Type elementType, string sql)
=> new RelationalShapedQueryExpression(_model.FindEntityType(elementType), sql);
return CreateShapedQueryExpression(entityType, queryExpression);
}

private ShapedQueryExpression CreateShapedQueryExpression(IEntityType entityType, SelectExpression selectExpression)
{
return new RelationalShapedQueryExpression(
selectExpression,
new EntityShaperExpression(
entityType,
new ProjectionBindingExpression(
selectExpression,
new ProjectionMember(),
typeof(ValueBuffer)),
false));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline
public class RelationalEntityQueryableTranslatorFactory : EntityQueryableTranslatorFactory
{
private readonly IModel _model;
private readonly ISqlExpressionFactory _sqlExpressionFactory;

public RelationalEntityQueryableTranslatorFactory(IModel model)
public RelationalEntityQueryableTranslatorFactory(IModel model, ISqlExpressionFactory sqlExpressionFactory)
{
_model = model;
_sqlExpressionFactory = sqlExpressionFactory;
}

public override EntityQueryableTranslator Create(QueryCompilationContext2 queryCompilationContext)
{
return new RelationalEntityQueryableTranslator(_model);
return new RelationalEntityQueryableTranslator(_model, _sqlExpressionFactory);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline
public class RelationalEntityQueryableTranslator : EntityQueryableTranslator
{
private readonly IModel _model;
private readonly ISqlExpressionFactory _sqlExpressionFactory;

public RelationalEntityQueryableTranslator(IModel model)
public RelationalEntityQueryableTranslator(IModel model, ISqlExpressionFactory sqlExpressionFactory)
{
_model = model;
_sqlExpressionFactory = sqlExpressionFactory;
}

public override Expression Visit(Expression query)
{
return new RelationalEntityQueryableExpressionVisitor2(_model).Visit(query);
return new RelationalEntityQueryableExpressionVisitor2(_model, _sqlExpressionFactory).Visit(query);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Query.Pipeline;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;

Expand Down Expand Up @@ -57,7 +58,7 @@ protected override ShapedQueryExpression TranslateAll(ShapedQueryExpression sour
}

translation = _sqlExpressionFactory.Exists(selectExpression, true);
source.QueryExpression = selectExpression.SetProjectionAsResult(translation);
source.QueryExpression = _sqlExpressionFactory.Select(translation);
source.ShaperExpression = new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool));

return source;
Expand All @@ -82,7 +83,7 @@ protected override ShapedQueryExpression TranslateAny(ShapedQueryExpression sour
}

var translation = _sqlExpressionFactory.Exists(selectExpression, false);
source.QueryExpression = selectExpression.SetProjectionAsResult(translation);
source.QueryExpression = _sqlExpressionFactory.Select(translation);
source.ShaperExpression = new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool));

return source;
Expand Down Expand Up @@ -158,7 +159,7 @@ protected override ShapedQueryExpression TranslateContains(ShapedQueryExpression

selectExpression.ApplyProjection();
translation = _sqlExpressionFactory.In(translation, selectExpression, false);
source.QueryExpression = selectExpression.SetProjectionAsResult(translation);
source.QueryExpression = _sqlExpressionFactory.Select(translation);
source.ShaperExpression = new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool));

return source;
Expand Down Expand Up @@ -512,7 +513,53 @@ protected override ShapedQueryExpression TranslateMin(ShapedQueryExpression sour
return AggregateResultShaper(source, projection, throwOnNullResult: true, resultType);
}

protected override ShapedQueryExpression TranslateOfType(ShapedQueryExpression source, Type resultType) => throw new NotImplementedException();
protected override ShapedQueryExpression TranslateOfType(ShapedQueryExpression source, Type resultType)
{
if (source.ShaperExpression is EntityShaperExpression entityShaperExpression)
{
var entityType = entityShaperExpression.EntityType;
if (entityType.ClrType == resultType)
{
return source;
}

var baseType = entityType.GetAllBaseTypes().SingleOrDefault(et => et.ClrType == resultType);
if (baseType != null)
{
source.ShaperExpression = new EntityShaperExpression(
baseType, entityShaperExpression.ValueBufferExpression, entityShaperExpression.Nullable);

return source;
}

var derivedType = entityType.GetDerivedTypes().SingleOrDefault(et => et.ClrType == resultType);
if (derivedType != null)
{
var selectExpression = (SelectExpression)source.QueryExpression;
var concreteEntityTypes = derivedType.GetConcreteTypesInHierarchy().ToList();
var discriminatorColumn = selectExpression
.BindProperty(entityShaperExpression.ValueBufferExpression, entityType.GetDiscriminatorProperty());

var predicate = concreteEntityTypes.Count == 1
? _sqlExpressionFactory.Equal(discriminatorColumn,
_sqlExpressionFactory.Constant(concreteEntityTypes[0].GetDiscriminatorValue()))
: (SqlExpression)_sqlExpressionFactory.In(discriminatorColumn,
_sqlExpressionFactory.Constant(concreteEntityTypes.Select(et => et.GetDiscriminatorValue())),
negated: false);

selectExpression.ApplyPredicate(predicate);

source.ShaperExpression = new EntityShaperExpression(
derivedType, entityShaperExpression.ValueBufferExpression, entityShaperExpression.Nullable);

return source;
}

// If the resultType is not part of hierarchy then we don't know how to materialize.
}

throw new NotImplementedException();
}

protected override ShapedQueryExpression TranslateOrderBy(ShapedQueryExpression source, LambdaExpression keySelector, bool ascending)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,8 +388,8 @@ private class RelationalProjectionBindingRemovingExpressionVisitor : ExpressionV
public static readonly ParameterExpression DataReaderParameter
= Expression.Parameter(typeof(DbDataReader), "dataReader");

private readonly IDictionary<ParameterExpression, int> _materializationContextBindings
= new Dictionary<ParameterExpression, int>();
private readonly IDictionary<ParameterExpression, IDictionary<IProperty, int>> _materializationContextBindings
= new Dictionary<ParameterExpression, IDictionary<IProperty, int>>();

public RelationalProjectionBindingRemovingExpressionVisitor(SelectExpression selectExpression)
{
Expand All @@ -405,7 +405,8 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression)
var newExpression = (NewExpression)binaryExpression.Right;
var projectionBindingExpression = (ProjectionBindingExpression)newExpression.Arguments[0];

_materializationContextBindings[parameterExpression] = GetProjectionIndex(projectionBindingExpression);
_materializationContextBindings[parameterExpression]
= (IDictionary<IProperty, int>)GetProjectionIndex(projectionBindingExpression);

var updatedExpression = Expression.New(newExpression.Constructor,
Expression.Constant(ValueBuffer.Empty),
Expand All @@ -422,14 +423,12 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
if (methodCallExpression.Method.IsGenericMethod
&& methodCallExpression.Method.GetGenericMethodDefinition() == EntityMaterializerSource.TryReadValueMethod)
{
var originalIndex = (int)((ConstantExpression)methodCallExpression.Arguments[1]).Value;
var indexOffset = methodCallExpression.Arguments[0] is ProjectionBindingExpression projectionBindingExpression
? GetProjectionIndex(projectionBindingExpression)
: _materializationContextBindings[(ParameterExpression)((MethodCallExpression)methodCallExpression.Arguments[0]).Object];

var property = (IProperty)((ConstantExpression)methodCallExpression.Arguments[2]).Value;
var propertyProjectionMap = methodCallExpression.Arguments[0] is ProjectionBindingExpression projectionBindingExpression
? (IDictionary<IProperty, int>)GetProjectionIndex(projectionBindingExpression)
: _materializationContextBindings[(ParameterExpression)((MethodCallExpression)methodCallExpression.Arguments[0]).Object];

var projectionIndex = originalIndex + indexOffset;
var projectionIndex = propertyProjectionMap[property];
var projection = _selectExpression.Projection[projectionIndex];

return CreateGetValueExpression(
Expand All @@ -446,7 +445,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
{
if (extensionExpression is ProjectionBindingExpression projectionBindingExpression)
{
var projectionIndex = GetProjectionIndex(projectionBindingExpression);
var projectionIndex = (int)GetProjectionIndex(projectionBindingExpression);
var projection = _selectExpression.Projection[projectionIndex];

return CreateGetValueExpression(
Expand All @@ -459,11 +458,13 @@ protected override Expression VisitExtension(Expression extensionExpression)
return base.VisitExtension(extensionExpression);
}

private int GetProjectionIndex(ProjectionBindingExpression projectionBindingExpression)
private object GetProjectionIndex(ProjectionBindingExpression projectionBindingExpression)
{
return projectionBindingExpression.ProjectionMember != null
? (int)((ConstantExpression)_selectExpression.GetProjectionExpression(projectionBindingExpression.ProjectionMember)).Value
: projectionBindingExpression.Index;
? ((ConstantExpression)_selectExpression.GetProjectionExpression(projectionBindingExpression.ProjectionMember)).Value
: (projectionBindingExpression.Index != null
? (object)projectionBindingExpression.Index
: projectionBindingExpression.IndexMap);
}

private static bool IsNullableProjection(ProjectionExpression projection)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,17 @@
// 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 Microsoft.EntityFrameworkCore.Metadata;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query.Pipeline;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;

namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline
{
public class RelationalShapedQueryExpression : ShapedQueryExpression
{
public RelationalShapedQueryExpression(IEntityType entityType)
public RelationalShapedQueryExpression(Expression queryExpression, Expression shaperExpression)
{
QueryExpression = new SelectExpression(entityType);
ShaperExpression = new EntityShaperExpression(
entityType,
new ProjectionBindingExpression(
QueryExpression,
new ProjectionMember(),
typeof(ValueBuffer)),
false);
}

public RelationalShapedQueryExpression(IEntityType entityType, string sql)
{
QueryExpression = new SelectExpression(entityType, sql);
ShaperExpression = new EntityShaperExpression(
entityType,
new ProjectionBindingExpression(
QueryExpression,
new ProjectionMember(),
typeof(ValueBuffer)),
false);
QueryExpression = queryExpression;
ShaperExpression = shaperExpression;
}
}
}
Loading

0 comments on commit 2405a18

Please sign in to comment.