Денис Цветцих — LINQ Expressions: искусство запрашивать данные

Денис Цветцих — LINQ Expressions: искусство запрашивать данные57:52

Информация о загрузке и деталях видео Денис Цветцих — LINQ Expressions: искусство запрашивать данные

Автор:

DotNext — конференция для .NET‑разработчиков

Дата публикации:

24.05.2024

Просмотров:

4.8K

Транскрибация видео

Спикер 1

Друзья, ну что ж, мы продолжаем.

А для того, чтобы представить нашего следующего спикера, я приглашаю сюда на сцену нашего замечательного эксперта Андрея.

Андрей, тебе слово.

Спикер 4

Спасибо.

Меня зовут Андрей, я предоставляю нашего следующего спикера, Денис.

Привет.

Привет.

Да, смотрите, не перепутайте.

Да, именно так.

Во-первых, мы братья, вы, наверное, поняли, да?

Близнецы, а не однофамильцы.

Судя по кофтам, тоже, наверное, понятно, что мы оба работаем в Тинькофф.

Вот еще написано, что Dev Brothers.

Это что такое?

Ну, собственно, это вот наш творческий дуэт.

Мы достаточно много выступаем на конференциях, делаем какие-то обучающие курсы.

В общем, делимся сообществом какими-то интересными и полезными для него материалами.

Да, прекрасно.

А вот LinkExpressions, расскажи, будешь ли ты рассказывать про спецификацию?

Потому что сегодня уже был один доклад про DDD, и там упоминали спецификацию, но так достаточно поверхностно.

Будут ли у тебя подробности?

Да, конечно.

Именно ради этого я и сделал доклад, что про E-Expressions в целом и в частности про спецификацию достаточно много разговоров и в разной литературе, посвященной DDD, и в каких-то там статьях, видосах.

Очень часто его упоминают, а в каких-то книгах не упоминают.

Вот.

И, в общем, понять, и что это такое для решения каких задач, спецификацию использовать, и как это сделать вот в современном мире, в современном .NET.

Ну, как правило, информации мало.

Ну вот, я решил этот пробел заполнить.

Скажи, а это вообще информация для всех, то есть это все потом могут использовать на практике?

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

Но, скорее всего, нет.

Или это очень практичный материал?

Да, это материал очень практичный, и он как раз должен показать, что все вот эти вот Link Expressions, какие-то спецификации, какие-то автофильтры, это не какая-то там магия, а наоборот, это инструменты, которые хорошо бы каждому разработчику в своих проектах использовать.

Отлично, тогда еще раз, Денис Цветцех, Link Expressions, искусство запрашивает данные.

Поехали.

Ну что ж, коллеги, немного про себя и про свой опыт.

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

Вот, в общем, такой...

Не то чтобы эксперт, но есть определенный опыт в этом.

И этим опытом я вам сегодня поделюсь и расскажу про экспрешены подробнее.

Итак, любое приложение реализует эти четыре операции create, read, update, delete.

При этом read операции, то есть операции чтения, их, как правило, на порядок больше.

То есть там говорят 20 на 80, там какая-то точная цифра не так важна.

Важно, что их много, действительно много.

И эти запросы дублируются.

Причем, как правило, дублируются не целиком, а частично.

То есть какие-то условия фильтрации дублируются между разными выражениями.

А еще вот эти вот какие-то запросы, они с течением жизненного цикла приложения меняются.

То есть это вот логика запросов, нам ее надо как-то инкапсулировать для того, чтобы менять потом в одном месте.

И иногда есть какие-то задачи, например, там типичная админка, где разные там списки, гриды, у них там фильтрация и вот эту вот всю рутину по...

Тому, чтобы реализовать всю эту фильтрацию, хотелось бы как-то так же быстро и просто реализовать, чтобы не писать большое количество более plate-кода.

Для решения этих задач немножко подходят такие инструменты, как GraphQL, например, Udata, JSON API, но это не серебряная пуля, они вот прям повсеместно на всех проектах не используются, но и так, в общем-то, как были, так и остаются экзотикой.

Итак, о чем мы сегодня с вами поговорим?

Мы поговорим о том, что такое спецификация, что это за паттерн.

Мы поговорим о том, как его выглядит современная реализация в 2023 году и как ее использовать на практике.

И также посмотрим, как лучше всего фильтровать вложенные коллекции, тоже отдельная задача.

И последнее, это как упростить вот эту рутину генерации запросов.

Для того, чтобы со всеми этими задачами разобраться, мы их будем рассматривать на таком сквозном примере.

Сквозной пример это интернет-магазин.

У нас в интернет-магазине будут некоторые товары, у них там будет какой-нибудь айдишник, название товара, цена.

И у каждого товара будет такое свойство sale enabled.

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

И для того, чтобы нам получить список товаров, вот у нас там будет какой-нибудь ORM, это Entity Framework, у него там будет Product свойства, и мы выбираем те товары, которые доступны, вот как раз где это свойство включено, те товары, которые выложены на витрину.

Ну, окей, все понятно.

Хорошо, идем дальше.

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

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

Нехорошо.

Давайте будем от таких кейсов тоже защищаться.

Давайте посмотрим, что мы можем сделать.

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

Хорошо.

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

Вот, например, здесь в классе «продукт» мы можем сделать вот такое вычислимое свойство из «available».

И попробовать сделать вот такой вот метод, extension метод, где мы будем выбирать только те товары, которые являются там доступными.

То есть эти оба условия, они соблюдаются.

И тогда мы можем вот этот extension метод использовать, ну и получить, собственно, список доступных товаров.

Ну хорошо, какую-то одну задачу мы решили, то есть вот при помощи такого extension метода мы инкапсулировали это условие.

