Amey
Amey

Reputation: 1296

Improving SQL query generated by LINQ to EF

I have student table and I want to write a Linq query to get multiple counts. The SQL query that Linq generates is too complicated and un-optimized.

Following is definition of my table:

[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](100) NULL,
[Age] [int] NULL,

I need to get one count with students with name = test and one count for students with age > 10. This is one of the query I have tried:

 var sql = from st in school.Students
                          group st by 1 into grp
                          select new
                          {
                              NameCount = grp.Count(k => k.Name == "Test"),
                              AgeCount = grp.Count(k => k.Age > 5)
                          };

The SQL query that is generated is:

SELECT 
    [Limit1].[C1] AS [C1], 
    [Limit1].[C2] AS [C2], 
    [Limit1].[C3] AS [C3]
    FROM ( SELECT TOP (1) 
        [Project2].[C1] AS [C1], 
        [Project2].[C2] AS [C2], 
        (SELECT 
            COUNT(1) AS [A1]
            FROM [dbo].[Student] AS [Extent3]
            WHERE ([Project2].[C1] = 1) AND ([Extent3].[Age] > 5)) AS [C3]
        FROM ( SELECT 
            [Distinct1].[C1] AS [C1], 
            (SELECT 
                COUNT(1) AS [A1]
                FROM [dbo].[Student] AS [Extent2]
                WHERE ([Distinct1].[C1] = 1) AND (N'Test' = [Extent2].[Name])) AS [C2]
            FROM ( SELECT DISTINCT 
                1 AS [C1]
                FROM [dbo].[Student] AS [Extent1]
            )  AS [Distinct1]
        )  AS [Project2]
    )  AS [Limit1]

For me this seems to be complex. This can be achieved by following simple query:

select COUNT(CASE WHEN st.Name = 'Test' THEN 1 ELSE 0 END) NameCount,
 COUNT(CASE WHEN st.Age > 5 THEN 1 ELSE 0 END) AgeCount from Student st

Is there a way in LINQ with which the SQL query that gets generated will have both the aggregation rather than having it two separate queries joined with nested queries?

Upvotes: 1

Views: 216

Answers (2)

NetMage
NetMage

Reputation: 26917

A much simpler query results from not using the unnecessary group by and just querying the table twice in the select:

var sql = from st in school.Students.Take(1)
          select new {
              NameCount = school.Students.Count(k => k.Name == "Test"),
              AgeCount = school.Students.Count(k => k.Age > 5)
          };

Upvotes: 0

Ivan Stoev
Ivan Stoev

Reputation: 205589

From my experience with EF6, conditional Sum (i.e. Sum(condition ? 1 : 0)) is translated much better to SQL than Count with predicate (i.e. Count(condition)):

var query =
    from st in school.Students
    group st by 1 into grp
    select new
    {
        NameCount = grp.Sum(k => k.Name == "Test" ? 1 : 0),
        AgeCount = grp.Sum(k => k.Age > 5 ? 1 : 0)
    };

Btw, your SQL example should be using SUM as well. In order to utilize the SQL COUNT which excludes NULLs, it should be ELSE NULL or no ELSE:

select COUNT(CASE WHEN st.Name = 'Test' THEN 1 END) NameCount,
    COUNT(CASE WHEN st.Age > 5 THEN 1 END) AgeCount
    from Student st

But there is no equivalent LINQ construct for this, hence no way to let EF6 generate such translation. But IMO the SUM is good enough equivalent.

Upvotes: 5

Related Questions