-
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
Set operations: cleanup #16249
Set operations: cleanup #16249
Conversation
if (commonParentEntityType != projection1.EntityType) | ||
{ | ||
// The first source has been up-cast by the set operation, so we also need to change the shaper expression. | ||
var entityShaperExpression = |
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.
@smitpatel I know remove the cast only if it was casting to our common parent type, for which we're setting the new shaper anyway. This code doesn't handle all cases - it's possible there will be two cast nodes, in which case this will not work (but I've seen this pattern in other places in the code). I'm still not exactly sure what we expect to happen, but this may be enough for now. Let me know if you want us to do something else here.
src/EFCore.Relational/Query/Pipeline/SqlExpressions/SelectExpression.cs
Outdated
Show resolved
Hide resolved
src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor_MethodCall.cs
Outdated
Show resolved
Hide resolved
// Find that. | ||
var clrType1 = preProcessResult1.state.CurrentParameter.Type; | ||
var clrType2 = preProcessResult2.state.CurrentParameter.Type; | ||
var parentState = clrType1.IsAssignableFrom(clrType2) ? preProcessResult1.state : preProcessResult2.state; |
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 way we only propagate include information applied on less derived type. e.g.
customers.Include(c => c.Nav1).Concat(customers.OfType().Include(c => c.Nav2)), we would end up including only Nav1. We either need to merge include information (which is embedded in NavigationTreeNode on the NavigationExpansionState) or detect when the include paths on both sides are different and throw - can probably be done in a future PR as the scenario is somewhat contrived.
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 can use IncludeHelpers.CopyIncludeInformation
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.
Yeah, I thought about this a bit. I think that trying to do a set operation with differing includes should throw, since merging includes doesn't correspond to the presumed user expectation, which would be to only include each nav the set operand it's specified. This can't be done in SQL (i.e. unless we fetch the information and don't materialize it).
If users want merged includes, they can simply write the following themselves:
customers.Union(customers.OfType<...>())
.Include(c => c.Nav1)
.Include(c => c.Nav2)
We may want to discuss this in design (@divega @ajcvickers). For now I've implemented a NotSupportedException for this (@maumar hope you can take a look to make sure I did it right).
test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.SetOperations.cs
Outdated
Show resolved
Hide resolved
var select2 = otherSelectExpression; | ||
|
||
if (_projection.Any()) | ||
{ | ||
throw new NotImplementedException("Set operation on SelectExpression with populated _projection"); | ||
throw new NotSupportedException("Can't process set operations after client evaluation, consider moving the operation before the last Select() call (see issue #16243)"); |
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.
Which exception type to use? @ajcvickers
We had some recent discussion around this but I don't remember the outcome.
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.
I don't remember that discussion 😄 But I am leaning towards NotImplementedException InvalidOperationException and simplifying the message, but that is because I think #16243 is by design (of course I could be wrong and I may need someone to explain to me why it isn't).
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.
FYI, I created https://go.microsoft.com/fwlink/?linkid=2097910 for exceptions we throw when something would require client evaluation. We tweak what it points to in the future.
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.
@divega I don't have the full picture, but @smitpatel seemed to indicate this should be possible by us moving the union before the client evaluation.
I'm leaning towards NotSupportedException (in the sense of not supported in this version), or to a less extent NotImplementedException (although that's usually used as a very temporary marker for a dev to remember they still have to do something). InvalidOperationException seems used more because an object is in a wrong state, which doesn't seem to be the case here.
However, I'm changing to InvalidOperationException for now we we really want this PR to get merged in time for preview7.
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.
Ah, I see. That is a good idea. I guess it would only about lifting the client eval part and not the actual projection, because lifting the projection over Union() would not be correct in all cases. I personally would love to do something when we can, but I suspect it is going to take some time for this to get to the top of the list.
Re the exception type, I actually don't feel strongly about which one would be the most correct to throw, but what I heard from @ajcvickers and @smitpatel is that keeping the set of exception types customers should catch beats being absolutely correct.
{ | ||
shaperExpression = new EntityShaperExpression( | ||
commonParentEntityType, entityShaperExpression.ValueBufferExpression, entityShaperExpression.Nullable); | ||
} |
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.
What is else result? Just ignore?
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.
OK, I went with a different approach - I now replace the EntityShaperExpression, and if it's wrapped by Convert nodes I recreate those exactly as they previously were. It's effectively as if the entity was originally queried as a less derived type as what it is.
Sorry this part is taking so many iterations, please let me know if it's still not the way you want - I still don't feel like I understand all the possibilities here 100%.
src/EFCore.Relational/Query/Pipeline/SqlExpressions/SelectExpression.cs
Outdated
Show resolved
Hide resolved
src/EFCore.Relational/Query/Pipeline/SqlExpressions/SelectExpression.cs
Outdated
Show resolved
Hide resolved
Pushed a new commit with various fixes. |
src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor_MethodCall.cs
Outdated
Show resolved
Hide resolved
If navigation part of set operation is going to take time then can we split out, push down for order by in separate PR and get it merged sooner? |
Also set operations with fromsql. |
I'm going to take a quick stab at it - I have to leave very soon anyway. In any case I will look at it later tonight. @smitpatel, maybe comment on what you consider OK (e.g. there's still the convert nodes when replacing the EntityShaperExpression). If necessary I can split out the problematic parts. Finally, even if the PR isn't perfect as-is, we can also decide to merge and I'll fix anything right afterwards (i.e. tomorrow during my day). Up to you. |
Fix whatever fails with that, (disable/pushdown). (Apparently this PR does not update code about null check). Split out above in a new PR. |
6de3436
to
8b0fce1
Compare
@smitpatel, removed support entirely for set operations over operands of different entity types - scoped this out to #16298 (this is what the ColumnExpression was for). We can look at this later, hopefully (but not 100% necessarily for 3.0). @maumar implemented the better way to compare includes on both sides, you can take a look. Let's try to get it into preview7. |
test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs
Show resolved
Hide resolved
src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor_MethodCall.cs
Outdated
Show resolved
Hide resolved
src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor_MethodCall.cs
Outdated
Show resolved
Hide resolved
src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor_MethodCall.cs
Outdated
Show resolved
Hide resolved
@roji nav rewrite changes look good! |
src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor_MethodCall.cs
Outdated
Show resolved
Hide resolved
Addressed @maumar's comments and rebased on latest master. |
Can you split out PR in 2 parts?
Since @maumar is gone for vacation, it may take some time to understand nav expansion changes. |
Done, this PR now only fixes the ColumnExpression alias issue. Once it's merged I'll submit a separate one for nav/include support. |
* We no longer allow ColumnExpression without table alias * Set operations over operands with different types have been disabled for now. * We now push down set operations into a subquery on Orderby, Skip or Take, which shouldn't be necessary (#16244). * Some test consolidation and cleanup. Continues #6812
NOTE: This PR was changed to only cleanup some aspects, other parts were split out
INTERSECT ALL
,EXCEPT ALL
).Completes #6812
Fixes #13196
Fixes #16065
Fixes #16165