Skip to content

Commit

Permalink
Cosmos Query: Fix nested owned entities in queries
Browse files Browse the repository at this point in the history
  • Loading branch information
AndriySvyryd committed Jun 26, 2019
1 parent d3a9210 commit b0f3ac7
Show file tree
Hide file tree
Showing 22 changed files with 411 additions and 210 deletions.
Expand Up @@ -6,6 +6,7 @@
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Query.Pipeline;
using Microsoft.EntityFrameworkCore.Storage;

Expand Down Expand Up @@ -162,7 +163,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
// return _clientEval ? base.VisitExtension(includeExpression) : null;
//}

throw new InvalidOperationException();
throw new InvalidOperationException(new ExpressionPrinter().Print(extensionExpression));
}

protected override Expression VisitNew(NewExpression newExpression)
Expand All @@ -187,7 +188,7 @@ protected override Expression VisitNew(NewExpression newExpression)
}
else
{
var projectionMember = _projectionMembers.Peek().AddMember(newExpression.Members[i]);
var projectionMember = _projectionMembers.Peek().AddMember(newExpression.Members[i].Name);
_projectionMembers.Push(projectionMember);
newArguments[i] = Visit(newExpression.Arguments[i]);
if (newArguments[i] == null)
Expand Down Expand Up @@ -219,7 +220,7 @@ protected override Expression VisitMemberInit(MemberInitExpression memberInitExp
}
else
{
var projectionMember = _projectionMembers.Peek().AddMember(memberAssignment.Member);
var projectionMember = _projectionMembers.Peek().AddMember(memberAssignment.Member.Name);
_projectionMembers.Push(projectionMember);

var visitedExpression = Visit(memberAssignment.Expression);
Expand Down
Expand Up @@ -4,12 +4,14 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
Expand Down Expand Up @@ -47,13 +49,12 @@ public class CosmosShapedQueryCompilingExpressionVisitor : ShapedQueryCompilingE

protected override Expression VisitShapedQueryExpression(ShapedQueryExpression shapedQueryExpression)
{
var shaperBody = InjectEntityMaterializer(shapedQueryExpression.ShaperExpression);
var selectExpression = (SelectExpression)shapedQueryExpression.QueryExpression;
selectExpression.ApplyProjection();
var jObjectParameter = Expression.Parameter(typeof(JObject), "jObject");

shaperBody = new CosmosProjectionBindingRemovingExpressionVisitor(selectExpression, jObjectParameter)
.Visit(shaperBody);
var jObjectParameter = Expression.Parameter(typeof(JObject), "jObject");
var shaperBody = new CosmosProjectionBindingRemovingExpressionVisitor(selectExpression, jObjectParameter, this)
.Visit(shapedQueryExpression.ShaperExpression);

var shaperLambda = Expression.Lambda(
shaperBody,
Expand All @@ -75,8 +76,6 @@ protected override Expression VisitShapedQueryExpression(ShapedQueryExpression s

private class CosmosProjectionBindingRemovingExpressionVisitor : ExpressionVisitor
{
private SelectExpression _selectExpression;
private readonly ParameterExpression _jObjectParameter;
private static readonly MethodInfo _getItemMethodInfo
= typeof(JObject).GetTypeInfo().GetRuntimeProperties()
.Single(pi => pi.Name == "Item" && pi.GetIndexParameters()[0].ParameterType == typeof(string))
Expand All @@ -88,14 +87,24 @@ private class CosmosProjectionBindingRemovingExpressionVisitor : ExpressionVisit
= typeof(CosmosProjectionBindingRemovingExpressionVisitor).GetTypeInfo().GetRuntimeMethods()
.Single(mi => mi.Name == nameof(IsNull));

private readonly SelectExpression _selectExpression;
private ParameterExpression _jObjectParameter;
private readonly CosmosShapedQueryCompilingExpressionVisitor _shapedQueryCompilingExpressionVisitor;

private readonly IDictionary<ProjectionBindingExpression, Expression> _valueBufferBindings
= new Dictionary<ProjectionBindingExpression, Expression>();
private readonly IDictionary<ParameterExpression, Expression> _materializationContextBindings
= new Dictionary<ParameterExpression, Expression>();
private int _currentEntityIndex;

public CosmosProjectionBindingRemovingExpressionVisitor(
SelectExpression selectExpression, ParameterExpression jObjectParameter)
SelectExpression selectExpression,
ParameterExpression jObjectParameter,
CosmosShapedQueryCompilingExpressionVisitor shapedQueryCompilingExpressionVisitor)
{
_selectExpression = selectExpression;
_jObjectParameter = jObjectParameter;
_shapedQueryCompilingExpressionVisitor = shapedQueryCompilingExpressionVisitor;
}

protected override Expression VisitBinary(BinaryExpression binaryExpression)
Expand All @@ -105,13 +114,8 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression)
&& parameterExpression.Type == typeof(MaterializationContext))
{
var newExpression = (NewExpression)binaryExpression.Right;
var projectionBindingExpression = (ProjectionBindingExpression)newExpression.Arguments[0];
var projectionIndex = (int)GetProjectionIndex(projectionBindingExpression);
var projection = _selectExpression.Projection[projectionIndex];

_materializationContextBindings[parameterExpression] = Expression.Convert(
CreateReadJTokenExpression(_jObjectParameter, projection.Alias),
typeof(JObject));
_materializationContextBindings[parameterExpression] = _jObjectParameter;

var updatedExpression = Expression.New(newExpression.Constructor,
Expression.Constant(ValueBuffer.Empty),
Expand All @@ -128,7 +132,6 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression)
return memberExpression.Assign(Visit(binaryExpression.Right));
}


return base.VisitBinary(binaryExpression);
}

Expand Down Expand Up @@ -181,9 +184,92 @@ protected override Expression VisitExtension(Expression extensionExpression)
projectionBindingExpression.Type);
}

