Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cosmos: Add embedded collection support #16631

Merged
merged 1 commit into from
Jul 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should remove commented code

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we'll need it for non-cross-partition joins

// _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