EF Core+OData查询CosmosDB使用$select选集合属性抛出类型异常
问题场景
在EF Core上层使用OData从Cosmos DB(SQL API)查询数据时,全集合查询、选择基元类型字段的请求可正常执行,当$select包含嵌套集合类型字段Subscriptions时抛出类型不匹配异常。
环境依赖
<PackageReference Include="Microsoft.AspNetCore.OData" Version="8.0.10" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Cosmos" Version="6.0.4" />
现有配置
$metadata配置
<?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="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" /> </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>
DbContext与EDM模型配置
var entity = modelBuilder.Entity<PartnerTenantSubscriptions>(); entity.HasNoDiscriminator(); modelBuilder.Entity<PartnerTenantSubscriptions>() .ToContainer(containerName) .HasPartitionKey(x => x.PartnerMPNId); var builder = new ODataConventionModelBuilder(); builder.EntitySet<PartnerTenantSubscriptions>("PartnerTenantSubscriptions"); return builder.GetEdmModel();
异常信息
触发异常的请求地址:
api/odata/v1.0.0/PartnerTenantSubscriptions?$select=PartnerMPNId,ReportingDate,CustomerTenantId,PartnerAssociationType,CustomerTenantName,Subscriptions
核心报错:类型System.Collections.Generic.ICollection<SampleIksApi.Models.PartnerTenantSubscription>不能作为System.Linq.IQueryable<SampleIksApi.Models.PartnerTenantSubscription>类型参数传入Take方法
完整异常堆栈:
System.ArgumentException: 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') 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, IEnumerable`1 arguments) at System.Linq.Expressions.MethodCallExpression.Update(Expression object, IEnumerable`1 arguments) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.Visit(Expression expression) at Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosProjectionBindingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) 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 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 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 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 Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.CosmosQueryableMethodTranslatingExpressionVisitor.Visit(Expression expression) at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query) at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0() at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query) at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression) at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator() at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection) at Microsoft.AspNetCore.OData.Query.Container.TruncatedCollection`1..ctor(IQueryable`1 source, Int32 pageSize, Boolean parameterize) at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.LimitResults[T](IQueryable`1 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 System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters) 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.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) 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)
根因
这是EF Core 6.0.4版本Cosmos DB提供程序与OData 8.x分页逻辑的适配缺陷:
- OData默认开启结果截断,对查询中投影的所有集合类型属性自动调用
Take()方法,该方法是IQueryable的扩展方法,要求入参实现IQueryable接口 - EF Core Cosmos 6.0.4版本翻译嵌套集合(包括OwnsMany配置的自有集合、复杂类型集合)的投影时,会将嵌套集合生成为内存中的
ICollection类型,不会包装为IQueryable,类型不匹配直接触发参数异常 - 此前尝试的OwnsMany、ComplexType配置仅解决了EF Core侧嵌套集合的基础映射问题,未处理OData分页逻辑与EF Core投影类型的适配冲突。
解决方案
按落地成本从低到高排序,可选择以下任意一种方案:
- 调整OData查询配置,关闭嵌套集合自动截断
修改OData服务注册配置,关闭空值传播和常量参数化,避免OData对嵌套集合自动应用Take操作:
同时在对应接口的services.AddOData(opt => { opt.Select().Filter().OrderBy().Expand().Count().SetMaxTop(100) .AddRouteComponents("api/odata/v1.0.0", GetEdmModel(), services => services.AddSingleton(new ODataQuerySettings { EnableConstantParameterization = false, HandleNullPropagation = HandleNullPropagationOption.False })); });[EnableQuery]特性上同步配置:[EnableQuery( HandleNullPropagation = HandleNullPropagationOption.False, PageSize = 50 // 按业务需求设置顶层实体分页大小 )] public IActionResult GetPartnerTenantSubscriptions() { return Ok(_context.PartnerTenantSubscriptions); } - 升级EF Core Cosmos依赖版本
将Microsoft.EntityFrameworkCore.Cosmos升级到6.0.20及以上的6.0 LTS补丁版本,或升级到7.0+正式版本。新版本已修复嵌套集合投影的类型映射问题,会自动将集合投影包装为支持IQueryable的类型,可直接适配OData默认分页逻辑,无需额外修改配置。 - 自定义OData结果截断逻辑
替换OData默认的LimitResults实现,在检测到集合类型为ICollection时,先调用AsQueryable()转换类型后再执行Take操作,从根本上解决类型不匹配问题,适合需要保留OData默认嵌套集合截断能力的场景。
内容的提问来源于stack exchange,提问作者Hari Durairaj
相关产品推荐
相关产品推荐

