7

我有一个由匿名对象组成的 LINQ 查询。

在给定的点上,我想通过传入的搜索参数来限制结果,但这可以是一个或多个参数,我想使用这些参数执行“LIKE x OR LIKE y OR LIKE z”。

在代码中,它看起来像这样:

reservations = reservations.Where(r =>
  r.GuestLastName.Contains(parameter1) || r.GuestFirstName.Contains(parameter1) || 
  r.GuestLastName.Contains(parameter2) || r.GuestFirstName.Contains(parameter2) || 
  // Parameter 3, 4, 5,..
);

我怎么能动态地构造它,知道它reservations是 type 的IQueryable<'a> (anonymous object)?我环顾了各种资源,似乎只有在我知道类型时才能找到一种方法,而不是在使用匿名类型时。

重要的是要知道它是Linq to SQL,所以它应该被翻译成SQL查询而不是在内存中过滤......

4

3 回答 3

3

有两种可能的方式:

  1. Expression正如 Coincoin 所指出的,建立一个
  2. 将所有参数放入数组并使用Any

    var parameters = new [] { parameter1, parameter2, /*...*/ }
    reservations = reservations
        .Where(r => 
            parameters.Any(p => r.GuestFirstName.Contains(p)
                                || r.GuestLastName.Contains(p)));
    
于 2012-11-27T15:32:57.243 回答
1

我会编写自己的通用扩展方法:

public static class CollectionHelper
{
    public static IQueryable Filter<T>(this IQueryable source, string[] properties, string[] values)
    {
        var lambda = CombineLambdas<T>(properties, values);
        var result = typeof (Queryable).GetMethods().First(
            method => method.Name == "Where"
                      && method.IsGenericMethodDefinition)
                                       .MakeGenericMethod(typeof (T))
                                       .Invoke(null, new object[] {source, lambda});
        return (IQueryable<T>) result;
    }

    // combine lambda expressions using OR operator
    private static LambdaExpression CombineLambdas<T>(string[] properties, string[] values)
    {
        var param = Expression.Parameter(typeof (T));
        LambdaExpression prev = null;
        foreach (var value in values)
        {
            foreach (var property in properties)
            {
                LambdaExpression current = GetContainsExpression<T>(property, value);
                if (prev != null)
                {
                    Expression body = Expression.Or(Expression.Invoke(prev, param),
                                                    Expression.Invoke(current, param));
                    prev = Expression.Lambda(body, param);
                }
                prev = prev ?? current;
            }
        }
        return prev;
    }

    // construct expression tree to represent String.Contains
    private static Expression<Func<T, bool>> GetContainsExpression<T>(string propertyName, string propertyValue)
    {
        var parameterExp = Expression.Parameter(typeof (T), "type");
        var propertyExp = Expression.Property(parameterExp, propertyName);
        var method = typeof (string).GetMethod("Contains", new[] {typeof (string)});
        var someValue = Expression.Constant(propertyValue, typeof (string));
        var containsMethodExp = Expression.Call(propertyExp, method, someValue);

        return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
    }
}

和用法:

var reservations = new List<TheType>()  // sample collection
    {
        new TheType {FirstName = "aa", LastName = "bb"},
        new TheType {FirstName = "cc", LastName = "dd"},
        new TheType {FirstName = "ee", LastName = "ff"}
    }.AsQueryable();

var filtered = reservations
    .Filter<TheType>(new[] {"FirstName", "LastName"}, new[] {"d", "e"});
/* returnes 2 elements:
 * {FirstName = "cc", LastName = "dd"} and {FirstName = "ee", LastName = "ff"} */

我不知道您想要的通用解决方案 - 如果存在的话,但我希望它可以是可接受的替代方案,通过动态构建所需的过滤器来解决您的问题。

于 2012-11-27T17:57:33.843 回答
0

经过一些调试,我找到了解决方案,但是我创建了一个带有多个选择器的 WhereFilter,一个用于 FirstName,一个用于 LastName..

这是扩展方法:

public static IQueryable<T> WhereFilter<T>(this IQueryable<T> source, string[] possibleValues, params Expression<Func<T, string>>[] selectors)
{
    List<Expression> expressions = new List<Expression>();

    var param = Expression.Parameter(typeof(T), "p");

    var bodies = new List<MemberExpression>();
    foreach (var s in selectors)
    {
        bodies.Add(Expression.Property(param, ((MemberExpression)s.Body).Member.Name));
    }

    foreach (var v in possibleValues)
    {
        foreach(var b in bodies) {
            expressions.Add(Expression.Call(b, "Contains", null, Expression.Constant(v)));
        }
    }

    var finalExpression = expressions.Aggregate((accumulate, equal) => Expression.Or(accumulate, equal));

    return source.Where(Expression.Lambda<Func<T, bool>>(finalExpression, param));
}

它可以这样使用:

reservations = reservations.WhereFilter(
    array_of_allowed_values,
    r => r.GuestFirstName,
    r => r.GuestLastName
);

我检查了查询的跟踪字符串,它实际上转换为 SQL,所以过滤是在数据库中执行的。

于 2012-11-28T10:36:59.023 回答