Skip to content

Commit

Permalink
InMemory: Support for Weak/Owned entityTypes
Browse files Browse the repository at this point in the history
Part of #16963
  • Loading branch information
smitpatel committed Sep 3, 2019
1 parent f0c0991 commit 45d61b0
Show file tree
Hide file tree
Showing 11 changed files with 517 additions and 126 deletions.
30 changes: 30 additions & 0 deletions src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal
public class EntityProjectionExpression : Expression, IPrintableExpression
{
private readonly IDictionary<IProperty, Expression> _readExpressionMap;
private readonly IDictionary<INavigation, EntityShaperExpression> _navigationExpressionsCache
= new Dictionary<INavigation, EntityShaperExpression>();

public EntityProjectionExpression(
IEntityType entityType, IDictionary<IProperty, Expression> readExpressionMap)
Expand Down Expand Up @@ -52,6 +54,34 @@ public virtual Expression BindProperty(IProperty property)
return _readExpressionMap[property];
}

public virtual void AddNavigationBinding(INavigation navigation, EntityShaperExpression entityShaper)
{
if (!EntityType.IsAssignableFrom(navigation.DeclaringEntityType)
&& !navigation.DeclaringEntityType.IsAssignableFrom(EntityType))
{
throw new InvalidOperationException(
$"Called EntityProjectionExpression.AddNavigationBinding() with incorrect INavigation. " +
$"EntityType:{EntityType.DisplayName()}, Property:{navigation.Name}");
}

_navigationExpressionsCache[navigation] = entityShaper;
}

public virtual EntityShaperExpression BindNavigation(INavigation navigation)
{
if (!EntityType.IsAssignableFrom(navigation.DeclaringEntityType)
&& !navigation.DeclaringEntityType.IsAssignableFrom(EntityType))
{
throw new InvalidOperationException(
$"Called EntityProjectionExpression.BindNavigation() with incorrect INavigation. " +
$"EntityType:{EntityType.DisplayName()}, Property:{navigation.Name}");
}

return _navigationExpressionsCache.TryGetValue(navigation, out var expression)
? expression
: null;
}

