Skip to content

Many sql join when use read-write projectable property #217

Description

@CoreDX9

If an entity contains read-write properties and there are one to many sub entities, including sub entities in the query will result in the generated query having many join clauses. The number of join clauses is the same as the number of properties of the entity.

Minimum reproducible code.

Entities

public class Entity1
{
    public virtual int Id { get; set; }
    public virtual string? Name1 { get; set; }

    [NotMapped]
    [Projectable(NullConditionalRewriteSupport = NullConditionalRewriteSupport.Ignore, UseMemberBody = nameof(Name1LenQuery))]
    public virtual int? Name1Len { get; set; }

    protected virtual int? Name1LenQuery => Name1?.Length;

    public virtual List<Entity2> Entity2s { get; set; } = [];
}

public class Entity2
{
    public virtual int Id { get; set; }
    public virtual string? Name2 { get; set; }

    public virtual int? Entity1Id { get; set; }
    public virtual Entity1? Entity1 { get; set; }
}

The projectable property Name1Len and navigation Entity2s are the key part that causes the problem.

DbContext

public class DemoDbContext(DbContextOptions<DemoDbContext> options) : DbContext(options)
{
    public DbSet<Entity1> Entity1s => Set<Entity1>();
    public DbSet<Entity2> Entity2s => Set<Entity2>();
}

QueryExpressionMonitor(for breakpoint to view final expression)

public class MonitingExpressionInterceptor : IQueryExpressionInterceptor
{
    public Expression QueryCompilationStarting(Expression queryExpression, QueryExpressionEventData eventData)
    {
        return queryExpression;
    }
}

Program.Main

using var context = new DemoDbContext(new DbContextOptionsBuilder<DemoDbContext>()
    .UseSqlite("Data Source=test.db")
    .UseProjectables()
    .AddInterceptors(new MonitingExpressionInterceptor())
    .Options);

var q = context.Entity1s
    .AsNoTrackingWithIdentityResolution()
    .Include(e1 => e1.Entity2s);

var qs = q.ToQueryString();

;

Final expression(output from VS 2026 plugin: ReadableExpressions v4.8.0)

[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression]
    .AsNoTrackingWithIdentityResolution()
    .Include((Entity1 e1) => e1.Entity2s)
    .Select(
        (Entity1 entity1) => new Entity1
        {
            k__BackingField = entity1.k__BackingField,
            k__BackingField = entity1.k__BackingField,
            k__BackingField = entity1.k__BackingField,
            Name1Len = (int?)entity1.Name1.Length
        })

Entity1's Properties Id, Name1 and Entity2s has been rewritten as field.

SQL

SELECT "e"."Id", "e"."Name1", "e0"."Id", "e0"."Entity1Id", "e0"."Name2", "e1"."Id", "e1"."Entity1Id", "e1"."Name2", "e2"."Id", "e2"."Entity1Id", "e2"."Name2", length("e"."Name1")
FROM "Entity1s" AS "e"
LEFT JOIN "Entity2s" AS "e0" ON "e"."Id" = "e0"."Entity1Id"
LEFT JOIN "Entity2s" AS "e1" ON "e"."Id" = "e1"."Entity1Id"
LEFT JOIN "Entity2s" AS "e2" ON "e"."Id" = "e2"."Entity1Id"
ORDER BY "e"."Id", "e0"."Id", "e1"."Id"

The number of join clauses in the generated SQL is the same as the number of fields being rewritten.

Version info
Framework: net10.0
EntityFrameworkCore.Projectables: 6.0.5
Microsoft.EntityFrameworkCore.Sqlite: 10.0.9

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions