[release/8.0] Reduce memory usage during relationship cycle detection by keeping track of visited properties whenever there are FK forks #33211
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.
Fixes #33176
Description
During model building, when calculating certain aspects of entity properties, we default to the value on the corresponding principal property if the current property is part of a foreign key. This process is repeated until a non-default value is found or we determine that the foreign keys form a cycle.
In 8.0.2 we improved our cycle detection to detect cycles even when starting on a property that itself is not part of the cycle and we also started examining all the branches to increase the chance of finding a non-default value.
This is the algorithm that was used:
AFAIK the algorithm is correct, but for some models it needs a traversal queue that doesn't fit in memory. Consider the model in the added test, where
BaseEntity
has an owned propertyOwnedType
with a nested owned propertyNestedOwnedType
and it also has 4 derived types in TPT hierarchy. For this type of hierarchy, we create a foreign key between the derived type and the base using the primary key properties, this effectively creates a 1-length cycle. So, if we start traversing this model from theNestedOwnedType
when we get toBaseEntity
P is 7 and on each level of the traversal it will recursively branch 4 times. And since the starting property is not on any cycle a new reference property needs to be used and P needs to be increased to 11. So it will take 4^(7 - 2 + 11) = 2^17 visits for the method to give up and since it uses BFS, the queue will need to be able to accommodate 2^15 nodes, which is way too large for just 7 entity types.To fix this we'll start recording visited properties as soon as we hit a branching path. With this we'll still not use any additional memory if there are no branches, otherwise, we'll use at most O(n) where n is the number of entity types in the model.
Customer impact
Users that hit this scenario will get an
OutOfMemoryException
during model building. Theoretically, they could work around this by explicitly specifying anull
ValueConverter on every primary key property, but this isn't practical, and the exception has no information that could help them. There is anAppContext
switch that they could use to disable the previous fix, however if they also have cycles in the model that are longer than 2, they'll just hit a different exception.How found
Customer reported on 8.0.2. So far there's been only 1 report.
The most likely scenario where this issue manifests is in TPT mapping with 4 or more derived types where the base type is the principal of a relationship chain at least 2 entity types long. While TPT mapping is not very common, if it's used it's likely that there will be 4 or more derived types and relationship chains of length 2 or more are also common.
Regression
Yes, from 8.0.1, introduced in #32598
Testing
Tests added.
Risk
Low, the main logic is not changed. Quirk added.