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

Improve binding to parent query models #7696

Closed
wants to merge 1 commit into from

Conversation

tuespetre
Copy link
Contributor

This is a much smaller, less radical version of #7663 that brings only enhancements (no regressions) to the plate.

@dnfclas
Copy link

dnfclas commented Feb 23, 2017

@tuespetre,
Thanks for having already signed the Contribution License Agreement. Your agreement was validated by .NET Foundation. We will now review your pull request.
Thanks,
.NET Foundation Pull Request Bot

Copy link
Contributor

@smitpatel smitpatel left a comment

Choose a reason for hiding this comment

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

Add a test where subquery tries to bind with outer qmv which has client projection.

/// <returns>
/// An Expression.
/// </returns>
protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
protected override Expression VisitMethodCall(MethodCallExpression node)
Copy link
Contributor

Choose a reason for hiding this comment

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

revert the name change

/// <returns>
/// An Expression.
/// </returns>
protected override Expression VisitMember(MemberExpression expression)
protected override Expression VisitMember(MemberExpression node)
Copy link
Contributor

Choose a reason for hiding this comment

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

change to memberExpression

Copy link
Contributor Author

Choose a reason for hiding this comment

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

change to memberExpression or revert to expression?


if (aliasExpression == null)
private Expression TryBindQuerySourcePropertyExpression(MemberExpression expression)
Copy link
Contributor

Choose a reason for hiding this comment

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

memberExpression

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this method being used elsewhere? If not then we don't need to create this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are several other methods (even in this class) that are only used in one place. I feel that the readability was greatly improved by making this into its own method:

return TryBindAliasExpression(node, (visitor, binder)
        => visitor.BindMemberExpression(node, binder))
    ?? TryBindQuerySourcePropertyExpression(node)
    ?? _queryModelVisitor.BindMemberToOuterQueryParameter(node);

Additionally, this method can be expanded on (for instance, to add support for translating access of a grouping key.)

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good then.

{
var parentSelectExpression = ParentQueryModelVisitor?.TryGetQuery(querySource);
if (parentSelectExpression != null)
var ancestor = ParentQueryModelVisitor;
Copy link
Contributor

Choose a reason for hiding this comment

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

variable name -> outerQueryModelVisitor

@@ -1594,10 +1605,19 @@ public override Expression BindMemberToValueBuffer(MemberExpression memberExpres

private ParameterExpression BindPropertyToOuterParameter(IQuerySource querySource, IProperty property, bool isMemberExpression)
{
if (querySource != null && _canBindPropertyToOuterParameter)
if (querySource != null && _canBindPropertyToOuterParameter && ParentQueryModelVisitor != null)
Copy link
Contributor

Choose a reason for hiding this comment

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

remove null check on qmv

var parentSelectExpression = ParentQueryModelVisitor?.TryGetQuery(querySource);
if (parentSelectExpression != null)
var ancestor = ParentQueryModelVisitor;
SelectExpression outerQuery = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

var name -> outerSelectExpression

var ancestor = ParentQueryModelVisitor;
SelectExpression outerQuery = null;

do
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove do part of loop by assigning values when declaring variables.

var column = _relationalAnnotationProvider.For(property).ColumnName;
var table = selectExpression.GetTableForQuerySource(querySource);

return new AliasExpression(new ColumnExpression(column, property, table));
Copy link
Contributor

Choose a reason for hiding this comment

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

make inline.

Copy link
Contributor Author

@tuespetre tuespetre Feb 24, 2017

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Inline in the sense, you don't need to declare variables for column & table just put them directly in constructor since they are not being used.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

K, thanks

}

var ancestor = _queryModelVisitor.ParentQueryModelVisitor;
var canBindToAncestor = _queryModelVisitor.CanBindToParentQueryModel;
Copy link
Contributor

Choose a reason for hiding this comment

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

You may not be able to bind to parent query model visitor if it requires client projection.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

CanBindToParentQueryModel should never be true if the parent qmv requires client projection.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you point me to the place where we are setting that flag appropriately?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The flag is set by RelationalQueryModelVisitor.VisitSubQueryModel, which is called in RelationalQueryModelVisitor.LiftSubQuery and SqlTranslatingExpressionVisitor.VisitSubQuery.

Copy link
Contributor

Choose a reason for hiding this comment

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

FWIW, When a new query model visitor is created, the flag is set to false. So in the case of client projections we create the qmv in ProjectExpressionVisitor which would be called for client projections only.

/// <value>
/// true if the query model visitor can bind to its parent's properties, false if not.
/// </value>
public virtual bool CanBindToParentQueryModel { get; protected set; } = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

no need to set to false. Its default

qmv => qmv.BindMemberExpression(expression, CreateAliasedColumnExpressionCore));
}
return TryBindAliasExpression(node, (visitor, binder)
=> visitor.BindMemberExpression(node, binder))
Copy link
Contributor

Choose a reason for hiding this comment

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

