Pull to refresh

Comments 38

Такое количество оверхеда и костылей чтобы не писать примитивный SQL запрос… впечатляет. Мне любопытно, вы таки достигли в этом потолка или готовы съесть ещё кактуса сверх того, чтобы только не изучать синтаксис оператора SELECT ?

Ну и да. А потом прибегает DBA и кричит «покажите мне этого… который пишет такие запросы»

"2 сущности User и Product." — без ентитифрэймворка и полллитры можно мозги прям вывихнуть


DBA: WTF???
кодер: это не я, это ентитифрэймворк!

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

Из вашего комента следует, что ВСЕ крупные проекты используют ентитфреймворк. Вы с луны свалились?


Ок, расскажите чего именно я не понимаю из того, что следовало бы.

Ок, расскажите чего именно я не понимаю из того, что следовало бы.

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

а потом это всё тупит беспощадно…

поддерживаем одно такое приложение, которое судя по профайлеру — один и тот же запрос делает 2-3 раза подряд :( да и вообще данные выбирает, в своей массе одиночными запросами, как будто курсором проходится по таблице, для выбора нескольких подряд записей :(

Простите, а какое отношение описанные вами симптомы имеют к той функциональности, о которой я говорю?

если мне нужно нетривиальное генерирование SQL, я использую другой язык программирования, который позволяет делать более мощные абстракции, чем паттерн визитор и дерево выражений Linq Expression. Наример, с провайдерами типов или с синтаксическими макросами. И в котором нет нужды в инструментах наподобие EF

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


(ну и да, мне, конечно, интересно, что за абстракцию мощнее, чем AST, вы будете использовать)

У других людей ее может и не быть.

В таком случае коль скоро язык и среда не предоставляют других адекватных средств — ado net. Впрочем, я не могу себе представить проект .net, в котором нельзя использовать F#, то есть лично я бы создавал запросы из Quoted Expressions если этого оказалось бы не достаточно.


что за абстракцию мощнее, чем AST, вы будете использовать

Вопрос не верный. AST бываю разные. Сабжеваое — полный отстой. Правильный вопрос — где можно использовать наиболее простой алгоритм и структуры данных для генерации SQL, а так же синтаксис в дереве. Ответ — Clojure.

В таком случае коль скоро язык и среда не предоставляют других адекватных средств — ado net.

Ну вот видите, вам ado.net, а я предпочту иметь хоть какой-нибудь работающий AST вместо строк. Каждому свое.


Впрочем, я не могу себе представить проект .net, в котором нельзя использовать F#

Корпоративный стандарт на C#?


лично я бы создавал запросы из Quoted Expressions

И что, такая большая разница с System.Expressions?


Вопрос не верный. AST бываю разные.

Паттерн-то один и тот же.


Сабжеваое — полный отстой.

Аргументы (отличные от "мне неудобно") в студию.

хоть какой-нибудь работающий AST

ado net тоже таки работает. Откуда следует, что этот подход чем-то хуже?


Корпоративный стандарт на C#?

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


И что, такая большая разница с System.Expressions?

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


Паттерн-то один и тот же.

это абсолютно ни о чём не говорит

Откуда следует, что этот подход чем-то хуже?

Оттуда, что ручной парсинг SQL для его изменения более хрупок, чем работа с деревом.


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

Обучать остальных 50+ программистов F# кто будет?

А при чём здесь ручной парсинг SQL?


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

А при чём здесь ручной парсинг SQL?

При описанной выше задаче: "Например, если вам надо на лету дописывать критерии (бизнес-фильтрация, "логическое удаление", безопасность)."


Но если надо — обучатся сами, или вон из профессии.

Бизнес с вами не согласен. По крайней мере, в моем опыте.

Это не ответ. Вопрос остаётся — зачем при для создания SQL запросов ado net для взаимодействия с SQL сервером парсить стороку с SQL запросом только потому, что я не хочу Linq Expressions.


Не всегда есть человек, который способен внятно объяснить менеджменту выгоды от применения F#.

Вопрос остаётся — зачем при для создания SQL запросов ado net для взаимодействия с SQL сервером парсить стороку с SQL запросом только потому, что я не хочу Linq Expressions.

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

Нет. У меня есть функция, у которой на входе бизнес требования, на выходе данные. Строка запроса, по которому она получает эти данные, является внутренyей деталью работы этой функции. Эта строка либо кэшируется, либо отдаётся сборщику мусора.

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

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

Вам бы более реальный пример. Потому что целый метод обёртка ради фильтрации по имени — выглядит оверинжинирингом. Мне быстрее написать Where(e => e.Name == name), чем вспоминать где и как назывался метод WhereProductName(name).
Я скорее описывал подход и то, как можно работать с выражениями. Класс ExpressionVisitor есть, а примеров для чего он нужен и как с ним работать в интернете не так уж и много. Вы его можете использовать там, где он вам нужен или не использовать. Реальный продакшн код, где я использую этот подход похож на тот, что я привёл в примере, только больше и немного сложнее. Приводить его я не вижу смысла. Конечно ради одного where это оверинжениринг, но это чисто показательный пример.
Также есть очень полезная штука, PredicateBuilder, позволяет объединять выражения с помощью And, Or, Not.
Ваш пример не показывает выгоды вашего решения, вместо 2-х функций написали 3.
Да, на таких простых примерах выгоды совсем не видно. Статья выиграет, если рассмотрите сложный случай, когда нужно применять динамическую фильтрацию по любому полю, а также сравните сгенерированный SQL с использованием конструктора выражений с написанными вручную выражениями для каждого случая
Просто гениально!!! В своё время тоже как то пытался разобраться с этой проблемой стандартными методами но потратив пол дня и не придя к готовому результату я просто забил на это. В итоге пришлось прибегнуть к копипасту. Мне кажется статья может иметь продолжение по этой же теме для разбора например множественной сортировки, по 3м полям и более или обьединение нескольких условий через оператор «или»
Люди с язвительными комментариями видимо весьма далеки от разработки крупных приложений, где борьба со сложностью это всегда привнесение некоторой дополнительной сложности, чтобы уменьшить более крупную.
Возможно многие просто используют, например, хранимые процедуры для работы из приложения с БД. Не говорю, что это плохо, у всех разные архитектурные принципы. Кому-то важна поддерживаемость кода, кому-то — например скорость. Кто-то просто не решает задачи, где может понадобиться переиспользование кода генерации запросов.

Что касается темы статьи, то мы используем нашу же разработку Mindbox Expressions, в которой есть метод ExpandExpressions, делающий то же самое, только интерфейс чуть поприятнее :-)

Так же есть LINQKit, в котором есть AsExpandable, позволяющий примерно то же самое.
Да, спасибо. Не знал об этих библиотеках. Внутри они используют как раз очень похожие визиторы. Хорошая идея использовать метод ExpandExpressions у IQueryable, а не у самих выражений.
Я так понял, что вся статья о том как сделать работающим:

FilterContainsText<TEntity>(entities, getProperty, text)

Сделать это просто, достаточно поменять сигнатуру у метода и внутри собрать новый LambdaExpression:
public IQueryable<TEntity> FilterContainsText<TEntity>(
    IQueryable<TEntity> entities, 
    Expression<Func<TEntity, string>> getProperty, // компилятор C# так умеет
    string text)
{
	return entities.Where(Expression.Lambda<Func<TEntity, bool>>
	(
		body: Expression.Call
		(
			getProperty.Body,
			nameof(string.Contains),
			Type.EmptyTypes,
			Expression.Constant(text)
		),
		parameters: getProperty.Parameters
	));
}


Да, с примером статье не очень повезло. Но прием, описанный в статье, также позволяет реализовать динамическую фильтрацию.
Что такое «динамическая фильтрация»? Это текстовые запросы как в OData?
Динамическая фильтрация — один из способов фильтрации, который предполагает то, что фильтр мы строим динамически. Можно на основе запросов OData, можно на основе запросов API.
Как пример — есть таблица каких-нибудь студентов, хочется иметь фильтр по ФИО, по группе, по статусу дипломных работ. Причем, поведение фильтров в комбинации зависит от специальности студента. Бизнес-правила могут быть сложные. Вот тут и приходит на помощь динамическая фильтрация.
Мы можем определить несколько выражений, которые соответствуют каждому фильтру по отдельности, потом при помощи ExpressionVisitor и его реализации в PredicateBuilder строить динамически выражение, которое описывает комбинацию этих фильтров. После чего это выражение уже запихивать в тот же Entity Framework, например.
Лично я использую подобную методику на своем проекте, генерируемый SQL-код действительно похож на тот, что я бы написал вручную.
А что Вы будете делать, если в какой-то момент захотите, чтобы метод FilterContainsText вместо property.Contains(text) делал, например, property.Replace(" ", "").Contains(text)?
Добавлю Expression.Call(.., nameof(string.Replace)), это очевидно. И такой код будет просто поддерживать.
До сих пор не понимаю зачем для такой тривиальной задачи использовать визитор.
Да уж… На столь хилом примере не показать пользу от ExpressionVisitor, да и мощь ExpressionTrees не раскрыта.
Тем более, что способ использования… не слишком очевиден, попахиает шаманством :)
Я недавно решал в чём-то схожую задачу: делал фильтр для js-грида. В используемом мной гриде был встроенный механизм автофильтров, но работал только на клиентской стороне, а мне очень хотелось не тащить ни клиента лишние данные… Итогом стал универсальный механизм фильтрации на стороне сервера, работающий для любых данных. Со стороны клиента прилетает список фильтров «имя поля/тип фильтра/фильтрующее значение», а коде контроллера получается что-то вроде
IQueryable<T> source = DAL.GetQuery<T>();
IQueryable<T> data = source.ApplyFilter(filters);
Если интересно — могу код выложить и статейку по это набросать.

Sign up to leave a comment.

Articles