Хорошо.

Если к этому добавится еще какое-то другое условие, что пользователь хочет посмотреть список товаров по какой-нибудь акции, для этого добавится еще одно условие.

Если эти условия, они группируются по «и», то есть и те, которые доступны, и те, которые по акции, в принципе, мы можем…

Ну как, выкрутиться тем же самым способом, то есть добавить еще один extension метод, туда добавить еще какое-то условие, там является ли товар каким-то доступным по акции.

И мы их вот так вот по и перечислим, ну и в принципе все окей.

Мы выберем нужный список товаров.

Вот.

А если что-то пойдет не так?

Ну, как раз для решения этой задачи, то есть инкапсулировать и группировать запросы, его решает паттерн спецификация.

То есть что это такое?

Это паттерн, который представляет бизнес-правила в виде какой-то цепочки объектов, и они связаны бизнес-логикой, то есть операторами булевой логики и или, может быть, не.

Ну и, собственно, да, спецификация решает две важные задачи.

Это инкапсуляция правил и это комбинация правил.

Когда-то давно, когда говорили про спецификацию, говорили, что это должен быть вот такой вот интерфейс iSpecification, у которого метод есть там satisfiedBy, и мы получаем некоторый объект, и для этого объекта реализация этого интерфейса должна вернуть там bool, true, false, то есть удовлетворяет объект от спецификации или нет.

В современном .NET уже есть func, мы можем там указать любой тип,

И он должен вернуть результат, удовлетворять спецификации или нет.

Хорошо, но если мы попробуем сделать делегат, то есть вот этот метод из available, это будет extension метод, он получает на вход наш продукт, наш товар, и мы для него вычисляем вот это выражение, является ли он выложенным на продукт,

На витрину, там есть ли не нулевое количество остатков на складе.

И если мы попытаемся вот такую спецификацию, некоторую каноническую использовать, то у нас ничего не получится.

Потому что по этому делегату построить некоторое линку выражения, а чтобы это выражение потом передать Entity Framework и по нему сгенерить SQL-запрос, ничего не получится.

Если, скажем так, чисто теоретически можно предположить, что можно сначала поднять из базы весь список товаров, потом фильтровать его в памяти, тогда делегация, конечно, работать будет, но это будет работать слишком долго.

Если у нас там товаров тысячи, миллионы, мы очень быстро упремся в производительность.

Поэтому...

Нам нужно спецификацию сделать не делегатом, а выражением.

То есть видим, что у нас вот этот вот func – это условие, которое… Типичный вопрос, чем iQueryable отличается от enumerable.

Вот func – это когда мы впишем условие where для enumerable.

То есть там это делегат, который проверяет уже в памяти для каких-то объектов условия.

А expression – он нужен для queryable, для того, чтобы по этому линку выражению дальше генерировать, например, SQL-запрос в базу.

Поэтому давайте посмотрим, есть ли некоторое решение, которое нам позволит вот от этой спецификации некоторого, ну как канонической, как делегата, построить по нему экспрешн.

Ну, здесь нужно сказать, что это задача такая, ну, не типовая, потому что существует такой стандартный способ из expression получить делегат, то есть его просто скомпилировать.

Вот, есть для этого и стандартная либо, есть быстрая компиляция, она, ну, говорится, что примерно в 10-40 раз быстрее работает, там такой метод compile fast называется, там ссылочку на него прилагаю.

А вот обратная задача, как из делегата получить expression, вот она уже совсем непростая.

Здесь нужно упомянуть, что есть такое решение, как делегатный компайлер.

Как он предлагает это сделать?

Что там у нас есть вычислимое свойство, например, мы его помечаем специальным атрибутом computed, и тогда мы можем построить вот такое выражение.

Что мы берем наши продукты, указываем вот это вычислимое свойство и указываем вот этот специальный метод decompile.

То есть это будет некоторая обертка над нашим линку выражения, над нашим querable, и такой будет декоратор.

И тогда у нас этот делегат decompiler, он возьмет, собственно, делегат, превратит его в линку expression, и дальше уже NFT-фреймворк по этому expression будет строить SQL-запросы.

То есть, с одной стороны, да, это будет работать, но вот этот вот Delegate Decompiler, ему уже примерно 10 лет, и он до сих пор, вот там вернемся, в какой версии, там 0.32, то есть до первой релизной версии он так и не дополз, и сам автор этой библиотеки, он говорит, что лучше ее не использовать в продакшене, потому что

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

Лучше поискать какой-то метод, который и работает побыстрее, ну и такой, и менее магический, где в случае чего, ну там, проще будет найти ошибку.

Для этого есть другое решение, называется линку спек.

Тут тоже есть ссылочка на него.

Что он предлагает сделать?

Он предлагает как раз спецификацию, ну, там есть некоторый базовый класс specification, и там класс abstract, на него есть метод expression.

Собственно, этот метод надо перегрузить вот в нашей реализации спецификации, и она уже будет возвращать не делегат, а именно expression.

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

И когда мы, например, эту спецификацию передадим вот в этот метод where для iQuery, ну то он у нас автоматически преобразуется в expression, ну там,

никаких дополнительных преобразований делать не надо будет.

То есть его удобно будет использовать.

Как будет выглядеть это на практике?

Мы создаем наш класс из Product Available Specification, наследуем его от этого специального класса в Libby, перегружаем нужный метод.

Дальше где-то в нашем классе Product

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

И дальше, когда мы будем выбирать наш список товаров, то да, в этом методе where для queryable, то есть для DBC, мы можем указать вот эту спецификацию, и все будет работать.

То есть эта спецификация будет успешно преобразована в нужный SQL-запрос.

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

