AspNetCoreOData
AspNetCoreOData copied to clipboard
$select on ICollection with EFCore CosmosDB provider throws ICollection to IQueryable casting exception
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.
- Clone repo at https://github.com/heyhari7/SampleIksApi/tree/master
- Update secrets config to point to CosmosDB
- 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
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 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 We do have pagination enabled per requirements of our API. I will try disabling it and post the results here.