使用 LINQ 使用复杂的过滤逻辑在 C# 中实现基于关键字的高级搜索

问题描述 投票:0回答:1

我目前正在致力于在 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 过滤条件。目标是通过基于括号的优先级分组来检索准确的搜索结果。

c# linq search filtering keyword-search
1个回答
0
投票

好的,所以您的目标是根据用户复合输入过滤一些数据集合。基本上,您需要做两件事:

  1. 解析用户输入
  2. 创建动态 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));
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.