-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Conversation
@tuespetre, |
There was a problem hiding this 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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
change to memberExpression
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
memberExpression
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make inline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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
Looks good. |
e7e61af
to
520ac6b
Compare
🆙📅 |
|
||
return expression | ||
?? _queryModelVisitor.BindMethodToOuterQueryParameter(methodCallExpression); | ||
return TryBindAliasExpression(methodCallExpression, (e, visitor, binder) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Format the doc.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
memberExpression
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops
520ac6b
to
d25427b
Compare
🆙📅 |
return binder(queryModelVisitor) | ||
?? TryBindParentExpression(queryModelVisitor.ParentQueryModelVisitor, binder); | ||
} | ||
var ancestor = _queryModelVisitor.ParentQueryModelVisitor; |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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>( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function name TryBindMemberOrMethodToAliasExpression
?
d25427b
to
5d33173
Compare
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) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
boundExpression
5d33173
to
7bea268
Compare
@smitpatel working on tests. |
🆙📅 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>(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✔️
There was a problem hiding this 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 👍
b03bae8
to
3d16914
Compare
🏁 done |
@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) |
There was a problem hiding this comment.
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
Oops, hadn't thought to run those before pushing. (What happened to the Travis logs BTW?) |
3d16914
to
40b1b43
Compare
🆙📅 fixed the tests (needed to change from |
fff887a
to
18b4d4c
Compare
Merged via 08bdfc9 |
This is a much smaller, less radical version of #7663 that brings only enhancements (no regressions) to the plate.