Skip to content

Query contains non-consecutive table aliases when tables are pruned #32757

@roji

Description

@roji

Note: I do not think this is a bug

We generate table aliases based on the first character of the table name, followed by an incrementing counter for uniquification; aliases are thus generally consecutive (we even have DEBUG-only checks that attempt to validate this).

However, when a table gets pruned, that causes a "hole" in the alias numbering. For example, with the following contrived TPT query:

_ = ctx.Blogs.Count(b =>
    ctx.Blogs.Count(b => b.Name == "foo") == 0 && ctx.BlogsExtraSpecial.Count(b => b.Name == "foo") == 0);
Full repro
await using var ctx = new BlogContext();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();

_ = ctx.Blogs.Count(b =>
    ctx.Blogs.Count(b => b.Name == "foo") == 0 && ctx.BlogsExtraSpecial.Count(b => b.Name == "foo") == 0);

public class BlogContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<BlogSpecial> BlogsSpecial { get; set; }
    public DbSet<BlogExtraSpecial> BlogsExtraSpecial { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer("Server=localhost;Database=test;User=SA;Password=Abcd5678;Connect Timeout=60;ConnectRetryCount=0;Encrypt=false")
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>().UseTptMappingStrategy();
    }
}

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class BlogSpecial : Blog
{
    public string SpecialProperty { get; set; }
}

public class BlogExtraSpecial : BlogSpecial
{
    public string ExtraSpecialProperty { get; set; }
}

... we generate the following SQL:

SELECT COUNT(*)
FROM [Blogs] AS [b]
WHERE (
    SELECT COUNT(*)
    FROM [Blogs] AS [b2]
    WHERE [b2].[Name] = N'foo') = 0 AND (
    SELECT COUNT(*)
    FROM [Blogs] AS [b5]

... with only [b], [b2] and [b5]. This is because ctx.Blogs generally causes 3 tables to be added (entire TPT hierarchy), but COUNT(*) causes the child tables to be pruned, isnce the only referenced property is in the root (Name).

I do not really consider this a bug, but there's some effort in the code to maintain consecutive aliases. Fully maintaining that in all cases requires another pass to check and rewrite aliases, and that doesn't seem worth it.

Metadata

Metadata

Assignees

No fields configured for Feature.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions