Odata错误:在调用Contains时翻译Linq表达式发生错误。

3

使用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]).Contains([10007].CustomerTitle) 不受支持。

API:

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>>);
    }
}
3个回答

7

由于客户端的字符串列表不是服务器端资源,所以不支持通过URI使用Contains构造。

Linq2Sql提供程序具有Contains的固有翻译,该翻译会转换为SQL的IN子句。

对于OData,不支持这样的转换。您需要构建一个扩展查询列表来使用所有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


包含构造现在已经被支持,从v7.8.1开始。但是显然,它只适用于字符串数组,而不适用于字符串列表。 - janonimus
很高兴知道 @janonimus。对于错过该功能的用户来说,数组部分不应该是问题。 - Raja Nadar

4
以下扩展方法可用于LINQ执行OData查询,测试属性值是否包含在类似于LINQ到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);

请注意,如果您希望按照更多条件进行过滤,则必须拥有单独的Where调用(如上所示)。如果可以将此组合成单个Where,那就太好了,但是我无法弄清楚如何做到这一点。

0

在使用Timothy的解决方案针对Configuration Manager的OData服务时,我遇到了问题。CM在内部使用WML,因此在可解析的内容方面更加受限。

我已经修改了代码以生成一个更加“干净”的查询,避免使用Expression.Constant(false)作为聚合基础 -> 它会产生0,而WML不接受。

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接