Odata 在调用时翻译 Linq 表达式时出错 包含
本文关键字:表达式 出错 包含 Linq 翻译 调用 Odata | 更新日期: 2023-09-27 17:56:50
是否可以使用 ASP.NET Web APi OData进行类似的操作:
List<string> customersTitles = Odata.OrdersService.Select(o=>o.CustomerTitle).Distinct().ToList();
List<Customer> customers = Odata.CustomerService.Where(m => customersTitles .Contains(m.CustomerTitle))
获取错误:
将 Linq 表达式转换为 URI 时出错:表达式 value(System.Collections.Generic.List'1[System.String]).包含([10007]。客户标题) 不受支持。
应用程序接口:
public class CustomerController : EntitySetController<Customer, int>
{
[Queryable]
public override IQueryable<Customer> Get()
{
Expression filter = this.QueryOptions.Filter.ToExpression<Customer>();
return db.Query<Customer>(filter as Expression<Func<Customer, bool>>);
}
}
URI 不支持包含构造,因为客户端存在的字符串列表不是服务器端资源。
Linq2Sql Provider 有一个 Contains 的先天翻译,它被翻译成 SQL 的 IN 子句。
对于 OData,不支持此类转换。您需要为使用 all Title 值为 where 子句构建一个扩展的查询列表:
因为这不起作用:
List<Customer> customers = Odata.CustomerService.Where(m => customersTitles .Contains(m.CustomerTitle))
扩展查询选项可帮助我们构建如下查询:
List<Customer> customers = Odata.CustomerService.Where(m => m.CustomerTitle == customerTitles[0] || m.CustomerTitle == customerTitles[1]); // and so on
以下是过滤器构建的代码:
var titleFilterList = customerTitles.Select(title => String.Format("(CustomerTitle eq {0})", title));
var titleFilter = String.Join(" or ", titleFilterList);
var customers = Odata.CustomerService.AddQueryOption("$filter", titleFilter).Execute().ToList(); // you may have to cast this.
还有另一种选择,可以使用一个很好的扩展方法以强类型的方式执行相同的操作,并构建一个基于动态表达式的谓词。请按照此处的步骤操作:
http://blogs.msdn.com/b/phaniraj/archive/2008/07/17/set-based-operations-in-ado-net-data-services.aspx
可以使用 LINQ 执行以下扩展方法执行 OData 查询,这些查询测试属性的值是否包含在类似于 LINQ to EF 的工作方式Contains
集中。它基于Raja Nadar的回答中提供的链接,特别是Nick最后的评论。
public static IQueryable<T> WherePropertyIsIn<T, TSet>(
this IQueryable<T> query,
IEnumerable<TSet> set,
Expression<Func<T, TSet>> propertyExpression
) {
var filterPredicate = set.Select(value => Expression.Equal(propertyExpression.Body, Expression.Constant(value)))
.Aggregate<Expression, Expression>(Expression.Constant(false), Expression.Or);
var filterLambdaExpression = Expression.Lambda<Func<T, bool>>(filterPredicate, propertyExpression.Parameters.Single());
return query.Where(filterLambdaExpression);
}
用法:
var allowed_states = getAllowedStates();
var maxPopulation = getMaxPopulation();
// Instead of...
var cities = context.Cities.Where(c => allowed_states.Contains(c.State) && c.Population <= maxPopulation);
// Use...
var cities = context.Cities.Where(c => c.Population <= maxPopulation).WherePropertyIsIn(allowed_states, c => c.Cities);
请注意,如果您希望按超过 WherePropertyIsIn
允许的次数进行筛选,则必须具有单独的Where
调用(如上所示)。如果这可以组合成一个Where
那就太好了,但我不知道怎么做。
Timothy 的解决方案用于 配置管理器的 OData 服务时遇到了问题。CM 在内部使用 WML,因此在可解析的内容方面更加有限。
我已经更改了代码以生成一个有点"干净"的查询,避免使用 Expression.Constant(false)
作为聚合基础 ->它会产生 WML 不接受的 0
public static IQueryable<T> WherePropertyIsIn<T, TSet>(this IQueryable<T> query,
IEnumerable<TSet> valuesList, Expression<Func<T, TSet>> propertySelector)
{
if (valuesList == null) throw new ArgumentNullException(nameof(valuesList));
//if there are no values, no entities can fullfil the condition -> return empty
if (!valuesList.Any())
return Enumerable.Empty<T>().AsQueryable();
//create a check for each value
var filters = valuesList.Select(value => Expression.Equal(propertySelector.Body, Expression.Constant(value)));
//build an expression aggregating checks with OR, use first check as starter (could be '0', but doesn't get mapped to WML)
var firstCheck = filters.First();
//we could duplicate first check, but why not just skip it
var filterPredicate = filters.Skip(1).Aggregate(firstCheck, (Func<Expression, Expression, Expression>)Expression.Or);
var filterLambdaExpression = Expression.Lambda<Func<T, bool>>(filterPredicate, propertySelector.Parameters.Single());
return query.Where(filterLambdaExpression);
}
}
副作用是,当没有值作为参数传递时,查询将立即返回空结果,与外部服务处理它相比,这可能会节省时间。同时,如果查询打算在服务器上实际运行,则可能是不需要的。