Для того, чтобы от него избавиться, можно поступить попроще.

Есть уже такой класс adhook specification, это такой generic спецификация.

Вот, например, здесь другой пример, есть ли скидка, участвует ли в акции этот товар.

И здесь вот увиден этот класс adhook specification, там указывается, какой тип сущности, и дальше указывается link expression.

То есть, например, там есть скидка, и там какая-нибудь цена товара больше 100.

И дальше мы вот эти спецификации можем группировать при помощи операторов и, либо или, вот в этом условии where для querable.

Выглядит хорошо.

Здесь нужно сказать, что есть вот за этой некоторой кажущейся простотой некоторый подводный камень.

Давайте на него посмотрим поподробнее.

Итак, у нас есть два условия.

Первое – выложен ли товар на витрину, а второе условие – доступен ли товар на складе.

И теперь, если мы попробуем их объединить, то есть вот это условие, где end –

по вот этому условию i, у нас будет вот это вот лямбда, условие, которое получилось, там будет один аргумент x, второй аргумент y. То есть в разных условиях будут разные аргументы.

То есть если мы даже и там, и там назовем x, ничего не изменится.

Будет два разных аргумента, которые по-одинаковому называются, но это будут две разные ссылки на два разных объекта.

Вот.

Entity framework вот по такому

условию построить LinkoExpression не сможет.

Ему нужно, чтобы вот эти вот аргументы, они были одинаковые.

Даже если, даже тут приведена ошибка exception, которую мы получим, если мы их не унифицируем.

Поэтому вот для унификации, да, вот он там, параметр и выражение.

Вот для того, чтобы эти параметры объединить, ну как, унифицировать, нам понадобится визитер,

Собственно, он будет принимать два выражения, ну как, два параметра.

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

Здесь менять там x на y, y на x, это не важно.

Важно просто взять одно выражение и в него подставить параметр из второго.

И давайте посмотрим, как это происходит.

Итак, у нас есть два параметра, я их специально обозначил как x и y, чтобы было проще понимать.

Sale и stock.

Хорошо, мы их получаем, дальше создаем наш визитер, туда передаем наши оба параметра.

Дальше наше выражение, то есть это некоторое body, в него подставляем параметр из одного выражения.

Дальше объединяем уже не изначальное выражение, а исправленное.

То есть у нас получается одинаковый параметр.

Пускай там и там это будет y. И дальше из нашего body мы из него делаем лямбдовыражение.

То есть подставляем туда параметр.

Ну а параметр у нас тут вот параметр y.

Это параметр, есть ли там товар на складе.

И у нас в итоге получается вот здесь вот нужное нам выражение.

То есть у нас и вот этот параметр, и там, и там называется Y, то есть он стал одинаковым.

Вот такая вот некоторая хитрушка, некоторая магия, которая происходит под капотом, когда мы объединяем две спецификации.

Эта задача достаточно типовая, достаточно стандартная для каких-то линку выражений.

Естественно, для нее уже существует решение, какая-то библиотека, где эта задача решена.

Она называется Predicate Builder, это часть вот этого движка, который называется линку кит.

Там, если мы берем два линку экспрессиона и дальше мы их объединяем, например, оператором and, либо оператором or, либо оператором not,

то у нас получается некоторое скомбинированное выражение, и там под капотом точно так же происходит вот эта подмена параметра.

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

Хорошо, спецификация, поговорили, что такое.

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

Но если мы захотим спецификацию объединить с каким-то еще выражением, которое не является спецификацией, то у нас возникнет ошибка.

Потому что какие-то там парами, вот эти булевские операторы, они умеют объединять только спецификацию со спецификацией.

Объединить спецификацию с каким-то

любым другим выражением, она уже не сможет.

Хотелось бы какое-то другое, более универсальное, более гибкое решение.

Вот давайте на него посмотрим.

Одним из таких решений является 9LinQ.

Вот это библиотека.

Что она позволяет сделать?

Она позволяет объявить некоторый extension метод, пометьте его специальным атрибутом, и этот метод, он будет как раз там

некоторым маркерным.

У него может не быть вообще никакой реализации, то есть здесь видно, что там просто complemented exception кидается.

Нам сам метод, он не важен.

Важно, что в том же классе будет похожий метод, который там называется, у него то же самое название, isAvailable, но он уже возвращает expression.

А дальше, когда мы

Вот этот вот движок на N-Link, он будет по названию, находит вот этот вот маркерный статический метод, он его подменяет на результат метода, который возвращает N-Link Expression.

И как он используется?

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

Что он делает?

Это будет еще один декоратор над iQueryble.

И вот это вот мы видим в выражении из available, это вызывается статический метод, вот тот самый маркерный метод.

И да, он хорошо уже объединяется с...

любым произвольным выражением, там никаких ошибок компиляции не возникает.

А дальше у нас что происходит под капотом?

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

То есть это выражение туда подставляется.

И на самом деле вот эта вот штука на AdLink, она достаточно гибкая, то есть она позволяет писать вот такие спецификации не только на сущности, но и на какие-то скалярные значения.

То есть, например, можно написать проверку, является какое-то значение, цена положительной или нет.

И точно так же мы можем указать, что...

Это decimal, extension method, точно так же пишем метод, он называется точно так же из positive, он уже возвращает expression, который будет подставляться, и по нему будет генериться SQL-запрос.

И дальше вот здесь вот в нашем условии, вот здесь мы можем использовать этот extension method для цены, а не для всего товара.

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

И еще у него есть такая интересная фишка.

Для того, чтобы вот эта вот магия работала, нужно все наши кверабл их заворачивать в специальные декораторы.

