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>>);
    }
}

Odata 在调用时翻译 Linq 表达式时出错 包含

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);
           
        }
    }

副作用是,当没有值作为参数传递时,查询将立即返回空结果,与外部服务处理它相比,这可能会节省时间。同时,如果查询打算在服务器上实际运行,则可能是不需要的。