AspNetCoreOData
AspNetCoreOData copied to clipboard
$it wrong implementation
I think that $it meaning is misenterpreted in the current implementation.
From the specs (5.1.1.14.4) (emphasis mine):
The $it literal can be used in expressions to refer to the current instance of the resource identified by the resource path.
And the examples clarify that well:
Here $it refers to the current element of the primitive collection resource "EmailAddresses"
http://host/service/Customers(1)/EmailAddresses?$filter=endswith($it,'.com')
And here $it refers to the current Customer entity of the entity set resource "Customers"
http://host/service/Customers?$expand=Orders($filter=$it/Address/City eq ShipTo/City)
However if I use the second URL I get the following error:
Instance property 'Address' is not defined for type '[...].Order' (Parameter 'propertyName')
So it seems that it treats $it as the element of the current collection, which is what $this is supposed to be for.
This also happens for complex collection properties.
This is backed by the following blog post: https://devblogs.microsoft.com/odata/select-enhancement-in-asp-net-core-odata/
Which also uses $it wrongly in all the examples instead of $this.
Also from the specs (5.1.1.14.6):
$this ... It refers to the current instance of the collection.
While $this seems to be supported it is not mentioned anywhere, and $it seems like an alias for it (in fact they produce identical results), while it should be totally different.
In the end $it should only refer to the root collection/element which is the one represented by the resource path.
Thanks, your understanding is correct. From our triage meeting, we had the following note:
This seems related to the query option context binder. We need to transfer the $it lambda to the child from the parent.
We need to make sure that we introduce a compatibility flag so that customer clients who are perhaps misusing $it or $this are not going to be broken as a result of the fix.
Hello, @Xriuk I tried to reproduce the issue, but it seems it works fine at my side. Let me share what I did and hope you can find the gap or maybe you can share a reproduce for me to dig more.
You can find my sample here
@xuzhg
The samples you provided are correct.
This is another query, that with the current implementation works, and it shouldn't. Because here $it mistakenly refers to the email type while it should refer to the customer type.
GET {{issue1432DollarIt_HostAddress}}/odata/customers(1)?$select=emails($filter=endswith($it,'.com'))
The query above should be like this (which already works):
GET {{issue1432DollarIt_HostAddress}}/odata/customers(1)?$select=emails($filter=endswith($this,'.com'))
@Xriuk
I tried to understand your sentences. Using my sample, I can test and get the results as below:
GET {{issue1432DollarIt_HostAddress}}/odata/customers(1)?$select=emails($filter=endswith($this,'.com'))
It gets
{
"@odata.context": "http://localhost:5171/odata/$metadata#Customers(Emails)/$entity",
"Emails": [
"efg.com",
"xyg.com"
]
}
GET {{issue1432DollarIt_HostAddress}}/odata/customers(1)?$select=emails($filter=endswith($it,'.com'))
It gets
{
"error": {
"code": "",
"message": "The query specified in the URI is not valid. No function signature for the function with name 'endswith' matches the specified arguments. The function signatures considered are: endswith(Edm.String Nullable=true, Edm.String Nullable=true).",
"details": [],
"innererror": {
"message": "No function signature for the function with name 'endswith' matches the specified arguments. The function signatures considered are: endswith(Edm.String Nullable=true, Edm.String Nullable=true).",
"type": "Microsoft.OData.ODataException",
"stacktrace": " at Microsoft.OData.UriParser.FunctionCallBinder.MatchSignatureToUriFunction(String functionCallToken, SingleValueNode[] argumentNodes, IList`1 nameSignatures)\r\n at Microsoft.OData.UriParser.FunctionCallBinder.BindAsUriFunction(FunctionCallToken functionCallToken, List`1 argumentNodes)\r\n at Microsoft.OData.UriParser.FunctionCallBinder.BindFunctionCall(FunctionCallToken functionCallToken)\r\n at Microsoft.OData.UriParser.MetadataBinder.BindFunctionCall(FunctionCallToken functionCallToken)\r\n at Microsoft.OData.UriParser.MetadataBinder.Bind(QueryToken token)\r\n at Microsoft.OData.UriParser.FilterBinder.BindFilter(QueryToken filter)\r\n at Microsoft.OData.UriParser.SelectExpandBinder.BindFilter(QueryToken filterToken, IEdmNavigationSource resourcePathNavigationSource, IEdmNavigationSource targetNavigationSource, IEdmTypeReference elementType, HashSet`1 generatedProperties, Boolean collapsed)\r\n at Microsoft.OData.UriParser.SelectExpandBinder.GenerateSelectItem(SelectTermToken tokenIn)\r\n at Microsoft.OData.UriParser.SelectExpandBinder.Bind(ExpandToken expandToken, SelectToken selectToken)\r\n at Microsoft.OData.UriParser.SelectExpandSemanticBinder.Bind(ODataPathInfo odataPathInfo, ExpandToken expandToken, SelectToken selectToken, ODataUriParserConfiguration configuration, BindingState state)\r\n at Microsoft.OData.UriParser.ODataQueryOptionParser.ParseSelectAndExpandImplementation(String select, String expand, ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo)\r\n at Microsoft.OData.UriParser.ODataQueryOptionParser.ParseSelectAndExpand()\r\n at Microsoft.AspNetCore.OData.Query.SelectExpandQueryOption.get_SelectExpandClause()\r\n at Microsoft.AspNetCore.OData.Query.Validator.SelectExpandQueryValidator.Validate(SelectExpandQueryOption selectExpandQueryOption, ODataValidationSettings validationSettings)\r\n at Microsoft.AspNetCore.OData.Query.SelectExpandQueryOption.Validate(ODataValidationSettings validationSettings)\r\n at Microsoft.AspNetCore.OData.Query.Validator.ODataQueryValidator.Validate(ODataQueryOptions options, ODataValidationSettings validationSettings)\r\n at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.Validate(ODataValidationSettings validationSettings)\r\n at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.ValidateQuery(HttpRequest request, ODataQueryOptions queryOptions)\r\n at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.OnActionExecuting(ActionExecutingContext actionExecutingContext)"
}
}
}
@xuzhg
I was mistaken, $it seems to work in $filter. But, I have tested this, and it does not work in nested $compute expressions.
{{issue1432DollarIt_HostAddress}}/odata/Customers(1)?$expand=Orders($compute=$it/Name as Test;$select=Test)
Gets:
System.ArgumentException: Instance property 'Name' is not defined for type 'Order' (Parameter 'propertyName')
at System.Linq.Expressions.Expression.Property(Expression expression, String propertyName)
at Microsoft.AspNetCore.OData.Query.Expressions.QueryBinder.GetPropertyExpression(Expression source, String propertyPath, Boolean isAggregated)
at Microsoft.AspNetCore.OData.Query.Expressions.QueryBinder.CreatePropertyAccessExpression(Expression source, QueryBinderContext context, IEdmProperty property, String propertyPath)
at Microsoft.AspNetCore.OData.Query.Expressions.QueryBinder.BindPropertyAccessQueryNode(SingleValuePropertyAccessNode propertyAccessNode, QueryBinderContext context)
at Microsoft.AspNetCore.OData.Query.Expressions.QueryBinder.BindSingleValueNode(SingleValueNode node, QueryBinderContext context)
at Microsoft.AspNetCore.OData.Query.Expressions.QueryBinder.Bind(QueryNode node, QueryBinderContext context)
at Microsoft.AspNetCore.OData.Query.Expressions.SelectExpandBinder.BindComputedProperty(Expression source, QueryBinderContext context, String computedProperty, 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.ProjectCollection(QueryBinderContext context, Expression source, Type elementType, SelectExpandClause selectExpandClause, IEdmStructuredType structuredType, IEdmNavigationSource navigationSource, OrderByClause orderByClause, Nullable`1 topOption, Nullable`1 skipOption, Nullable`1 modelBoundPageSize)
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.BuildExpandedProperty(QueryBinderContext context, Expression source, IEdmStructuredType structuredType, IEdmNavigationProperty navigationProperty, ExpandedReferenceSelectItem expandedItem, 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, Object source, SelectExpandClause selectExpandClause, QueryBinderContext context)
at Microsoft.AspNetCore.OData.Query.SelectExpandQueryOption.ApplyTo(Object entity, ODataQuerySettings settings)
at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.ApplySelectExpand[T](T entity, ODataQuerySettings querySettings)
at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.ApplyTo(Object entity, ODataQuerySettings querySettings)
at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.ApplyQuery(Object entity, 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)
at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.OnActionExecuted(ActionExecutedContext actionExecutedContext)
at Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.OData.Routing.ODataRouteDebugMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
HEADERS
=======
...