我有一个由匿名对象组成的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
属于IQueryable<'a> (anonymous object)
类型,我怎样才能动态构建它?我查看了各种资源,当我知道类型时,我似乎只能找到一种方法,而不是在使用匿名类型时。
重要的是要知道它是Linq to SQL,所以它应该被翻译成SQL查询而不是在内存中过滤......
答案 0 :(得分:2)
有两种可能的方式:
Expression
将所有参数放入数组并使用Any
:
var parameters = new [] { parameter1, parameter2, /*...*/ }
reservations = reservations
.Where(r =>
parameters.Any(p => r.GuestFirstName.Contains(p)
|| r.GuestLastName.Contains(p)));
答案 1 :(得分: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"} */
我不知道你想要的一般解决方案 - 如果存在的话,但是我希望通过动态构建所需的过滤器来解决你的问题是可接受的替代方案。
答案 2 :(得分: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,因此在数据库中执行过滤。