LINQ: Динамическое построение фильтров запросов

в 6:55, , рубрики: .net, C#, expressions, linq, reflection

Наверняка, рано или поздно каждому разработчику приходилось создавать таблицы данных с возможностью сортировки по столбцам и пр. Я не исключение. В нашем проекте подобные таблицы есть чуть ли не на каждой странице, можно сказать что 90% контента выводится через них. Поиск и сортировка по таблицам, естественно, работает без перезагрузки страницы.

Само собой, расширять семантики методов API контроллеров до бесконечной простыни было бы абсолютно глупо непрактично, поэтому нам требовалось универсальное решение для всех наших таблиц. Найти его помогли рефлексия и деревья выражений.

LINQ: Динамическое построение фильтров запросов - 1

Мы решили, что формировать фильтр для выборки данных на клиенте и передавать его в виде JSON объекта на сервер гораздо удобнее, чем навешивать кучу условий в фабрике запроса.

Как все это выглядит

Допустим мы имеем модели пользователя и автомобилей, которыми он владеет:

public class User 
{
  public int Id { get; set; }
  public string Name { get; set; }
  public int Age { get; set; }
  public IEnumerable<Car> Cars { get; set; }
}

public class Car
{
  public int CarId { get; set; }
  public string Model { get; set; } 
  public int MaxSpeed { get; set; }
}

Допустим, я хочу получить пользователей, у которых имя начинается на A и автомобиль может развивать скорость больше 300 км/ч ну или с Id больше нуля, а потом отсортировать их по имени по убыванию, после по Id по возрастанию. Для это создадим такой объект:

{
    "Where": {
        "OperatorType": "Or",
        "Operands": [
            {
                "OperatorType": "And",
                "Operands": [
                  {
                    "Field": "Name",
                    "FilterType": "StartsWith",
                    "Value": "A"
                  },
                  {
                    "Field": "Cars.MaxSpeed",
                    "FilterType": "GreaterThan",
                    "Value": 300
                  }
                ]
            },
            {
                "Field": "Id",
                "FilterType": "GreaterThan",
                "Value": 0
            }
        ]
    },
    "OrderBy": [
        {
            "Field": "Name",
        },
        {
            "Field": "Flag",
            "Order": "Desc"
        }
    ],
}

Можно заметить, что получается объект, с неограниченной вложенностью операндов, что позволяет строить довольно большие фильтры.

Теперь осталось применить этот фильтр к выборке данных. Как передать на сервер и десериализовать json объект я расписывать не буду, не маленькие.

FilterContainer filter = ...; // десериализация объекта
IQueryable<User> query = dataAccess.MyUsers;

query = query.Request(filter);
// или
////query = query.Where(filter.Where).OrderBy(filter.OrderBy);

Вот собственно вся выборка.

Многие знают, а кто не знает, тем расскажу, что ORM типа Entity Framework или Linq2SQL используют деревья выражений для представления структурированных запросов к источникам данных. Поставщик запросов может пройти через структуру данных для дерева выражений и преобразовать ее в язык запросов.

Посредством рефлексии сборщик фильтра рекурсивно строит дерево запросов из соответствующих членов сущности.

Все методы и типы фильтраций рассмотрены в проекте на гитхабе.

P.S. В принципе, я не рассчитывал на звание первооткрывателя в данной задаче, подобное ни раз уже делалось в той или иной форме. В любом случае, это было прекрасным опытом.

Автор: mrxten

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js