所以,这很复杂。
我在集合中有一组规则,规则包含这三个属性。
Field, Op, and Data (all strings)
因此,规则可能看起来像“州”,“eq”,“CA”
我的一般规则是所有规则都是AND。但是,需要注意的是,如果它们具有相同的Field值,那么这些都是ORed。这允许我们说“State”,“eq”,“CA”,或“State”,“eq”,“TX”和“FirstName”,“eq”,“John”。
问题在于,我目前应用规则的方式不起作用,因为它只是使用每条规则构建linq表达式,以使其越来越明确。
var result = rules.Aggregate(_repository.All, (current, rule) => current.ExtendQuery(rule))
ExtendQuery
是我编写的扩展方法,它使用ExpressionTrees生成一个新查询,将当前规则应用于传入的查询。 (有效地将它们整合在一起)
现在我不难修改.Aggregate
行以按字段对规则进行分组,然后为每个字段生成唯一查询,但是如何将其设置为“OR”他们在一起而不是“和”?
然后对于每个查询,我将如何将它们“和”在一起?联盟?
ExtendQuery看起来像这样
public static IQueryable<T> ExtendQuery<T>(this IQueryable<T> query, QueryableRequestMessage.WhereClause.Rule rule) where T : class
{
var parameter = Expression.Parameter(typeof(T), "x");
Expression property = Expression.Property(parameter, rule.Field);
var type = property.Type;
ConstantExpression constant;
if (type.IsEnum)
{
var enumeration = Enum.Parse(type, rule.Data);
var intValue = (int)enumeration;
constant = Expression.Constant(intValue);
type = typeof(int);
//Add "Id" by convention, this is all because enum support is lacking at this point in Entity Framework
property = Expression.Property(parameter, rule.Field + "Id");
}
else if(type == typeof(DateTime))
{
constant = Expression.Constant(DateTime.ParseExact(rule.Data, "dd/MM/yyyy", CultureInfo.CurrentCulture));
}
else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
//This will convert rule.Data to the baseType, not a nullable type (because that won't work)
var converter = TypeDescriptor.GetConverter(type);
var value = converter.ConvertFrom(rule.Data);
constant = Expression.Constant(value);
//We change the type of property to get converted to it's base type
//This is because Expression.GreaterThanOrEqual can't compare a decimal with a Nullable<decimal>
var baseType = type.GetTypeOfNullable();
property = Expression.Convert(property, baseType);
}
else
{
constant = Expression.Constant(Convert.ChangeType(rule.Data, type));
}
switch (rule.Op)
{
case "eq": //Equals
case "ne": //NotEquals
{
var condition = rule.Op.Equals("eq")
? Expression.Equal(property, constant)
: Expression.NotEqual(property, constant);
var lambda = Expression.Lambda(condition, parameter);
var call = Expression.Call(typeof(Queryable), "Where", new[] { query.ElementType }, query.Expression, lambda);
query = query.Provider.CreateQuery<T>(call);
break;
}
case "lt": //Less Than
query = type == typeof (String)
? QueryExpressionString(query, Expression.LessThan, type, property, constant, parameter)
: QueryExpression(query, Expression.LessThan, property, constant, parameter); break;
case "le": //Less Than or Equal To
query = type == typeof (String)
? QueryExpressionString(query, Expression.LessThanOrEqual, type, property, constant, parameter)
: QueryExpression(query, Expression.LessThanOrEqual, property, constant, parameter); break;
case "gt": //Greater Than
query = type == typeof (String)
? QueryExpressionString(query, Expression.GreaterThan, type, property, constant, parameter)
: QueryExpression(query, Expression.GreaterThan, property, constant, parameter); break;
case "ge": //Greater Than or Equal To
query = type == typeof (String)
? QueryExpressionString(query, Expression.GreaterThanOrEqual, type, property, constant, parameter)
: QueryExpression(query, Expression.GreaterThanOrEqual, property, constant, parameter); break;
case "bw": //Begins With
case "bn": //Does Not Begin With
query = QueryMethod(query, rule, type, "StartsWith", property, constant, "bw", parameter); break;
case "ew": //Ends With
case "en": //Does Not End With
query = QueryMethod(query, rule, type, "EndsWith", property, constant, "cn", parameter); break;
case "cn": //Contains
case "nc": //Does Not Contain
query = QueryMethod(query, rule, type, "Contains", property, constant, "cn", parameter); break;
case "nu": //TODO: Null
case "nn": //TODO: Not Null
break;
}
return query;
}
private static IQueryable<T> QueryExpression<T>(
IQueryable<T> query,
Func<Expression, Expression, BinaryExpression> expression,
Expression property,
Expression value,
ParameterExpression parameter
) where T : class
{
var condition = expression(property, value);
var lambda = Expression.Lambda(condition, parameter);
var call = Expression.Call(typeof(Queryable), "Where", new[] { query.ElementType }, query.Expression, lambda);
query = query.Provider.CreateQuery<T>(call);
return query;
}
private static IQueryable<T> QueryExpressionString<T>(
IQueryable<T> query,
Func<Expression, Expression, BinaryExpression> expression,
Type type,
Expression property,
Expression value,
ParameterExpression parameter)
{
var containsmethod = type.GetMethod("CompareTo", new[] { type });
var callContains = Expression.Call(property, containsmethod, value);
var call = expression(callContains, Expression.Constant(0, typeof(int)));
return query.Where(Expression.Lambda<Func<T, bool>>(call, parameter));
}
private static IQueryable<T> QueryMethod<T>(
IQueryable<T> query,
QueryableRequestMessage.WhereClause.Rule rule,
Type type,
string methodName,
Expression property,
Expression value,
string op,
ParameterExpression parameter
) where T : class
{
var containsmethod = type.GetMethod(methodName, new[] { type });
var call = Expression.Call(property, containsmethod, value);
var expression = rule.Op.Equals(op)
? Expression.Lambda<Func<T, bool>>(call, parameter)
: Expression.Lambda<Func<T, bool>>(Expression.IsFalse(call), parameter);
query = query.Where(expression);
return query;
}
答案 0 :(得分:1)
您可以使用PredicateBuilder
from LINQKit执行此操作。如果您为每个规则创建Expression
,则可以使用And()
和Or()
方法将它们合并(可能使用PredicateBuilder.True()
和False()
作为基本情况)。最后,调用Expand()
,以便查询采用查询提供程序可理解的形式。
假设您更改ExtendQuery()
以返回Expression
并将其重命名为CreateRuleQuery()
,您的代码可能如下所示:
static IQueryable<T> ApplyRules<T>(
this IQueryable<T> source, IEnumerable<Rule> rules)
{
var predicate = PredicateBuilder.True<T>();
var groups = rules.GroupBy(r => r.Field);
foreach (var group in groups)
{
var groupPredicate = PredicateBuilder.False<T>();
foreach (var rule in group)
{
groupPredicate = groupPredicate.Or(CreateRuleQuery(rule));
}
predicate = predicate.And(groupPredicate);
}
return source.Where(predicate.Expand());
}
用法如下:
IQueryable<Person> source = …;
IQueryable<Person> result = source.ApplyRules(rules);
如果你在这些规则上使用它:
Name, eq, Peter
Name, eq, Paul
Age, ge, 18
然后谓词的主体(来自谓词的Debug视图):
True && (False || $f.Name == "Peter" || $f.Name == "Paul") && (False || $f.Age >= 18)
所有这些True
和False
都不应成为问题,但您可以通过使ApplyRules()
更复杂来摆脱它们。
答案 1 :(得分:1)
实际上这很容易。
现在,您的代码会为每个规则生成一个代码,您需要的是一个有一点复杂条件的代码,因此对代码进行了一些修改:
private static Expression GetComparisonExpression(this Rule rule, ParameterExpression parameter)
{
Expression property = Expression.Property(parameter, rule.Field);
ConstantExpression constant = Expression.Constant(4);
/* the code that generates constant and does some other stuff */
switch (rule.Op)
{
case "eq": //Equals
case "ne": //NotEquals
{
var condition = rule.Op.Equals("eq")
? Expression.Equal(property, constant)
: Expression.NotEqual(property, constant);
return condition;
}
default:
throw new NotImplementedException();
}
}
这是您原始代码所需内容的摘要。此方法不会包装查询,只是简单地使用rule
中的任何内容生成给定参数的比较表达式。
现在从生成查询的此语句开始:
var result = rules.Generate(_repository.All);
生成方法按属性名称Field
对规则进行分组,并为每个组生成and also
(这只是&&
运算符)条件表达式:
(group1Comparision) && (group2Comparison) && so on
public static IQueryable<T> Generate<T>(this IEnumerable<Rule> rules, IQueryable<T> query) where T : class
{
if (rules.Count() == 0)
return query;
var groups = rules.GroupBy(x => x.Field).ToArray();
var parameter = Expression.Parameter(typeof(T));
var comparison = groups.First().GetComparisonForGroup(parameter);
foreach (var group in groups.Skip(1))
{
var otherComparions = group.GetComparisonForGroup(parameter);
comparison = Expression.AndAlso(comparison, otherComparions);
}
var lambda = Expression.Lambda(comparison, parameter);
var call = Expression.Call(typeof(Queryable), "Where", new[] { query.ElementType }, query.Expression, lambda);
return query.Provider.CreateQuery<T>(call);
}
请注意,按属性名称分组会使规则的原始顺序无关。
最后一件事是为组创建比较,以便||
运算符:
public static Expression GetComparisonForGroup(this IEnumerable<Rule> group, ParameterExpression parameter)
{
var comparison = group.Select((rule) => rule.GetComparisonExpression(parameter)).ToArray();
return comparison.Skip(1).Aggregate(comparison.First(),
(left, right) => Expression.OrElse(left, right));
}
因此,对于给定的规则列表,不需要外部库:
var rules = new Rule[]
{
new Rule{ Field = "A", Data = "4", Op="ne"},
new Rule{ Field = "B", Data = "4", Op="eq"},
new Rule{ Field = "A", Data = "4", Op="eq"},
new Rule{ Field = "C", Data = "4", Op="ne"},
new Rule{ Field = "A", Data = "4", Op="eq"},
new Rule{ Field = "C", Data = "4", Op="eq"},
};
我生成了这样的条件,引入了对您的查询的单Where
次调用:
($var1.A != 4 || $var1.A == 4 || $var1.A == 4) && $var1.B == 4 && ($var1.C != 4 || $var1.C == 4)
答案 2 :(得分:0)
LINQKit的替代方法是.AsExpandable()
以下我的ExpressionBuilder
类,它避免了对Expression<Func<T,bool>>
的需求。它通过使用And或Or来组合两个Expression
来创建一个新的表达式,并使用纯using System;
using System.Linq;
using System.Linq.Expressions;
using System.Collections.Generic;
public static class ExpressionBuilder
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<T> Compose<T>(this Expression<T> first,
Expression<T> second,
Func<Expression, Expression, Expression> merge)
{
// build parameter map (from parameters of second to parameters of first)
var map = first.Parameters
.Select((f, i) => new { f, s = second.Parameters[i] })
.ToDictionary(p => p.s, p => p.f);
// replace parameters in the second lambda expression with parameters from
// the first
var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
// apply composition of lambda expression bodies to parameters from
// the first expression
return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
}
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> first,
Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.And);
}
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> first,
Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.Or);
}
public class ParameterRebinder : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, ParameterExpression> map;
public ParameterRebinder(
Dictionary<ParameterExpression,
ParameterExpression> map)
{
this.map = map??new Dictionary<ParameterExpression,ParameterExpression>();
}
public static Expression ReplaceParameters(
Dictionary<ParameterExpression,
ParameterExpression> map,
Expression exp)
{
return new ParameterRebinder(map).Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression p)
{
ParameterExpression replacement;
if (map.TryGetValue(p, out replacement))
{
p = replacement;
}
return base.VisitParameter(p);
}
}
}
代码(没有.Compile()或类似代码)来完成它。 我不记得这方面的一些想法的原始来源,但如果有人知道,我们很乐意添加它们。
{{1}}