AspNetCoreOData icon indicating copy to clipboard operation
AspNetCoreOData copied to clipboard

$select on ICollection with EFCore CosmosDB provider throws ICollection to IQueryable casting exception

Open heyhari7 opened this issue 3 years ago • 3 comments
trafficstars

Assemblies affected ASP.NET Core OData 8.0.10 with Microsoft.EntityFrameworkCore.Cosmos 6.0.4

Describe the bug $select on ICollection property fails with exception

Expression of type 'System.Collections.Generic.ICollection`1[SampleIksApi.Models.PartnerTenantSubscription]' cannot be used for parameter of type 'System.Linq.IQueryable`1[SampleIksApi.Models.PartnerTenantSubscription]' of method 'System.Linq.IQueryable`1[SampleIksApi.Models.PartnerTenantSubscription] Take[PartnerTenantSubscription](System.Linq.IQueryable`1[SampleIksApi.Models.PartnerTenantSubscription], Int32)' (Parameter 'arg0')

I have described the issue in greater detail at my stackoverflow question here - https://stackoverflow.com/questions/72570499/querying-for-owned-collection-in-cosmosdb-throws-exception-when-using-odata-sel

Reproduce steps The simplest set of steps to reproduce the issue. If possible, reference a commit that demonstrates the issue.

  1. Clone repo at https://github.com/heyhari7/SampleIksApi/tree/master
  2. Update secrets config to point to CosmosDB
  3. Make a OData call to api/odata/v1.0.0/PartnerTenantSubscriptions?$select=PartnerMPNId,ReportingDate,CustomerTenantId,PartnerAssociationType,CustomerTenantName,Subscriptions

Data Model Please share your Data model, for example, your C# class.

public class PartnerTenantSubscriptions
    {
        public string PartnerMPNId { get; set; }
        public string PartnerName { get; set; }
        public string CustomerTenantId { get; set; }
        public string CustomerTenantName { get; set; }
        public string PartnerAssociationType { get; set; }
        public DateTimeOffset ReportingDate { get; set; }
        public string id { get; set; }
        public ICollection<PartnerTenantSubscription> Subscriptions { get; set; }
    }

    public class PartnerTenantSubscription
    {
        public string SubscriptionId { get; set; }
        public string SubscriptionState { get; set; }
        public DateTimeOffset? PaidStartDate { get; set; }
        public DateTimeOffset? PaidEndDate { get; set; }
        public DateTimeOffset? TrialEndDate { get; set; }
        public bool? IsAutoRenew { get; set; }
        public DateTimeOffset? StartDate { get; set; }
        public DateTimeOffset? EndDate { get; set; }
    }

EDM (CSDL) Model

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
    <edmx:DataServices>
        <Schema Namespace="SampleIksApi.Models" xmlns="http://docs.oasis-open.org/odata/ns/edm">
            <EntityType Name="PartnerTenantSubscriptions">
                <Key>
                    <PropertyRef Name="id" />
                </Key>
                <Property Name="PartnerMPNId" Type="Edm.String" Nullable="false" />
                <Property Name="PartnerName" Type="Edm.String" Nullable="false" />
                <Property Name="CustomerTenantId" Type="Edm.String" Nullable="false" />
                <Property Name="CustomerTenantName" Type="Edm.String" Nullable="false" />
                <Property Name="PartnerAssociationType" Type="Edm.String" Nullable="false" />
                <Property Name="ReportingDate" Type="Edm.DateTimeOffset" Nullable="false" />
                <Property Name="id" Type="Edm.String" Nullable="false" />
                <Property Name="Subscriptions" Type="Collection(SampleIksApi.Models.PartnerTenantSubscription)" />
            </EntityType>
            <ComplexType Name="PartnerTenantSubscription">
                <Property Name="SubscriptionId" Type="Edm.String" Nullable="false" />
                <Property Name="SubscriptionState" Type="Edm.String" Nullable="false" />
                <Property Name="PaidStartDate" Type="Edm.DateTimeOffset" />
                <Property Name="PaidEndDate" Type="Edm.DateTimeOffset" />
                <Property Name="TrialEndDate" Type="Edm.DateTimeOffset" />
                <Property Name="IsAutoRenew" Type="Edm.Boolean" />
                <Property Name="StartDate" Type="Edm.DateTimeOffset" />
                <Property Name="EndDate" Type="Edm.DateTimeOffset" />
            </ComplexType>
        </Schema>
        <Schema Namespace="Default" xmlns="http://docs.oasis-open.org/odata/ns/edm">
            <EntityContainer Name="Container">
                <EntitySet Name="PartnerTenantSubscriptions" EntityType="SampleIksApi.Models.PartnerTenantSubscriptions" />
            </EntityContainer>
        </Schema>
    </edmx:DataServices>
