dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.71k stars 3.17k forks source link

Cosmos: Projecting out owned entities retrieves the entire document #34067

Open roji opened 3 months ago

roji commented 3 months ago
public virtual Task Project_owned_reference_navigation_which_owns_additional(bool async)
    => AssertQuery(
        async,
        ss => ss.Set<OwnedPerson>().OrderBy(o => o.Id).Select(p => p.PersonAddress));

Since PersonAddress owns further entity types (OwnedCountry), the expression inside the Select() contains IncludeExpression; this leads CosmosProjectionBindingExpressionVisitor into a specific path where we end up projecting the entire document. Compare how this works in relational - CosmosProjectionBindingExpressionVisitor is one place where Cosmos is still quite behind.

roji commented 2 months ago

Not actually closed by #34355

ajcvickers commented 1 month ago

@roji This is not limited to owned entities that have additional types. Projecting either an owned reference or collection results in bringing back the entire document. For example:

    await context
        .Set<Customer>()
        .AsNoTracking()
        .Select(e => e.Addresses)
        .ToListAsync();

    await context
        .Set<Customer>()
        .AsNoTracking()
        .Select(e => e.PhoneNumber)
        .ToListAsync();
info: 8/28/2024 12:33:56.712 CosmosEventId.ExecutingSqlQuery[30100] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executing SQL query for container 'SomeDbContext' in partition 'None' [Parameters=[]]
      SELECT VALUE c
      FROM root c
info: 8/28/2024 12:33:56.719 CosmosEventId.ExecutedReadNext[30102] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed ReadNext (6.7595 ms, 2.25 RU) ActivityId='9616a1c2-9e2a-4858-a426-68e6001d2ac1', Container='SomeDbContext', Partition='None', Parameters=[]
      SELECT VALUE c
      FROM root c
public class Customer
{
    public Guid Id { get; set; }
    public required string Name { get; set; }
    public required string PrimaryCountry { get; set; }
    public required string Region { get; set; }
    public List<string>? Notes { get; set; }
    public Phone PhoneNumber { get; set; }
    public List<Address> Addresses { get; } = new();
}

public class Address
{
    public required string Street { get; set; }
    public required string City { get; set; }
    public required string Postcode { get; set; }
    public required string Country { get; set; }
}

public class Phone
{
    public required int CountryCode { get; set; }
    public required string Number { get; set; }
}
roji commented 1 month ago

@ajcvickers understood and thanks for checking... I think this makes this even more important to fix (in 10).