Remove redundant join for table splitting

Open
#10,067 3 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
38/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Stale
Tech stack
csharp, sql
Domain
databases

Research direction

Start with the table-splitting model and mapping in the reproduction, then compare the SQL generated for the queries with and without Include. Trace the table-splitting include query path and determine whether the self-join can be omitted without losing the cancellation fields. Done means the include query loads the mapped columns without the redundant self-join and preserves the expected results.

Written by the indexing model from the issue text.

Description

area-perf area-query

When using table splitting, EF Core is joining the table with itself to get the second entity field. It seems that was not the case in EF6 (see Select scenario, test C).

The objective is not to load all the columns of a table each time, but group them by functionality and load them when they are going to be used.

Am I doing anything wrong? Is there anything I can do better?

Steps to reproduce
Model
public class PurchaseOrder
{
    public int Id { get; set; }
    public string Status { get; set; }
    public PurchaseOrderCancellation Cancellation { get; set; }
}

public class PurchaseOrderCancellation
{
    public string CancelReason { get; set; }
    public DateTime? CancelDate { get; set; }
}
Mapping
class PurchaseOrderConfig : IEntityTypeConfiguration<PurchaseOrder>
{
    public void Configure(EntityTypeBuilder<PurchaseOrder> builder)
    {
        builder.ToTable("purchase_order");
        builder.HasKey(x => x.Id);
        builder.Property(x => x.Id).HasColumnName("id").ForSqlServerUseSequenceHiLo();
        builder.Property(x => x.Status).HasColumnName("status");
        builder.HasOne(x => x.Cancellation).WithOne().HasForeignKey<PurchaseOrder>(x => x.Id);
    }
}

class PurchaseOrderCancellationConfig : IEntityTypeConfiguration<PurchaseOrderCancellation>
{
    public void Configure(EntityTypeBuilder<PurchaseOrderCancellation> builder)
    {
        builder.ToTable("purchase_order");
        builder.Property(x => x.CancelReason).HasColumnName("cancel_reason").HasMaxLength(500);
        builder.Property(x => x.CancelDate).HasColumnName("cancel_date").HasColumnType("datetime2");
        builder.Property<int>("Id").HasColumnName("id").ForSqlServerUseSequenceHiLo();
        builder.HasKey("Id");
    }
}
Queries:
Without include
var query1 = ctx.Set<PurchaseOrder>()
                .Where(x => x.Id == 1)
                .ToArray();
SELECT [x].[id], [x].[status]
FROM [purchase_order] AS [x]
WHERE [x].[id] = 1
With include
var query2 = ctx.Set<PurchaseOrder>()
                .Include(x => x.Cancellation)
                .Where(x => x.Id == 1);
SELECT [x].[id], [x].[status], [x.Cancellation].[id], [x.Cancellation].[cancel_date], [x.Cancellation].[cancel_reason]
FROM [purchase_order] AS [x]
INNER JOIN [purchase_order] AS [x.Cancellation] ON [x].[id] = [x.Cancellation].[id]
WHERE [x].[id] = 1
Further technical details

EF Core version: 2.0
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating system: Windows 10
IDE: VS2017 15.3

Dominant language
C#
Stars
14.8k
Forks
3.4k
Avg merge
2d 5h
Merged PRs (30d)
134

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from dotnet/efcore

All issues in dotnet/efcore

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.