jeudi 11 avril 2019

Creating dynamic Expression Tree for selected search criteria

I have an Expression Tree to create a dynamic where clause based on the criteria a user selects on a checkbox.

Eg: - User wants to search for: "test"

User selects
1. Prop1
2. Prop2

for an Object MyDBObject

The search query will look like

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

The reason to use an Expression Tree is so that it can be used for any unknown number of properties of an unknown object.

I almost have it working, but I get Argument Expression is not valid

Also how does one initialize an empty boolean expression other than using "something that evaluates to -- true/false" ? I've only read about them for a few hours by now so maybe there's something I didn't see yet.

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





Aucun commentaire:

Enregistrer un commentaire