То есть нужно каждый раз, когда мы захотим использовать эту спецификацию как extension метод, нужно написать toInjectable.

Можно при регистрации DB-контекста один раз написать вот этот метод withLambdaInjection и все.

После этого тогда все вот эти вот свойства этого DB-контекста, все эти querable, они будут автоматически заворачиваться вот в этот декоратор.

И вручную каждый раз это писать не придется.

Хорошо.

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

Но с другой стороны, все-таки есть некоторые неудобства в том, что нам надо два метода.

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

Ну, в общем, есть где накосячить.

Поэтому хотелось бы какое-то другое решение, еще более продвинутое, где все то же самое пишется один раз, где у нас есть некоторый метод, и он же возвращает некоторую реализацию, то есть возвращает тот expression, который надо подставлять в...

в условиях для фильтрации.

И как раз вот это позволяет делать следующий библиотеку, она называется Projectable.

Точно так же, как на NLINK, можно один раз при регистрации в DI-контейнере DB-контекста указать Use Projectables, и вот эти свойства будут автоматически у DB-контекста декорироваться.

То есть точно так же каждый раз там with Projectable писать...

Что оно позволяет сделать?

Мы можем сделать какое-то вычислимое свойство в нашем классе, в товаре, и там написать вот это условие для фильтрации.

А дальше вот там, когда мы пишем условие, выражение v, мы можем использовать вот это вычислимое свойство.

И дальше там вместо вот этого вычислимого свойства ProjectLib подставит вот это условие, которое здесь указано.

То же самое можно сделать и при помощи extension метода.

То есть можно написать extension метод, пометить его специальным атрибутом, и уже вот этот extension метод будет содержать тело, он будет содержать то условие, которое нужно подставить, а дальше вот у нас там ниже идет условие where, там isAvailable, и вместо вот этого статического extension метода будет подставлено у него тело.

То есть видим, что как раз вот там,

Вот тот кейс, где в Nine Link мы до этого смотрели, нужно два метода.

Один маркерный, а второй с реализацией.

Здесь будет всего один метод, который будет использоваться и как маркерный, и будет подставляться его реализацией.

Итак, что у этого проекта был под капотом?

Там будут source генераторы, которые генерируют линейку выражения для подстановки.

И там точно так же используется декоратор для того, чтобы вот это сгенированное выражение его подставить в линейку expression для entity framework.

С одной стороны тоже это опять шаг вперед, но опять же здесь используются

Этот механизм для декорации.

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

Можно ли обойтись без вот этого декоратора?

На самом деле, вот теперь можно, начиная с Entity Framework 7.

Когда там выходил этот Entity Framework 7, ну как-то так, у него появилась интересная возможность, вот этот вот Query Expression Interceptor.

И что-то как-то в сообществе про него там говорили очень мало, хотя это очень важная возможность.

Это возможность, которая позволяет

работать и как-то модифицировать тот expression, который пойдет уже там entity framework там вот эти вот query provider для того, чтобы по нему строить SQL-запрос.

И мы получаем штатный механизм для того, чтобы этот expression заменить.

Раньше такой возможности не было.

И приходилось создавать вот эти вот декораторы.

А здесь это просто в качестве демо.

Вот здесь у нас есть вот этот метод.

Он там принимает...

он принимает expression, ну и возвращает тоже expression.

И мы создаем там какой-нибудь визитер, который как-то этот expression модифицирует.

И здесь просто в качестве демо-примера написано, что если у нас там

встречается какой-то метод, который называется isAvailable, то мы этот метод isAvailable там подменяем на проверку какой-то свойства.

То есть примерно это некоторая демо, понятно, не такое там функциональное, как вот те примеры, которые мы смотрели, но вот из такого интерсептора вполне можно сделать что-то похожее на Projectable, и уже никаких декораторов не понадобится для того, чтобы работать с этим линковым выражением.

Возможно, кто-нибудь из разработчиков вдохновится и таки сделает это.

Хорошо.

Все, что мы сейчас смотрели, это были какие-то надстройки над Entity Framework, какие-то плагины к нему.

Но, как говорится, не Entity Framework единым.

Вчера уже был доклад про Link2DB.

Вот там уже есть поддержка вот этой спецификации из коробки.

Там кратко, что такое Link2DB, это еще один там ORM, он сам себя позиционирует как такой типа безопасный SQL, когда мы там делаем маппинг и пишем какую-то линку, и оно все преобразуется там в SQL.

Там нет какого-то там функционала тяжелых ORM, типа антифреймворка, там нет ченчтрекинга, ну и там не очень документации, вот какие-то там примеры, типа спецификации, ну его приходится достаточно так долго искать.

Что она позволяет сделать?

Здесь спецификация, она похожа на реализацию в библиотеке на NLinQ,

Там есть опять же вот этот вот метод isAvailable.

Это опять же статический метод, он чисто маркерный.

То есть здесь тело написано, но в принципе его может и не быть.

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

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

И дальше, ну здесь это уже как фича самого 12DB, здесь никаких декораторов не надо.

Дальше у нас там будет некоторый вот этот вот

Там table product, и мы указываем наш метод isAvailable.

И дальше, да, там будет вот этот вот метод, его фактически из запроса уберут.

И вместо него подставятся условия, которые в нашем втором методе, который возвращает к линку expression.

То есть то же самое, но очень похоже на nlink.

Хорошо, и то же самое позволяет сделать через свойства.

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

Опять, это достаточно похоже нам на nlink, там тоже возможность работы с методом была.

Хорошо.

Есть еще одна реализация спецификации.

Она есть в eShop.onWeb.

То есть это такой пример от Microsoft, как делать современный веб, как делать современный бэкэнд.

