简体   繁体   中英

Search a term in multiple columns

I'd like to create an extension to search terms in multiple columns. Terms are separated with space, each term must appears to at least one given column.

Here what I've done so far:

public static IQueryable<TSource> SearchIn<TSource>(this IQueryable<TSource> query, 
    string searchText, 
    Expression<Func<TSource, string>> expression, 
    params Expression<Func<TSource, string>>[] expressions)
{
    if (string.IsNullOrWhiteSpace(searchText))
    {
        return query;
    }

    // Concat expressions
    expressions = new[] { expression }.Concat(expressions).ToArray();

    // Format search text
    var formattedSearchText = searchText.FormatForSearch();
    var searchParts = formattedSearchText.Replace('\'', ' ').Split(' ');

    // Initialize expression
    var pe = Expression.Parameter(typeof(TSource), "entity");
    var predicateBody = default(Expression);

    // Search in each expressions, put OR in between
    foreach (var expr in expressions)
    {
        var exprBody = default(Expression);

        // Search for each words, put AND in between
        foreach (var searchPart in searchParts)
        {
            // Create property or field expression
            var left = Expression.PropertyOrField(pe, ((MemberExpression)expr.Body).Member.Name);

            // Create the constant expression with current word
            var search = Expression.Constant(searchPart, typeof(string));

            // Create the contains function
            var contains = Expression.Call(left, typeof(string).GetMethod(nameof(string.Contains), new Type[] { typeof(string) }), search);

            // Check if there already a predicate body
            if (exprBody == null)
            {
                exprBody = contains;
            }
            else
            {
                exprBody = Expression.And(exprBody, contains);
            }
        }

        if (predicateBody == null)
        {
            predicateBody = exprBody;
        }
        else
        {
            predicateBody = Expression.OrElse(predicateBody, exprBody);
        }
    }

    // Build the where method expression
    var whereCallExpression = Expression.Call(
        typeof(Queryable),
        nameof(Queryable.Where),
        new Type[] { query.ElementType },
        query.Expression,
        Expression.Lambda<Func<TSource, bool>>(predicateBody, new ParameterExpression[] { pe }));

    // Apply the condition to the query and return it
    return query.Provider.CreateQuery<TSource>(whereCallExpression);
}

It works well as long as given expressions are simple:

// It works well
query.SearchIn("foo", x => x.Column1, x => x.Column2);

But it does not work when trying to navigate through navigation properties:

// Not working
query.SearchIn("foo", x => x.Nav1.Column1);

It gives me an exception.

'Column1' is not a member of type 'Nav1'.

I understand the problem but I can't find the solution to pass through Nav1 .

I need help with this one.

Instead of parsing lambda expression body just call it with given parameter:

var left = Expression.Invoke(expr, pe);

However it works only in EF Core.

In EF6 you would need to get property or field of each nested member like this:

var left = expr.Body.ToString()
    .Split('.')
    .Skip(1) //skip the original parameter name
    .Aggregate((Expression)pe, (a, c) => Expression.PropertyOrField(a, c));

It will work only for simple lambdas like:

x => x.Prop1.Nav1

If that's not enough you would need some more advanced parsing algorithm with ExpressionVisitor for example.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM