Repository navigation
Project abstract types instead of falling back to includes - #1437
Merged
Merged
Conversation
An abstract type cannot be created by a member init, so a query for one, or a navigation to one, was loaded whole through includes: every column of it and of every entity under it, whatever was selected. No row is ever of the abstract type itself, so it is now projected as the chain of type tests already used for a concrete type with derived types, ending in null instead of a member init of its own. Navigations on a type with derived types are bound once, to whichever type the chain created. Bound inside each type's member init they were repeated once per concrete type, so a collection was joined, or split out, once for every type, and a hierarchy under a hierarchy multiplied the size of the expression. Foreign keys declared on derived types are loaded by a query for the base type, as the keys of the base type are. The count query of a connection no longer carries the select projection. A resolver on an abstract type that reads a property of its source without declaring a projection used to work, since the whole entity was loaded. It now needs the projection, as one on a concrete type does. Bump version to 35.5.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
An abstract type cannot be created by a member init, so a query for an abstract type, or a navigation to one, fell back to
Include. That loads every column of the entity and of every entity under it, whatever the request selected, and builds include chains as deep as the selection:Change
Abstract types are projected
No row is ever of the abstract type itself: each is one of its concrete derived types. So an abstract type is now projected as the chain of type tests already used for a concrete type with derived types, ending in
nullinstead of a member init of its own.An abstract type with no concrete derived type in the model still falls back, as does one where a derived type has a selected property a select cannot bind.
Navigations on a type with derived types are bound once
Each type test creates its own member init, so a navigation bound in them was repeated once per concrete type. EF does not recognise a repeated collection subquery as the same one, so the collection was joined, or split out, once for every type. A hierarchy under a hierarchy also multiplied the size of the expression.
They are now bound once, to whichever type the chain created, through
HoistedNavigation.Set, a call EF evaluates on the client as it does the member inits. This applies to a concrete type with derived types too, which had the same repetition.Foreign keys of derived types
A query for a base type now loads the foreign keys its derived types declare, as it loads the base type's own. They used to arrive only because the whole entity did.
Connection count
The count query no longer carries the select projection. EF drops it from the SQL, but only after compiling the whole projection, and every collection in it, as part of the count.
Behaviour change
A resolver on an abstract type that reads a property of its source without declaring a projection used to work, because the whole entity was loaded. It now sees a default value, with no exception, exactly as it would on a concrete type. It needs a projection:
Tests
Abstract_root_type_query_is_projectedandNavigation_to_abstract_type_is_projected, renamed from..._falls_back_to_includeand..._falls_back_to_full_include.Abstract_root_type_collection_is_loaded_once: a collection on an abstract root with two concrete types is joined once.Navigation_under_abstract_navigation_is_loadednow covers the same under a reference navigation.TphRootEntityhas a read onlySummary, selected by the four include guard tests so they stay on the includes path they exist to cover.