Skip to content
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

Query: Prevent client evaluation and N+1 queries when calling result operators before other operators #6611

Closed
felipepessoto opened this issue Sep 27, 2016 · 6 comments
Assignees
Labels
area-perf closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-enhancement
Milestone

Comments

@felipepessoto
Copy link
Contributor

felipepessoto commented Sep 27, 2016

Steps to reproduce

Full example: EfCoreMultipleQueriesIssue.zip

IQueryable queryBug = db.Posts.OrderBy(x => x.Id).Include(x => x.PostTags).Where(x => x.PostTags.Any());
//Hundreds of
//SELECT[p].[PostId]
//FROM[PostTag] AS[p]

IQueryable queryBug2 = db.Posts.OrderBy(x => x.Id).Include(x => x.PostTags).Where(x => x.PostCategories.Any());
//Hundreds of
//SELECT[p].[PostId]
//FROM[PostTag] AS[p]

IQueryable queryOk = db.Posts.Where(x => x.PostTags.Any()).OrderBy(x => x.Id).Include(x => x.PostTags);

IQueryable queryOk2 = db.Posts.Include(x => x.PostTags).Where(x => x.PostTags.Any()).OrderBy(x => x.Id);

IQueryable queryOk3 = db.Posts.Include(x => x.PostTags).OrderBy(x => x.Id).Where(x => x.PostTags.Any());

The issue

Looks like part of the query is being evaluated locally, generating all those queries. It's not clear why.

felipepessoto added a commit to felipepessoto/FujiyBlog that referenced this issue Oct 1, 2016
@rowanmiller rowanmiller added this to the 1.1.0 milestone Oct 3, 2016
@maumar maumar modified the milestones: 1.2.0, 1.1.0 Oct 5, 2016
@maumar
Copy link
Contributor

maumar commented Oct 22, 2016

Initially when the query is parsed, each Include call is treated as result operator, effectively producing a subquery, unless it's the last operation, e.g.:

db.Posts.Include(x => x.PostTags).Where(x => x.PostTags.Any()).OrderBy(x => x.Id)

translates to:

from Post x in 
    (from Post x2 in DbSet<Post>
    select x2)
    .Include(x.PostTags)
where 
    (from PostTag <generated>_1 in x.PostTags
    select <generated>_1)
    .Any()
order by x.Id asc
select x

in the next step we extract the include operators out of the query:

from Post x in 
    from Post x2 in DbSet<Post>
    select x2
where 
    (from PostTag <generated>_1 in x.PostTags
    select <generated>_1)
    .Any()
order by x.Id asc
select x

and then we remove redundant subqueries:

from Post x in DbSet<Post>
where 
    (from PostTag <generated>_1 in x.PostTags
    select <generated>_1)
    .Any()
order by x.Id asc
select x

However, if there is orderby before the include

db.Posts.OrderBy(x => x.Id).Include(x => x.PostTags).Where(x => x.PostTags.Any())

we get a more complicated query:

from Post x in 
    (from Post x2 in DbSet<Post>
    order by x2.Id asc
    select x2)
    .Include(x.PostTags)
where 
    (from PostTag <generated>_1 in x.PostTags
    select <generated>_1)
    .Any()
select x

We extract include operators, but we can't remove the additional subquery it generated because of the orderby:

from Post x in 
    from Post x2 in DbSet<Post>
    order by x2.Id asc
    select x2
where 
    (from PostTag <generated>_1 in x.PostTags
    select <generated>_1)
    .Any()
select x

@maumar
Copy link
Contributor

maumar commented Oct 22, 2016

Perhaps, this could be improved for some cases by lifting orderby out of the subquery into the main query:

from Post x in 
    from Post x2 in DbSet<Post>
    order by x2.Id asc
    select x2
select x

could be translated into:

from Post x in 
    from Post x2 in DbSet<Post>
    select x2
order by x.Id asc
select x

and then subquery could be optimized out:

from Post x in DbSet<Post>
order by x.Id asc
select x

If the outer query has an orderby:

from Post x in 
    from Post x2 in DbSet<Post>
    order by x2.Id asc
    select x2
order by x.Name desc
select x

we could do something like this (append inner ordeby to the end of the outer one):

from Post x in DbSet<Post>
order by x.Name desc, x.Id asc
select x

Question is what should we do when there are multiple subqueries due to joins or few levels of subqueries

@maumar
Copy link
Contributor

maumar commented Jan 31, 2017

reassigning to Smit as he is working on a similar issue

@maumar
Copy link
Contributor

maumar commented Apr 3, 2017

poaching this

@maumar maumar assigned maumar and unassigned smitpatel Apr 3, 2017
maumar added a commit that referenced this issue Apr 4, 2017
…en Include then Where to a one to many relationship

Problem was that during initial parsing of query model, Include, AsTracking, AsNoTracking force subqueries to be created (since they are result operators). Those operators are then removed, but the subqueries persist, making it much harder to translate queries efficiently. We try to prune "empty" subqueries, but sometimes it is safe to lift subqueries that contain body clauses.

Fix is to recognize those patterns and perform the lift during query model optimizations. We do it for cases when the subquery is coming from the MainFromClause, it doesn't contain any result operators and is projecting it's MainFromClause only in the selector.
The optimization is the most prominent for queries with Include but will help other cases also.
maumar added a commit that referenced this issue Apr 4, 2017
…en Include then Where to a one to many relationship

Problem was that during initial parsing of query model, Include, AsTracking, AsNoTracking force subqueries to be created (since they are result operators). Those operators are then removed, but the subqueries persist, making it much harder to translate queries efficiently. We try to prune "empty" subqueries, but sometimes it is safe to lift subqueries that contain body clauses.

Fix is to recognize those patterns and perform the lift during query model optimizations. We do it for cases when the subquery is coming from the MainFromClause, it doesn't contain any result operators and is projecting it's MainFromClause only in the selector.
The optimization is the most prominent for queries with Include but will help other cases also.
@maumar
Copy link
Contributor

maumar commented Apr 5, 2017

Fixed in f022d66

@maumar maumar closed this as completed Apr 5, 2017
@maumar maumar added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Apr 5, 2017
@felipepessoto
Copy link
Contributor Author

Thanks! @maumar

@ajcvickers ajcvickers changed the title Hundred of queries being ran. When calling OrderBy, then Include then Where to a one to many relationship Query: Many queries run when calling OrderBy, then Include, then Where for a one-to-many relationship May 9, 2017
@divega divega changed the title Query: Many queries run when calling OrderBy, then Include, then Where for a one-to-many relationship Query: Prevent client evaluation and N+1 queries when calling result operators before other operators May 10, 2017
@ajcvickers ajcvickers modified the milestones: 2.0.0-preview1, 2.0.0 Oct 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-perf closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-enhancement
Projects
None yet
Development

No branches or pull requests

5 participants