Там вот этот Ардалис, Стив Смит зовут, он тоже предлагает реализацию спецификации.

Давайте мы на нее посмотрим.

Итак, начинается все достаточно классически.

Там есть спецификация, есть метод SatisfiedBy для Entity.

Ну, окей.

Идем дальше.

А дальше будет группа методов.

Where expressions, order expressions, include expressions.

Дальше идет пожинаться, типа take, skip.

Дальше идет какое-то кэширование.

Дальше там группа каких-то флагов, as no tracking, split query, то есть какие-то extension методы.

которые вызываются именно для Entity Framework.

Видим, что вот эта спецификация уже прибита гвоздями к Entity Framework.

Все, что мы смотрели до этого, фактически от ORM не сильно зависело.

То есть эта спецификация на самом деле, она как спецификация по названию, а по сути это Query Object, который в себе инкапсулирует абсолютно все условия фильтрации, которые мы хотим применять.

То есть там есть некоторые критерии,

Там они в результате объединяются при помощи каких-то операторов.

Ну и да, посмотрим на реализацию.

Это был интерфейс.

Как предлагается реализовать эту спецификацию?

То есть там будет конструктор, и он, например, указывает условия where.

Там можно указать условия для фильтрации данных.

И дальше будет опять же в этой библиотеке extension метод, там with specification, а где можно эту спецификацию указать.

Но у этой спецификации есть ограничения.

Если мы попробуем скомбинировать две такие спецификации, у нас ничего не получится.

Потому что в одной спецификации, например, указан один размер страницы, а у другой другой.

И какой нам выбрать, непонятно.

Или указаны разные условия для сортировки.

В одном там по одной колонке, а в другой по другой колонке.

И как эти два условия объединить?

Ну, непонятно.

То есть query, они не предназначены для какой-то группировки, для комбинации.

И если мы используем query object, то у нас фактически будет всего одна вот такая вот строка, miss query object, и все.

И там будет какая-нибудь и пожинация, и сортировка, и фильтрация.

А спецификация, про которую мы до этого говорили, она используется только для фильтрации, только для условия where, то есть вот здесь у нас miss specification, и все.

То есть там указывается...

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

И еще одна библиотека, про которую достаточно много говорят, это динамик линку, но она не очень часто используется в продакшене.

Там, в принципе, также можно сделать два линку выражения, и для того, чтобы их объединить, мы пишем буквально текстом

Там одно выражение, второе, и мы их при помощи оператора end текстом объединяем.

В принципе, ну вот такую возможность, ее тоже можно рассматривать как некоторую реализацию спецификации, потому что мы можем вот эти условия end or менять.

Но если честно, на практике в production, чтобы вот этот вот динамик линку использовался, я не помню, чтобы я видел.

И тоже там часто спрашивают, хорошо, а куда нам положить спецификацию?

Обычно, когда говорят про спецификацию, говорят, что она предназначена для каких-то там условий, связанных с бизнес-энтити.

И здесь, например, когда мы спецификацию создаем, мы ее кладем в наш...

проект в Солюшене, который связан с доменом, с нашими бизнес-сущностями.

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

В общем, так делать тоже можно, это нормально.

Хорошо, раздел посвященный спецификациям мы закончили.

Следующая задача – это фильтрация по вложенным коллекциям.

Итак, у нас есть наши товары, и у каждого из них есть категория.

Не точно так же категория, в категории может быть много товаров.

Окей, давайте мы хотим…

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

То есть те товары, те категории, которые имеют смысл показывать пользователю.

Окей, а линку спецификация, она для этого не подходит, потому что product это коллекция, метод коллекции any, он должен получать делегат, а не expression.

Если мы туда подставим нашу спецификацию как линку expression, у нас ничего не выйдет, у нас будет ошибка компиляции.

Нужно что-то еще, нужно как-то выкручиваться.

Здесь нам приходит на помощь линку kit, и он говорит, что ага, ребята, а давайте мы

создадим спецификацию как expression, то есть мы получаем все возможности по комбинации этих спецификаций, и мы точно так же сделаем некоторый as-expandable, то есть некоторый декоратор для expression, а там, где нужно в условии указать спецификацию, мы ее скомпилируем.

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

В общем, там дальше этот линкокит

все сделает, чтобы было хорошо.

С одной стороны, это правда.

Вот та спецификация, которая здесь передается в метод any, она действительно не компилируется.

Но там компилируется некоторое другое выражение.

Я вот специально там нашел, где это происходит.

То есть, в принципе, INCOOKIT, он с одной стороны не то, чтобы врет, но и с другой стороны не то, чтобы говорит правду.

То есть там, с одной стороны, спецификация наша, она не компилируется, но компилируется другой expression.

Так что вот здесь немножко автор

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

То есть можно точно так же создать это свойство в нашем библиотеке.

классе вычислимое свойство, там пометь специальным атрибутом, и тогда во вложенной коллекции, вот в этом условии any, мы можем использовать вот это вычислимое свойство.

А дальше projectable, в общем, когда мы будем декорировать вот это наше выражение, вместо вот этого вычислимого свойства подставить, собственно, его тело.

И с extension методом то же самое тоже сработает.

То есть мы можем сделать спецификацию extension метод, который возвращает там, вычисляет, нужно ли этот товар показывать, и дальше extension метод точно так же вызывать здесь вот внутри вот этого метода any во вложенной коллекции.

Это тоже будет работать.

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

Хорошо.

И третья задача, про которую мы хотим поговорить, это рутинная фильтрация.

Наверное, большинство из вас видело какие-то там большие формы, какие-то длинные списки, где есть большое количество колонок, и по этим колонкам есть фильтры.