public virtual void Print(ExpressionPrinter expressionPrinter)
{
expressionPrinter.AppendLine(nameof(EntityProjectionExpression) + ":");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal
{
public class InMemoryProjectionBindingExpressionVisitor : ExpressionVisitor
{
private readonly QueryableMethodTranslatingExpressionVisitor _queryableMethodTranslatingExpressionVisitor;
private readonly InMemoryQueryableMethodTranslatingExpressionVisitor _queryableMethodTranslatingExpressionVisitor;
private readonly InMemoryExpressionTranslatingExpressionVisitor _expressionTranslatingExpressionVisitor;

private InMemoryQueryExpression _queryExpression;
Expand All @@ -25,7 +25,7 @@ private readonly IDictionary<ProjectionMember, Expression> _projectionMapping
private readonly Stack<ProjectionMember> _projectionMembers = new Stack<ProjectionMember>();

public InMemoryProjectionBindingExpressionVisitor(
QueryableMethodTranslatingExpressionVisitor queryableMethodTranslatingExpressionVisitor,
InMemoryQueryableMethodTranslatingExpressionVisitor queryableMethodTranslatingExpressionVisitor,
InMemoryExpressionTranslatingExpressionVisitor expressionTranslatingExpressionVisitor)
{
_queryableMethodTranslatingExpressionVisitor = queryableMethodTranslatingExpressionVisitor;
Expand All @@ -39,13 +39,15 @@ public virtual Expression Translate(InMemoryQueryExpression queryExpression, Exp

_projectionMembers.Push(new ProjectionMember());

var result = Visit(expression);
var expandedExpression = _queryableMethodTranslatingExpressionVisitor.ExpandWeakEntities(_queryExpression, expression);
var result = Visit(expandedExpression);

if (result == null)
{
_clientEval = true;

result = Visit(expression);
expandedExpression = _queryableMethodTranslatingExpressionVisitor.ExpandWeakEntities(_queryExpression, expression);
result = Visit(expandedExpression);

_projectionMapping.Clear();
}
Expand Down Expand Up @@ -179,21 +181,26 @@ protected override Expression VisitExtension(Expression extensionExpression)
{
if (extensionExpression is EntityShaperExpression entityShaperExpression)
{
var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression;
VerifyQueryExpression(projectionBindingExpression);

if (_clientEval)
EntityProjectionExpression entityProjectionExpression;
if (entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression)
{
var entityProjection = (EntityProjectionExpression)_queryExpression.GetMappedProjection(
VerifyQueryExpression(projectionBindingExpression);
entityProjectionExpression = (EntityProjectionExpression)_queryExpression.GetMappedProjection(
projectionBindingExpression.ProjectionMember);
}
else
{
entityProjectionExpression = (EntityProjectionExpression)entityShaperExpression.ValueBufferExpression;
}

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

return entityShaperExpression.Update(
new ProjectionBindingExpression(_queryExpression, _projectionMembers.Peek(), typeof(ValueBuffer)));
Expand Down
146 changes: 146 additions & 0 deletions src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,152 @@ public virtual void AddSelectMany(InMemoryQueryExpression innerQueryExpression,
_projectionMapping = projectionMapping;
}

public virtual EntityShaperExpression AddNavigationToWeakEntityType(
EntityProjectionExpression entityProjectionExpression,
INavigation navigation,
InMemoryQueryExpression innerQueryExpression,
LambdaExpression outerKeySelector,
LambdaExpression innerKeySelector)
{
// GroupJoin phase
var groupTransparentIdentifierType = TransparentIdentifierFactory.Create(
typeof(ValueBuffer), typeof(IEnumerable<ValueBuffer>));
var outerParameter = Parameter(typeof(ValueBuffer), "outer");
var innerParameter = Parameter(typeof(IEnumerable<ValueBuffer>), "inner");
var outerMemberInfo = groupTransparentIdentifierType.GetTypeInfo().GetDeclaredField("Outer");
var innerMemberInfo = groupTransparentIdentifierType.GetTypeInfo().GetDeclaredField("Inner");
var resultSelector = Lambda(
New(
groupTransparentIdentifierType.GetTypeInfo().DeclaredConstructors.Single(),
new[] { outerParameter, innerParameter },
new[] { outerMemberInfo, innerMemberInfo }),
outerParameter,
innerParameter);

var groupJoinExpression = Call(
InMemoryLinqOperatorProvider.GroupJoin.MakeGenericMethod(
typeof(ValueBuffer), typeof(ValueBuffer), outerKeySelector.ReturnType, groupTransparentIdentifierType),
ServerQueryExpression,
innerQueryExpression.ServerQueryExpression,
outerKeySelector,
innerKeySelector,
resultSelector);

// SelectMany phase
var collectionParameter = Parameter(groupTransparentIdentifierType, "collection");
var collection = MakeMemberAccess(collectionParameter, innerMemberInfo);
outerParameter = Parameter(groupTransparentIdentifierType, "outer");
innerParameter = Parameter(typeof(ValueBuffer), "inner");

var resultValueBufferExpressions = new List<Expression>();
var projectionMapping = new Dictionary<ProjectionMember, Expression>();
var replacingVisitor = new ReplacingExpressionVisitor(
new Dictionary<Expression, Expression>
{
{ CurrentParameter, MakeMemberAccess(outerParameter, outerMemberInfo) },
{ innerQueryExpression.CurrentParameter, innerParameter }
});
var index = 0;

EntityProjectionExpression copyEntityProjectionToOuter(EntityProjectionExpression entityProjection)
{
var readExpressionMap = new Dictionary<IProperty, Expression>();
foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType))
{
var replacedExpression = replacingVisitor.Visit(entityProjection.BindProperty(property));
resultValueBufferExpressions.Add(replacedExpression);
readExpressionMap[property] = CreateReadValueExpression(replacedExpression.Type, index++, property);
}

var newEntityProjection = new EntityProjectionExpression(entityProjection.EntityType, readExpressionMap);
if (ReferenceEquals(entityProjectionExpression, entityProjection))
{
entityProjectionExpression = newEntityProjection;
}

// Also lift nested entity projections
foreach (var navigation in entityProjection.EntityType.GetTypesInHierarchy()
.SelectMany(EntityTypeExtensions.GetDeclaredNavigations))
{
var boundEntityShaperExpression = entityProjection.BindNavigation(navigation);
if (boundEntityShaperExpression != null)
{
var innerEntityProjection = (EntityProjectionExpression)boundEntityShaperExpression.ValueBufferExpression;
var newInnerEntityProjection = copyEntityProjectionToOuter(innerEntityProjection);
boundEntityShaperExpression = boundEntityShaperExpression.Update(newInnerEntityProjection);
newEntityProjection.AddNavigationBinding(navigation, boundEntityShaperExpression);
}
}

return newEntityProjection;
}

foreach (var projection in _projectionMapping)
{
if (projection.Value is EntityProjectionExpression entityProjection)
{
projectionMapping[projection.Key] = copyEntityProjectionToOuter(entityProjection);
}
else
{
var replacedExpression = replacingVisitor.Visit(projection.Value);
resultValueBufferExpressions.Add(replacedExpression);
projectionMapping[projection.Key]
= CreateReadValueExpression(replacedExpression.Type, index++, InferPropertyFromInner(projection.Value));
}
}

_projectionMapping = projectionMapping;

var outerIndex = index;
var nullableReadValueExpressionVisitor = new NullableReadValueExpressionVisitor();
var innerEntityProjection = (EntityProjectionExpression)innerQueryExpression.GetMappedProjection(new ProjectionMember());

var innerReadExpressionMap = new Dictionary<IProperty, Expression>();
foreach (var property in GetAllPropertiesInHierarchy(innerEntityProjection.EntityType))
{
var replacedExpression = replacingVisitor.Visit(innerEntityProjection.BindProperty(property));
replacedExpression = nullableReadValueExpressionVisitor.Visit(replacedExpression);
resultValueBufferExpressions.Add(replacedExpression);
innerReadExpressionMap[property] = CreateReadValueExpression(replacedExpression.Type, index++, property);
}
innerEntityProjection = new EntityProjectionExpression(innerEntityProjection.EntityType, innerReadExpressionMap);

var collectionSelector = Lambda(
Call(
InMemoryLinqOperatorProvider.DefaultIfEmptyWithArgument.MakeGenericMethod(typeof(ValueBuffer)),
collection,
New(
_valueBufferConstructor,
NewArrayInit(
typeof(object),
Enumerable.Range(0, index - outerIndex).Select(i => Constant(null))))),
collectionParameter);

resultSelector = Lambda(
New(
_valueBufferConstructor,
NewArrayInit(
typeof(object),
resultValueBufferExpressions
.Select(e => e.Type.IsValueType ? Convert(e, typeof(object)) : e)
.ToArray())),
outerParameter,
innerParameter);

ServerQueryExpression = Call(
InMemoryLinqOperatorProvider.SelectManyWithCollectionSelector.MakeGenericMethod(
groupTransparentIdentifierType, typeof(ValueBuffer), typeof(ValueBuffer)),
groupJoinExpression,
collectionSelector,
resultSelector);

var entityShaper = new EntityShaperExpression(innerEntityProjection.EntityType, innerEntityProjection, nullable: true);
entityProjectionExpression.AddNavigationBinding(navigation, entityShaper);

return entityShaper;
}

public virtual void Print(ExpressionPrinter expressionPrinter)
{
expressionPrinter.AppendLine(nameof(InMemoryQueryExpression) + ": ");
Expand Down
Loading

0 comments on commit 45d61b0

Please sign in to comment.