This is actually a closure rather than a Func since you are passing value of node into the lambda expression. If you look at the function TryBindAliasExpression then you will see the first parameter is never used in its method body. We don't want to create a closure here. Instead change this lambda to take the node as argument and pass the node to it in TryBindAliasExpression

@smitpatel
Copy link
Contributor

Looks good.

@tuespetre
Copy link
Contributor Author

🆙📅


return expression
?? _queryModelVisitor.BindMethodToOuterQueryParameter(methodCallExpression);
return TryBindAliasExpression(methodCallExpression, (e, visitor, binder)
Copy link
Contributor

Choose a reason for hiding this comment

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

e -> expression

@@ -646,7 +630,7 @@ protected override Expression VisitMember(MemberExpression expression)
{
var newExpression = Visit(expression.Expression);

if (newExpression != null
if (newExpression != null
Copy link
Contributor

Choose a reason for hiding this comment

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

Format the doc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

huh, wonder why Visual Studio diff didn't show that

_queryModelVisitor.ParentQueryModelVisitor,
qmv => qmv.BindMemberExpression(expression, CreateAliasedColumnExpressionCore));
}
return TryBindAliasExpression(expression, (e, visitor, binder)
Copy link
Contributor

Choose a reason for hiding this comment

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

e -> expression


if (aliasExpression == null)
private Expression TryBindQuerySourcePropertyExpression(MemberExpression expression)
Copy link
Contributor

Choose a reason for hiding this comment

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

memberExpression

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oops

@tuespetre
Copy link
Contributor Author

🆙📅

return binder(queryModelVisitor)
?? TryBindParentExpression(queryModelVisitor.ParentQueryModelVisitor, binder);
}
var ancestor = _queryModelVisitor.ParentQueryModelVisitor;
Copy link
Contributor

Choose a reason for hiding this comment

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

can we move away from this variable name. ancestor does not specify ancestor of what particularly.
Better name would be outerQueryModelVisitor (since its not direct parent always)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done (also changed canBindtoAncestor to canBindToOuterQueryModelVisitor)

private static AliasExpression TryBindParentExpression(
RelationalQueryModelVisitor queryModelVisitor,
Func<RelationalQueryModelVisitor, AliasExpression> binder)
private Expression TryBindAliasExpression<TExpression>(
Copy link
Contributor

@smitpatel smitpatel Feb 24, 2017

Choose a reason for hiding this comment

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

Do we need generics here? Does a simple Expression work?
Probably won't work as binder methods are type specific.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Generic due to MemberExpression vs. MethodCallExpression

Copy link
Contributor

Choose a reason for hiding this comment

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

function name TryBindMemberOrMethodToAliasExpression?

@smitpatel
Copy link
Contributor

Can you add some test around improved binding? Multi-level queries which can be bound with outer select expression through alias expression and through parameter.

property,
selectExpression.GetTableForQuerySource(querySource)));

var bound = binder(sourceExpression, _queryModelVisitor, (property, querySource, selectExpression) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

boundExpression

@tuespetre
Copy link
Contributor Author

@smitpatel working on tests.

@tuespetre
Copy link
Contributor Author

🆙📅 Added some tests and also a fix for an error that was revealed by the tests. Did in a separate commit so you can see the isolated change.

@@ -1605,6 +1605,8 @@ public override Expression BindMemberToValueBuffer(MemberExpression memberExpres

private const string OuterQueryParameterNamePrefix = @"_outer_";

private readonly Dictionary<string, Expression> _boundParameters = new Dictionary<string, Expression>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we name it injectedParameters?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

✔️

Copy link
Contributor

@smitpatel smitpatel left a comment

Choose a reason for hiding this comment

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

Squash-rebase on latest dev 👍

@tuespetre
Copy link
Contributor Author

🏁 done

@smitpatel
Copy link
Contributor

@tuespetre - QueryInMemoryTests are failing.

/// the subquery can be lifted.
/// </summary>
/// <param name="subQueryModelVisitor"> The query model visitor for the subquery being lifted. </param>
public void LiftInjectedParameters(RelationalQueryModelVisitor subQueryModelVisitor)
Copy link
Contributor

Choose a reason for hiding this comment

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

add virtual, NotNull annotation and null check on parameter

@tuespetre
Copy link
Contributor Author

Oops, hadn't thought to run those before pushing. (What happened to the Travis logs BTW?)

@tuespetre tuespetre force-pushed the bind-parent-queries branch from 3d16914 to 40b1b43 Compare March 1, 2017 03:02
@tuespetre
Copy link
Contributor Author

🆙📅 fixed the tests (needed to change from Region to City)

@tuespetre tuespetre force-pushed the bind-parent-queries branch 2 times, most recently from fff887a to 18b4d4c Compare March 1, 2017 03:05
@smitpatel
Copy link
Contributor

Merged via 08bdfc9

@smitpatel smitpatel closed this Mar 1, 2017
@tuespetre tuespetre deleted the bind-parent-queries branch March 19, 2017 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants