为选定的搜索条件创建动态表达式树

Creating dynamic Expression Tree for selected search criteria

我有一个 Expression Tree 可以根据用户在复选框上选择的条件创建一个动态的 where 子句。

例如:- 用户想要搜索:"test"

用户选择
1. 道具 1
2. 提案2

对于对象 MyDBObject

搜索查询看起来像

dbRecords.Where(r=> r.Prop1.Contains("test") || r.Prop2.Contains("test"))

使用 Expression Tree 的原因是它可以用于未知对象的任意数量的未知属性。

我几乎可以正常工作了,但我得到了 Argument Expression is not valid

还有除了使用之外如何初始化一个空的布尔表达式 "something that evaluates to -- true/false" ? 到目前为止,我只读了几个小时,所以也许有些东西我还没有看到。

public static Expression<Func<T, bool>> CreatePredicateFromCrtieriaAndSearchTerm<T>(List<string> checkedCriteria, string searchTerm)
    {
        // sample checked records
        checkedCriteria = new[]
        {
            new { Name = "Prop1", DisplayValue = "Checkbox value 1" },
            new { Name = "Prop2", DisplayValue = "Checkbox value 2" }
        }
        .Select(x => x.Name).ToList();



        var param = Expression.Parameter(typeof(T), "record");

        Expression oneEqualsOne = Expression.Equal(Expression.Constant(1), Expression.Constant(1));

        // Creates (record => (1=1) AND ...)
        Expression<Func<T, bool>> finalExpression = Expression.Lambda<Func<T, bool>>(oneEqualsOne, param);

        Console.WriteLine(finalExpression);

        try
        {
            // Iterate through properties, find selected props and create
            // (record.SelectedProp1.Contains("searchTerm") || record.SelectedProp2.Contains("searchTerm") ... )

            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));

            List<Expression> matchExpressions = new List<Expression>();

            for (int i = 0; i < props.Count; i++)
            {
                PropertyDescriptor prop = props[i];

                for (int j = 0; j < checkedCriteria.Count; j++)
                {
                    if (prop.Name == checkedCriteria[j])
                    {
                        // add to where expression

                        Expression left = Expression.Property(param, prop.Name);
                        MethodInfo contains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
                        Expression right = Expression.Constant(searchTerm, searchTerm.GetType());

                        Expression matchExpression = Expression.Call(left, contains, right);

                        matchExpressions.Add(matchExpression);
                    }
                }
            }

            // Creates (1=0 OR ... OR ...)
            Expression currentPredicateBody = Expression.Equal(Expression.Constant(1), Expression.Constant(0));
            foreach (var matchExpression in matchExpressions)
            {
                currentPredicateBody = Expression.MakeBinary(ExpressionType.OrElse, matchExpression, currentPredicateBody);
                Console.WriteLine(currentPredicateBody);
            }

            // ( (1=0) || record.SelectedProp1.Contains("searchTerm") || record.SelectedProp2.Contains("searchTerm") )
            if (matchExpressions.Count > 0)
            {
                oneEqualsOne = Expression.AndAlso(oneEqualsOne, currentPredicateBody);
                Console.WriteLine(oneEqualsOne);
            }

            // Full expression: 
            // ( record => (1=1) AND ( (1=0) || record.SelectedProp1.Contains("searchTerm") || record.SelectedProp2.Contains("searchTerm") ))

            finalExpression = Expression.Lambda<Func<T, bool>>(oneEqualsOne, new ParameterExpression[] { param });

            Console.WriteLine(finalExpression);
        }
        catch (Exception ex)
        {
            throw new Exception(string.Format(@"Error occurred creating where predicate from checked criteria: {0}", ex.Message));
        }

        return finalExpression;
    }

    internal class MyDBObject
    {
        public int Id { get; set; }
        public string Prop1 { get; set; }
        public string Prop2 { get; set; }
        public string Prop3 { get; set; }
        public string Prop4 { get; set; }
        public string Prop5 { get; set; }
        public string Prop11 { get; set; }
        public string Prop12 { get; set; }
        public string Prop13 { get; set; }
        public string Prop14 { get; set; }
        public string Prop15 { get; set; }
        public string Prop21 { get; set; }
        public string Prop22 { get; set; }
        public string Prop23 { get; set; }
        public string Prop24 { get; set; }
        public string Prop25 { get; set; }
    }

    public static void Main(string[] args)
    {
        List<MyDBObject> dbRecords = new List<MyDBObject>
        {
            new MyDBObject { Id = 1, Prop2 = "O1_P2", Prop3 = "O1_P3", Prop12 = "O1_P12", Prop15 = "O1_P15", Prop24 = "O1_P24", Prop25 = "O1_P25" },
            new MyDBObject { Id = 2, Prop15 = "O2_P15", Prop21 = "test", Prop22 = "O2_P22", Prop23 = "O2_P23", Prop24 = "O2_P24", Prop25 = "O2_P25" },
            new MyDBObject { Id = 3, Prop21 = "O3_P21", Prop22 = "O3_P22", Prop23 = "O3_P23", Prop24 = "test", Prop25 = "O3_P25" }
        };

        try
        {
            var predicate = CreatePredicateFromCrtieriaAndSearchTerm<MyDBObject>(null, "test");

            var query = dbRecords.AsQueryable().Provider.CreateQuery<MyObject>(predicate);

            List<MyObject> results = query.ToList();

            foreach (var rs in results)
            {
                Console.WriteLine("Id: " + rs.Id);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error->> " + ex.Message);
        }
    }

试试这个代码:

public static Expression<Func<T, bool>> CreatePredicate<T>(List<string> propsToSearch,
   string valueToSearch)
{
    var parameter = Expression.Parameter(typeof(T), "record");

    // filtering is not required
    if (!propsToSearch.Any() || string.IsNullOrEmpty(valueToSearch))
        return Expression.Lambda<Func<T, bool>>(Expression.Constant(true), parameter);

    var props = typeof(T).GetProperties()
        .Select(p => p.Name)
        .Intersect(propsToSearch.Distinct());

    var containsMethod = typeof(string).GetMethod("Contains");

    var body = props
        .Select(p => Expression.PropertyOrField(parameter, p))
        .Aggregate((Expression) Expression.Constant(false),
            (c, n) => Expression.OrElse(c,
                Expression.Call(n, containsMethod, Expression.Constant(valueToSearch)))
        );

    var lambda = Expression.Lambda<Func<T, bool>>(body, parameter);
    return lambda;
}

它 return record => true 如果没有要搜索的属性或搜索模式为空。 QueryProvider 可以足够聪明,在这种情况下不会生成 sql where

更新:我创建了一个 demo(由于 dotNetFiddle 的安全限制,它不工作,但本地工作正常)