我目前正在致力于在 C# 中实现基于关键字的高级搜索功能。目的是允许用户使用“AND”和“OR”等关键字以及括号对搜索条件进行分组来执行复杂的搜索。尽管存在类似的问题,但考虑到括号分组,它们往往缺乏全面的解决方案。
问题详情: 我在创建一个强大的搜索算法时面临着挑战,该算法可以解析用户提供的关键字并使用 LINQ 将它们转换为一组复杂的过滤条件。主要目标是处理多个“AND”/“OR”条件并优先考虑括号内的搜索条件,以实现准确的数据检索。
我尝试过的:
// Sample code skeleton for implementing complex keyword-based search using LINQ
public class SearchManager
{
public IEnumerable<ResultItem> PerformComplexSearch(string searchQuery)
{
// Parsing searchQuery and constructing LINQ query with complex filtering logic
// ...
}
}
// Example usage
SearchManager manager = new SearchManager();
var searchResults = manager.PerformComplexSearch(userInputQuery);
// Process and display the searchResults
预期结果: 我期望有关如何有效解析用户输入的包含“AND”、“OR”和括号的搜索查询的指南或代码示例,以将它们转换为一组全面的 LINQ 过滤条件。目标是通过基于括号的优先级分组来检索准确的搜索结果。
好的,所以您的目标是根据用户复合输入过滤一些数据集合。基本上,您需要做两件事:
我认为第一步不是问题,所以我会尝试解决第二步。
因此,假设我们已经神奇地将输入解析为 RPN(逆波兰表示法)标记的集合。有两种类型的标记:过滤器(Autor 以“St”开头)和运算符(AND)。让我们为此类代币定义模型:
class RPNToken
{
public int Index { get; set; }
}
class RPNFilter : RPNToken
{
public string Field { get; set; }
public string Value { get; set; }
public FilterType FilterType { get; set; } // Filter type can be "equals", "start with" etc.
}
class RPNOperator: RpnToken
{
public OperatorType Type { get; set; } // Can be "AND" or "OR"
}
所以在输入解析之后我们有
IEnumerable<RPNToken>
。
下一步是创建一个Where表达式(Expression<Func<TModel, bool>>
),我们可以将其放入LINQWhere方法中。
public IEnumerable<T> FilterCollection<T>(IQueryable<T> data, IEnumerable<RPNToken> filters)
{
var whereExpression = GetWhereExpression<T>(filters) // this is our main goal
var result = data.Where(whereExpression);
return result;
}
注意:表达式作为 LINQ 有效,其中参数仅适用于
IQueryable<T>
,如果有 IEnumerable<T>
,可以使用 .AsQueryable()
扩展方法转换为 IQueryable<T>
让我们实现
GetWhereExpression<T>
:
public Expression<Func<T, bool>> WhereBodyExpression<T>(IEnumerable<RPNToken> filters)
{
// create an expression parameter of type T
var param = Expression.Parameter(typeof(T), "m");
// parse RPN token collection. Our goal is to have one big expression that contains all filters and operators.
var buffer = new Stack<Expression>();
foreach (var token in filters)
{
if (token is RPNOperator op)
{
var operand2 = buffer.Pop();
var operand1 = buffer.Pop();
var expressionType = op.Type == OperatorType.And ? ExpressionType.AndAlso : ExpressionType.OrElse;
buffer.Push(Expression.MakeBinary(expressionType, operand1, operand2));
}
else
{
// if token is a filter then convert it to expression
buffer.Push(ExpressionFromFilter((RPNFilter)token, param));
}
}
var lambdaExpression = Expression.Lambda<Func<T, bool>>(buffer.Pop(), param);
return lambdaExpression;
}
那么我们来实现
ExpressionFromFilter
方法:
public Expression ExpressionFromFilter(RPNFilter filter, ParameterExpression param)
{
// if a filter field is complex (for example Author.Address.City.Street) the logic will be different
var property = Expression.Property(param, filter.Field);
// if FilterType is Equals
if (filter.FilterType == FilterType.Equals)
{
return Expression.MakeBinary(ExpressionType.Equal, property,
ConstExpression(filter.Value!, property.Type));
}
throw new NotImplementedException();
// if there are more filter types, then you will need to implement all of then here
}
public ConstantExpression ConstExpression(string val, Type targetType)
{
// As an example I handle only the case when filter type is a string
if(targetType is typeof(string)
{
return Expression.Constant(val, typeof(string));
}
}