Skip to content

Simple query by open type generates complex expression, cannot be handled by e.g. Cosmos #1530

@Tvde1

Description

@Tvde1

Assemblies affected
Microsoft.AspNetCore.OData 9.4.0

Describe the bug
Slightly related to #1325

We are using OData, together with CosmosDB to retrieve documents. These documents are free-form, so we use an Open Type.

We do this by compiling the OData query with this package (this repo) and then passing the generated Expression to the .Where() method of the CosmosDB FeedIterator.

The query we are using is:

unknownPropert/subProperty eq 'Some text'

We expect this to translate in some expression like x["unknownProperty"]["subProperty"] == "Some text" however it generates an increadibly complex query:

Generated expression tree

.Call Queryable.Where(
    .Constant<EnumerableQuery`1[MyModel]>(List`1[MyModel]),
    '(.Lambda #Lambda1<Func`2[MyModel,Boolean]>))

.Lambda #Lambda1<Func`2[MyModel,Boolean]>(MyModel $$it) {
    (String).If (.Call (.Call (.If (
        .Call ($$it.OpenProperties).ContainsKey("en")
    ) {
        ($$it.OpenProperties).Item["en"]
    } .Else {
        null
    }).GetType()).GetProperty("jobTitle") != null) {
        .Call (.Call (.Call (.If (
            .Call ($$it.OpenProperties).ContainsKey("en")
        ) {
            ($$it.OpenProperties).Item["en"]
        } .Else {
            null
        }).GetType()).GetProperty("jobTitle")).GetValue(.If (
                .Call ($$it.OpenProperties).ContainsKey("en")
            ) {
                ($$it.OpenProperties).Item["en"]
            } .Else {
                null
            })
    } .Else {
        .If (
            .Call ((IDictionary`2[String,Object]).Call (.Call QueryBinder.GetDynamicPropertyContainer(
                .If (.Call Edm.EdmClrTypeMapExtensions.GetEdmTypeReference(
                    .Constant<Microsoft.OData.Edm.IEdmModel>(Microsoft.OData.Edm.EdmModel),
                    .Call (.If (
                        .Call ($$it.OpenProperties).ContainsKey("en")
                    ) {
                        ($$it.OpenProperties).Item["en"]
                    } .Else {
                        null
                    }).GetType()) == null) {
                    .Throw .New ODataException(.Call String.Format(
                            "Cannot find the resource type '{0}' in the model.",
                            (.Call (.If (
                                .Call ($$it.OpenProperties).ContainsKey("en")
                            ) {
                                ($$it.OpenProperties).Item["en"]
                            } .Else {
                                null
                            }).GetType()).FullName))
                } .Else {
                    .Call Microsoft.AspNetCore.OData.Edm.EdmClrTypeMapExtensions.GetEdmTypeReference(
                        .Constant<Microsoft.OData.Edm.IEdmModel>(Microsoft.OData.Edm.EdmModel),
                        .Call (.If (
                            .Call ($$it.OpenProperties).ContainsKey("en")
                        ) {
                            ($$it.OpenProperties).Item["en"]
                        } .Else {
                            null
                        }).GetType())
                },
                .Constant<Microsoft.OData.UriParser.QueryNodeKind>(SingleValueOpenPropertyAccess),
                .Constant<Microsoft.OData.Edm.IEdmModel>(Microsoft.OData.Edm.EdmModel))).GetValue(.If (
                    .Call ($$it.OpenProperties).ContainsKey("en")
                ) {
                    ($$it.OpenProperties).Item["en"]
                } .Else {
                    null
                })).ContainsKey("jobTitle")
        ) {
            ((IDictionary`2[String,Object]).Call (.Call QueryBinder.GetDynamicPropertyContainer(
                .If (.Call Microsoft.AspNetCore.OData.Edm.EdmClrTypeMapExtensions.GetEdmTypeReference(
                    .Constant<Microsoft.OData.Edm.IEdmModel>(Microsoft.OData.Edm.EdmModel),
                    .Call (.If (
                        .Call ($$it.OpenProperties).ContainsKey("en")
                    ) {
                        ($$it.OpenProperties).Item["en"]
                    } .Else {
                        null
                    }).GetType()) == null) {
                    .Throw .New ODataException(.Call String.Format(
                            "Cannot find the resource type '{0}' in the model.",
                            (.Call (.If (
                                .Call ($$it.OpenProperties).ContainsKey("en")
                            ) {
                                ($$it.OpenProperties).Item["en"]
                            } .Else {
                                null
                            }).GetType()).FullName))
                } .Else {
                    .Call Edm.EdmClrTypeMapExtensions.GetEdmTypeReference(
                        .Constant<IEdmModel>(Microsoft.OData.Edm.EdmModel),
                        .Call (.If (
                            .Call ($$it.OpenProperties).ContainsKey("en")
                        ) {
                            ($$it.OpenProperties).Item["en"]
                        } .Else {
                            null
                        }).GetType())
                },
                .Constant<Microsoft.OData.UriParser.QueryNodeKind>(SingleValueOpenPropertyAccess),
                .Constant<Microsoft.OData.Edm.IEdmModel>(Microsoft.OData.Edm.EdmModel))).GetValue(.If (
                    .Call ($$it.OpenProperties).ContainsKey("en")
                ) {
                    ($$it.OpenProperties).Item["en"]
                } .Else {
                    null
                })).Item["jobTitle"]
        } .Else {
            null
        }
    } == .Constant<LinqParameterContainer+TypedLinqParameterContainer`1[String]>(LinqParameterContainer+TypedLinqParameterContainer`1[String]).TypedProperty
}

Please let me know if this is expected / unexpected / or if it's the scope of this project.
If there is a better way of using odata with CosmosDB, I would also like to try it and find out what works or doesn't.

Reproduce steps
I created a repository: https://github.com/Tvde1/ODataOpenTypeRepro/tree/complex-query
Check out the branch complex-query. It has a Program.cs which can be ran without side effects.

Data Model

[DataContract]
public class MyModel : Dictionary<string, object>
{
    [Key]
    [DataMember(Name = "id")]
    [JsonPropertyName("id")]
    public int Id { get; set; }

    [DataMember(Name = "knownString")]
    [JsonPropertyName("knownString")]
    public string KnownString { get; set; } = null!;

    [Required]
    [DataMember(Name = "knownInt")]
    [JsonPropertyName("knownInt")]
    public int KnownInt { get; set; }

    [DataMember(Name = "knownComplexTypeArray")]
    [JsonPropertyName("knownComplexTypeArray")]
    public KnownComplexType[] KnownComplexTypeArray { get; set; } = null!;

    [JsonExtensionData]
    public Dictionary<string, object> OpenProperties => this;
}

[DataContract]
public class KnownComplexType
{
    [DataMember(Name = "fileName")]
    public string FileName { get; set; } = null!;
}

EDM (CSDL) Model
It's auto generated, but can supply if needed

Request/Response
See above

Expected behavior
I expect a query which is relatively simple and can be fed to CosmosDB along the lines of x["unknownProperty"]["subProperty"] == "Some text" without too many checks and exceptions

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions