Skip to content

Commit

Permalink
Query: Enable client eval in InMemory
Browse files Browse the repository at this point in the history
Also add support for Join/LeftJoin/SelectMany
  • Loading branch information
smitpatel committed Jul 11, 2019
1 parent 1a4b575 commit aec9bcf
Show file tree
Hide file tree
Showing 17 changed files with 1,278 additions and 377 deletions.
26 changes: 22 additions & 4 deletions src/EFCore.InMemory/Query/Pipeline/EntityValuesExpression.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
// 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 Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.InMemory.Query.Pipeline
{
public class EntityProjectionExpression : Expression
{
public EntityProjectionExpression(IEntityType entityType, int startIndex)
private readonly IDictionary<IProperty, Expression> _readExpressionMap;

public EntityProjectionExpression(
IEntityType entityType, IDictionary<IProperty, Expression> readExpressionMap)
{
EntityType = entityType;
StartIndex = startIndex;
_readExpressionMap = readExpressionMap;
}

public IEntityType EntityType { get; }
public int StartIndex { get; }
}
public override Type Type => EntityType.ClrType;
public override ExpressionType NodeType => ExpressionType.Extension;

public Expression BindProperty(IProperty property)
{
if (!EntityType.GetTypesInHierarchy().Contains(property.DeclaringEntityType))
{
throw new InvalidOperationException(
$"Called EntityProjectionExpression.BindProperty() with incorrect IProperty. EntityType:{EntityType.DisplayName()}, Property:{property.Name}");
}

return _readExpressionMap[property];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ private static IEnumerable<MethodInfo> GetMethods(string name, int parameterCoun
.Single(mi => mi.GetParameters()[1].ParameterType.GetGenericArguments().Length == 2);

public static MethodInfo Join = GetMethod(nameof(Enumerable.Join), 4);
public static MethodInfo GroupJoin = GetMethod(nameof(Enumerable.GroupJoin), 4);
public static MethodInfo DefaultIfEmptyWithArg = GetMethod(nameof(Enumerable.DefaultIfEmpty), 1);
public static MethodInfo SelectMany = GetMethods(nameof(Enumerable.SelectMany), 2)
.Single(mi => mi.GetParameters()[1].ParameterType.GetGenericArguments().Length == 2);
public static MethodInfo Contains = GetMethod(nameof(Enumerable.Contains), 1);

public static MethodInfo OrderBy = GetMethod(nameof(Enumerable.OrderBy), 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,50 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Query.NavigationExpansion;
using Microsoft.EntityFrameworkCore.Query.Pipeline;
using Microsoft.EntityFrameworkCore.Storage;

namespace Microsoft.EntityFrameworkCore.InMemory.Query.Pipeline
{
public class InMemoryProjectionBindingExpressionVisitor : ExpressionVisitor
{
private readonly QueryableMethodTranslatingExpressionVisitor _queryableMethodTranslatingExpressionVisitor;
private readonly InMemoryExpressionTranslatingExpressionVisitor _expressionTranslatingExpressionVisitor;

private InMemoryQueryExpression _queryExpression;
private bool _clientEval;
private readonly IDictionary<ProjectionMember, Expression> _projectionMapping
= new Dictionary<ProjectionMember, Expression>();

private readonly Stack<ProjectionMember> _projectionMembers = new Stack<ProjectionMember>();
private readonly InMemoryExpressionTranslatingExpressionVisitor _expressionTranslatingExpressionVisitor;

public InMemoryProjectionBindingExpressionVisitor(
QueryableMethodTranslatingExpressionVisitor queryableMethodTranslatingExpressionVisitor,
InMemoryExpressionTranslatingExpressionVisitor expressionTranslatingExpressionVisitor)
{
_queryableMethodTranslatingExpressionVisitor = queryableMethodTranslatingExpressionVisitor;
_expressionTranslatingExpressionVisitor = expressionTranslatingExpressionVisitor;
}

public Expression Translate(InMemoryQueryExpression queryExpression, Expression expression)
{
_queryExpression = queryExpression;
_clientEval = false;

_projectionMembers.Push(new ProjectionMember());

var result = Visit(expression);

_queryExpression.ReplaceProjectionMapping(_projectionMapping);
if (result == null)
{
_clientEval = true;

result = Visit(expression);

_projectionMapping.Clear();
}

_queryExpression.ReplaceProjectionMapping(_projectionMapping);
_queryExpression = null;
_projectionMapping.Clear();
_projectionMembers.Clear();
Expand All @@ -52,35 +65,79 @@ public override Expression Visit(Expression expression)

if (!(expression is NewExpression
|| expression is MemberInitExpression
|| expression is EntityShaperExpression))
|| expression is EntityShaperExpression
|| expression is IncludeExpression))
{
// TODO: Remove when InMemory implements client eval projection
// This converts object[] from GetDatabaseValues to appropriate projection.
if (expression is NewArrayExpression newArrayExpression
&& newArrayExpression.NodeType == ExpressionType.NewArrayInit
&& newArrayExpression.Expressions.Count > 0
&& newArrayExpression.Expressions[0] is UnaryExpression unaryExpression
&& unaryExpression.NodeType == ExpressionType.Convert
&& unaryExpression.Type == typeof(object)
&& unaryExpression.Operand is MethodCallExpression methodCall
&& methodCall.TryGetEFPropertyArguments(out var source, out _)
&& source is EntityShaperExpression entityShaperExpression
&& entityShaperExpression.EntityType.GetProperties().Count() == newArrayExpression.Expressions.Count)
// This skips the group parameter from GroupJoin
if (expression is ParameterExpression parameter
&& parameter.Type.IsGenericType
&& parameter.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression;
VerifyQueryExpression(projectionBindingExpression);
return parameter;
}

_projectionMapping[_projectionMembers.Peek()]
= _queryExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember);
if (_clientEval)
{
switch (expression)
{
case ConstantExpression _:
return expression;

return new EntityValuesExpression(entityShaperExpression.EntityType, projectionBindingExpression);
case MaterializeCollectionNavigationExpression materializeCollectionNavigationExpression:
//return _selectExpression.AddCollectionProjection(
// _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(
// materializeCollectionNavigationExpression.Subquery),
// materializeCollectionNavigationExpression.Navigation, null);
throw new NotImplementedException();

case MethodCallExpression methodCallExpression:
{
if (methodCallExpression.Method.IsGenericMethod
&& methodCallExpression.Method.DeclaringType == typeof(Enumerable)
&& methodCallExpression.Method.Name == nameof(Enumerable.ToList))
{
//var elementType = methodCallExpression.Method.GetGenericArguments()[0];

//var result = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(methodCallExpression.Arguments[0]);

//return _selectExpression.AddCollectionProjection(result, null, elementType);
throw new NotImplementedException();
}

var subquery = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(methodCallExpression);

if (subquery != null)
{
//if (subquery.ResultType == ResultType.Enumerable)
//{
// return _selectExpression.AddCollectionProjection(subquery, null, subquery.ShaperExpression.Type);
//}
throw new NotImplementedException();
}

break;
}
}


var translation = _expressionTranslatingExpressionVisitor.Translate(expression);
return translation == null
? base.Visit(expression)
: new ProjectionBindingExpression(_queryExpression, _queryExpression.AddToProjection(translation), expression.Type);
}
else
{
var translation = _expressionTranslatingExpressionVisitor.Translate(expression);
if (translation == null)
{
return null;
}

var translation = _expressionTranslatingExpressionVisitor.Translate(_queryExpression, expression);
_projectionMapping[_projectionMembers.Peek()] = translation;

_projectionMapping[_projectionMembers.Peek()] = translation;
return new ProjectionBindingExpression(_queryExpression, _projectionMembers.Peek(), expression.Type);
}

return new ProjectionBindingExpression(_queryExpression, _projectionMembers.Peek(), expression.Type);
}

return base.Visit(expression);
Expand All @@ -93,28 +150,60 @@ protected override Expression VisitExtension(Expression extensionExpression)
var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression;
VerifyQueryExpression(projectionBindingExpression);

_projectionMapping[_projectionMembers.Peek()]
= _queryExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember);
if (_clientEval)
{
var entityProjection = (EntityProjectionExpression)_queryExpression.GetMappedProjection(
projectionBindingExpression.ProjectionMember);

return entityShaperExpression.Update(
new ProjectionBindingExpression(_queryExpression, _projectionMembers.Peek(), typeof(ValueBuffer)),
entityShaperExpression.NestedEntities);
return entityShaperExpression.Update(
new ProjectionBindingExpression(_queryExpression, _queryExpression.AddToProjection(entityProjection)),
entityShaperExpression.NestedEntities);
}
else
{
_projectionMapping[_projectionMembers.Peek()]
= _queryExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember);

return entityShaperExpression.Update(
new ProjectionBindingExpression(_queryExpression, _projectionMembers.Peek(), typeof(ValueBuffer)),
entityShaperExpression.NestedEntities);
}
}

throw new InvalidOperationException();
}

protected override Expression VisitNew(NewExpression newExpression)
{
var newArguments = new Expression[newExpression.Arguments.Count];
for (var i = 0; i < newExpression.Arguments.Count; i++)
if (newExpression.Arguments.Count == 0)
{
return newExpression;
}

if (!_clientEval
&& newExpression.Members == null)
{
// TODO: Members can be null????
var projectionMember = _projectionMembers.Peek().AddMember(newExpression.Members[i]);
_projectionMembers.Push(projectionMember);
return null;
}

newArguments[i] = Visit(newExpression.Arguments[i]);
_projectionMembers.Pop();
var newArguments = new Expression[newExpression.Arguments.Count];
for (var i = 0; i < newArguments.Length; i++)
{
if (_clientEval)
{
newArguments[i] = Visit(newExpression.Arguments[i]);
}
else
{
var projectionMember = _projectionMembers.Peek().AddMember(newExpression.Members[i]);
_projectionMembers.Push(projectionMember);
newArguments[i] = Visit(newExpression.Arguments[i]);
if (newArguments[i] == null)
{
return null;
}
_projectionMembers.Pop();
}
}

return newExpression.Update(newArguments);
Expand All @@ -123,17 +212,33 @@ protected override Expression VisitNew(NewExpression newExpression)
protected override Expression VisitMemberInit(MemberInitExpression memberInitExpression)
{
var newExpression = (NewExpression)Visit(memberInitExpression.NewExpression);
if (newExpression == null)
{
return null;
}

var newBindings = new MemberAssignment[memberInitExpression.Bindings.Count];
for (var i = 0; i < newBindings.Length; i++)
{
// TODO: Members can be null????
var memberAssignment = (MemberAssignment)memberInitExpression.Bindings[i];
if (_clientEval)
{
newBindings[i] = memberAssignment.Update(Visit(memberAssignment.Expression));
}
else
{
var projectionMember = _projectionMembers.Peek().AddMember(memberAssignment.Member);
_projectionMembers.Push(projectionMember);

var projectionMember = _projectionMembers.Peek().AddMember(memberAssignment.Member);
_projectionMembers.Push(projectionMember);
var visitedExpression = Visit(memberAssignment.Expression);
if (visitedExpression == null)
{
return null;
}

newBindings[i] = memberAssignment.Update(Visit(memberAssignment.Expression));
_projectionMembers.Pop();
newBindings[i] = memberAssignment.Update(visitedExpression);
_projectionMembers.Pop();
}
}

return memberInitExpression.Update(newExpression, newBindings);
Expand Down
Loading

0 comments on commit aec9bcf

Please sign in to comment.