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

Having inheritance creates excessive query #15374

Closed
RamunasAdamonis opened this issue Apr 16, 2019 · 8 comments
Closed

Having inheritance creates excessive query #15374

RamunasAdamonis opened this issue Apr 16, 2019 · 8 comments
Assignees
Labels
area-perf area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported punted-for-3.0 type-enhancement
Milestone

Comments

@RamunasAdamonis
Copy link

I had a simple class with some value objects, but decided to extend it by using inheritance and having discriminator. It started to generate some excessive queries. I replicated this with sample below:

using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;


public class JustARegularClass
{
    [Key]
    public int Id { get; set; }
    public ValueObject VO1 { get; set; }
    public ValueObject VO2 { get; set; }
    public ValueObject VO3 { get; set; }
    public ValueObject VO4 { get; set; }
    public ValueObject VO5 { get; set; }
}

public abstract class BaseClass
{
    [Key]
    public int Id { get; set; }
    public ValueObject VO1 { get; set; }
    public ValueObject VO2 { get; set; }
    public ValueObject VO3 { get; set; }
    public ValueObject VO4 { get; set; }
    public ValueObject VO5 { get; set; }
}

public class ChildClass : BaseClass
{
    public int MyColA { get; set; }
}

public class ValueObject
{
    public ValueObject(int myInt, string myString)
    {
        MyInt = myInt;
        MyString = myString;
    }

    public int MyInt { get; private set; }
    public string MyString { get; private set; }
}

public enum MyType
{
    Type0 = 0,
    Type1 = 1
}

public class MyContext : DbContext
{
    public MyContext(DbContextOptions options) : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<JustARegularClass>(e =>
        {
            e.OwnsOne(nav => nav.VO1);
            e.OwnsOne(nav => nav.VO2);
            e.OwnsOne(nav => nav.VO3);
            e.OwnsOne(nav => nav.VO4);
            e.OwnsOne(nav => nav.VO5);
        });

        modelBuilder.Entity<BaseClass>(e =>
        {
            e.OwnsOne(nav => nav.VO1);
            e.OwnsOne(nav => nav.VO2);
            e.OwnsOne(nav => nav.VO3);
            e.OwnsOne(nav => nav.VO4);
            e.OwnsOne(nav => nav.VO5);
        });

        modelBuilder.Entity<BaseClass>().HasDiscriminator<MyType>(nameof(MyType))
            .HasValue<BaseClass>(MyType.Type0)
            .HasValue<ChildClass>(MyType.Type1);
    }

    public DbSet<JustARegularClass> RegularClasses { get; set; }
    public DbSet<ChildClass> ChildClasses { get; set; }
    public DbSet<BaseClass> BaseClasses { get; set; }
}
using Microsoft.AspNetCore.Mvc;

namespace ef.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        private readonly MyContext _context;
        public ValuesController(MyContext context)
        {
            _context = context;
        }

        [HttpGet("RegularClass")]
        public ActionResult<JustARegularClass> GetRegular()
        {
            var result = _context.RegularClasses.Find(1);
            return Ok(result);
        }

        [HttpGet("ChildClass")]
        public ActionResult<ChildClass> GetChild()
        {
            var result = _context.ChildClasses.Find(1);
            return Ok(result);
        }
    }
}

Calling api/values/RegularClass generates normal query:

exec sp_executesql N'SELECT TOP(1) [e].[Id], [e].[Id], [e].[VO5_MyInt], [e].[VO5_MyString], [e].[Id], [e].[VO4_MyInt], [e].[VO4_MyString], [e].[Id], [e].[VO3_MyInt], [e].[VO3_MyString], [e].[Id], [e].[VO2_MyInt], [e].[VO2_MyString], [e].[Id], [e].[VO1_MyInt], [e].[VO1_MyString]
FROM [RegularClasses] AS [e]
WHERE [e].[Id] = @__get_Item_0',N'@__get_Item_0 int',@__get_Item_0=1

while calling api/values/ChildClass generates this monster query:

