Pull to refresh

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

Reading time2 min
Views9.2K

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


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



Мы решили, что формировать фильтр для выборки данных на клиенте и передавать его в виде 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. В принципе, я не рассчитывал на звание первооткрывателя в данной задаче, подобное ни раз уже делалось в той или иной форме. В любом случае, это было прекрасным опытом.

Tags:
Hubs:
Total votes 25: ↑21 and ↓4+17
Comments21

Articles