-
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
Must be reducible node exception when performing Join #3103
Comments
I'm new to EF and LINQ, and I am receiving the same error, though I don't know if for the same reason. Also using beta7. Here is the query I would like to perform that throws the "Must be reducible node exception":
Here is the modification that fixes the exception:
You can see the only difference is the addition of "ToList()". Please let me know if I'm doing anything wrong or inefficient. Thanks. |
Exact same issue, my simple join query like so do not work: but if I add .ToList() after _db.A and _db.B then it works :( |
@kskalski the problem is because of the way we translate navigation properties in the key selectors. consider a simpler version of the query:
What we do is expand the nav prop p.Good into a join like so:
this works great, however if the nav prop is in the inner selector:
this logic fails due to chicken and egg problem:
We need to do a different expansion pattern for the case like yours. In the meantime, you could mitigate this by using foreign keys rather than nav props, like so:
(you would need to add the foreign keys to the model first). @Sharpiro @AlexTo adding ToList() to a query makes it so that it no longer uses Entity Framework, but Linq to Objects instead, and may cause inefficiencies in your queries, e.g.
will query the database for all the customers and perform the filter on the client. @Sharpiro in your case this is not that impactful, because you add ToList() before the projection. Filter, sorting and include is done using EF. I'll investigate your case as well, probably the root cause is similar even though the query is little different. @AlexTo can you share the query that fails for your, I'm mostly interested in the part that comes after "on" (key selectors). Also could you post the model that you used? |
Thanks for analysis. On the other hand I'm not sure what is the standardized or expected semantics when using navigational properties for filtering or joins, do they imply materialization of intermediate objects or not. I can imagine somebody doing 'from p in ctx.Assets where p.Good.Id == 3 select p' would expect returned p to have non-null p.Good and if the navigation property doesn't actually translate to foreign key in the source table, e.g. 'from p in ctx.Assets where p.Good.Name == "x" select p' then DB access need to do the join anyway. So maybe for the sake of consistency it's better to always do it and materialize intermediate objects. |
Here is the complete query that works, to make it failed, just remove all ToList()
And my model
And I have this configuration in OnModelCreating
Thank you |
I am currently working on a fix that will detect this case and translate to FK, if possible. Problem is that this can only be done for a subset of scenarios (no composite keys, no chaining of nav props, you need to use PK as a key selector, as opposed to e.g. "name", and navigation needs to point in the right direction). For other cases we will try to do a more involved and much less optimal translation, probably involving CROSS APPLY. When it comes to materialization, we only materialize stuff that is specified in the select clause, intermediate results will not get materialized. In the example you provided, p will not load in the "Good" nav prop, unless you explicitly ask for it (using Include). We always want to materialize as little as possible to minimize the amount of data sent over the wire. |
…ng Join Problem is in a query with join, that has a navigation property in inner key selector. We normally try to expand the navigation property into a join clause, however if done for inner key selector, leads to chicken-and-egg problem: e.g. from p in ctx.Payouts join a in ctx.Assets on p.Id equals a.Good.Id select p translated to: from p in ctx.Payouts join g in ctx.Good on a.Id equals g.Id // invalid, because a was not declared yet join a in ctx.Assets on g.Id equals a.Id (alternatively) from p in ctx.Payouts join a in ctx.Assets on g.Id equals a.Id // invalid, because g was not declared yet join g in ctx.Good on a.Id equals g.Id Is some cases this can we worked around by translating navigation property into a FK: from p in ctx.Payouts join a in ctx.Assets on p.Id equals a.GoodId select p This is only possible if we actually have foreign key on this side of the navigation, and if the navigation itself is simple (not nested, not composite and we are accessing PK after the navigation).
Problem: We were incorrectly expanding navigations that appeared in the inner key selector of a join expression (right side). Normally we expand nav props into additional join. E.g.: from p in ctx.Payouts join a in ctx.Assets on p.Good.Id equals a.Id select p converts to: from p in ctx.Payouts join g from ctx.Goods on p.Id equals g.Id join a in ctx.Assets on g.Id equals a.Id select p However if navigation propery is in the inner key selector, like so: from p in ctx.Payouts join a in ctx.Assets on p.Id equals a.Good.Id select p we encounter chicken-and-egg problem: from p in ctx.Payouts join g in ctx.Good on a.Id equals g.Id // invalid, because a was not declared yet join a in ctx.Assets on g.Id equals a.Id or from p in ctx.Payouts join a in ctx.Assets on g.Id equals a.Id // invalid, because g was not declared yet join g in ctx.Good on a.Id equals g.Id Solution: Some navigation properties can be translated into FKs, which removes the problem: from c in Customers join d in Details on c.Id equals d.Customer.Id select c can be translated into: from c in Customers join d in Details on c.Id equals c.CustomerId select c This can only be done for a subset of queries - join needs to be based on key property, FK must be pointing the right way, and key must not be composite. For all other cases we translate this into a subquery: from c in Customers join d in Details on c.Id equals (from c2 in Customers where c2.CustomerId equals d.Id select c2.Id).FirstOrDefault() In case of nested navigation property we expand the first one into a subquery, and add extra joins to the subquery, just like we did originally. Only the first navigation is "problematic", the following ones can be safely translated to joins
Problem: We were incorrectly expanding navigations that appeared in the inner key selector of a join expression (right side). Normally we expand nav props into additional join. E.g.: from p in ctx.Payouts join a in ctx.Assets on p.Good.Id equals a.Id select p converts to: from p in ctx.Payouts join g from ctx.Goods on p.Id equals g.Id join a in ctx.Assets on g.Id equals a.Id select p However if navigation propery is in the inner key selector, like so: from p in ctx.Payouts join a in ctx.Assets on p.Id equals a.Good.Id select p we encounter chicken-and-egg problem: from p in ctx.Payouts join g in ctx.Good on a.Id equals g.Id // invalid, because a was not declared yet join a in ctx.Assets on g.Id equals a.Id or from p in ctx.Payouts join a in ctx.Assets on g.Id equals a.Id // invalid, because g was not declared yet join g in ctx.Good on a.Id equals g.Id Solution: Some navigation properties can be translated into FKs, which removes the problem: from c in Customers join d in Details on c.Id equals d.Customer.Id select c can be translated into: from c in Customers join d in Details on c.Id equals c.CustomerId select c This can only be done for a subset of queries - join needs to be based on key property, FK must be pointing the right way, and key must not be composite. For all other cases we translate this into a subquery: from c in Customers join d in Details on c.Id equals (from c2 in Customers where c2.CustomerId equals d.Id select c2.Id).FirstOrDefault() In case of nested navigation property we expand the first one into a subquery, and add extra joins to the subquery, just like we did originally. Only the first navigation is "problematic", the following ones can be safely translated to joins
Problem: We were incorrectly expanding navigations that appeared in the inner key selector of a join expression (right side). Normally we expand nav props into additional join. E.g.: from p in ctx.Payouts join a in ctx.Assets on p.Good.Id equals a.Id select p converts to: from p in ctx.Payouts join g from ctx.Goods on p.Id equals g.Id join a in ctx.Assets on g.Id equals a.Id select p However if navigation propery is in the inner key selector, like so: from p in ctx.Payouts join a in ctx.Assets on p.Id equals a.Good.Id select p we encounter chicken-and-egg problem: from p in ctx.Payouts join g in ctx.Good on a.Id equals g.Id // invalid, because a was not declared yet join a in ctx.Assets on g.Id equals a.Id or from p in ctx.Payouts join a in ctx.Assets on g.Id equals a.Id // invalid, because g was not declared yet join g in ctx.Good on a.Id equals g.Id Solution: Some navigation properties can be translated into FKs, which removes the problem: from c in Customers join d in Details on c.Id equals d.Customer.Id select c can be translated into: from c in Customers join d in Details on c.Id equals c.CustomerId select c This can only be done for a subset of queries - join needs to be based on key property, FK must be pointing the right way, and key must not be composite. For all other cases we translate this into a subquery: from c in Customers join d in Details on c.Id equals (from c2 in Customers where c2.CustomerId equals d.Id select c2.Id).FirstOrDefault() In case of nested navigation property we expand the first one into a subquery, and add extra joins to the subquery, just like we did originally. Only the first navigation is "problematic", the following ones can be safely translated to joins
Problem: We were incorrectly expanding navigations that appeared in the inner key selector of a join expression (right side). Normally we expand nav props into additional join. E.g.: from p in ctx.Payouts join a in ctx.Assets on p.Good.Id equals a.Id select p converts to: from p in ctx.Payouts join g from ctx.Goods on p.Id equals g.Id join a in ctx.Assets on g.Id equals a.Id select p However if navigation propery is in the inner key selector, like so: from p in ctx.Payouts join a in ctx.Assets on p.Id equals a.Good.Id select p we encounter chicken-and-egg problem: from p in ctx.Payouts join g in ctx.Good on a.Id equals g.Id // invalid, because a was not declared yet join a in ctx.Assets on g.Id equals a.Id or from p in ctx.Payouts join a in ctx.Assets on g.Id equals a.Id // invalid, because g was not declared yet join g in ctx.Good on a.Id equals g.Id Solution: Some navigation properties can be translated into FKs, which removes the problem: from c in Customers join d in Details on c.Id equals d.Customer.Id select c can be translated into: from c in Customers join d in Details on c.Id equals c.CustomerId select c This can only be done for a subset of queries - join needs to be based on key property, FK must be pointing the right way, and key must not be composite. For all other cases we translate this into a subquery: from c in Customers join d in Details on c.Id equals (from c2 in Customers where c2.CustomerId equals d.Id select c2.Id).FirstOrDefault() In case of nested navigation property we expand the first one into a subquery, and add extra joins to the subquery, just like we did originally. Only the first navigation is "problematic", the following ones can be safely translated to joins CR: Andrew
Fixed in c2b0179 |
@AlexTo the fix is now in, I double checked the sample you provided and the bug is not happening on it anymore (and I verified it was happening on the bits prior to the fix) |
Thank you, so if I understand correctly, this fix will be included in RC1, but not in the beta8 recently released right? |
@AlexTo yes, the fix didn't make it to beta8, so you need to wait till the next release or try our nightly builds |
…ation inside a subquery inside join inner key selector Problem was that navigation inside inner key selector of a JoinClause was always being rewritten to subquery (needed for #3103). However, we should only be doing this for "naked" navs - if the nav itself is inside a subquery it can be safely rewritten into a join. Fix is to "reset" the state indicating whether we are inside join inner key selector every time we visit SubQuery.
…ation inside a subquery inside join inner key selector Problem was that navigation inside inner key selector of a JoinClause was always being rewritten to subquery (needed for #3103). However, we should only be doing this for "naked" navs - if the nav itself is inside a subquery it can be safely rewritten into a join. Fix is to "reset" the state indicating whether we are inside join inner key selector every time we visit SubQuery.
Doing following join:
ScheduledPayouts.Join(Assets, p => p.Good.Id, a => a.Good.Id, (p, a) => new Tuple<Asset, ScheduledPayout>(a, p));
throws following exception:
the model is:
and debug string for the failed expression:
Note that materializing the collections and doing the join then works fine:
return ScheduledPayouts.Include(p => p.Good).ToList().Join(Assets.Include(a => a.Good).ToList(), p => p.Good.Id, a => a.Good.Id, (p, a) => new Tuple<Asset, ScheduledPayout>(a, p));
this is with beta7, in beta6 I had different kind of exception (something about Single not allowing multiple elements in collection).
The text was updated successfully, but these errors were encountered: