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

Using .Count on a ICollection<T> does not produce EXISTS as it does when using List<T> #35365

Open
alaatm opened this issue Dec 20, 2024 · 4 comments

Comments

@alaatm
Copy link
Contributor

alaatm commented Dec 20, 2024

In the whats new in .net 9, it says that queries using .Count are now optimized to use EXISTS instead of COUNT. However, this seems to be true for collections of type List<T>. We use ICollection<T> in our projects and we would love to replace .Any() with .Count > 0 but this optimization isn't applied as shown below

public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; set; } = default!;
}

public class Post
{
    public int Id { get; set; }
}

context.Blogs.Where(b => b.Posts.Count > 0);

produces:

SELECT [b].[Id]
FROM [Blogs] AS [b]
WHERE (
    SELECT COUNT(*)
    FROM [Post] AS [p]
    WHERE [b].[Id] = [p].[BlogId]) > 0

Using context.Blogs.Where(b => b.Posts.Count != 0); will make it even worse:

SELECT [b].[Id]
FROM [Blogs] AS [b]
WHERE (
    SELECT COUNT(*)
    FROM [Post] AS [p]
    WHERE [b].[Id] = [p].[BlogId]) <> 0 OR (
    SELECT COUNT(*)
    FROM [Post] AS [p]
    WHERE [b].[Id] = [p].[BlogId]) IS NULL

EF Core version: 9.0.0
Database provider: Microsoft.EntityFrameworkCore.SqlServer

@roji
Copy link
Member

roji commented Dec 21, 2024

@cincuranet interested in giving this a look?

@ChrisJollyAU
Copy link
Contributor

@roji See QueryableMethodNormalizingExpressionVisitor line 73/74

when (member.DeclaringType.GetGenericTypeDefinition().GetInterfaces().Any(

GetInterfaces only gets those inherited/implemented. Normally fine but if the type is also an interface that is NOT returned as part of the list. So in this case member.DeclaringType is an ICollection, however GetInterfaces only returns IEnumerable and its generic version.

Changing that section to the following should do the trick

when (member.DeclaringType.GetGenericTypeDefinition() == typeof(ICollection<>) || member.DeclaringType.GetGenericTypeDefinition().GetInterfaces().Any(
    x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>)))

@roji
Copy link
Member

roji commented Dec 22, 2024

@ChrisJollyAU thanks for looking into it... Are you interested in submitting a PR?

@ChrisJollyAU
Copy link
Contributor

Sure. I haven't got a test sorted yet so will do that sometime

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants