Создание динамического дерева выражений для выбранных критериев поиска - PullRequest
0 голосов
/ 11 апреля 2019

У меня есть 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

Кроме того, как можно инициализировать пустое логическое выражение, кроме использования «что-то, что оценивается как - истина / ложь»?Я читаю о них только несколько часов, так что, может быть, есть кое-что, чего я еще не видел.

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);
        }
    }

1 Ответ

1 голос
/ 11 апреля 2019

Попробуйте этот код:

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;
}

Возвращает record => true, если нет свойств для поиска, или поиск пустой. QueryProvider может быть достаточно умным, чтобы не генерировать sql where в этом случае.

Обновление : я создал demo (он не работает из-за ограничений безопасности dotNetFiddle, но локально работает нормально)

...