Skip to content

Commit

Permalink
Cosmos: Add embedded collection support
Browse files Browse the repository at this point in the history
Flatten out shaper expression visitors
Don't expand owned collection

Fixes #16620
Part of #12086
  • Loading branch information
AndriySvyryd committed Jul 18, 2019
1 parent 1c675ca commit 04b90b5
Show file tree
Hide file tree
Showing 57 changed files with 1,212 additions and 759 deletions.
1 change: 1 addition & 0 deletions EFCore.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ Licensed under the Apache License, Version 2.0. See License.txt in the project r
<s:Boolean x:Key="/Default/UserDictionary/Words/=requiredness/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=shaper/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=sqlite/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=subquery/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unignore/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=fixup/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=attacher/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
26 changes: 26 additions & 0 deletions src/EFCore.Cosmos/Metadata/Internal/CosmosNavigationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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;

namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// 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.
/// </summary>
public static class CosmosNavigationExtensions
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// 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.
/// </summary>
public static bool IsEmbedded(this INavigation navigation)
=> !navigation.IsDependentToPrincipal()
&& !navigation.ForeignKey.DeclaringEntityType.IsDocumentRoot();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Query.NavigationExpansion;
Expand Down Expand Up @@ -59,68 +61,63 @@ public override Expression Visit(Expression expression)
return null;
}

if (!(expression is NewExpression
|| expression is MemberInitExpression
|| expression is EntityShaperExpression))
if (expression is NewExpression
|| expression is MemberInitExpression
|| expression is EntityShaperExpression)
{
// This skips the group parameter from GroupJoin
if (expression is ParameterExpression parameter
&& parameter.Type.IsGenericType
&& parameter.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
return parameter;
}
return base.Visit(expression);
}

if (_clientEval)
// This skips the group parameter from GroupJoin
if (expression is ParameterExpression parameter
&& parameter.Type.IsGenericType
&& parameter.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
return parameter;
}

if (_clientEval)
{
switch (expression)
{
if (expression is ConstantExpression)
{
case ConstantExpression _:
return expression;
}

if (expression is ParameterExpression parameterExpression)
{
case ParameterExpression parameterExpression:
return Expression.Call(
_getParameterValueMethodInfo.MakeGenericMethod(parameterExpression.Type),
QueryCompilationContext.QueryContextParameter,
Expression.Constant(parameterExpression.Name));
}

//if (expression is MethodCallExpression methodCallExpression
// && methodCallExpression.Method.Name == "MaterializeCollectionNavigation")
//{
// var result = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(methodCallExpression.Arguments[0]);
// var navigation = (INavigation)((ConstantExpression)methodCallExpression.Arguments[1]).Value;

// return _selectExpression.AddCollectionProjection(result, navigation);
//}

var translation = _sqlTranslator.Translate(expression);
if (translation == null)
{
case MaterializeCollectionNavigationExpression materializeCollectionNavigationExpression:
return base.Visit(expression);
}
else
{
return new ProjectionBindingExpression(
_selectExpression, _selectExpression.AddToProjection(translation), expression.Type);
}
//return _selectExpression.AddCollectionProjection(
// _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(
// materializeCollectionNavigationExpression.Subquery),
// materializeCollectionNavigationExpression.Navigation, null);
}
else
{
var translation = _sqlTranslator.Translate(expression);
if (translation == null)
{
return null;
}

_projectionMapping[_projectionMembers.Peek()] = translation;

return new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), expression.Type);
var translation = _sqlTranslator.Translate(expression);
if (translation == null)
{
return base.Visit(expression);
}

