OData / AspNetCoreOData

ASP.NET Core OData: A server library built upon ODataLib and ASP.NET Core
Other
446 stars 156 forks source link

Select query throw an error on a dynamic property dictionary #1224

Open TARGAZ opened 2 months ago

TARGAZ commented 2 months ago

Hello,

On a entity with a complex property that has dynamic property dictionary. I can't do a select clause on this complex property.

Assemblies affected Microsoft.AspNetCore.OData 8.2.5

Reproduce steps 1.Clone the repository : https://github.com/TARGAZ/ODataSelectDynamicPropertyBug 2.Run 3.Try queries :

Expected result First query : https://localhost:7092/odata/customers?$select=Name

{
  "@odata.context": "https://localhost:7092/odata/$metadata#Customers",
  "value": [
    {
      "Name": {
        "en": "english",
        "fr": "french"
      }
    },
    {
      "Name": {
        "en": "english",
        "fr": "french"
      }
    },
    {
      "Name": {
        "en": "english",
        "fr": "french"
      }
    }
  ]
}

Second Query : https://localhost:7092/odata/customers?$select=Name/en

{
  "@odata.context": "https://localhost:7092/odata/$metadata#Customers",
  "value": [
    {
      "Name": {
        "en": "english"
      }
    },
    {
      "Name": {
        "en": "english"
      }
    },
    {
      "Name": {
        "en": "english"
      }
    }
  ]
}

Actual result The error is the same for both queries :

The query specified in the URI is not valid. The given model does not contain the type 'System.Collections.Generic.KeyValuePair`2[[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'.

Stack trace :

at Microsoft.AspNetCore.OData.Query.Expressions.QueryBinderContext..ctor(QueryBinderContext context, ODataQuerySettings querySettings, Type clrType)
at Microsoft.AspNetCore.OData.Query.Expressions.SelectExpandBinder.ProjectAsWrapper(QueryBinderContext context, Expression source, SelectExpandClause selectExpandClause, IEdmStructuredType structuredType, IEdmNavigationSource navigationSource, OrderByClause orderByClause, ComputeClause computeClause, Nullable`1 topOption, Nullable`1 skipOption, Nullable`1 modelBoundPageSize)
at Microsoft.AspNetCore.OData.Query.Expressions.SelectExpandBinder.BuildSelectedProperty(QueryBinderContext context, Expression source, IEdmStructuredType structuredType, IEdmStructuralProperty structuralProperty, PathSelectItem pathSelectItem, IList`1 includedProperties)
at Microsoft.AspNetCore.OData.Query.Expressions.SelectExpandBinder.BuildPropertyContainer(QueryBinderContext context, Expression source, IEdmStructuredType structuredType, IDictionary`2 propertiesToExpand, IDictionary`2 propertiesToInclude, IList`1 computedProperties, ISet`1 autoSelectedProperties, Boolean isSelectingOpenTypeSegments, Boolean isSelectedAll)
at Microsoft.AspNetCore.OData.Query.Expressions.SelectExpandBinder.ProjectElement(QueryBinderContext context, Expression source, SelectExpandClause selectExpandClause, IEdmStructuredType structuredType, IEdmNavigationSource navigationSource)
at Microsoft.AspNetCore.OData.Query.Expressions.SelectExpandBinder.BindSelectExpand(SelectExpandClause selectExpandClause, QueryBinderContext context)
at Microsoft.AspNetCore.OData.Query.Expressions.BinderExtensions.ApplyBind(ISelectExpandBinder binder, IQueryable source, SelectExpandClause selectExpandClause, QueryBinderContext context)
at Microsoft.AspNetCore.OData.Query.SelectExpandQueryOption.ApplyTo(IQueryable queryable, ODataQuerySettings settings)
at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.ApplySelectExpand[T](T entity, ODataQuerySettings querySettings)
at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.ApplyTo(IQueryable query, ODataQuerySettings querySettings)
at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.ExecuteQuery(Object responseValue, IQueryable singleResultCollection, ControllerActionDescriptor actionDescriptor, HttpRequest request)
at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.OnActionExecuted(ActionExecutedContext actionExecutedContext, Object responseValue, IQueryable singleResultCollection, ControllerActionDescriptor actionDescriptor, HttpRequest request)
julealgon commented 2 months ago

On a entity with a complex property that has dynamic property dictionary.

Would you mind sharing a sample of what that model looks like as well?

TARGAZ commented 2 months ago

You can find the EDM model here : https://github.com/TARGAZ/ODataSelectDynamicPropertyBug/blob/main/Program.cs

var modelBuilder = new ODataModelBuilder();

var customerType = modelBuilder.EntityType<Customer>();
customerType.HasKey(c => c.Id);
customerType.ComplexProperty(c => c.Name);

Type localizableStringType = typeof(LocalizableString);
ComplexTypeConfiguration localizableStringConfiguration = modelBuilder.AddComplexType(localizableStringType);
localizableStringConfiguration.AddDynamicPropertyDictionary(localizableStringType.GetProperty(nameof(LocalizableString.ExtendedProperties)));

modelBuilder.EntitySet<Customer>("Customers");
julealgon commented 2 months ago

Oh I'm dumb, I didn't realize you provided a sample repo there, my bad @TARGAZ .

julealgon commented 2 months ago

Based on your code..... can you check whether replacing IDictionary on the ExtendedProperties property with Dictionary (the concrete class) makes a difference here?

        public Dictionary<string, object> ExtendedProperties

IIRC, OData's EDM builder won't automatically match interfaces to implementations when they are found in the model. You either need to specify the type explicitly in that case, or just type it to the concrete type and it will be picked up automatically.

Let us know if that changes the error in any way.

TARGAZ commented 2 months ago

Unfortunately, this doesn't work I have the same error. Note that the $filter works on this property.

xuzhg commented 2 months ago

@TARGAZ OData needs the 'model type' as POCO class, so your 'LocalizableString' can't be treated correctly since it's derived from 'ReadOnlyDictionary< >'.

If you replace your 'LocalizableString' definition using composition, not using inheritance, it should work:

public sealed class LocalizableString/* : ReadOnlyDictionary<string, string>*/
{
    private IDictionary<string, object> _values;
    ......
}

image

TARGAZ commented 2 months ago

Hello, Thank you for your help. It works really well :).