if (extensionExpression is EntityShaperExpression shaperExpression)
{
var jObjectVariable = Expression.Variable(typeof(JObject),
"jObject" + _currentEntityIndex++);
var variables = new List<ParameterExpression> { jObjectVariable };

var expressions = new List<Expression>();

if (shaperExpression.ParentNavigation == null)
{
var projectionIndex = (int)GetProjectionIndex(shaperExpression.ValueBufferExpression);
var projection = _selectExpression.Projection[projectionIndex];

expressions.Add(
Expression.Assign(
jObjectVariable,
Expression.TypeAs(
CreateReadJTokenExpression(_jObjectParameter, projection.Alias),
typeof(JObject))));

shaperExpression = shaperExpression.Update(
GetNestedShapers(shaperExpression.EntityType, shaperExpression.ValueBufferExpression));
}
else
{
expressions.Add(
Expression.Assign(
jObjectVariable,
Expression.TypeAs(
CreateReadJTokenExpression(_jObjectParameter, shaperExpression.ValueBufferExpression.ProjectionMember.LastMember),
typeof(JObject))));
}

var parentJObject = _jObjectParameter;
_jObjectParameter = jObjectVariable;
expressions.Add(Expression.Condition(
Expression.Equal(jObjectVariable, Expression.Constant(null, jObjectVariable.Type)),
Expression.Constant(null, shaperExpression.Type),
Visit(_shapedQueryCompilingExpressionVisitor.InjectEntityMaterializer(shaperExpression))));
_jObjectParameter = parentJObject;

return Expression.Block(
shaperExpression.Type,
variables,
expressions);
}

if (extensionExpression is CollectionShaperExpression collectionShaperExpression)
{
throw new NotImplementedException();
}

return base.VisitExtension(extensionExpression);
}

private static List<EntityShaperExpression> GetNestedShapers(
IEntityType entityType, ProjectionBindingExpression valueBufferExpression)
{
var nestedEntities = new List<EntityShaperExpression>();
foreach (var ownedNavigation in entityType.GetNavigations().Concat(entityType.GetDerivedNavigations()))
{
var fk = ownedNavigation.ForeignKey;
if (!fk.IsOwnership
|| ownedNavigation.IsDependentToPrincipal()
|| fk.DeclaringEntityType.IsDocumentRoot())
{
continue;
}

var targetType = ownedNavigation.GetTargetType();
var projectionMember = valueBufferExpression.ProjectionMember ?? new ProjectionMember();

var nestedValueBufferExpression = new ProjectionBindingExpression(
valueBufferExpression.QueryExpression,
projectionMember.AddMember(targetType.GetCosmosContainingPropertyName()),
valueBufferExpression.Type);

var nestedShaper = new EntityShaperExpression(
targetType, nestedValueBufferExpression, nullable: true, ownedNavigation,
GetNestedShapers(targetType, nestedValueBufferExpression));
nestedEntities.Add(nestedShaper);
}

return nestedEntities.Count == 0 ? null : nestedEntities;
}

