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.65k stars 3.15k forks source link

Incorrect naming of calculated values in projections #32021

Open grassgit opened 11 months ago

grassgit commented 11 months ago

File a bug

When creating a LINQ query that includes a field that is calculated in the projection it results in an exception depending on the order of fields in the projection.

Cause analysis Observed behaviour:

Based on the error and the fact that changing the order affects the outcome it appears that during deserialization the object used for nested collection is always expected to be in a field named c.

Include your code

Repository with reproduction: https://github.com/grassgit/repro-efcosmos

This code is the core of the issue:

This fails:

.Select(x => new
{
    Mapped = x.Status == 2 ? "a" : "b",
    Children = x.Children.Select(x => new { x.Name })
});

Switching the order of the fields makes it work:

.Select(x => new
{
    Children = x.Children.Select(x => new { x.Name }),
    Mapped = x.Status == 2 ? "a" : "b",
});

Include stack traces

From the test project:

Message: 
Newtonsoft.Json.JsonSerializationException : Deserialized JSON type 'Newtonsoft.Json.Linq.JValue' is not compatible with expected type 'Newtonsoft.Json.Linq.JObject'. Path 'c'.

  Stack Trace: 
JsonSerializerInternalReader.CreateJToken(JsonReader reader, JsonContract contract)
JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
JsonSerializer.Deserialize(JsonReader reader, Type objectType)
JToken.ToObject(Type objectType, JsonSerializer jsonSerializer)
lambda_method382(Closure , QueryContext , JObject )
Enumerator.MoveNext()
Example.WithNestedFails() line 51

When using LINQ pad on the same model (from a dll) the exception type and stacktrace are slightly different but otherwise the behaviour is the same. Note that this is the behaviour we see in or environment when

Unable to cast object of type 'Newtonsoft.Json.Linq.JValue' to type 'Newtonsoft.Json.Linq.JObject'.
   at lambda_method339(Closure , QueryContext , JObject )
   at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosShapedQueryCompilingExpressionVisitor.QueryingEnumerable`1.Enumerator.MoveNext()

Error from our test environment:

System.InvalidCastException: Unable to cast object of type 'Newtonsoft.Json.Linq.JValue' to type 'Newtonsoft.Json.Linq.JObject'.
   at Newtonsoft.Json.Linq.JToken.ToObject[T](JsonSerializer jsonSerializer)
   at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.SafeToObjectWithSerializer[T](JToken token)
   at lambda_method2651(Closure , QueryContext , JObject )
   at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosShapedQueryCompilingExpressionVisitor.QueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()

Include verbose output

When extracting the SQL as generated by Entity Framework it looks like the problem is caused by the naming of calculated fields in the query.

Failing example

SELECT VALUE {"c" : ((c["Status"] = 2) ? "a" : "b"), "c0" : c}
FROM root c

Successful

SELECT VALUE {"c": c, "c0" : ((c["Status"] = 2) ? "a" : "b")}
FROM root c

Include provider and version information

EF Core version: Database provider: Microsoft.EntityFrameworkCore.Cosmos (7.0.12) Target framework: .NET 6.0 Operating system: Windows 11 IDE: Visual Studio 2022 17.6.4

ajcvickers commented 11 months ago

/cc @AndriySvyryd

AndriySvyryd commented 11 months ago

Related to #25527