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

Group by with DefaultIfEmpty on a sub-query with a left join changes the subquery's joins to inner joins #27480

Open
Tracked by #30173
neoGeneva opened this issue Feb 21, 2022 · 3 comments
Labels
area-groupby area-query customer-reported punted-for-7.0 Originally planned for the EF Core 7.0 (EF7) release, but moved out due to resource constraints. type-bug
Milestone

Comments

@neoGeneva
Copy link

File a bug

The projection starts of being correct, but after joining to it using DefaultIfEmpty() the resulting query ends up incorrect. This only seems to happen when using from x in db.xx.Where().DefaultIfEmpty() syntax to join, join x in db.xx version seems okay (see below).

So, this sample code using Microsoft.EntityFrameworkCore.Sqlite but this also happens in Microsoft.EntityFrameworkCore.SqlServer. I've attached the full project here: Console3.zip

using Console3;
using Microsoft.EntityFrameworkCore;

using (var db = new BloggingContext())
{
    var postCount = db.Posts
        .GroupBy(x => x.BlogId, (x, g) => new { BlogId = x, Count = (int?)g.Count() });

    var totalCounts =
        from b in db.Blogs
        from pc1 in postCount.Where(x => x.BlogId == b.BlogId).DefaultIfEmpty()
        group new { b.BlogId, pc1.Count } by b.BlogId into g
        select new
        {
            BlogId = g.Key,
            Count = g.Sum(x => x.Count)
        };

    var query =
        from b in db.Blogs
        from c in totalCounts.Where(x => x.BlogId == b.BlogId).DefaultIfEmpty()
        select new
        {
            b.BlogId,
            b.Url,
            c.Count
        };

    Console.WriteLine(totalCounts.ToQueryString());
    Console.WriteLine(query.ToQueryString());
}

Produces the following output:

SELECT "b"."BlogId", COALESCE(SUM("t"."Count"), 0) AS "Count"
FROM "Blogs" AS "b"
LEFT JOIN (
    SELECT "p"."BlogId", COUNT(*) AS "Count"
    FROM "Posts" AS "p"
    GROUP BY "p"."BlogId"
) AS "t" ON "b"."BlogId" = "t"."BlogId"
GROUP BY "b"."BlogId"
SELECT "b"."BlogId", "b"."Url", "t0"."Count"
FROM "Blogs" AS "b"
LEFT JOIN (
    SELECT "b0"."BlogId", COALESCE(SUM("t"."Count"), 0) AS "Count"
    FROM "Blogs" AS "b0"
    INNER JOIN (
        SELECT "p"."BlogId", COUNT(*) AS "Count"
        FROM "Posts" AS "p"
        GROUP BY "p"."BlogId"
    ) AS "t" ON "b0"."BlogId" = "t"."BlogId"
    GROUP BY "b0"."BlogId"
) AS "t0" ON "b"."BlogId" = "t0"."BlogId"

As you can see the inner most join is changed for a left join to an inner join.

I've found that using more traditional join syntax produces the correct SQL. So this works:

    var totalCounts =
        from b in db.Blogs
        // from pc1 in postCount.Where(x => x.BlogId == b.BlogId).DefaultIfEmpty()
        join x in postCount on b.BlogId equals x.BlogId into x
        from pc1 in x.DefaultIfEmpty()
        group new { b.BlogId, pc1.Count } by b.BlogId into g
        select new
        {
            BlogId = g.Key,
            Count = g.Sum(x => x.Count)
        };

Include provider and version information

EF Core version: 6.0.2 (and I've confirmed it exists in 6.0.0 and 6.0.1)
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 6.0
Operating system: Windows 11
IDE: VSCode 1.64.2

@AndriySvyryd AndriySvyryd changed the title Left join on a sub-query with a left join changes the subquery's joins to inner joins Group by with DefaultIfEmpty on a sub-query with a left join changes the subquery's joins to inner joins Feb 22, 2022
@AndriySvyryd
Copy link
Member

Could be related to #27297

@ajcvickers ajcvickers added this to the 7.0.0 milestone Mar 11, 2022
@smitpatel
Copy link
Member

smitpatel commented May 3, 2022

CorrelationFindingExpressionVisitor removes DefaultIfEmpty but it is supposed to remove the one in the last chain. It ends removing nested inner ones too. Added regression test skipped with issue number. Also add regression test with non-group by scenario.

@smitpatel
Copy link
Member

Related #20291

@smitpatel smitpatel added the punted-for-7.0 Originally planned for the EF Core 7.0 (EF7) release, but moved out due to resource constraints. label Aug 13, 2022
@smitpatel smitpatel modified the milestones: 7.0.0, Backlog Aug 13, 2022
@smitpatel smitpatel removed their assignment Sep 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-groupby area-query customer-reported punted-for-7.0 Originally planned for the EF Core 7.0 (EF7) release, but moved out due to resource constraints. type-bug
Projects
None yet
Development

No branches or pull requests

4 participants