Home > Software engineering >  create a lambda expression tree, Expression<Func<T, bool>>, dynamically that performs an
create a lambda expression tree, Expression<Func<T, bool>>, dynamically that performs an

Time:11-23

I have a search string that can be "sub1 sub2 sub3" and I want to write a proper Expression<Func<T, bool>> that can find "sub1", "sub2", "sub3" and "sub1 sub2 sub3" in the x.Name

In the other hand I want to modify x.Name.ToLower().Contains(productParams.Search) for my purpose. Now I can search the term "sub1 sub2 sub3". However, I want to search for sub-strings as well.

my expectation for the search is: "sub1" || "sub2" || "sub3" || "sub1 sub2 sub3"

productParams.Search = "sub1 sub2 sub3"

How can do it?

public class ProductsSpecification : BaseSpecifcation<Product>
{
   public ProductsSpecification(ProductSpecParams productParams) : base(x =>
      (string.IsNullOrEmpty(productParams.Search) || 
      x.Name.ToLower().Contains(productParams.Search)) &&
      (!productParams.BrandId.HasValue || x.ProductBrandId == productParams.BrandId))
}

BaseSpecifcation:

public class BaseSpecifcation<T> : ISpecification<T>
{
    public BaseSpecifcation(Expression<Func<T, bool>> criteria)
    {
        Criteria = criteria;
    }

    public Expression<Func<T, bool>> Criteria { get; }
}

CodePudding user response:

First of all, I created a helper class for generating predicates:

 public static class PredicateBuilder
    {
        public static Expression<Func<T, bool>> True<T>() { return f => true; }
        public static Expression<Func<T, bool>> False<T>() { return f => false; }

        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
        {
            var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
            return Expression.Lambda<Func<T, bool>> (Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
        }

        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
        {
            var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
            return Expression.Lambda<Func<T, bool>> (Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
        }
    }

I created a private method in ProductsSpecification and used my class helper:

private Expression<Func<Product, bool>> CreateProductFilter(ProductSpecParams productParams)
        {
            Expression<Func<Product, bool>> pr = PredicateBuilder.True<Product>(); // pr.Body.ToString() is "True"

            if (!string.IsNullOrEmpty(productParams.Search) && !string.IsNullOrEmpty(productParams.Search.Trim()))
            {
                var searchValue = productParams.Search.Trim().ToLower();
                pr = pr.And(a => a.Name.ToLower().Contains(searchValue));

                foreach (var term in productParams.Search.ToLower().Split(' '))
                {
                    string temp = term.Trim();
                    pr = pr.Or(a => a.Name.ToLower().Contains(temp));
                }
            }
            if (productParams.BrandId.HasValue)
            {
                pr = pr.And(p => p.ProductBrandId == productParams.BrandId);
            }
            

            if (pr.Body.ToString() == "True")
            {
                return null;
            }

            return pr;
        }

I modified the construcor of ProductsSpecification:

public ProductsSpecification(ProductSpecParams productParams) : base()
    {
        Criteria = CreateProductFilter(productParams);

        // rest of the code
        
    }

Now, the filter works well!

  • Related