我尝试创建一种方法,允许我的应用用户创建自己的数据过滤视图。为此,我编写了一个Key-Operator-Value对列表,然后我在多个步骤之后尝试解析表达式以用于Where
调用。到目前为止,我可以达到创建表达式的程度,但我无法弄清楚如何将它们合并为单个表达式。
有人能指出我正确的方向吗?通过在线阅读类似的帖子,我已经决定尝试使用LINQKit PredicateBuilder
来做,但我回来的只是初始的起始谓词。这是我到目前为止的代码,它绝不是最终的,我只是在LINQPad中进行原型设计:
void Main() {
// 1. Create form post object
var query = new QueryEdit {
Filters = new List<Filter> {
new Filter {
Id = 1,
Key = "Name",
Operator = "=",
Value = "New York"
},
new Filter {
Id = 2,
Key = "Name",
Operator = "!=",
Value = "Boston"
},
new Filter {
Id = 3,
Key = "Name",
Operator = "=",
Value = "Washington"
}
},
FilterClause = "1 AND 3 OR 2 OR 4"
};
// 2. Compose filter groups
var filterGroups = GetFilterGroups(query);
// 3. Compose expression groups
var expressionGroups = GetExpressionGroups<Region>(filterGroups);
expressionGroups.Dump();
// 4. Compose full expression
var expression = GetExpression(expressionGroups);
expression.Dump();
// 5. Serialize expression to blob
// 6. Deserialize blob to expression
// 7. Execute expression against db
}
static IEnumerable<FilterGroup> GetFilterGroups(
QueryEdit query) {
// The parentheses are meaningless, so they need to be removed.
// The groups are all conditions with ANDs, separated by ORs.
return query.FilterClause.Replace(")", string.Empty).Replace("(", string.Empty).Split(new[] { "OR" }, StringSplitOptions.None).Select(
fc => new FilterGroup {
Filters = fc.Split(new[] { "AND" }, StringSplitOptions.None).Select(
i => {
var id = Convert.ToByte(i);
return query.Filters.SingleOrDefault(
f => f.Id == id);
}).Where(
f => f != null).OrderBy(
f => f.Id)
}).Where(
fg => fg.Filters.Any());
}
static IEnumerable<IEnumerable<Expression<Func<T, bool>>>> GetExpressionGroups<T>(
IEnumerable<FilterGroup> filterGroups) {
var type = typeof(T);
var parameter = Expression.Parameter(type);
return filterGroups.Select(
fg => {
return fg.Filters.Select(
f => {
var left = Expression.Property(parameter, type.GetProperty(f.Key));
var right = Expression.Constant(f.Value);
var body = GetExpressionBody(f.Operator, left, right);
if (body == null) {
return null;
}
return Expression.Lambda<Func<T, bool>>(body, parameter);
}).Where(
e => e != null);
});
}
static object GetExpression<T>(
IEnumerable<IEnumerable<Expression<Func<T, bool>>>> expressionGroups) {
var predicate = PredicateBuilder.True<T>();
foreach (var expressionGroup in expressionGroups) {
var expression = GetPredicateForGroup<T>(expressionGroup);
predicate.Or(expression);
}
return predicate;
}
static Expression<Func<T, bool>> GetPredicateForGroup<T>(
IEnumerable<Expression<Func<T, bool>>> expressionGroup) {
var predicate = PredicateBuilder.True<T>();
foreach (var expression in expressionGroup) {
predicate.And(expression);
}
return predicate;
}
static Expression GetExpressionBody(
string @operator,
Expression left,
Expression right) {
switch (@operator) {
case "=":
return Expression.Equal(left, right);
case "!=":
return Expression.NotEqual(left, right);
case ">":
return Expression.GreaterThan(left, right);
case ">=":
return Expression.GreaterThanOrEqual(left, right);
case "<":
return Expression.LessThan(left, right);
case "<=":
return Expression.LessThanOrEqual(left, right);
default:
return null;
}
}
sealed class QueryEdit {
public IEnumerable<Filter> Filters { get; set; }
public string FilterClause { get; set; }
public int Id { get; set; }
public string Name { get; set; }
}
sealed class Filter {
public byte Id { get; set; }
public string Key { get; set; }
public string Operator { get; set; }
public object Value { get; set; }
}
sealed class FilterGroup {
public IEnumerable<Filter> Filters { get; set; }
}
我也对如何实现这一目标或整体改进的其他建议持开放态度。提前谢谢!
答案 0 :(得分:1)
尝试
predicate = predicate.Or(expression)
和
predicate = predicate.And(expression)
答案 1 :(得分:0)
您可以使用规范模式来组合谓词。我发现这非常有用 - http://gurmitteotia.blogspot.co.uk/2015/06/specification-pattern-with-repository.html#more。他的博客包括链式谓词表达式和一般模式的扩展方法,&amp;它基于EF。