WildGums / Orc.FilterBuilder

WPF UI to help users build collection filters
Other
60 stars 21 forks source link

Feature Request: Add lambda expression exporting feature to FilterScheme #8

Closed sunnycase closed 6 years ago

sunnycase commented 9 years ago

It will be efficient to use a compiled lambda expression to filter the collection. And lambda expressions can be directly passed to entity framework to query the database. So it's a cool feature I want the library will have in the future.

GeertvanHorrik commented 9 years ago

This might actually be a very good idea. Will research the possibilities. Interested in doing a PR?

sunnycase commented 9 years ago

There is an trial implementation in our project, but it's under development and I can't promise it will fit your developing guidlines.

GeertvanHorrik commented 9 years ago

You can keep pushing commits to a PR, so we can always adjust :)

SergeyIlyin commented 7 years ago

I am using this project with Arch/UnitOfWork for built Expressions. The extention code in progress. Now implements boolean and part of string

using Catel;
using Orc.FilterBuilder.Models;
using System;
using System.Linq;
using System.Linq.Expressions;

namespace Orc.FilterBuilder.Conditions
{
    public static class Conditions_Extention
    {

        public static Expression<Func<T, bool>> MakeFunction<T>(this ConditionTreeItem conditionTreeItem)
        {
            var type = typeof(T);
            var pe = Expression.Parameter(type, "item");
            Expression expression = conditionTreeItem.MakeExpression(pe);
            if (expression == null)
            {
                return null;
            }
            var lambda = Expression.Lambda<Func<T, bool>>(expression, pe);
            return lambda;
        }

        private static Expression MakeExpression(this ConditionTreeItem conditionTreeItem, ParameterExpression parametr)
        {
            if (conditionTreeItem.GetType() == typeof(ConditionGroup))
            {
                return ((ConditionGroup)conditionTreeItem).MakeExpression(parametr);
            }
            else if (conditionTreeItem.GetType() == typeof(PropertyExpression))
            {
                return ((PropertyExpression)conditionTreeItem).MakeExpression(parametr);
            }
            else
            {
                return null;
            }
        }

        private static Expression MakeExpression(this ConditionGroup conditionGroup, ParameterExpression parametr)
        {

            if (!conditionGroup.Items.Any())
            {
                return null;
            }

            Expression final = null;
            Expression left = null;
            Expression rigth = null;
            foreach (var item in conditionGroup.Items)
            {
                var curExp = item?.MakeExpression(parametr);
                if (curExp == null) continue;
                if (left == null)
                {
                    left = curExp;
                }
                else
                {
                    rigth = curExp;
                    if (conditionGroup.Type == ConditionGroupType.And)
                    {
                        final = Expression.AndAlso(left, rigth);
                    }
                    else
                    {
                        final = Expression.OrElse(left, rigth);
                    }
                    left = final;
                    rigth = null;
                }
            }

            return final != null ? final : left;

        }

        private static Expression MakeExpression(this PropertyExpression propertyExpression, ParameterExpression pe)
        {
            return propertyExpression.DataTypeExpression.MakeExpression(pe, propertyExpression.Property);
        }

        private static Expression MakeExpression(this DataTypeExpression dataTypeExpression, ParameterExpression pe, IPropertyMetadata propertyMetadata)
        {

            if (dataTypeExpression.GetType() == typeof(BooleanExpression))
            {
                return ((BooleanExpression)dataTypeExpression).MakeExpression(pe, propertyMetadata.Name);
            }
            else if (dataTypeExpression.GetType() == typeof(StringExpression))
            {
                return ((StringExpression)dataTypeExpression).MakeExpression(pe, propertyMetadata.Name);
            }
            else
            {
                return null;
            }
        }

        private static Expression MakeExpression(this BooleanExpression expression, ParameterExpression pe, string propertyName)
        {
            var Value = expression.Value;
            var SelectedCondition = expression.SelectedCondition;

            switch (SelectedCondition)
            {
                case Condition.EqualTo:

                    Expression left = Expression.Property(pe, propertyName);
                    Expression rigth = Expression.Constant(true);
                    Expression e = Expression.Equal(left, rigth);
                    return e;
                default:
                    throw new NotSupportedException(string.Format(LanguageHelper.GetString("FilterBuilder_Exception_Message_ConditionIsNotSupported_Pattern"), SelectedCondition));
            }
        }
        private static Expression MakeExpression(this StringExpression expression, ParameterExpression pe, string propertyName)
        {
            var Value = expression.Value;
            var SelectedCondition = expression.SelectedCondition;

            Expression left;
            Expression rigth;
            Expression e;
            switch (SelectedCondition)
            {

                case Condition.Contains:

                    left = Expression.Property(pe, propertyName);
                    rigth = Expression.Constant(Value);
                    e = Expression.Call(left, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), rigth);
                    return e;

                case Condition.DoesNotContain:

                    left = Expression.Property(pe, propertyName);
                    rigth = Expression.Constant(Value);
                    e = Expression.Call(left, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), rigth);
                    Expression final = Expression.Not(e);
                    return final;

                case Condition.EndsWith:
                    left = Expression.Property(pe, propertyName);
                    rigth = Expression.Constant(Value);

