question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

EF Core generates non-optimized SQL for .Include().First() queries

See original GitHub issue

Pomelo is not correctly generating SQL code. The following code produces the following SQL:

SELECT 't'.'UserId', 't'.'Username', 'o'.'OrderId', 'o'.'Total', 'o'.'UserId'
FROM (
    SELECT 'u'.'UserId', 'u'.'Username'
    FROM 'Users' AS 'u'
    WHERE 'u'.'UserId' = 1
    LIMIT 1
) AS 't'
LEFT JOIN 'Orders' AS 'o' ON 't'.'UserId' = 'o'.'UserId'
ORDER BY 't'.'UserId'

There is absolutely no reason that a second select statement is being generated. It is redundant and as far as I am aware it is a performance problem. It should be generating the following SQL code:

SELECT u.userid, u.username, o.orderid, o.total, o.userid 
FROM users AS u 
JOIN orders AS o ON o.userid = u.userid
WHERE u.userid = 1

C# Dummy code

namespace LearningCSharp1
{
    class Program
    {
        public static async Task Main(string[] args)
        {
            var context = new Context();

            var user = await context.Users
                .Where(u => u.UserId == 1)
                .Include(u => u.Order)
                .FirstAsync();
        }
    }

    class User
    {
        public int UserId { get; set; }
        public string Username { get; set; } = null!;
        public IEnumerable<Order> Order { get; set; } = new List<Order>();

        public override string ToString()
        {
            return $"Id: {UserId} | Username: {Username}";
        }
    }

    class Order
    {
        public int OrderId { get; set; }
        public decimal Total { get; set; }
        public User User { get; set; } = null!;
        public int UserId { get; set; }
    }

    class Context : DbContext
    {
        public DbSet<User> Users => Set<User>();
        public DbSet<Order> Orders => Set<Order>();

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseMySql(
                    "Server=localhost;Database=efcore;User=root;Password=password.",
                    ServerVersion.AutoDetect("Server=localhost;Database=efcore;User=root;Password=password.")
                )
                .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
                .LogTo(Console.WriteLine, LogLevel.Information)
                .EnableSensitiveDataLogging();
        }
    }
}

Further technical details

Pomelo.EntityFrameworkCore.MySql version: 6.0.1 Microsoft.AspNetCore.App version: 6.0.6

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:9

github_iconTop GitHub Comments

2reactions
lauxjpncommented, Jun 17, 2022

Now I realized why perf numbers are that way @lauxjpn . Both are running different SQL which generates different results. MultiSelect version takes first User and associated orders which can be one or more so result set could have 5 rows too. SingleSelect is doing Limit 1 on top level so it will only take first User and first matching order and discard other orders if they are matching and should be included in the result when populating Orders collection via EF. Hence it is only selecting 1 row and not 5. Hence results makes sense, why SingleSelect is faster.

@smitpatel Good catch, that’s it! And that is why the statement needs to be generated by EF Core the way it already currently is, since otherwise only the first Order entity would be returned, which would be an incorrect translation of the LINQ query.

So looks good and works as expected.

2reactions
smitpatelcommented, Jun 17, 2022

Why do both single and first produce the same SQL?

They don’t produce same SQL either. The LIMIT in both queries is different number.

Now I realized why perf numbers are that way @lauxjpn . Both are running different SQL which generates different results. MultiSelect version takes first User and associated orders which can be one or more so result set could have 5 rows too. SingleSelect is doing Limit 1 on top level so it will only take first User and first matching order and discard other orders if they are matching and should be included in the result when populating Orders collection via EF. Hence it is only selecting 1 row and not 5. Hence results makes sense, why SingleSelect is faster.

Read more comments on GitHub >

github_iconTop Results From Across the Web

EF Core 1.0 - Include() generates more than one queries
I am using EF 7.0.0-rc1-final. The following statement generates multiple queries on the server. Is this normal or I am missing something ?...
Read more >
SQL Queries - EF Core
SQL queries are useful if the query you want can't be expressed using LINQ, or if a LINQ query causes EF to generate...
Read more >
Query IncludeOptimized in Entity Framework Plus (EF Plus)
The SQL generated by EF+ IncludeOptimized performs multiple SELECT but drastically decreases the amount of data transferred.
Read more >
Querying in Entity Framework Core
Querying in Entity Framework Core remains the same as in EF 6.x, with more optimized SQL queries and the ability to include C#/VB....
Read more >
Writing Better Performing Queries with LINQ on EF Core 6.0 ⚙️
With LINQ, a query is a first-class language construct, just like classes, ... for EF generate the needed groupings, and can have an...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found