Динамический выбор запроса в списке содержит список - PullRequest
3 голосов
/ 29 апреля 2019

Я пытаюсь создать механизм динамической фильтрации с помощью c #.

У меня есть пользовательский класс со свойствами, такими как Имя, Фамилия, Дата рождения и т. Д., А также список UserInformations, который является списком объекта UserInformation. Объект UserInformation имеет атрибуты Id, Name и Value. И userList из списка пользователей.

Я создал пользовательский интерфейс для создания страницы фильтра, используя https://querybuilder.js.org/demo.html

Создает фильтр как

{
  "condition": "AND",
  "rules": [
    {
     "id": "name",
      "field": "name",
      "type": "string",
      "input": "text",
      "operator": "equal",
      "value": "huseyin"
    },
    {
      "condition": "OR",
      "rules": [
        {
          "id": "surname",
          "field": "surname",
          "type": "string",
          "input": "text",
          "operator": "equal",
          "value": "34"
        },
        {
          "id": "ix_2_Weight",
          "field": "ix_2_Weight",
          "type": "integer",
          "input": "select",
          "operator": "equal",
          "value": 50
        }
      ]
    }
  ],
  "valid": true
}

У меня нет проблем с фильтрами с атрибутами пользователя. Тем не менее, я пытаюсь фильтровать информацию в массиве UserInformations. Я делю имя поля, если оно начинается с ix и получает идентификатор информации о пользователе. Но я не мог найти, как сравнить его значение. если бы это был статический linq, он был бы похож на

users.Where(u=>u.Informations.FirstOrDefault(i=>i.id==2/*id of information in filter*/)?.Value=="something"))

Фильтр вызывается с кодом ниже

 private async Task<List<User>> FilterUsers(FilterRule rule, List<UserFilterDto> users = null)
        {
            List<User> list = users ?? await GetUsersForFilter();

            var tempList = list.BuildQuery(rule).ToList();
            return tempList;

        }

И часть динамического запроса, как показано ниже.


  public static IQueryable<User> BuildQuery(this IQueryable<User> queryable, FilterRule filterRule, out string parsedQuery, bool useIndexedProperty = false, string indexedPropertyName = null)
        {
            if (filterRule == null)
            {
                parsedQuery = "";
                return queryable;
            }

            var pe = Expression.Parameter(typeof(UserDetailedDto), "item");

            var expressionTree = BuildExpressionTree(pe, filterRule, useIndexedProperty, indexedPropertyName);
            if (expressionTree == null)
            {
                parsedQuery = "";
                return queryable;
            }

            parsedQuery = expressionTree.ToString();

            var whereCallExpression = Expression.Call(
                typeof(Queryable),
                "Where",
                new[] { queryable.ElementType },
                queryable.Expression,
                Expression.Lambda<Func<UserDetailedDto, bool>>(expressionTree, pe));

            var filteredResults = queryable.Provider.CreateQuery<UserDetailedDto>(whereCallExpression);

            return filteredResults;

        }


public static IQueryable<User> BuildQuery(this IQueryable<User> queryable, FilterRule filterRule, out string parsedQuery, bool useIndexedProperty = false, string indexedPropertyName = null)
        {
            if (filterRule == null)
            {
                parsedQuery = "";
                return queryable;
            }

            var pe = Expression.Parameter(typeof(UserDetailedDto), "item");

            var expressionTree = BuildExpressionTree(pe, filterRule, useIndexedProperty, indexedPropertyName);
            if (expressionTree == null)
            {
                parsedQuery = "";
                return queryable;
            }

            parsedQuery = expressionTree.ToString();

            var whereCallExpression = Expression.Call(
                typeof(Queryable),
                "Where",
                new[] { queryable.ElementType },
                queryable.Expression,
                Expression.Lambda<Func<UserDetailedDto, bool>>(expressionTree, pe));

            var filteredResults = queryable.Provider.CreateQuery<UserDetailedDto>(whereCallExpression);

            return filteredResults;

        }

        private static Expression BuildExpressionTree(ParameterExpression pe, FilterRule rule, bool useIndexedProperty = false, string indexedPropertyName = null)
        {

            if (rule.Rules != null && rule.Rules.Any())
            {
                var expressions =
                    rule.Rules.Select(childRule => BuildExpressionTree(pe, childRule, useIndexedProperty, indexedPropertyName))
                        .Where(expression => expression != null)
                        .ToList();

                var expressionTree = expressions.First();

                var counter = 1;
                while (counter < expressions.Count)
                {
                    expressionTree = rule.Condition.ToLower() == "or"
                        ? Expression.Or(expressionTree, expressions[counter])
                        : Expression.And(expressionTree, expressions[counter]);
                    counter++;
                }

                return expressionTree;
            }
            if (rule.Field != null)
            {
                Type type;

                switch (rule.Type)
                {
                    case "integer":
                        type = typeof(int);
                        break;
                    case "long":
                        type = typeof(long);
                        break;
                    case "double":
                        type = typeof(double);
                        break;
                    case "string":
                        type = typeof(string);
                        break;
                    case "date":
                    case "datetime":
                        type = typeof(DateTime);
                        break;
                    case "boolean":
                        type = typeof(bool);
                        break;
                    default:
                        throw new Exception($"Unexpected data type {rule.Type}");
                }

                Expression propertyExp = null;

                if (rule.Field.StartsWith("ix"))
                {
                    long informationId =long.Parse(rule.Field.Split("_")[1]);

                  ????
????

                } else if (useIndexedProperty)
                {
                    propertyExp = Expression.Property(pe, indexedPropertyName, Expression.Constant(rule.Field));
                }
                else
                {
                    propertyExp = Expression.Property(pe, rule.Field);
                }

                Expression expression;

                if (propertyExp.Type.Name.Contains("ICollection") || propertyExp.Type.Name.Contains("List"))
                {
                    // Rule Field is a Collection
                    expression = BuildCollectionExpression(pe, rule);
                }
                else
                {

                    switch (rule.Operator.ToLower())
                    {
                        case "in":
                            expression = In(type, rule.Value, propertyExp);
                            break;
                        case "not_in":
                            expression = NotIn(type, rule.Value, propertyExp);
                            break;
                        case "equal":
                            expression = Equals(type, rule.Value, propertyExp);
                            break;
                        case "not_equal":
                            expression = NotEquals(type, rule.Value, propertyExp);
                            break;
                        case "between":
                            expression = Between(type, rule.Value, propertyExp);
                            break;
                        case "not_between":
                            expression = NotBetween(type, rule.Value, propertyExp);
                            break;
                        case "less":
                            expression = LessThan(type, rule.Value, propertyExp);
                            break;
                        case "less_or_equal":
                            expression = LessThanOrEqual(type, rule.Value, propertyExp);
                            break;
                        case "greater":
                            expression = GreaterThan(type, rule.Value, propertyExp);
                            break;
                        case "greater_or_equal":
                            expression = GreaterThanOrEqual(type, rule.Value, propertyExp);
                            break;
                        case "begins_with":
                            expression = BeginsWith(type, rule.Value, propertyExp);
                            break;
                        case "not_begins_with":
                            expression = NotBeginsWith(type, rule.Value, propertyExp);
                            break;
                        case "contains":
                            expression = Contains(type, rule.Value, propertyExp);
                            break;
                        case "not_contains":
                            expression = NotContains(type, rule.Value, propertyExp);
                            break;
                        case "ends_with":
                            expression = EndsWith(type, rule.Value, propertyExp);
                            break;
                        case "not_ends_with":
                            expression = NotEndsWith(type, rule.Value, propertyExp);
                            break;
                        case "is_empty":
                            expression = IsEmpty(propertyExp);
                            break;
                        case "is_not_empty":
                            expression = IsNotEmpty(propertyExp);
                            break;
                        case "is_null":
                            expression = IsNull(propertyExp);
                            break;
                        case "is_not_null":
                            expression = IsNotNull(propertyExp);
                            break;
                        default:
                            throw new Exception($"Unknown expression operator: {rule.Operator}");
                    }

                }
                return expression;


            }
            return null;

        }

