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.48k stars 3.12k forks source link

Cosmos: Stop nesting results in an extra JSON object #25527

Open roji opened 2 years ago

roji commented 2 years ago

Our basic Cosmos query looks like this:

SELECT c FROM root c

This produces results such as the following:

[
    {
        "c": {
            "id": "...",
            "Foo": 1,
        }
    },
    {
        "c": {
            "id": "...",
            "Foo": 2,
        }
    }
]

Instead, we could generate the following simpler star-based query:

SELECT VALUE c FROM root c

Which produces:

[
    {
        "id": "...",
        "Foo": 1,
    },
    {
        "id": "...",
        "Foo": 2,
    }
]
ajcvickers commented 4 months ago

Note: breaking change because it impacts raw queries.

roji commented 1 week ago

This creates issues since subqueries need to project out the bare object, rather than an object wrapping the object (interferes with query). We could have different logic for top-level projections (which are processed by the shaper) and subquery projections (which aren't), but we really should just do the right thing.

roji commented 1 week ago

Example test for trivial scalar projection, NorthwindSelectQueryTestBase.Select_scalar:

public virtual Task Select_scalar(bool async)
    => AssertQuery(
        async,
        ss => ss.Set<Customer>().Select(c => c.City));

Currently-generated NoSQL:

SELECT c["City"]
FROM root c
WHERE (c["Discriminator"] = "Customer")

Cosmos results:

[
    {
        "City": "Berlin"
    },
    {
        "City": "México D.F."
    },
    {
        "City": "México D.F."
    },
    {
        "City": "London"
    },
    ...
]

Proposed NoSQL:

SELECT VALUE c["City"]
FROM root c
WHERE (c["Discriminator"] = "Customer")

Cosmos results:

[
    "Berlin",
    "México D.F.",
    "México D.F.",
    "London",
    ...
]
roji commented 1 week ago

And the same thing for a structural type projected out of the database, NorthwindSelectQueryTestBase.Select_customer_identity:

public virtual Task Select_customer_identity(bool async)
    => AssertQuery(
        async,
        ss => ss.Set<Customer>().Select(c => c));

Currently-generated NoSQL:

SELECT c
FROM root c
WHERE (c["Discriminator"] = "Customer")

Cosmos results:

[
    {
        "c": {
            "id": "Customer|ALFKI",
            ...
        }
    },
    {
        "c": {
            "id": "Customer|ANATR",
            ...
    },
    ....
]

Proposed NoSQL:

SELECT VALUE c
FROM root c
WHERE (c["Discriminator"] = "Customer")

Cosmos results:

[
    {
        "id": "Customer|ALFKI",
        ...
    },
    {
        "id": "Customer|ANATR",
        ...
    },
    ...
]