通过表达式树构建表达式列表
Building a list of expressions via Expression Trees
我有以下基于列名和值构建表达式的方法:
public Func<TSource, bool> SimpleComparison<TSource>(string property, object value)
{
var type = typeof (TSource);
var pe = Expression.Parameter(type, "p");
var propertyReference = Expression.Property(pe, property);
var constantReference = Expression.Constant(value);
return Expression.Lambda<Func<TSource, bool>>
(Expression.Equal(propertyReference, constantReference),
new[] { pe }).Compile();
}
我正在尝试创建一个表达式列表,然后使用最终的 concatenated/compiled 表达式查询我的数据源。
我尝试过使用Expression.Block
,但这个概念对我来说有点难以理解。我也看到了 Expression.Loop
但不确定它是否是我需要在这里使用的。
理想情况下,我可以这样做:
var filters = request.Filter.Filters;
IQueryable<MyDTO> myDataSource = context.MyDataSource;
var expressions = null;
foreach (var filter in filters)
{
expressions.Add(SimpleExpression<MyDTO>(filter.Name, filter.Value));
}
return myDataSource.Where(expressions);
关于如何做这样的事情有什么想法吗?
您可以将函数更改为 return 编译后的 lambda 使其成为 return 基本表达式。一旦将这些表达式存储在列表中,就可以使用 Linq Aggregate()
函数构建最终谓词。
public Func<TSource, bool> CreatePredicate<TSource>(IEnumerable<Expression> expressions)
{
var parameter = Expression.Parameter(typeof(TSource), "x");
var body = expressions.Aggregate((e, next) => Expression.AndAlso(e, next));
var predicate = Expression.Lambda<Func<TSource, bool>>(body, parameter).Compile();
return predicate;
}
这假设您希望使用 &&
连接所有语句。如果您希望他们使用 ||
加入,请使用 Expression.OrElse
代替 Expression.AndAlso
。如果混合使用 and 过滤器和/或过滤器,解决方案将变得相当复杂。
你上面的例子会变成
var filters = request.Filter.Filters;
IQueryable<MyDTO> myDataSource = context.MyDataSource;
var expressions = new List<Expression>();
foreach (var filter in filters)
{
expressions.Add(SimpleComparison<MyDTO>(filter.Name, filter.Value));
}
var predicate = CreatePredicate<MyDTO>(expressions);
return myDataSource.Where(predicate);
你想多了。您根本不需要组合您的表达式。唯一复杂的部分是实施 SimpleComparison
,但您已经完成了。好吧,主要是。您应该返回 Expression<Func<...>>
,而不是 Func<...>
,所以应该是
public Expression<Func<TSource, bool>> SimpleComparison<TSource>(string property, object value)
{
// ...
return Expression.Lambda<Func<TSource, bool>>
(Expression.Equal(propertyReference, constantReference),
new[] { pe });
}
完成后,您可以通过重复调用 Where
来链接过滤器,如下所示:
var filters = request.Filter.Filters;
IQueryable<MyDTO> query = context.MyDataSource;
foreach (var filter in filters)
query = query.Where(SimpleComparison<MyDTO>(filter.Name, filter.Value));
return query;
我最近遇到了你的确切问题,当我尝试将多个表达式与 && 或 || 组合时,以下解决方案对我有用:
public Expression<Func<TSource, bool>> SimpleComparison<TSource>(List<QueryFilterObject> queryFilterObjects)
{
//initialize the body expression
BinaryExpression bodyExpression = null;
BinaryExpression andExpressionBody = null;
BinaryExpression orExpressionBody = null;
//create parameter expression
ParameterExpression parameterExpression = Expression.Parameter(typeof(TSource), "DynamicFilterQuery");
//list of binary expressions to store either the || or && operators
List<BinaryExpression> andExpressions = new List<BinaryExpression>();
List<BinaryExpression> orExpressions = new List<BinaryExpression>();
foreach (var queryFilterObject in queryFilterObjects)
{
//create member property expression
var property = Expression.Property(parameterExpression, queryFilterObject.PropertyName);
//create the constant expression value
var constantExpressionValue = Expression.Constant(queryFilterObject.Value, queryFilterObject.PropertyType);
//create the binary expression clause based on the comparison operator
BinaryExpression clause = null;
if (queryFilterObject.ComparisonOperator == "==")
{
clause = Expression.Equal(property, constantExpressionValue);
}
else if (queryFilterObject.ComparisonOperator == "!=")
{
clause = Expression.NotEqual(property, constantExpressionValue);
}
else if (queryFilterObject.ComparisonOperator == ">")
{
clause = Expression.GreaterThan(property, constantExpressionValue);
}
else if (queryFilterObject.ComparisonOperator == ">=")
{
clause = Expression.GreaterThan(property, constantExpressionValue);
}
else if (queryFilterObject.ComparisonOperator == "<")
{
clause = Expression.LessThan(property, constantExpressionValue);
}
else if (queryFilterObject.ComparisonOperator == "<=")
{
clause = Expression.LessThanOrEqual(property, constantExpressionValue);
}
//you should validate against a null clause....
//assign the item either to the relevant logical comparison expression list
if (queryFilterObject.LogicalOperator == "and" || queryFilterObject.LogicalOperator == "&&")
{
andExpressions.Add(clause);
}
else if (queryFilterObject.LogicalOperator == "or" || queryFilterObject.LogicalOperator == "||")
{
orExpressions.Add(clause);
}
}
if (andExpressions.Count > 0)
andExpressionBody = andExpressions.Aggregate((e, next) => Expression.And(e, next));
if (orExpressions.Count > 0)
orExpressionBody = orExpressions.Aggregate((e, next) => Expression.Or(e, next));
if (andExpressionBody != null && orExpressionBody != null)
bodyExpression = Expression.OrElse(andExpressionBody, orExpressionBody);
if (andExpressionBody != null && orExpressionBody == null)
bodyExpression = andExpressionBody;
if (andExpressionBody == null && orExpressionBody != null)
bodyExpression = orExpressionBody;
if (bodyExpression == null)
throw new Exception("Null Expression.");
var finalExpression = Expression.Lambda<Func<WorkOrder, bool>>(bodyExpression, parameterExpression);
return finalExpression;
}
public class QueryFilterObject
{
public string PropertyName { get; set; }
public Type PropertyType { get; set; }
public object Value { get; set; }
public string ComparisonOperator { get; set; }
public string LogicalOperator { get; set; }
}
在我的例子中,我 return func 的表达式 (Expression<Func<TSource, bool>>
) 而不是 func 本身 (Func<TSource, bool>
)。这允许我的 where 子句保留为 Iqueryable,否则 returning Func<TSource, bool>
而不是 *Expression<Func<TSource, bool>>*
会将您的 where 子句变成一个 ienumerable。
最后,我只是像下面这样调用我的表达式:
IQueryable<MyDTO> myDataSource = context.MyDataSource;
var filter = SimpleComparison(queryFilterObjects);
if (filter != null)
myDataSource = myDataSource.Where(filter);
//perfom other operations such as order by
return myDataSource.ToList();
我有以下基于列名和值构建表达式的方法:
public Func<TSource, bool> SimpleComparison<TSource>(string property, object value)
{
var type = typeof (TSource);
var pe = Expression.Parameter(type, "p");
var propertyReference = Expression.Property(pe, property);
var constantReference = Expression.Constant(value);
return Expression.Lambda<Func<TSource, bool>>
(Expression.Equal(propertyReference, constantReference),
new[] { pe }).Compile();
}
我正在尝试创建一个表达式列表,然后使用最终的 concatenated/compiled 表达式查询我的数据源。
我尝试过使用Expression.Block
,但这个概念对我来说有点难以理解。我也看到了 Expression.Loop
但不确定它是否是我需要在这里使用的。
理想情况下,我可以这样做:
var filters = request.Filter.Filters;
IQueryable<MyDTO> myDataSource = context.MyDataSource;
var expressions = null;
foreach (var filter in filters)
{
expressions.Add(SimpleExpression<MyDTO>(filter.Name, filter.Value));
}
return myDataSource.Where(expressions);
关于如何做这样的事情有什么想法吗?
您可以将函数更改为 return 编译后的 lambda 使其成为 return 基本表达式。一旦将这些表达式存储在列表中,就可以使用 Linq Aggregate()
函数构建最终谓词。
public Func<TSource, bool> CreatePredicate<TSource>(IEnumerable<Expression> expressions)
{
var parameter = Expression.Parameter(typeof(TSource), "x");
var body = expressions.Aggregate((e, next) => Expression.AndAlso(e, next));
var predicate = Expression.Lambda<Func<TSource, bool>>(body, parameter).Compile();
return predicate;
}
这假设您希望使用 &&
连接所有语句。如果您希望他们使用 ||
加入,请使用 Expression.OrElse
代替 Expression.AndAlso
。如果混合使用 and 过滤器和/或过滤器,解决方案将变得相当复杂。
你上面的例子会变成
var filters = request.Filter.Filters;
IQueryable<MyDTO> myDataSource = context.MyDataSource;
var expressions = new List<Expression>();
foreach (var filter in filters)
{
expressions.Add(SimpleComparison<MyDTO>(filter.Name, filter.Value));
}
var predicate = CreatePredicate<MyDTO>(expressions);
return myDataSource.Where(predicate);
你想多了。您根本不需要组合您的表达式。唯一复杂的部分是实施 SimpleComparison
,但您已经完成了。好吧,主要是。您应该返回 Expression<Func<...>>
,而不是 Func<...>
,所以应该是
public Expression<Func<TSource, bool>> SimpleComparison<TSource>(string property, object value)
{
// ...
return Expression.Lambda<Func<TSource, bool>>
(Expression.Equal(propertyReference, constantReference),
new[] { pe });
}
完成后,您可以通过重复调用 Where
来链接过滤器,如下所示:
var filters = request.Filter.Filters;
IQueryable<MyDTO> query = context.MyDataSource;
foreach (var filter in filters)
query = query.Where(SimpleComparison<MyDTO>(filter.Name, filter.Value));
return query;
我最近遇到了你的确切问题,当我尝试将多个表达式与 && 或 || 组合时,以下解决方案对我有用:
public Expression<Func<TSource, bool>> SimpleComparison<TSource>(List<QueryFilterObject> queryFilterObjects)
{
//initialize the body expression
BinaryExpression bodyExpression = null;
BinaryExpression andExpressionBody = null;
BinaryExpression orExpressionBody = null;
//create parameter expression
ParameterExpression parameterExpression = Expression.Parameter(typeof(TSource), "DynamicFilterQuery");
//list of binary expressions to store either the || or && operators
List<BinaryExpression> andExpressions = new List<BinaryExpression>();
List<BinaryExpression> orExpressions = new List<BinaryExpression>();
foreach (var queryFilterObject in queryFilterObjects)
{
//create member property expression
var property = Expression.Property(parameterExpression, queryFilterObject.PropertyName);
//create the constant expression value
var constantExpressionValue = Expression.Constant(queryFilterObject.Value, queryFilterObject.PropertyType);
//create the binary expression clause based on the comparison operator
BinaryExpression clause = null;
if (queryFilterObject.ComparisonOperator == "==")
{
clause = Expression.Equal(property, constantExpressionValue);
}
else if (queryFilterObject.ComparisonOperator == "!=")
{
clause = Expression.NotEqual(property, constantExpressionValue);
}
else if (queryFilterObject.ComparisonOperator == ">")
{
clause = Expression.GreaterThan(property, constantExpressionValue);
}
else if (queryFilterObject.ComparisonOperator == ">=")
{
clause = Expression.GreaterThan(property, constantExpressionValue);
}
else if (queryFilterObject.ComparisonOperator == "<")
{
clause = Expression.LessThan(property, constantExpressionValue);
}
else if (queryFilterObject.ComparisonOperator == "<=")
{
clause = Expression.LessThanOrEqual(property, constantExpressionValue);
}
//you should validate against a null clause....
//assign the item either to the relevant logical comparison expression list
if (queryFilterObject.LogicalOperator == "and" || queryFilterObject.LogicalOperator == "&&")
{
andExpressions.Add(clause);
}
else if (queryFilterObject.LogicalOperator == "or" || queryFilterObject.LogicalOperator == "||")
{
orExpressions.Add(clause);
}
}
if (andExpressions.Count > 0)
andExpressionBody = andExpressions.Aggregate((e, next) => Expression.And(e, next));
if (orExpressions.Count > 0)
orExpressionBody = orExpressions.Aggregate((e, next) => Expression.Or(e, next));
if (andExpressionBody != null && orExpressionBody != null)
bodyExpression = Expression.OrElse(andExpressionBody, orExpressionBody);
if (andExpressionBody != null && orExpressionBody == null)
bodyExpression = andExpressionBody;
if (andExpressionBody == null && orExpressionBody != null)
bodyExpression = orExpressionBody;
if (bodyExpression == null)
throw new Exception("Null Expression.");
var finalExpression = Expression.Lambda<Func<WorkOrder, bool>>(bodyExpression, parameterExpression);
return finalExpression;
}
public class QueryFilterObject
{
public string PropertyName { get; set; }
public Type PropertyType { get; set; }
public object Value { get; set; }
public string ComparisonOperator { get; set; }
public string LogicalOperator { get; set; }
}
在我的例子中,我 return func 的表达式 (Expression<Func<TSource, bool>>
) 而不是 func 本身 (Func<TSource, bool>
)。这允许我的 where 子句保留为 Iqueryable,否则 returning Func<TSource, bool>
而不是 *Expression<Func<TSource, bool>>*
会将您的 where 子句变成一个 ienumerable。
最后,我只是像下面这样调用我的表达式:
IQueryable<MyDTO> myDataSource = context.MyDataSource;
var filter = SimpleComparison(queryFilterObjects);
if (filter != null)
myDataSource = myDataSource.Where(filter);
//perfom other operations such as order by
return myDataSource.ToList();