Treat lookups by key as bounded, and skip Includes EF ignores - #17
Merged
Merged
Conversation
RejectUnbounded no longer fires for a Where that compares a primary or alternate key with a value, such as Where(_ => _.Id == id), since it returns at most one row. A key looked up in a list, such as ids.Contains(_.Id), is only bounded when MaxInValues is set, and each set of levels decides that with its own MaxInValues. A lookup only counts on the rows of a DbSet, since after Concat, SelectMany, Join, Select or FromSql the same key can be in many rows. MaxSingleQueryCollections no longer counts an Include that a later Select or aggregate makes Entity Framework ignore, since the query returns no entity for it to load into. A projection that could return an entity keeps the Includes counted.
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.
Two false positives, both found by comparing the checks with the rules in LinqContraband (LC031 and LC049).
Lookups by key are bounded
RejectUnboundedfired forEmployees.Where(_ => _.Id == id), which returns at most one row. AWherethat compares a key with a value now bounds the query like aTake:&&. With||, each side has to be a lookup. A composite key needs every part compared.EF.Propertywork. A unique index does not count: a filter can make it unique among only some rows, and a column that allows null can hold null in many rows.ids.Contains(_.Id)returns a row for each value, so it is only bounded whenMaxInValuesis set. The log and throw levels each decide that with their ownMaxInValues, so the shape carries the unbounded row types both with lists limited and without.DbSetcan be looked up, with only filters, ordering,Skip,Take,Distinct,OfTypeand options such asIncludebetween them. AfterConcat,SelectMany,JoinorSelect, or onFromSql, the same key can be in many rows.Includes Entity Framework ignores are not counted
MaxSingleQueryCollectionscounted 2 forDepartments.Include(_ => _.Employees).Include(_ => _.Projects).Select(_ => _.Name), which loads no collections. An Include before aSelectthat returns no entity, or an aggregate likeCount(), is now skipped. A projection that could return an entity, including one passing it to a method, keeps the Includes counted.MaxIncludesandMaxIncludeDepthstill count every Include as written.Checked against Entity Framework on six queries: in each, the count agrees with Entity Framework's
MultipleCollectionIncludeWarningand with the joins in the SQL it generates.Tests
38 new tests, in
KeyLookupTests(with aKeyContextmodel for composite, alternate, unique index and derived keys),UnboundedTestsandShapeTests. The full suite passes locally, 216 tests including the LocalDB ones.