1 Ответ

2 голосов
/ 29 апреля 2019

если бы это был статический LINQ, он должен был выглядеть следующим образом:

u => u.Informations.FirstOrDefault(i => i.id == 2)?.Value == "something"))

Оператор ?. не поддерживается в деревьях выражений, поэтому вместо этого лучше создать что-то подобное:

u => u.Informations.Any(i => i.id == 2 && i.Value == "something"))

Чтобы сделать это, извлеките необработанное выражение в отдельный метод:

private static Expression BuildCondition(FilterRule rule, Expression propertyExp)
{
    Type type;

    switch (rule.Type)
    {
        case "integer":
            type = typeof(int);
            break;
        case "long":
            type = typeof(long);
            break;
        case "double":
            type = typeof(double);
            break;
        case "string":
            type = typeof(string);
            break;
        case "date":
        case "datetime":
            type = typeof(DateTime);
            break;
        case "boolean":
            type = typeof(bool);
            break;
        default:
            throw new Exception($"Unexpected data type {rule.Type}");
    }

    Expression expression;

    switch (rule.Operator.ToLower())
    {
        case "in":
            expression = In(type, rule.Value, propertyExp);
            break;
        case "not_in":
            expression = NotIn(type, rule.Value, propertyExp);
            break;
        case "equal":
            expression = Equals(type, rule.Value, propertyExp);
            break;
        case "not_equal":
            expression = NotEquals(type, rule.Value, propertyExp);
            break;
        case "between":
            expression = Between(type, rule.Value, propertyExp);
            break;
        case "not_between":
            expression = NotBetween(type, rule.Value, propertyExp);
            break;
        case "less":
            expression = LessThan(type, rule.Value, propertyExp);
            break;
        case "less_or_equal":
            expression = LessThanOrEqual(type, rule.Value, propertyExp);
            break;
        case "greater":
            expression = GreaterThan(type, rule.Value, propertyExp);
            break;
        case "greater_or_equal":
            expression = GreaterThanOrEqual(type, rule.Value, propertyExp);
            break;
        case "begins_with":
            expression = BeginsWith(type, rule.Value, propertyExp);
            break;
        case "not_begins_with":
            expression = NotBeginsWith(type, rule.Value, propertyExp);
            break;
        case "contains":
            expression = Contains(type, rule.Value, propertyExp);
            break;
        case "not_contains":
            expression = NotContains(type, rule.Value, propertyExp);
            break;
        case "ends_with":
            expression = EndsWith(type, rule.Value, propertyExp);
            break;
        case "not_ends_with":
            expression = NotEndsWith(type, rule.Value, propertyExp);
            break;
        case "is_empty":
            expression = IsEmpty(propertyExp);
            break;
        case "is_not_empty":
            expression = IsNotEmpty(propertyExp);
            break;
        case "is_null":
            expression = IsNull(propertyExp);
            break;
        case "is_not_null":
            expression = IsNotNull(propertyExp);
            break;
        default:
            throw new Exception($"Unknown expression operator: {rule.Operator}");
    }

    return expression;
}

используйте его внутри текущей ветви для прямых членов и используйте следующее для "indexed "branch:

if (rule.Field.StartsWith("ix_"))
{
    var tokens = rule.Field.Split("_");
    var infoParameter = Expression.Parameter(typeof(UserInformation), "i");
    var infoCondition = Expression.AndAlso(
        Expression.Equal(
            Expression.Property(infoParameter, nameof(UserInformation.Id)),
            Expression.Constant(long.Parse(tokens[1]))),
        BuildCondition(rule, Expression.Property(infoParameter, tokens[2])));
    return Expression.Call(
        typeof(Enumerable), nameof(Enumerable.Any), new[] { infoParameter.Type },
        Expression.Property(pe, nameof(User.Informations)),
        Expression.Lambda(infoCondition, infoParameter));
}

В качестве примечания, логические операторы C # && и || представлены Expression.AndAlso и Expression.OrElse.

...