如何解决如何使用多个'Where'表达式,并使用C#/NET将它们与AND和OR链接在一起?
我正在尝试在Web应用程序中创建一个过滤系统。问题是我不知道从客户端向API请求多少个过滤器。我已经构建好了,所以过滤器的数组来自像这样的单个字符串:?sizeFilters=big,small,medium
然后我使用string[] names = sizeFilters.Split(',');
来获得像Where(x => x.listOfSizes.contains(names[index]));
这样的单个表达式
我还需要使用AND和OR制作表达式的链,因为我将使用另一个过滤器,例如:'?typeFilters=normal,extra,spicy'
因此,我需要使整个表达式看起来像这样,但可能要长几倍,它需要使用不同大小的数组:
退回物品Where size is big OR small OR medium AND Where type is normal OR extra OR spicy
Where(x => x.Sizes == "Small" || x => x.Sizes == "Medium" || x => x.Sizes == "Big" &&
x => x.Types == "normal" || x => x.Types == "extra" || x => x.Types == "Spicy")
解决方法
我认为关注应该有效
var query = _context.Set<[Entity]>();
if (sizeFilterPresent)
{
query = query.Where(r => sizes.Contains(r.Size));
}
if(typesFilterPresent)
{
query = query.Where(r => types.Contains(r.Type));
}
var results = query.ToList();
,
您可以尝试一下
var result = data.Where(p => sizeFilters.Contains(p.Size) && typeFilters.Contains(p.Type));
,
您可以简单地多次调用.Where
一起与AND表达式。动态地对表达式进行“或”运算要困难得多。您需要重新构建表达式图以包含OrElse
运算符,并确保所有表达式都基于相同的ParameterExpression
。
public class Replacer : ExpressionVisitor
{
private readonly Dictionary<Expression,Expression> _replacements;
public Replacer(IEnumerable<Expression> before,IEnumerable<Expression> after)
{
_replacements = new Dictionary<Expression,Expression>(before.Zip(after,(a,b) => KeyValuePair.Create(a,b)));
}
public override Expression Visit(Expression node)
{
if (node != null && _replacements.TryGetValue(node,out var replace))
return base.Visit(replace);
return base.Visit(node);
}
}
public static Expression<Func<T,bool>> Or<T>(this Expression<Func<T,bool>> expr1,Expression<Func<T,bool>> expr2)
{
if (expr1 == null)
return expr2;
if (expr2 == null)
return expr1;
return Expression.Lambda<Func<T,bool>>(
Expression.OrElse(
expr1.Body,new Replacer(expr2.Parameters,expr1.Parameters).Visit(expr2.Body)
),expr1.Parameters);
}
public static Expression<Func<T,bool>> And<T>(this Expression<Func<T,bool>>(
Expression.AndAlso(
expr1.Body,expr1.Parameters);
}
// Usage
Expression<Func<TableObject,bool>> where = null;
if (...)
where = where.Or(x => sizeFilters.Contains(x.Size));
if (...)
where = where.Or(x => typeFilters.Contains(x.Type));
if (where!=null)
query = query.Where(where);
,
为使外观整洁,我的建议是创建和扩展方法。这样,您可以将其用作任何其他LINQ方法。参见extension methods demystified
假设您的来源是IQuertyable<TSource>
。
public static IQueryable<TSource> WhereAnd<TSource>(
this IQueryable<TSource> source,IEnumerable<Expression<Func<TSource,bool>>> filterPredicates)
{
// TODO: handle null source,expressions;
IQueryable<TSource> filteredSource = source;
foreach (var predicate in filterPredicates)
{
filteredSource = filteredSource.Where(predicate);
}
}
用法:
var predicates = new List<Expression<Func<TSource,bool>>>()
{
customer => customer.BirthDay.Year <= 1950,customer => customer.CityId == GetCityId("New York"),customer => customer.Gender == Gender.Male,}
var oldNewYorkMaleCustomers = dbContext.Customers.WhereAnd(predicates).ToList();
注意:空的filterpredicate集合将不过滤任何谓词:您将获得原始数据:
var emptyFilter = Queryable.Empty<Expression<Func<Customer,bool>>>();
var allCustomers = dbContext.Customers.WhereAnd(emptyFilter);
,
最简单的选择是,如其他人所述,在表达式中使用OR
构建Enumerable.Contains
。并通过多次调用AND
来构建Where
。
// using these values as an example
string[] sizeTerms = /* initialize */;
string[] typeTerms = /* initialize */;
IQueryable<Item> items = /* initialize */
if (sizeTerms.Any()) {
items = items.Where(x => sizeTerms.Contains(x.Size));
}
if (typeTerms.Any()) {
items = items.Where(x => typeTerms.Contains(x.Type));
}
如果需要,可以将此逻辑包装到扩展方法中,该方法接受要过滤的表达式,并使用IEnumerable<string>
过滤值;并构造并应用Contains
方法:
// using System.Reflection
// using static System.Linq.Expressions.Expression
private static MethodInfo containsMethod = typeof(List<>).GetMethod("Contains");
public static IQueryable<TElement> WhereValues<TElement,TFilterTarget>(
this IQueryable<TElement> qry,Expression<Func<TElement,TFilterTarget>> targetExpr,IEnumerable<string> values
) {
var lst = values.ToList();
if (!lst.Any()) { return qry; }
return qry.Where(
Lambda<Expression<Func<TElement,bool>>>(
Call(
Constant(lst),containsMethod.MakeGenericMethod(typeof(T)),targetExpr.Body
),targetExpr.Parameters.ToArray()
)
);
}
可以这样称呼:
qry = qry
.WhereValues(x => x.Size,sizeTerms)
.WhereValues(x => x.Type,typeTerms);
一个警告:查询将基于传递给方法的值进行构建;如果以后进行更改,查询将不会反映这些更改。如果这是一个问题:
- 获取
Enumerable.Contains
而不是List.Contains
的适当重载,并且 - 使用
Expression.Call
的重载来生成静态方法调用,而不是实例方法调用。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。