Skip to content

Commit

Permalink
Refactor RelationalProjectionExpressionVisitor
Browse files Browse the repository at this point in the history
More accurate marking of RequiresClientProjection (less false positives), which results in less duplicate visitation of child query models in many cases
  • Loading branch information
tuespetre committed Mar 8, 2017
1 parent 7341248 commit 4ec2d54
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 162 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,66 +50,98 @@ public RelationalProjectionExpressionVisitor(
/// <summary>
/// Visit a method call expression.
/// </summary>
/// <param name="node"> The expression to visit. </param>
/// <param name="methodCallExpression"> The expression to visit. </param>
/// <returns>
/// An Expression corresponding to the translated method call.
/// </returns>
protected override Expression VisitMethodCall(MethodCallExpression node)
protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
{
Check.NotNull(node, nameof(node));
Check.NotNull(methodCallExpression, nameof(methodCallExpression));

var sqlExpression = TranslateExpression(methodCallExpression);

var objectExpression
= EntityQueryModelVisitor.IsPropertyMethod(methodCallExpression.Method)
? methodCallExpression.Arguments[0].RemoveConvert()
: methodCallExpression.Object;

var handledExpression
= TryHandleMemberOrMethodCallExpression(
methodCallExpression,
objectExpression,
sqlExpression);

if (EntityQueryModelVisitor.IsPropertyMethod(node.Method))
if (handledExpression != null)
{
var newArg0 = Visit(node.Arguments[0]);
return handledExpression;
}

if (newArg0 != node.Arguments[0])
{
return Expression.Call(
node.Method,
newArg0,
node.Arguments[1]);
}
QueryModelVisitor.RequiresClientProjection = true;
return methodCallExpression;
}

return node;
/// <summary>
/// Visit a member expression.
/// </summary>
/// <param name="memberExpression"> The expression to visit. </param>
/// <returns>
/// An Expression corresponding to the translated member.
/// </returns>
protected override Expression VisitMember(MemberExpression memberExpression)
{
Check.NotNull(memberExpression, nameof(memberExpression));

var sqlExpression = TranslateExpression(memberExpression);

var handledExpression
= TryHandleMemberOrMethodCallExpression(
memberExpression,
memberExpression.Expression.RemoveConvert(),
sqlExpression);

if (handledExpression != null)
{
return handledExpression;
}

return base.VisitMethodCall(node);
QueryModelVisitor.RequiresClientProjection = true;
return memberExpression;
}

/// <summary>
/// Visit a new expression.
/// </summary>
/// <param name="expression"> The expression to visit. </param>
/// <param name="newExpression"> The expression to visit. </param>
/// <returns>
/// An Expression corresponding to the translated new expression.
/// </returns>
protected override Expression VisitNew(NewExpression expression)
protected override Expression VisitNew(NewExpression newExpression)
{
Check.NotNull(expression, nameof(expression));
Check.NotNull(newExpression, nameof(newExpression));

var newNewExpression = base.VisitNew(expression);
var visitedNewExpression = base.VisitNew(newExpression);

var selectExpression = QueryModelVisitor.TryGetQuery(_querySource);

if (selectExpression != null)
{
for (var i = 0; i < expression.Arguments.Count; i++)
for (var i = 0; i < newExpression.Arguments.Count; i++)
{
var aliasExpression
= selectExpression.Projection
.OfType<AliasExpression>()
.SingleOrDefault(ae => ae.SourceExpression == expression.Arguments[i]);
.SingleOrDefault(ae => ae.SourceExpression == newExpression.Arguments[i]);

if (aliasExpression != null)
{
aliasExpression.SourceMember
= expression.Members?[i]
?? (expression.Arguments[i] as MemberExpression)?.Member;
= newExpression.Members?[i]
?? (newExpression.Arguments[i] as MemberExpression)?.Member;
}
}
}

return newNewExpression;
return visitedNewExpression;
}

/// <summary>
Expand All @@ -121,108 +153,140 @@ var aliasExpression
/// </returns>
public override Expression Visit(Expression node)
{
if (node == null)
{
return null;
}

if (node is ConstantExpression
|| node is NewExpression
|| node is MemberExpression
|| node is MethodCallExpression)
{
return base.Visit(node);
}

var selectExpression = QueryModelVisitor.TryGetQuery(_querySource);

if (node != null
&& !(node is ConstantExpression)
&& selectExpression != null)
if (selectExpression == null)
{
var existingProjectionsCount = selectExpression.Projection.Count;
return base.Visit(node);
}

var sqlExpression
= _sqlTranslatingExpressionVisitorFactory
.Create(QueryModelVisitor, selectExpression, inProjection: true)
.Visit(node);
var sqlExpression = TranslateExpression(node);

if (sqlExpression == null)
if (sqlExpression == null)
{
if (node is QuerySourceReferenceExpression qsre)
{
var qsre = node as QuerySourceReferenceExpression;
if (qsre == null)
if (QueryModelVisitor.ParentQueryModelVisitor != null)
{
QueryModelVisitor.RequiresClientProjection = true;
}
else
{
if (QueryModelVisitor.ParentQueryModelVisitor != null
&& selectExpression.HandlesQuerySource(qsre.ReferencedQuerySource))
{
selectExpression.ProjectStarTable = selectExpression.GetTableForQuerySource(qsre.ReferencedQuerySource);
}
selectExpression.ProjectStarTable
= selectExpression.GetTableForQuerySource(qsre.ReferencedQuerySource);
}
}
else
{
selectExpression.RemoveRangeFromProjection(existingProjectionsCount);
QueryModelVisitor.RequiresClientProjection = true;
}

if (!(node is NewExpression))
{
AliasExpression aliasExpression;
return base.Visit(node);
}

int index;
if (!(node is QuerySourceReferenceExpression) && sqlExpression.TryGetColumnExpression() != null)
{
var index = selectExpression.AddToProjection(sqlExpression);

if (!(node is QuerySourceReferenceExpression))
{
var columnExpression = sqlExpression.TryGetColumnExpression();
if (selectExpression.Projection[index] is AliasExpression aliasExpression)
{
aliasExpression.SourceExpression = node;
}

if (columnExpression != null)
{
index = selectExpression.AddToProjection(sqlExpression);
return node;
}

aliasExpression = selectExpression.Projection[index] as AliasExpression;
if (!(sqlExpression is ConstantExpression))
{
var targetExpression
= QueryModelVisitor.QueryCompilationContext.QuerySourceMapping
.GetExpression(_querySource);

if (aliasExpression != null)
{
aliasExpression.SourceExpression = node;
}
return targetExpression.Type == typeof(ValueBuffer)
? CreateReadValueExpression(node, targetExpression, selectExpression, sqlExpression)
: node;
}

return node;
}
}
return base.Visit(node);
}

if (!(sqlExpression is ConstantExpression))
{
var targetExpression
= QueryModelVisitor.QueryCompilationContext.QuerySourceMapping
.GetExpression(_querySource);
private Expression TranslateExpression(Expression expression)
=> _sqlTranslatingExpressionVisitorFactory
.Create(QueryModelVisitor, inProjection: true)
.Visit(expression);

if (targetExpression.Type == typeof(ValueBuffer))
{
index = selectExpression.AddToProjection(sqlExpression);
private Expression TryHandleMemberOrMethodCallExpression(
Expression node, Expression objectExpression, Expression sqlExpression)
{
if (sqlExpression == null)
{
return null;
}

aliasExpression = selectExpression.Projection[index] as AliasExpression;
Expression targetExpression = null;
SelectExpression targetQuery = null;

if (aliasExpression != null)
{
aliasExpression.SourceExpression = node;
}
if (objectExpression is QuerySourceReferenceExpression qsre)
{
targetExpression
= QueryModelVisitor.QueryCompilationContext.QuerySourceMapping
.GetExpression(qsre.ReferencedQuerySource);

var readValueExpression
= _entityMaterializerSource
.CreateReadValueCallExpression(targetExpression, index);
targetQuery
= QueryModelVisitor.TryGetQuery(qsre.ReferencedQuerySource);
}

var outputDataInfo
= (node as SubQueryExpression)?.QueryModel
.GetOutputDataInfo();
if (targetQuery == null)
{
targetExpression
= QueryModelVisitor.QueryCompilationContext.QuerySourceMapping
.GetExpression(_querySource);

if (outputDataInfo is StreamedScalarValueInfo)
{
// Compensate for possible nulls
readValueExpression
= Expression.Coalesce(
readValueExpression,
Expression.Default(node.Type));
}
targetQuery
= QueryModelVisitor.TryGetQuery(_querySource);
}

return Expression.Convert(readValueExpression, node.Type);
}
return targetQuery != null && targetExpression.Type == typeof(ValueBuffer)
? CreateReadValueExpression(node, targetExpression, targetQuery, sqlExpression)
: node;
}

return node;
}
}
}
private Expression CreateReadValueExpression(
Expression node,
Expression targetExpression,
SelectExpression selectExpression,
Expression sqlExpression)
{
var index = selectExpression.AddToProjection(sqlExpression);

if (selectExpression.Projection[index] is AliasExpression aliasExpression)
{
aliasExpression.SourceExpression = node;
}

return base.Visit(node);
if (node is SubQueryExpression subQueryExpression
&& subQueryExpression.QueryModel.GetOutputDataInfo() is StreamedScalarValueInfo)
{
// Compensate for possible nulls
return Expression.Convert(
Expression.Coalesce(
_entityMaterializerSource.CreateReadValueCallExpression(targetExpression, index),
Expression.Default(node.Type)),
node.Type);
}
else
{
return _entityMaterializerSource.CreateReadValueExpression(targetExpression, node.Type, index);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1845,10 +1845,14 @@ public override void Order_by_key_of_anonymous_type_projected_navigation_doesnt_
Assert.Equal(
@"@__p_0: 10
SELECT TOP(@__p_0) [l3.OneToOne_Required_FK_Inverse0].[Id], [l3.OneToOne_Required_FK_Inverse0].[Date], [l3.OneToOne_Required_FK_Inverse0].[Level1_Optional_Id], [l3.OneToOne_Required_FK_Inverse0].[Level1_Required_Id], [l3.OneToOne_Required_FK_Inverse0].[Name], [l3.OneToOne_Required_FK_Inverse0].[OneToMany_Optional_InverseId], [l3.OneToOne_Required_FK_Inverse0].[OneToMany_Optional_Self_InverseId], [l3.OneToOne_Required_FK_Inverse0].[OneToMany_Required_InverseId], [l3.OneToOne_Required_FK_Inverse0].[OneToMany_Required_Self_InverseId], [l3.OneToOne_Required_FK_Inverse0].[OneToOne_Optional_PK_InverseId], [l3.OneToOne_Required_FK_Inverse0].[OneToOne_Optional_SelfId], [l30].[Name]
FROM [Level3] AS [l30]
INNER JOIN [Level2] AS [l3.OneToOne_Required_FK_Inverse0] ON [l30].[Level2_Required_Id] = [l3.OneToOne_Required_FK_Inverse0].[Id]
ORDER BY [l3.OneToOne_Required_FK_Inverse0].[Id]",
SELECT [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_InverseId], [t].[OneToMany_Optional_Self_InverseId], [t].[OneToMany_Required_InverseId], [t].[OneToMany_Required_Self_InverseId], [t].[OneToOne_Optional_PK_InverseId], [t].[OneToOne_Optional_SelfId], [t].[c0]
FROM (
SELECT TOP(@__p_0) [l3.OneToOne_Required_FK_Inverse].[Id], [l3.OneToOne_Required_FK_Inverse].[Date], [l3.OneToOne_Required_FK_Inverse].[Level1_Optional_Id], [l3.OneToOne_Required_FK_Inverse].[Level1_Required_Id], [l3.OneToOne_Required_FK_Inverse].[Name], [l3.OneToOne_Required_FK_Inverse].[OneToMany_Optional_InverseId], [l3.OneToOne_Required_FK_Inverse].[OneToMany_Optional_Self_InverseId], [l3.OneToOne_Required_FK_Inverse].[OneToMany_Required_InverseId], [l3.OneToOne_Required_FK_Inverse].[OneToMany_Required_Self_InverseId], [l3.OneToOne_Required_FK_Inverse].[OneToOne_Optional_PK_InverseId], [l3.OneToOne_Required_FK_Inverse].[OneToOne_Optional_SelfId], [l3].[Name] AS [c0]
FROM [Level3] AS [l3]
INNER JOIN [Level2] AS [l3.OneToOne_Required_FK_Inverse] ON [l3].[Level2_Required_Id] = [l3.OneToOne_Required_FK_Inverse].[Id]
ORDER BY [l3.OneToOne_Required_FK_Inverse].[Id]
) AS [t]
ORDER BY [t].[Id]",
Sql);
}

Expand Down Expand Up @@ -1885,11 +1889,14 @@ public override void Projection_select_correct_table_with_anonymous_projection_i
Assert.Equal(
@"@__p_0: 3
SELECT TOP(@__p_0) [l20].[Id], [l20].[Date], [l20].[Level1_Optional_Id], [l20].[Level1_Required_Id], [l20].[Name], [l20].[OneToMany_Optional_InverseId], [l20].[OneToMany_Optional_Self_InverseId], [l20].[OneToMany_Required_InverseId], [l20].[OneToMany_Required_Self_InverseId], [l20].[OneToOne_Optional_PK_InverseId], [l20].[OneToOne_Optional_SelfId], [l10].[Id], [l10].[Date], [l10].[Name], [l10].[OneToMany_Optional_Self_InverseId], [l10].[OneToMany_Required_Self_InverseId], [l10].[OneToOne_Optional_SelfId]
FROM [Level2] AS [l20]
INNER JOIN [Level1] AS [l10] ON [l20].[Level1_Required_Id] = [l10].[Id]
INNER JOIN [Level3] AS [l30] ON [l10].[Id] = [l30].[Level2_Required_Id]
WHERE ([l10].[Name] = N'L1 03') AND ([l30].[Name] = N'L3 08')",
SELECT [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_InverseId], [t].[OneToMany_Optional_Self_InverseId], [t].[OneToMany_Required_InverseId], [t].[OneToMany_Required_Self_InverseId], [t].[OneToOne_Optional_PK_InverseId], [t].[OneToOne_Optional_SelfId], [t].[c0], [t].[c1], [t].[c2], [t].[c3], [t].[c4], [t].[c5]
FROM (
SELECT TOP(@__p_0) [l2].[Id], [l2].[Date], [l2].[Level1_Optional_Id], [l2].[Level1_Required_Id], [l2].[Name], [l2].[OneToMany_Optional_InverseId], [l2].[OneToMany_Optional_Self_InverseId], [l2].[OneToMany_Required_InverseId], [l2].[OneToMany_Required_Self_InverseId], [l2].[OneToOne_Optional_PK_InverseId], [l2].[OneToOne_Optional_SelfId], [l1].[Id] AS [c0], [l1].[Date] AS [c1], [l1].[Name] AS [c2], [l1].[OneToMany_Optional_Self_InverseId] AS [c3], [l1].[OneToMany_Required_Self_InverseId] AS [c4], [l1].[OneToOne_Optional_SelfId] AS [c5]
FROM [Level2] AS [l2]
INNER JOIN [Level1] AS [l1] ON [l2].[Level1_Required_Id] = [l1].[Id]
INNER JOIN [Level3] AS [l3] ON [l1].[Id] = [l3].[Level2_Required_Id]
WHERE ([l1].[Name] = N'L1 03') AND ([l3].[Name] = N'L3 08')
) AS [t]",
Sql);
}

Expand Down
Loading

0 comments on commit 4ec2d54

Please sign in to comment.