ASP.NET Core WebAPI使用OData时如何将SelectExpandQueryOption转为IQueryable<T>
该报错和IEdmModel的导航关系配置无关,是SelectExpand.ApplyTo方法的特性导致的:当你调用该方法应用$expand或$select查询时,OData会返回装载了动态包装类(如SelectAllAndExpand<T>)的IQueryable,这类包装类用于同时承载原始实体数据、扩展的导航属性和投影字段,和TModel没有继承关系,直接强制类型转换必然失败。
你现有代码的另一个问题是查询子句的应用顺序错误:SelectExpand必须放在所有过滤、排序逻辑之后执行,放在最前面会导致后续的过滤、排序逻辑无法作用于原始实体。
方案1:调整查询顺序,直接返回通用IQueryable
如果你的接口是直接对外提供OData能力,不需要在服务层对实体做额外业务处理,直接调整查询顺序,最后应用SelectExpand后无需强转,OData序列化器会自动识别包装类并输出标准结构:
protected IQueryable BuildQuery(ODataQueryOptions<TModel> queryOptions, ODataQuerySettings settings) { IQueryable<TModel> query = DbSet; // 先应用过滤 if (queryOptions.Filter != null) { query = queryOptions.Filter.ApplyTo(query, settings) as IQueryable<TModel>; } // 再应用排序 if (queryOptions.OrderBy != null) { query = queryOptions.OrderBy.ApplyTo(query) as IQueryable<TModel>; } // 最后应用SelectExpand,直接返回处理后的IQueryable即可 if (queryOptions.SelectExpand != null) { return queryOptions.SelectExpand.ApplyTo(query, settings); } return query; }
接口的返回值类型改为IActionResult或者直接返回IQueryable即可,不需要限定为IQueryable<TModel>。
方案2:手动解析Expand导航属性,用EF原生Include加载
如果你需要保留IQueryable<TModel>类型用于后续业务逻辑处理,可以自己解析SelectExpand子句中的导航属性路径,手动调用EF Core的Include/ThenInclude加载导航属性,全程不会产生OData包装类:
if (queryOptions.SelectExpand?.SelectExpandClause != null) { foreach (var navProperty in queryOptions.SelectExpand.SelectExpandClause.SelectedItems .OfType<ExpandedNavigationSelectItem>()) { var path = string.Join(".", navProperty.PathToNavigationProperty.Select(p => p.Identifier)); query = query.Include(path); } }
方案3:内存层面提取原始实体(不推荐)
如果查询已经执行完毕,只需要从结果中提取原始TModel对象,可以利用SelectAllAndExpand<T>的Instance属性获取包装的原始实体,该方案仅适用于内存集合处理,不能作用于数据库查询的IQueryable:
var entityList = queryable .Cast<dynamic>() .Select(x => x.Instance as TModel) .ToList();
内容的提问来源于stack exchange,提问作者Jay