Там разные условия, нужно по этим условиям как-то фильтровать.

Это типичное бизнес-приложение, где разные списки с фильтрацией, с пожинацией, с сортировкой.

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

Если мы захотим по вот такому фильтру сделать фильтрацию, ну как мы это будем делать?

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

Если там цена от не нулевая, она указана в запросе, мы добавляем фильтр по цене там снизу.

Если там price to опять же не нулевая, мы добавляем фильтр по цене сверху.

Это как бы нормально, здесь все вот эти условия, они там комбинируются по булевому и. То есть нужно, чтобы там и название соответствовало, и цена тоже находилась в заданном диапазоне.

И хотелось бы вот этого вот всей, ну такого однотипного boilerplate кода как-то избавиться, как-то это все упростить.

Как мы это можем сделать?

Здесь есть такая библиотека, она называется CIF, то есть CITA.

Что она позволяет сделать?

Вот, например, у нас есть вот такой фильтр, мы его помечаем там свойства специальным атрибутом и говорим, можем ли мы по этому свойству сортировать и можем ли мы по нему фильтровать.

А дальше специальный процессор, который получает модель.

И вот этот продакс, который туда передается, это querable.

Посмотрим, что это такое за SIF-модель, которая используется.

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

Там есть фильтры, есть свойства для сортировки, и там есть свойства для пожинации.

Пэдж и пэдж сайдс.

Вот эти фильтры, они задаются вот в такой вот нотации.

Например...

Фильтр цена там меньше 100 и название должно совпадать с каким-то там названием, которое ему указали в фильтре.

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

Если кто-то хочет, чтобы вот эти фильтры, они были какими-то более типизированными, более управляемыми, можно использовать другое решение, оно называется ActiveFilterer, вот там ссылочки на него тоже есть.

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

И, например, даже указать, что специальным атрибутом мы хотим, чтобы запрос генерировался так, чтобы мы получили все

Там продукты, там все товары.

По условию, вот оно, условие, как раз указано в атрибуте, что это Pullover Contains, то есть без учета регистра, чтобы название товара содержало ту строчку, которую мы передаем.

Там можно вместо Contains Start Twist настроить, но это уже такие детали.

И я изобрел свой собственный автофильтр, когда я смотрел на автофильтр R, предыдущий мне очень понравился, захотел сделать свой, но это еще и весело, свои велики изобретать.

Он похож по функционалу, точно так же есть и фильтр,

DTO, которая передается с фронта на бэк.

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

И дальше, по какому условию мы будем фильтровать.

Может быть, contains, может быть, start with, end with.

И тот же самый есть специальный примитив для диапазона.

А дальше, когда мы захотим отфильтровать наши товары, мы указываем эксцентричный метод, автофильтр и все.

Этот метод берет экспрессион и возвращает экспрессион.

Если мы после этого захотим еще добавить какую-нибудь пожинацию, сортировку, можно это добавить в тот экспрессион, который получился, в тот кверабл, который получился после фильтрации.

Давайте так, кратко заглянем под капот этих фотофильтров, посмотрим, что там происходит.

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

Ну здесь условия просто там, где такой достаточно демо-пример, где

Там свойство равно какому-то значению.

Дальше все эти свойства объединяем через условие and else и добавляем туда параметр.

Все, у нас получилось линковое выражение.

То есть здесь как его можно...

Оптимизировать – это как раз добавить компиляцию.

То есть мы же с вами говорили, что можно выражение компилировать в делегат.

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

Такая небольшая, но достаточно интересная

Вот, например, здесь его можно использовать, то есть получать значение поля фильтра не при помощи Reflection, а при помощи скомпилированного делегата.

Да, эти делегаты там надо будет где-то закэшировать, это можно подробнее посмотреть в моем автофильтре.

Принципиально это выглядит вот так.

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

Вот он получился делегат.

Мы видим, что этот метод возвращает не экспресс, но он возвращает фанк, то есть он возвращает делегат.

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

Вот давайте поговорим про то, а сколько это вообще работает.

Например, здесь то, что связано с подстановкой параметров, rebind параметр, это то, что мы вручную написали, вот был слайд.

Дальше, predicate builder, он делает ту же самую подмену параметров.

Видим, что это время измеряется какими-то сотнями наносекунд.

Передикат Билдером оказался даже чуть-чуть побыстрее нашей реализации, но все равно 10 наносекунды.

Дальше, compile fast и compile.

Когда-то я видел выступление какого-то спикера, он говорил про компиляцию линку выражений, и у него там было типа вот линку выражения, точка compile, и дальше комментарий pull on.

Типа это работает очень долго, прям компиляция выражений это какое-то зло, которого надо всячески избегать.

Давайте посмотрим, на сколько времени это зло занимает.

Это занимает, ну вот, четыре

Это уже будет не микросекунды, это быстрая компиляция.

Вот эта компиляция, которая есть из коробки, но она работает примерно на порядок больше, она занимается 60 микросекунд.

Важно, что микросекунд не миллисекунд, а микро.

То есть если у нас, например, какой-то запрос отрабатывает за 50 миллисекунд, то там вот

50 микросекунд это будет в тысячу раз меньше.

То есть это будет одна десятая доля процента от времени работы этого запроса.

И фактически есть ли при работе запроса какая-то компиляция выражений, нет ли этой компиляции выражений, это ни на что не повлияет.

То есть это время не то, что в рамках статистической погрешности, а его вообще никак не замечешь.

И то же самое про автофильтр.

Там кто-то говорит, что это магия, это непонятно как работает, это долго работает.

Ну вот посмотрим, сколько долго, это сколько.

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

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