                    e = Expression.Call(left, typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) }), rigth);
                    return e;
                case Condition.DoesNotEndWith:
                    //  pe = Expression.Parameter(typeof(string), propertyMetadata.Name);
                    rigth = Expression.Constant(Value);
                    left = Expression.Call(pe, typeof(string).GetMethod("EndsWith"), rigth);
                    e = Expression.IsFalse(left);
                    return e;
                case Condition.EqualTo:
                    //  pe = Expression.Parameter(typeof(string), propertyMetadata.Name);
                    left = Expression.Property(pe, typeof(string).GetProperty(propertyName));
                    rigth = Expression.Constant(Value);
                    e = Expression.Equal(left, rigth);
                    return e;

                case Condition.GreaterThan:
                    return null;

                case Condition.GreaterThanOrEqualTo:
                    return null;

                case Condition.IsEmpty:
                    return null;

                case Condition.IsNull:
                    return null;

                case Condition.LessThan:
                    return null;

                case Condition.LessThanOrEqualTo:
                    return null;

                case Condition.NotEqualTo:
                    return null;
                case Condition.NotIsEmpty:
                    return null;
                case Condition.NotIsNull:
                    return null;
                case Condition.StartsWith:
                    return null;
                case Condition.DoesNotStartWith:
                   return null;
                case Condition.DoesNotMatch:
                    return null;
                default:
                    throw new NotSupportedException(string.Format(LanguageHelper.GetString("FilterBuilder_Exception_Message_ConditionIsNotSupported_Pattern"), SelectedCondition));
            }
        }
    }
}
`
The Code of Tests
`using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
using Orc.FilterBuilder.Models;
using Orc.FilterBuilder.Conditions;
using System.Linq.Expressions;

namespace Orc.FilterBuilder.Tests
{
    public class TestLinqExpression
    {
        [Fact]
        public void Bool_ExpressionTest()
        {

            var people = People.AsQueryable();
            var type = typeof(Human);
            var conditionGroup = new ConditionGroup()
            {
                Type = ConditionGroupType.And
            };
            var propertyExpression = new PropertyExpression()
            {
                Property = new PropertyMetadata(type, type.GetProperty("HasChailds")),
                DataTypeExpression = new BooleanExpression()
                {
                    SelectedCondition = Condition.EqualTo,
                    Value = true
                }

            };
            conditionGroup.Items.Add(propertyExpression);

            var expression = conditionGroup.MakeFunction<Human>();

            var result = people.Where(expression).ToList();

            Assert.Equal(result.Count, 1);
            Assert.Equal(result[0].Name, "Sergio");

        }

        [Fact]
        public void String_ExpressionTest()
        {
            var items = CalcHumansFor("Name", new StringExpression()
            {
                SelectedCondition = Condition.Contains,
                Value = "nn"
            });
            Assert.Equal(items.Count, 1);
            Assert.Equal(items[0].Name, "Ann");

            items = CalcHumansFor("Name", new StringExpression()
            {
                SelectedCondition = Condition.DoesNotContain,
                Value = "nn"
            });
            Assert.Equal(items.Count, 1);
            Assert.Equal(items[0].Name, "Sergio");

            items = CalcHumansFor("Name", new StringExpression()
            {
                SelectedCondition = Condition.EndsWith,
                Value = "io"
            });
            Assert.Equal(items.Count, 1);
            Assert.Equal(items[0].Name, "Sergio");
            items = CalcHumansFor("Name", new StringExpression()
            {
                SelectedCondition = Condition.EndsWith,
                Value = "n"
            });
            Assert.Equal(items.Count, 1);
            Assert.Equal(items[0].Name, "Ann");
        }
        List<Human> CalcHumansFor(string propertyName, DataTypeExpression dataTypeExpression)

        {
            var conditionGroup = new ConditionGroup()
            {
                Type = ConditionGroupType.And
            };

            var type = typeof(Human);

            var propertyExpression = new PropertyExpression()
            {
                Property = new PropertyMetadata(type, type.GetProperty(propertyName)),
                DataTypeExpression = dataTypeExpression
            };

            conditionGroup.Items.Add(propertyExpression);

            var expression = conditionGroup.MakeFunction<Human>();
            var result = People.AsQueryable().Where(expression).ToList();
            return result;
        }
        [Fact]
        public void LinqBuild_Test()
        {
            var people = People.AsQueryable();
            var type = typeof(Human);

            var item = Expression.Parameter(type, "human");

            var prop = Expression.Property(item, "HasChailds");

            var soap = Expression.Constant(true);

            var equal = Expression.Equal(prop, soap);

            var lambda = Expression.Lambda<Func<Human, bool>>(equal, item);

            var result = people.Where(lambda).ToList();

            Assert.Equal(result.Count, 1);
            Assert.Equal(result[0].Name, "Sergio");

        }
        public List<Human> People => new List<Human>()
        {
            new Human("Ann",5,false),
            new Human("Sergio", 35, true )
        };

        public class Human
        {
            public Human(string name, int age, bool hasChailds)
            {
                Name = name; Age = age; HasChailds = hasChailds;
            }
            public string Name { get; set; }
            public int Age { get; set; }
            public bool HasChailds { get; set; }
        }

    }
}
SergeyIlyin commented 7 years ago

P.S. In Progress. Many Errors in MakeExpression(this StringExpression expression`

SergeyIlyin commented 7 years ago

Ok. Now I have code/tests to build Expressions for bool, bool? and string May I add this code to 'Pull requests' or I must write all other functions?

GeertvanHorrik commented 7 years ago

You can create a PR Work In Progress. Then you can just keep pushing to the same PR branch and it will auto-update. Just make sure not to make any breaking changes.

SergeyIlyin commented 7 years ago

PR Linq extensions #39 ready to merge. Theare no RegularExpressions only. I don`t now how implement this to SQL. There no changes in parent projects - static extentions only.

stale[bot] commented 6 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

lock[bot] commented 5 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.