在多个过滤器中使用相同的 lambda 参数
Use the same lambda parameter across multiple filters
我正在编写一个 class,允许用户根据搜索词搜索实体,在幕后对抗 Entity Framework。每个可搜索字段都在 class 中注册。对于字符串字段,搜索只需调用 String.Contains
。对于查找值,需要调用数据库查找 table。
例如,这是如何注册一个简单的字符串过滤器:
Searcher<Account> searcher = new Searcher<Account>();
searcher.AddFilterMapping("Name", e => e.AccountName);
这就是注册查找值的样子:
searcher.AddFilterMapping("Type", e => context.AccountTypes.Where(t => t.Id == e.AccountTypeId).Select(t => t.Description).FirstOrDefault());
上面代码中的context
就是Entity Framework上下文。现在,理想情况下,有人会像这样指定搜索词:
searcher.SearchTerm = searchTerm;
然后搜索应该循环遍历过滤器映射(lambda 表达式)并更新它们的 lambda 表达式以应用 String.Contains
。我能够编写一些非常简单的代码来添加方法调用。
MethodInfo containsMethod = typeof(String).GetMethod("Contains");
ConstantExpression searchTermExpression = Expression.Constant(searchTerm);
foreach (Expression<Func<TEntity, string>> selector in selectors)
{
Expression converter = selector.Body;
MethodCallExpression containsExpression = Expression.Call(converter, containsMethod, searchTermExpression);
LambdaExpression lambdaExpression = Expression.Lambda<Func<TEntity, bool>>(containsExpression, selector.Parameters);
// Then apply the filter to the IQueryable<TEntity> via Where
}
如果我所做的只是一个接一个地应用每个过滤器,这就可以正常工作。但是,我想使用 OR 而不是 AND 进行过滤。在那种情况下,我想要一个 lambda 表达式。我只是使用 Expression.Or
.
组合 lambda 体
问题是 ParameterExpression
对于传递给 AddFilterMapping
的每个表达式树都是不同的。我收到类似 "The parameter 'e' was not bound in the LINQ to Entity expression." 的错误,我很确定这是因为它们是完全不同的参数表达式,即使它们具有相同的名称。
有没有办法确保我的所有 lambda 表达式共享相同的 ParameterExpression
?
您可以使用以下方法将一个表达式的所有实例替换为另一个:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
使用它,您可以创建一个参数表达式以用于您的 lambda,然后用您创建的新参数替换您拥有的 lambda 主体中的所有参数实例。
我正在编写一个 class,允许用户根据搜索词搜索实体,在幕后对抗 Entity Framework。每个可搜索字段都在 class 中注册。对于字符串字段,搜索只需调用 String.Contains
。对于查找值,需要调用数据库查找 table。
例如,这是如何注册一个简单的字符串过滤器:
Searcher<Account> searcher = new Searcher<Account>();
searcher.AddFilterMapping("Name", e => e.AccountName);
这就是注册查找值的样子:
searcher.AddFilterMapping("Type", e => context.AccountTypes.Where(t => t.Id == e.AccountTypeId).Select(t => t.Description).FirstOrDefault());
上面代码中的context
就是Entity Framework上下文。现在,理想情况下,有人会像这样指定搜索词:
searcher.SearchTerm = searchTerm;
然后搜索应该循环遍历过滤器映射(lambda 表达式)并更新它们的 lambda 表达式以应用 String.Contains
。我能够编写一些非常简单的代码来添加方法调用。
MethodInfo containsMethod = typeof(String).GetMethod("Contains");
ConstantExpression searchTermExpression = Expression.Constant(searchTerm);
foreach (Expression<Func<TEntity, string>> selector in selectors)
{
Expression converter = selector.Body;
MethodCallExpression containsExpression = Expression.Call(converter, containsMethod, searchTermExpression);
LambdaExpression lambdaExpression = Expression.Lambda<Func<TEntity, bool>>(containsExpression, selector.Parameters);
// Then apply the filter to the IQueryable<TEntity> via Where
}
如果我所做的只是一个接一个地应用每个过滤器,这就可以正常工作。但是,我想使用 OR 而不是 AND 进行过滤。在那种情况下,我想要一个 lambda 表达式。我只是使用 Expression.Or
.
问题是 ParameterExpression
对于传递给 AddFilterMapping
的每个表达式树都是不同的。我收到类似 "The parameter 'e' was not bound in the LINQ to Entity expression." 的错误,我很确定这是因为它们是完全不同的参数表达式,即使它们具有相同的名称。
有没有办法确保我的所有 lambda 表达式共享相同的 ParameterExpression
?
您可以使用以下方法将一个表达式的所有实例替换为另一个:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
使用它,您可以创建一个参数表达式以用于您的 lambda,然后用您创建的新参数替换您拥有的 lambda 主体中的所有参数实例。