</edmx:Edmx>

Request/Response Uri - https://localhost:7157/api/odata/v1.0.0/PartnerTenantSubscriptions?$select=PartnerMPNId,ReportingDate,CustomerTenantId,PartnerAssociationType,CustomerTenantName,Subscriptions Response - System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentException: Expression of type 'System.Collections.Generic.ICollection1[SampleIksApi.Models.PartnerTenantSubscription]' cannot be used for parameter of type 'System.Linq.IQueryable1[SampleIksApi.Models.PartnerTenantSubscription]' of method 'System.Linq.IQueryable1[SampleIksApi.Models.PartnerTenantSubscription] Take[PartnerTenantSubscription](System.Linq.IQueryable1[SampleIksApi.Models.PartnerTenantSubscription], Int32)' (Parameter 'arg0') at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index) at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression arg0, Expression arg1) at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable1 arguments) at System.Linq.Expressions.MethodCallExpression.Update(Expression object, IEnumerable1 arguments) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.Visit(Expression expression) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.Visit(Expression expression) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.VisitMemberAssignment(MemberAssignment memberAssignment) at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.VisitMemberInit(MemberInitExpression memberInitExpression) at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.Visit(Expression expression) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.VisitMemberAssignment(MemberAssignment memberAssignment) at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.VisitMemberInit(MemberInitExpression memberInitExpression) at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.Visit(Expression expression) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.VisitMemberAssignment(MemberAssignment memberAssignment) at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.VisitMemberInit(MemberInitExpression memberInitExpression) at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.Visit(Expression expression) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.Translate(SelectExpression selectExpression, Expression expression) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosQueryableMethodTranslatingExpressionVisitor.TranslateSelect(ShapedQueryExpression source, LambdaExpression selector) at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosQueryableMethodTranslatingExpressionVisitor.Visit(Expression expression) at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_01.<Execute>b__0() at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func1 compiler) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query) at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1.GetEnumerator() at System.Collections.Generic.List1..ctor(IEnumerable1 collection) at Microsoft.AspNetCore.OData.Query.Container.TruncatedCollection1..ctor(IQueryable1 source, Int32 pageSize, Boolean parameterize) at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.LimitResults[T](IQueryable1 queryable, Int32 limit, Boolean parameterize, Boolean& resultsLimited) --- End of inner exception stack trace --- at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.LimitResults(IQueryable queryable, Int32 limit, Boolean parameterize, Boolean& resultsLimited) at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.ApplyPaging(IQueryable result, ODataQuerySettings querySettings) at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.ApplyTo(IQueryable query, ODataQuerySettings querySettings) 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.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker) at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at SampleIksApi.Common.Startup.<>c.<<Configure>b__5_0>d.MoveNext() in C:\Users\haridura\source\repos\SampleIksApi\SampleIksApi\Startup.cs:line 37 --- End of stack trace from previous location --- at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS

Accept: / Connection: keep-alive Host: localhost:7157 User-Agent: PostmanRuntime/7.29.0 Accept-Encoding: gzip, deflate, br Cache-Control: no-cache Prefer: odata.maxpagesize=50 Postman-Token: 4546bf67-565e-40f7-9176-767b7fbc5b95

Expected behavior Response with only the properties in the select clause included

heyhari7 avatar Jun 16 '22 21:06 heyhari7

Gentle ping to understand if there is an ETA for when this issue will be looked into/fixed. Please let me know if I can help provide any additional info/context. Thanks!

heyhari7 avatar Jul 27 '22 18:07 heyhari7

@heyhari7 does it make any difference if you remove this header?

Prefer: odata.maxpagesize=50

Also, do you have any server-driven paging enabled for this endpoint/entity? If so, does disabling it change anything?

I'm asking this because of the mention of a IQueriable.Take call in the trace. If there was no pagination at all involved, there should be no Take call anywhere.

julealgon avatar Jul 27 '22 21:07 julealgon

@julealgon We do have pagination enabled per requirements of our API. I will try disabling it and post the results here.

heyhari7 avatar Aug 02 '22 23:08 heyhari7