将 Linq to EF 查询构建到可变列数中的可变关键字列表?
Building a Linq to EF query to a variable list of keywords in a variable number of columns?
我正在尝试提出一种实用方法来构建 Linq 查询或 Linq 谓词,以将其添加到 Linq to EF 查询中,以在可变列数的术语列表中搜索所有术语。
我正在尝试使用 PredicateBuilder 构建 where 子句。使用一个搜索词和一个固定的列列表就相对容易了。
到目前为止,我尝试编写的伪代码如下所示:
private static Predicate<Project> CreateDynamicSearch(IEnumerable<strings> searchableColumns, string[] searchTerms)
{
var predicate = PredicateBuilder.True<Project>();
foreach (var columnName in searchableColumns)
{
foreach (var term in searchTerms)
{
predicate = predicate.And(a => a.**columnName**.Contains(term));
}
predicate = predicate.Or(predicate);
}
return predicate;
}
我遇到的最大问题是处理 columnName 的表达式。以前的建议是使用表达式树,但我不明白在这种情况下它是如何工作的。
** 更新**
更新后,我已经采用了您拥有的代码。它构建但是当我实际调用它时 Extension.Property(param,columnName); 行出错,错误 Instance 属性 'Name' 没有为类型 'System.Func`2[Myclass,System.Boolean]' 消息定义。 columnName = "Name"
** 更新 2 **
它的调用方式:
var test = CreateDynamicSearch<Func<Project, bool>>(searchCols, searchTerms);
你可以自己构建谓词的表达式,在这种情况下相对容易:
private static Expression<Func<T, bool>> CreateDynamicSearch<T>(IEnumerable<string> searchableColumns, string[] searchTerms) {
// start with true, since we combine with AND
// and true AND anything is the same as just anything
var predicate = PredicateBuilder.True<T>();
foreach (var columnName in searchableColumns) {
// start with false, because we combine with OR
// and false OR anything is the same as just anything
var columnFilter = PredicateBuilder.False<T>();
foreach (var term in searchTerms) {
// a =>
var param = Expression.Parameter(typeof(T), "a");
// a => a.ColumnName
var prop = Expression.Property(param, columnName);
// a => a.ColumnName.Contains(term)
var call = Expression.Call(prop, "Contains", new Type[0], Expression.Constant(term));
columnFilter = columnFilter.Or(Expression.Lambda<Func<T, bool>>(call, param));
}
predicate = predicate.And(columnFilter);
}
return predicate;
}
回应评论
I was just curious if there was some way you could combine the
expression created by Expression.Property(param, columnName) with the
one the compiler generates for (string s) -> s.Contains(term)
您可以(例如)像这样:
// a =>
var param = Expression.Parameter(typeof(T), "a");
// a => a.ColumnName
var prop = Expression.Property(param, columnName);
// s => s.Contains(term)
Expression<Func<string, bool>> contains = (string s) => s.Contains(term);
// extract body - s.Contains(term)
var containsBody = (MethodCallExpression)contains.Body;
// replace "s" parameter with our property - a.ColumnName.Contains(term)
// Update accepts new target as first parameter (old target in this case is
// "s" parameter and new target is "a.ColumnName")
// and list of arguments (in this case it's "term" - we don't need to update that).
//
var call = containsBody.Update(prop, containsBody.Arguments);
columnFilter = columnFilter.Or(Expression.Lambda<Func<T, bool>>(call, param));
我正在尝试提出一种实用方法来构建 Linq 查询或 Linq 谓词,以将其添加到 Linq to EF 查询中,以在可变列数的术语列表中搜索所有术语。
我正在尝试使用 PredicateBuilder 构建 where 子句。使用一个搜索词和一个固定的列列表就相对容易了。 到目前为止,我尝试编写的伪代码如下所示:
private static Predicate<Project> CreateDynamicSearch(IEnumerable<strings> searchableColumns, string[] searchTerms)
{
var predicate = PredicateBuilder.True<Project>();
foreach (var columnName in searchableColumns)
{
foreach (var term in searchTerms)
{
predicate = predicate.And(a => a.**columnName**.Contains(term));
}
predicate = predicate.Or(predicate);
}
return predicate;
}
我遇到的最大问题是处理 columnName 的表达式。以前的建议是使用表达式树,但我不明白在这种情况下它是如何工作的。
** 更新** 更新后,我已经采用了您拥有的代码。它构建但是当我实际调用它时 Extension.Property(param,columnName); 行出错,错误 Instance 属性 'Name' 没有为类型 'System.Func`2[Myclass,System.Boolean]' 消息定义。 columnName = "Name"
** 更新 2 ** 它的调用方式:
var test = CreateDynamicSearch<Func<Project, bool>>(searchCols, searchTerms);
你可以自己构建谓词的表达式,在这种情况下相对容易:
private static Expression<Func<T, bool>> CreateDynamicSearch<T>(IEnumerable<string> searchableColumns, string[] searchTerms) {
// start with true, since we combine with AND
// and true AND anything is the same as just anything
var predicate = PredicateBuilder.True<T>();
foreach (var columnName in searchableColumns) {
// start with false, because we combine with OR
// and false OR anything is the same as just anything
var columnFilter = PredicateBuilder.False<T>();
foreach (var term in searchTerms) {
// a =>
var param = Expression.Parameter(typeof(T), "a");
// a => a.ColumnName
var prop = Expression.Property(param, columnName);
// a => a.ColumnName.Contains(term)
var call = Expression.Call(prop, "Contains", new Type[0], Expression.Constant(term));
columnFilter = columnFilter.Or(Expression.Lambda<Func<T, bool>>(call, param));
}
predicate = predicate.And(columnFilter);
}
return predicate;
}
回应评论
I was just curious if there was some way you could combine the expression created by Expression.Property(param, columnName) with the one the compiler generates for (string s) -> s.Contains(term)
您可以(例如)像这样:
// a =>
var param = Expression.Parameter(typeof(T), "a");
// a => a.ColumnName
var prop = Expression.Property(param, columnName);
// s => s.Contains(term)
Expression<Func<string, bool>> contains = (string s) => s.Contains(term);
// extract body - s.Contains(term)
var containsBody = (MethodCallExpression)contains.Body;
// replace "s" parameter with our property - a.ColumnName.Contains(term)
// Update accepts new target as first parameter (old target in this case is
// "s" parameter and new target is "a.ColumnName")
// and list of arguments (in this case it's "term" - we don't need to update that).
//
var call = containsBody.Update(prop, containsBody.Arguments);
columnFilter = columnFilter.Or(Expression.Lambda<Func<T, bool>>(call, param));