exec sp_executesql N'SELECT TOP(1) [e].[Id], [e].[MyType], [e].[MyColA], [t].[Id], [t].[VO5_MyInt], [t].[VO5_MyString], [t0].[Id], [t0].[VO4_MyInt], [t0].[VO4_MyString], [t1].[Id], [t1].[VO3_MyInt], [t1].[VO3_MyString], [t2].[Id], [t2].[VO2_MyInt], [t2].[VO2_MyString], [t3].[Id], [t3].[VO1_MyInt], [t3].[VO1_MyString]
FROM [BaseClasses] AS [e]
LEFT JOIN (
    SELECT [e.VO5].*
    FROM [BaseClasses] AS [e.VO5]
    WHERE [e.VO5].[MyType] = 1
) AS [t] ON [e].[Id] = [t].[Id]
LEFT JOIN (
    SELECT [e.VO4].*
    FROM [BaseClasses] AS [e.VO4]
    WHERE [e.VO4].[MyType] = 1
) AS [t0] ON [e].[Id] = [t0].[Id]
LEFT JOIN (
    SELECT [e.VO3].*
    FROM [BaseClasses] AS [e.VO3]
    WHERE [e.VO3].[MyType] = 1
) AS [t1] ON [e].[Id] = [t1].[Id]
LEFT JOIN (
    SELECT [e.VO2].*
    FROM [BaseClasses] AS [e.VO2]
    WHERE [e.VO2].[MyType] = 1
) AS [t2] ON [e].[Id] = [t2].[Id]
LEFT JOIN (
    SELECT [e.VO1].*
    FROM [BaseClasses] AS [e.VO1]
    WHERE [e.VO1].[MyType] = 1
) AS [t3] ON [e].[Id] = [t3].[Id]
WHERE ([e].[MyType] = 1) AND ([e].[Id] = @__get_Item_0)',N'@__get_Item_0 int',@__get_Item_0=1

If I compare the execution plans I get this:

image

My query now runs 6 times slower because of what seems to be simple design upgrade. Any thoughts on this?

Further technical details

EF Core version: 2.2.3
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating system: Windows 10
IDE: Visual Studio Code 1.33.1

@ajcvickers
Copy link
Member

@smitpatel is this a duplicate?

@smitpatel
Copy link
Contributor

No.

@divega divega added this to the 3.0.0 milestone Apr 29, 2019
@divega
Copy link
Contributor

divega commented Apr 29, 2019

Note from triage: We will see if we can optimize this in 3.0 or otherwise will punt.

@divega divega modified the milestones: 3.0.0, Backlog Jun 21, 2019
@smitpatel smitpatel removed their assignment Jun 24, 2019
@smitpatel
Copy link
Contributor

In new pipeline, we will at least not cause subqueries. We still generated joins which we can optimize easier.

@AndriySvyryd
Copy link
Member

@smitpatel Are you sure this is not a duplicate of #3419?

@smitpatel
Copy link
Contributor

This and #3419 both are them are not capturing the join elimination we are going to do. That is remove join for the cases when the table is same in database. I would suggest filing new issue and close both of them.

@smitpatel
Copy link
Contributor

Using 5.0 preview7
Generated SQLs

      SELECT TOP(1) [r].[Id], [r].[VO1_MyInt], [r].[VO1_MyString], [r].[VO2_MyInt], [r].[VO2_MyString], [r].[VO3_MyInt], [r].[VO3_MyString], [r].[VO4_MyInt], [r].[VO4_MyString], [r].[VO5_MyInt], [r].[VO5_MyString]
      FROM [RegularClasses] AS [r]
      WHERE [r].[Id] = @__p_0

      SELECT TOP(1) [b].[Id], [b].[MyType], [b].[MyColA], [b].[VO1_MyInt], [b].[VO1_MyString], [b].[VO2_MyInt], [b].[VO2_MyString], [b].[VO3_MyInt], [b].[VO3_MyString], [b].[VO4_MyInt], [b].[VO4_MyString], [b].[VO5_MyInt], [b].[VO5_MyString]
      FROM [BaseClasses] AS [b]
      WHERE [b].[Id] = @__p_0

@smitpatel smitpatel added closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. and removed consider-for-current-release labels Jul 28, 2020
@smitpatel smitpatel modified the milestones: Backlog, 5.0.0-preview7 Jul 28, 2020
@RamunasAdamonis
Copy link
Author

Great news, thank you!

@ajcvickers ajcvickers modified the milestones: 5.0.0-preview7, 5.0.0 Nov 7, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-perf area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported punted-for-3.0 type-enhancement
Projects
None yet
Development

No branches or pull requests

5 participants