Открыть список
Как стать автором
Обновить

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

.NETC#
Из песочницы

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

Теги:.netlinqreflectionexpressions
Хабы: .NET C#
Всего голосов 25: ↑21 и ↓4 +17
Просмотры7.7K

Комментарии 21

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Похожие публикации

.NET C# Software Engineer
от 3 500 до 4 000 $Hand2NoteМожно удаленно
Разработчик .NET / C#
от 90 000 до 150 000 ₽nopCommerceЯрославль
.Net Fullstack Developer (Mid-level)
от 1 000 до 2 000 $SNAPIOМожно удаленно
.NET Core C# Developer
до 350 000 ₽SimpleFinance GroupМосква
Senior .Net Engineer (C#)
до 230 000 ₽ItivitiСанкт-Петербург

Лучшие публикации за сутки