Автофильтр R, это другой автофильтр, он работает, ну, чуть-чуть подольше, в целых два раза дольше, но опять это время, оно измеряется...

микросекундами.

То есть, например, сделать вот эту линку выражения, это вызывает 2 или 4 микросекунды.

Если у нас, опять же, 50 миллисекунд работает наш запрос, который входит в базу по сети, это сетевой запрос, то вот это время, нужное для динамической генерации линку выражения, его никто не заметит, оно очень маленькое.

Вот.

Наверное, на этом все.

Давайте подводить итоги, о чем мы поговорили.

Итак, спецификация.

Ее современная реализация.

Это extension метод плюс source гены.

Здесь как раз из всех вариантов реализации спецификации, о которой мы говорили, лучше, что выглядит projectable.

Собственно, я рекомендую его использовать.

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

Автофильтр для автоматизации рутины, фильтрация, ну здесь

Не все так однозначно, все-таки я лицо заинтересованное, у меня свой автофильтр тоже есть.

Так что здесь, ну, ребята, можете сами выбрать, ну, либо автофильтр, либо CIF, ну, решения, они сопоставимые.

Какого-то решения, которое там на порядок круче других, ну, здесь нету.

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

И если говорить про производительность, то все то, о чем мы говорили, это группировка спецификаций, это компиляция вот этих вот экспрессионных делегатов, это все работает достаточно быстро.

То есть это не повлияет...

Если какие-то запросы будут тормозить, то они будут тормозить не из-за того, что у нас какие-то экспрессионные делегаты компилируются, либо у нас какие-то автофильтры прикручены, а это будет тормозить из-за того, что запросы какие-нибудь неоптимальные, у нас что-то не так с нашим хранилищем, либо с запросами.

Вот.

И там главная мысль, которую я пытался донести, что вот какие-то такие вещи, вот типа линкор экспрессионов, там каких-то source гены, линкор фреймворк интерсепторы, что это вот какой-то там хайп, про который говорят на конференциях.

На самом деле это вот не хайп, а это весьма полезные инструменты, просто нужно знать для решения каких задач их применять.

И вот как раз спецификации, автофильтры, это те самые задачи.

То есть вот как там есть там 20 на 80, у нас есть в приложении там 20%

Сложных запросов, для того, чтобы их упрощать, нам нужна спецификация.

То есть мы эти кусочки запросов инкапсулируем, комбинируем между собой, получаем профит.

А 80% простых запросов, вот как раз автофильтр для того, чтобы рутину генерации этих запросов автоматизировать.

И вот оно счастье.

Да, собственно, вот как раз их применять, мы об этом сегодня поговорили.

А полезные ссылки.

Главная там ссылка, это вот она, это Anti-Framework Core Extensions.

Большинство тех решений, про которые я сегодня говорил, они на этой странице есть.

Возможно, не все, но большинство.

Это та страница, которую всем разработчикам бизнес-приложений надо знать.

И время от времени на нее заходить, там появляются интересные вещи, типа Projectable.

Дальше есть статья на Хабре.

Она вообще посвящена паттерну репозиторий, но там описывается похожая проблематика.

То есть это как инкапсулировать части запросов и комбинировать их друг с другом.

И последнее, это у Макса Аршинова, у него там в 2018 году здесь на DotNext был доклад, посвященный, ну, похожей проблематике, опять же, как инкапсулировать и комбинировать там между собой части запросов.

Ну, там Макс немножко увлекся своими велосипедами, а вот, собственно, как показал этот доклад, что уже есть достаточно много существующих инструментов, ну, просто надо про них знать, надо научиться ими пользоваться.

Вот, на этом у меня все.

Всем спасибо за внимание.

И если есть вопросы, то давайте.

Да, круто, Денис, спасибо большое за доклад.

Пожалуйста, вопросы.

Спикер 8

Добрый вечер.

Подскажите, автофильтр вы использовали и привели достаточно простые кейсы.

А если фильтровать приходится очень сложно?

К примеру, я хочу поучить категорию товаров.

там, плюшевые мишки, которые там есть скидка, меньше там 100, да, или учить категорию, там, ноутбуки, у которых там тоже есть скидка, или что-то еще, то есть есть два блока или, и во втором или есть тоже

И два.

Как в таком случае.

Спикер 4

Прекрасный вопрос, спасибо.

Если вы хотите сделать что-нибудь этакое при помощи автофильтра, то при помощи автофильтра это делать не надо.

Автофильтр предназначен для того, чтобы просто решать простую задачу.

А тот же самый автофильтр R, там что-то более сложное сделать можно, но, если честно, я никогда не пытался.

То есть вот есть какая-нибудь простая задача, простой фильтр, ну вот для этого подходит автофильтр.

Если нужно сделать что-то более хитрое, ну вот такие условия уже лучше делать вручную.

Спикер 8

Я так понял, что и с сортировками то же самое.

Если мы хотим по трем колонкам сделать сортировку, там по первой, потом по второй и по третьей, он тоже не совсем подходит, да?

Надо искать что-то еще.

Спикер 4

Ну да.

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

В принципе, для этой задачи можно использовать.

Ну а там опять же надо смотреть, в какую сторону, в общем...

У меня в автофильтре такая возможность есть, там несколько, например, по нескольким колонкам сортировку добавить.

А в других автофильтрах, ну, я не знаю.

Спикер 8

Хорошо, понял.

Спасибо.

Спикер 4

Спасибо.

Еще вопросы?

Пожалуйста.

Спикер 6

Раз, раз.

А я правильно понимаю, что Projectable будет и с Mongo работать?

А что?

С MongoDB projectable можно использовать и для комбинации запросов для Монги.

Спикер 4

Правильно же я понимаю?