private object GetProjectionIndex(ProjectionBindingExpression projectionBindingExpression)
{
return projectionBindingExpression.ProjectionMember != null
Expand Down Expand Up @@ -216,7 +302,7 @@ private static Expression CreateReadJTokenExpression(Expression jObjectExpressio
return CreateGetStoreValueExpression(jObjectExpression, storeName, property.GetTypeMapping(), property.ClrType);
}

public static Expression CreateGetStoreValueExpression(
private static Expression CreateGetStoreValueExpression(
Expression jObjectExpression,
string storeName,
CoreTypeMapping typeMapping,
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Cosmos/Query/Pipeline/SelectExpression.cs
Expand Up @@ -60,7 +60,7 @@ public void ApplyProjection()
{
result[keyValuePair.Key] = Constant(AddToProjection(
keyValuePair.Value,
keyValuePair.Key.LastMember?.Name));
keyValuePair.Key.LastMember));
}

_projectionMapping = result;
Expand Down
Expand Up @@ -109,7 +109,7 @@ protected override Expression VisitNew(NewExpression newExpression)
for (var i = 0; i < newExpression.Arguments.Count; i++)
{
// TODO: Members can be null????
var projectionMember = _projectionMembers.Peek().AddMember(newExpression.Members[i]);
var projectionMember = _projectionMembers.Peek().AddMember(newExpression.Members[i].Name);
_projectionMembers.Push(projectionMember);

newArguments[i] = Visit(newExpression.Arguments[i]);
Expand All @@ -128,7 +128,7 @@ protected override Expression VisitMemberInit(MemberInitExpression memberInitExp
// TODO: Members can be null????
var memberAssignment = (MemberAssignment)memberInitExpression.Bindings[i];

var projectionMember = _projectionMembers.Peek().AddMember(memberAssignment.Member);
var projectionMember = _projectionMembers.Peek().AddMember(memberAssignment.Member.Name);
_projectionMembers.Push(projectionMember);

newBindings[i] = memberAssignment.Update(Visit(memberAssignment.Expression));
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore.InMemory/Query/Pipeline/InMemoryQueryExpression.cs
Expand Up @@ -254,7 +254,7 @@ public void ApplyServerProjection()
var outerMemberInfo = transparentIdentifierType.GetTypeInfo().GetDeclaredField("Outer");
foreach (var projection in _projectionMapping)
{
projectionMapping[projection.Key.ShiftMember(outerMemberInfo)] = projection.Value;
projectionMapping[projection.Key.ShiftMember(outerMemberInfo.Name)] = projection.Value;
}

replacingVisitor = new ReplacingExpressionVisitor(
Expand All @@ -280,7 +280,7 @@ public void ApplyServerProjection()
{
updatedProjection = new EntityProjectionExpression(entityValues.EntityType, entityValues.StartIndex + offset);
}
projectionMapping[projection.Key.ShiftMember(innerMemberInfo)] = updatedProjection;
projectionMapping[projection.Key.ShiftMember(innerMemberInfo.Name)] = updatedProjection;
}

var resultSelector = Lambda(
Expand Down
Expand Up @@ -12,7 +12,6 @@
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.InMemory.Query.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Query.Pipeline;
Expand Down
Expand Up @@ -194,7 +194,7 @@ protected override Expression VisitNew(NewExpression newExpression)
}
else
{
var projectionMember = _projectionMembers.Peek().AddMember(newExpression.Members[i]);
var projectionMember = _projectionMembers.Peek().AddMember(newExpression.Members[i].Name);
_projectionMembers.Push(projectionMember);
newArguments[i] = Visit(newExpression.Arguments[i]);
if (newArguments[i] == null)
Expand Down Expand Up @@ -226,7 +226,7 @@ protected override Expression VisitMemberInit(MemberInitExpression memberInitExp
}
else
{
var projectionMember = _projectionMembers.Peek().AddMember(memberAssignment.Member);
var projectionMember = _projectionMembers.Peek().AddMember(memberAssignment.Member.Name);
_projectionMembers.Push(projectionMember);

var visitedExpression = Visit(memberAssignment.Expression);
Expand Down
Expand Up @@ -478,8 +478,7 @@ protected override ShapedQueryExpression TranslateOfType(ShapedQueryExpression s
var baseType = entityType.GetAllBaseTypes().SingleOrDefault(et => et.ClrType == resultType);
if (baseType != null)
{
source.ShaperExpression = new EntityShaperExpression(
baseType, entityShaperExpression.ValueBufferExpression, entityShaperExpression.Nullable);
source.ShaperExpression = entityShaperExpression.Update(baseType);

return source;
}
Expand Down Expand Up @@ -514,8 +513,7 @@ protected override ShapedQueryExpression TranslateOfType(ShapedQueryExpression s
{ projectionMember, entityProjection.UpdateEntityType(derivedType)}
});

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

return source;
}
Expand Down

0 comments on commit b0f3ac7

Please sign in to comment.