dotnet / dotnet/efcore

When using UNION with owned entity types a LEFT JOIN to same table is generated

Open
#29,300 2 comments 15 reactions 0 assignees View on GitHub
area-perf area-query area-set-operations customer-reported needs-design
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

Hi,

this bug is related to #18299 which has already been fixed with EF 5.0. I found the following set-up where an overly complicated SQL is produced.

Please see the _very_ simplified example where a `Project` owns one optional `Address`. When we `UNION` two `Project` queries, a `LEFT JOIN` is performed for the `Address` owned entity type. When just selecting projects without a `UNION`, no left join is produced as expected (see output below):

```C#
using Microsoft.EntityFrameworkCore;

using var context = new DataContext();

var query1 = context.Projects;
var query2 = context.Projects;

Console.WriteLine("query1.Union(query2):\n=====================");
Console.WriteLine(query1.Union(query2).ToQueryString());
Console.WriteLine();
Console.WriteLine("query1:\n======");
Console.WriteLine(query1.ToQueryString());

public class Project
{
public Guid Id { get; set; }
public string? Name { get; set; }
public Address? Address { get; set; }
}

public record Address(
string Street,
string ZipCode,
string City,
string IsoCountryCode)
{
}

public class DataContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=.\;Database=EFCoreDemo;Trusted_Connection=True;MultipleActiveResultSets=true");
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity(e => e.OwnsOne(e => e.Address));
}

public DbSet Projects => Set();
}
```

The following output is produced:

```
query1.Union(query2):
=====================
SELECT [t].[Id], [t].[Name], [t0].[Id], [t0].[Address_City], [t0].[Address_IsoCountryCode], [t0].[Address_Street], [t0].[Address_ZipCode]
FROM (
SELECT [p].[Id], [p].[Name]
FROM [Projects] AS [p]
UNION
SELECT [p0].[Id], [p0].[Name]
FROM [Projects] AS [p0]
) AS [t]
LEFT JOIN (
SELECT [p1].[Id], [p1].[Address_City], [p1].[Address_IsoCountryCode], [p1].[Address_Street], [p1].[Address_ZipCode]
FROM [Projects] AS [p1]
INNER JOIN [Projects] AS [p2] ON [p1].[Id] = [p2].[Id]
WHERE ((([p1].[Address_City] IS NOT NULL) AND ([p1].[Address_IsoCountryCode] IS NOT NULL)) AND ([p1].[Address_Street] IS NOT NULL)) AND ([p1].[Address_ZipCode] IS NOT NULL)
) AS [t0] ON [t].[Id] = CASE
WHEN ((([t0].[Address_City] IS NOT NULL) AND ([t0].[Address_IsoCountryCode] IS NOT NULL)) AND ([t0].[Address_Street] IS NOT NULL)) AND ([t0].[Address_ZipCode] IS NOT NULL) THEN [t0].[Id]
END

query1:
======
SELECT [p].[Id], [p].[Name], [p].[Address_City], [p].[Address_IsoCountryCode], [p].[Address_Street], [p].[Address_ZipCode]
FROM [Projects] AS [p]
```

Why is a left join produced in the `UNION` example? I would have expected that the `Address` columns are selected within the inner `FROM (...) AS [t]` query and no left join is needed.

The left join can cause major performance issues as already discusssed in #18299. Please note that this is just a very simple example and the two UNION queries we actually use are more complicated.

### Additional info

To emphasise the performance penantly we encounter with the `LEFT JOIN` in our actual query (which I cannot share here): We see a _major_ difference in logical reads:
* 6 logical reads when the `LEFT JOIN` is removed and the owned entity type columns are directly selected in the two UNION statements
* _27746 (!)_ logical reads when using the `LEFT JOIN`.

The table has ~90k entries.

### Include provider and version information

EF Core version: 6.0.9
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 6.0
Operating system: Windows

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.