return new ProjectionBindingExpression(
_selectExpression, _selectExpression.AddToProjection(translation), expression.Type);
}
else
{
var translation = _sqlTranslator.Translate(expression);
if (translation == null)
{
return null;
}

return base.Visit(expression);
_projectionMapping[_projectionMembers.Peek()] = translation;

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

private static readonly MethodInfo _getParameterValueMethodInfo
Expand All @@ -132,39 +129,123 @@ private static T GetParameterValue<T>(QueryContext queryContext, string paramete
#pragma warning restore IDE0052 // Remove unread private members
=> (T)queryContext.ParameterValues[parameterName];

protected override Expression VisitExtension(Expression extensionExpression)
protected override Expression VisitMember(MemberExpression memberExpression)
{
if (extensionExpression is EntityShaperExpression entityShaperExpression)
if (!_clientEval)
{
var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression;
VerifySelectExpression(projectionBindingExpression);
return null;
}

if (_clientEval)
{
var entityProjection = (EntityProjectionExpression)_selectExpression.GetMappedProjection(
projectionBindingExpression.ProjectionMember);
var innerExpression = Visit(memberExpression.Expression);

return entityShaperExpression.Update(
new ProjectionBindingExpression(
_selectExpression, _selectExpression.AddToProjection(entityProjection), typeof(ValueBuffer)));
EntityShaperExpression shaperExpression;
switch (innerExpression)
{
case EntityShaperExpression shaper:
shaperExpression = shaper;
break;

case UnaryExpression unaryExpression:
shaperExpression = unaryExpression.Operand as EntityShaperExpression;
if (shaperExpression == null)
{
return memberExpression.Update(innerExpression);
}
break;

default:
return memberExpression.Update(innerExpression);
}

EntityProjectionExpression innerEntityProjection;
switch (shaperExpression.ValueBufferExpression)
{
case ProjectionBindingExpression innerProjectionBindingExpression:
innerEntityProjection = (EntityProjectionExpression)_selectExpression.Projection[
innerProjectionBindingExpression.Index.Value].Expression;
break;

case UnaryExpression unaryExpression:
innerEntityProjection = (EntityProjectionExpression)((UnaryExpression)unaryExpression.Operand).Operand;
break;

default:
throw new InvalidOperationException();
}

var navigationProjection = innerEntityProjection.BindMember(memberExpression.Member, innerExpression.Type, out var propertyBase);

if (!(propertyBase is INavigation navigation)
|| !navigation.IsEmbedded())
{
return memberExpression.Update(innerExpression);
}

switch (navigationProjection)
{
case EntityProjectionExpression entityProjection:
return new EntityShaperExpression(
navigation.GetTargetType(),
Expression.Convert(Expression.Convert(entityProjection, typeof(object)), typeof(ValueBuffer)),
nullable: true);

case ObjectArrayProjectionExpression objectArrayProjectionExpression:
{
var innerShaperExpression = new EntityShaperExpression(
navigation.GetTargetType(),
Expression.Convert(
Expression.Convert(objectArrayProjectionExpression.InnerProjection, typeof(object)), typeof(ValueBuffer)),
nullable: true);

return new CollectionShaperExpression(
objectArrayProjectionExpression,
innerShaperExpression,
navigation,
innerShaperExpression.EntityType.ClrType);
}
else

default:
throw new InvalidOperationException();
}
}

protected override Expression VisitExtension(Expression extensionExpression)
{
switch (extensionExpression)
{
case EntityShaperExpression entityShaperExpression:
{
_projectionMapping[_projectionMembers.Peek()]
= _selectExpression.GetMappedProjection(
var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression;
VerifySelectExpression(projectionBindingExpression);

if (_clientEval)
{
var entityProjection = (EntityProjectionExpression)_selectExpression.GetMappedProjection(
projectionBindingExpression.ProjectionMember);

return entityShaperExpression.Update(
new ProjectionBindingExpression(
_selectExpression, _selectExpression.AddToProjection(entityProjection), typeof(ValueBuffer)));
}

_projectionMapping[_projectionMembers.Peek()]
= _selectExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember);

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

if (extensionExpression is IncludeExpression includeExpression)
{
return _clientEval ? base.VisitExtension(includeExpression) : includeExpression;
}
case MaterializeCollectionNavigationExpression materializeCollectionNavigationExpression:
return materializeCollectionNavigationExpression.Navigation.IsEmbedded()
? base.Visit(materializeCollectionNavigationExpression.Subquery)
: base.VisitExtension(materializeCollectionNavigationExpression);

throw new InvalidOperationException(new ExpressionPrinter().Print(extensionExpression));
case IncludeExpression includeExpression:
return _clientEval ? base.VisitExtension(includeExpression) : null;

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

protected override Expression VisitNew(NewExpression newExpression)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Query.Pipeline;
using Microsoft.EntityFrameworkCore.Storage;

Expand Down Expand Up @@ -461,7 +462,7 @@ protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression so
return source;
}

throw new InvalidOperationException();
throw new InvalidOperationException("Unable to translate Where expression: " + new ExpressionPrinter().Print(predicate));
}

private SqlExpression TranslateExpression(Expression expression)
Expand Down
Loading

0 comments on commit 04b90b5

Please sign in to comment.