Но если там будет антифреймворк с Монгой, в принципе, да, должен работать.

Спикер 6

Нет, я имею в виду, там у Монги же собственный драйвер, там антифреймворком и не пахнет.

Вы не в курсе, будет, не будет?

Я не пробовал.

Спикер 4

Вообще вот этот Projectable, я его использовал для SQL, там будет работать.

В принципе, у Монги там тоже есть возможность вот эти линковые экспрессионы использовать, но прикрутить туда Projectable, я не знаю, не пробовал.

Можно тогда я вормусь?

А вот что лучше тогда использовать?

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

Все же что лучше, самому писать или использовать готовое решение?

Да, тоже хороший вопрос.

Зависит.

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

То есть мы с вами сегодня смотрели, а что такое спецификация?

Это там обертка на длинку выражением?

Как его комбинировать?

Да, там есть буквально маленькая хитрушка по комбинации параметров.

Ну, там любой разработчик, не знаю,

Ну не джун, но мидл точно с этой задачей справится.

То есть написать свою собственную спецификацию и свой собственный автофильтр, но если его нет под какое-то специализированное хранилище, это можно сделать.

Ну а если мы уже говорим про SQL, про NT-фреймворк, то здесь мы уже сегодня перечислили длинный список решений.

Я уверен, что уж хоть какой-нибудь из них подойдет для того, чтобы свой проект впилить.

Отлично.

Спикер 2

Денис, спасибо за доклад.

А в продолжение этого вопроса, насколько можно, мы рассмотрели несколько таких пакетов от разных разработчиков, насколько их можно использовать в корпоративном коде с точки зрения лицензии, безопасности, поддержки возможности?

Или про ваш пакет, если можете рассказать.

Спикер 4

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

То есть там авторы этих пакетов, но они все бесплатные, это просто экстеншн для антифреймворк кора.

Там нет ничего такого платного, нет ничего закрытого лицензиями, то есть проблем никаких нет.

Спикер 5

Здравствуйте.

А где вы?

Помашите рукой.

Все, я увидел.

Спасибо большое за доклад.

Тоже используем свой велосипед.

Поздравляю.

А вот по поводу фильтров, которые, грубо говоря, с фронта приходят.

Там вот у вас были поля со всякими константами, и я заметил, что когда экспрессион строится, там просто экспрессион констант используется.

В общем, вопрос к тому, что вы смотрели, какие запросы SQL в итоге формируются?

Они параметризированы или вот эта константа, она прям в тело запросов вставляется SQL-ного?

И, собственно, это проблема, если библиотеки, которые вот это обходят и умеют параметры констант уставлять.

Спикер 4

Ну, это...

Вообще говоря, это зависит уже от какого-то ORM, то есть что делает вот эта вот штука, там какая-то надстройка, например, projectable, она просто подставляет линку выражения.

А дальше уже какой-нибудь там query-провайдер, который по вот этому линку выражению строит SQL, уже он там либо хардкодит значение, либо подставляет его в качестве параметра.

То есть антифреймворк, он подставляет вот эти вот какие-то константы в качестве параметров, но только что здесь проблем никаких нет.

То есть это понятно.

Я в своем демо-примере захардкодил значение 100 либо 0, а в реальном коде, если в него подставить значение переменной, ну как оно там скорее всего будет, то да, это будет параметр в запросе, в SQL запросе.

Спикер 3

Хорошо, спасибо.

Спикер 7

Да, здравствуйте, спасибо за доклад.

У меня такой вопрос.

В общем, я тоже недавно такой велосипед написал, никто не оценил.

Спикер 4

Добро пожаловать в клуб.

Спикер 7

Никто не оценил.

И, в общем, основная претензия такая, что давайте тогда использовать GraphQL, потому что уже готовая спецификация, все для этого есть.

Вы с таким сталкивались?

Спикер 4

Ну...

Да, то есть здесь использовали какой-нибудь GraphQL или Odata, который был до этого.

В принципе, он решает те же задачи, как он их решает на бэкэнде.

Но он всю ту же самую проблематику выпихивает на фронтенд.

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

То есть здесь вот эти вот спецификации, это такой, ну как сказать, более такой путь срединный.

Что с одной стороны у нас есть вариант, когда ничего нету, ни автофильтров, ни спецификаций, мы там каждый раз эти запросы пишем вручную.

Это такая одна крайность.

Есть другая крайность, где у нас там всякие там...

даты либо GraphQL, ну и там, как говорится, тоже не проблема бэка, а проблема фронта.

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

Спикер 7

Да, спасибо.

Спикер 4

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

Вот Денис, Макс, Аршинов, все другие участники, тоже наверняка пытались что-то свое написать.

Объединяйтесь в клуб.

Да, на самом деле это тоже такой интересный кейс.

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

Так что, ну вот, да, сегодня мы с вами смотрели, что там находит свой автофильтр, ну вот это буквально там два экрана кода.

И там любой уважающий себя разработчик должен хотя бы попробовать это сделать, чтобы разобраться, ну как оно там под капотом работает, а дальше уже использовать какой-нибудь там Projectable.

Так, спасибо.

Еще вопросы есть?

Видимо, вопросов нет.

Тогда большое спасибо Денису за доклад.

И я приглашаю Александра.

Спикер 1

Денис Андрей, спасибо вам большое.

Приглашаю вас пройти в дискуссионную зону.

И я думаю, что там тебе зададут вопросы, те, которые постеснялись задать тут.

И еще, конечно же, нас ждет онлайн.

Друзья, вам я хочу напомнить, что здесь через 10 минут произойдет торжественное закрытие нашей конференции.

И организаторы будут очень рады видеть вас здесь, на этом закрытии.