Думаю, что именно поэтому программисты из тех стран, на которые удаленно работают программисты из РФ (и пр.) и не зарабатывают столько денег как их врачи-юристы. Глобальный рынок, однако.
Поскольку узлы и листья синтаксического дерева строго типизированы, то отследить все места, где возможна злонамеренная инъекция и принять советующие меры очень легко и это будет на порядок безопаснее чем "text based dynamic sql". В примерах кода статьи я вставил метод Escape, чтобы отразить этот момент.
Спасибо огромное за слова поддержки! Это очень важно для мотивации продолжать заниматься Open Source!
Касательно выборки по столбцу из переменной — тут действительно, скорее всего, придется руками LINQ выражения править, как советуют выше.
По поводу LEFT JOIN, ведь еще есть и FULL JOIN и CROSS APPLY, которые может быть и можно выразить в LINQ, но как сказать об этом query provider (так что бы в SQL появился текст “FULL JOIN”) я не знаю.
Вообще “Query Notation” в C# это довольно прикольная штука, которая является аналогом “Do” нотации из Haskell, где она призвана упростить работу с монадами и соответственно в C# c её помощью можно делать разные прикольные штуки с монадо-подобными структурами (вот например моя статья про это "Simplify working with parallel tasks in C#")
Но использовать это все для запросов к реляционными базам данных, это, на мой взгляд, попытка натянуть сову на глобус – можно, но получается не очень естественно.
Этот паттерн позволяет безгранично добавлять функциональность, никак не меняя исходную структуру. Так, например, в SQ Express c ходу появилось 4 имплементации этого интерфейса:
Возможно, я некорректно противопоставил Visitor и ФП, но конкретно в C# Pattern Matching не проверяет полноту выборки даже в switch expression из C#8 (кроме отсутствия “default arm”, без которого компилятор выдает предупреждение, и Exception в Runtime).
С подтипами это вполне объяснимо, поскольку есть возможность создать неограниченное число наследников базового класса, но вот с enum ситуация более странная. Казалось бы, в чем проблема понять, что проверяются все возможные значения? Но C# все равно выдает Warning без “default arm”.
Казалось бы чушь, но в C# enum-у можно присвоить любое числовое значение:
Можно :) Но в статье описан подход, который не зависит ни от LINQ, ни от Entity Framework и позволяет писать запросы к базе данных в наиболее близком к оригинальному SQL виде. Недаром я упомянул LEFT JOIN, для реализации которого я всегда лезу в гугл. Дело в том, что LINQ создан в первую очередь для работы с объектами (LINQ to Objects), и с реляционными базами данных в нем возникают типичные для ORM проблемы (тут я могу порекомендовать статью хабре за авторством maslyaev).
Тут вопрос какому именно движку. Помимо MS SQL, это могут быть Postgres SQL, My SQL и т. д. и как они себя поведут я не знаю. Но вообще идея в том, чтобы дать разработчику максимум контроля над запросом, и если нужен UNION, то будет UNION. Если очень надо, то можно написать кастомный модификатор финального AST.
Вполне допускаю, что можно, но свое решение, изначально заточенное под динамику, как-то ближе =) Да и не знали тогда про Linq2Db. Такие статьи (и комментарии к ним) как раз и помогают другим подобрать наиболее адекватное решение типовым, в общем-то, задачам.
Вот не вижу минусов, если только вам не надо полную динамику — не знаю какие будут поля, какие будут таблицы.
Ну так задача поставленная в самом начале статьи как бы и подразумевает полную динамику. В реальности это выглядело как: UI контрол создавал AST в JSON который десериализвался в AST булевских выражений который уже вставлялся в некий запрос. Кроме того была проверка на "вычисляемые" столбцы и если они в фильтре были, то добавлялись те или иные вьюхи или Derived Tables. Некоторые предикаты заменялись на выражения вида "EXISTS(SELECT 1 FROM...)". А еще была фильтрация и сортировка по полностью динамическим полям, которые пользователи сами добавляли (как в JIRA)
Опять же, это все создавалось не сразу, а по мере развития функциональности и по началу вообще работало в связке с хранимыми процедурами… какой уж там LINQ ))
Если вы про набор типов описанный в статье, то он сильно урезан для упрощения изложения, более-менее реальный будет выглядеть намного сложнее.
Касательно отдельного типа для столбцов, то он имеет смысл, поскольку в разных диалектах SQL (a SqExpress так же работает и с Postgres SQL) правила записи столбцов будут разными и очень удобно иметь для них отдельный тип, так как кодо-генерацию можно отдельно реализовать для каждого диалекта (условно разная имплементация метода VisitColumnName ).
Да, я потом в итоге её нашел. Хорошая библиотека, но это все-таки LINQ) Мне же хотелось чего-то совсем близкого к оригинальному SQL. К тому же с кастомым синтаксическим деревом можно делать пост-модификации и различные экспорты/импорты, что удобно для передачи по сети или сохранения в базе данных
Возможно, вы просто с каждым годом все больше об это задумываетесь, поэтому вам кажется, что ситуация ухудшается. На самом деле все население Земли активно стареет и воротить нос от возрастных кандидатов все сложнее просто по причине объективного отсутствия молодежи. Судя по этому графику на двух тридцатилетних приходится (приблизительно) один двадцатилетний, поэтому конкуренция на рынке труда в ближайшем будущем будет происходить, в основном, между теми, кому сейчас между 30 и 40 (пока не подрастут те, кому сейчас 0-10 лет)
Я говорил не про типизацию как таковую, а про то, что все проверки типов можно было бы полностью выполнить на этапе компиляции.
С ограничением мутабельности становится возможным отследить моменты, когда выделенная память может быть освобождена и освобождать ее сразу же, делая выполнение программы более предсказуемым (без внезапных остановок на сбор мусора).
В свое время тоже увлекался подобным подходом и даже написал библиотеку, используя которую можно вообще выключить Change-Detection (описание можно почитать тут Angular Components with Extracted Immutable State).
Думаю, что именно поэтому программисты из тех стран, на которые удаленно работают программисты из РФ (и пр.) и не зарабатывают столько денег как их врачи-юристы. Глобальный рынок, однако.
Я до этого комментария про JOOQ не слышал. А что с ней не так?
Поскольку узлы и листья синтаксического дерева строго типизированы, то отследить все места, где возможна злонамеренная инъекция и принять советующие меры очень легко и это будет на порядок безопаснее чем "text based dynamic sql". В примерах кода статьи я вставил метод Escape, чтобы отразить этот момент.
Спасибо огромное за слова поддержки! Это очень важно для мотивации продолжать заниматься Open Source!
Касательно выборки по столбцу из переменной — тут действительно, скорее всего, придется руками LINQ выражения править, как советуют выше.
По поводу LEFT JOIN, ведь еще есть и FULL JOIN и CROSS APPLY, которые может быть и можно выразить в LINQ, но как сказать об этом query provider (так что бы в SQL появился текст “FULL JOIN”) я не знаю.
Вообще “Query Notation” в C# это довольно прикольная штука, которая является аналогом “Do” нотации из Haskell, где она призвана упростить работу с монадами и соответственно в C# c её помощью можно делать разные прикольные штуки с монадо-подобными структурами (вот например моя статья про это "Simplify working with parallel tasks in C#")
Но использовать это все для запросов к реляционными базам данных, это, на мой взгляд, попытка натянуть сову на глобус – можно, но получается не очень естественно.
Чисто "на правах рекламы" могу порекомендовать свою статью Монада «Maybe» через async/await в C# (без Task-oв!) в которой описан ещё один, довольно необычный, подход к решению той же, по сути, проблемы.
Этот паттерн позволяет безгранично добавлять функциональность, никак не меняя исходную структуру. Так, например, в SQ Express c ходу появилось 4 имплементации этого интерфейса:
Когда дойдет дело до экспорта в другие диалекты SQL или ещё какие-нибудь хитрые форматы, то будут просто добавлены советующие имплементации.
Это инструменты для построения AST путем анализа текста. Здесь же задача обратная — есть AST и надо получить текст.
Возможно, я некорректно противопоставил Visitor и ФП, но конкретно в C# Pattern Matching не проверяет полноту выборки даже в switch expression из C#8 (кроме отсутствия “default arm”, без которого компилятор выдает предупреждение, и Exception в Runtime).
С подтипами это вполне объяснимо, поскольку есть возможность создать неограниченное число наследников базового класса, но вот с enum ситуация более странная. Казалось бы, в чем проблема понять, что проверяются все возможные значения? Но C# все равно выдает Warning без “default arm”.
Казалось бы чушь, но в C# enum-у можно присвоить любое числовое значение:
И тут предупреждение становится понятным.
Возможно, полнота будет проверяться для Discriminated Unions которые может быть добавят в следующие версии C#, но пока так.
Можно :) Но в статье описан подход, который не зависит ни от LINQ, ни от Entity Framework и позволяет писать запросы к базе данных в наиболее близком к оригинальному SQL виде. Недаром я упомянул LEFT JOIN, для реализации которого я всегда лезу в гугл. Дело в том, что LINQ создан в первую очередь для работы с объектами (LINQ to Objects), и с реляционными базами данных в нем возникают типичные для ORM проблемы (тут я могу порекомендовать статью хабре за авторством maslyaev).
del (ошибся веткой)
Нет, ORM он не использовал. Изначально все было сделано на хранимых процедурах.
Тут вопрос какому именно движку. Помимо MS SQL, это могут быть Postgres SQL, My SQL и т. д. и как они себя поведут я не знаю. Но вообще идея в том, чтобы дать разработчику максимум контроля над запросом, и если нужен UNION, то будет UNION. Если очень надо, то можно написать кастомный модификатор финального AST.
Вполне допускаю, что можно, но свое решение, изначально заточенное под динамику, как-то ближе =) Да и не знали тогда про Linq2Db. Такие статьи (и комментарии к ним) как раз и помогают другим подобрать наиболее адекватное решение типовым, в общем-то, задачам.
Ну так задача поставленная в самом начале статьи как бы и подразумевает полную динамику. В реальности это выглядело как: UI контрол создавал AST в JSON который десериализвался в AST булевских выражений который уже вставлялся в некий запрос. Кроме того была проверка на "вычисляемые" столбцы и если они в фильтре были, то добавлялись те или иные вьюхи или Derived Tables. Некоторые предикаты заменялись на выражения вида "EXISTS(SELECT 1 FROM...)". А еще была фильтрация и сортировка по полностью динамическим полям, которые пользователи сами добавляли (как в JIRA)
Опять же, это все создавалось не сразу, а по мере развития функциональности и по началу вообще работало в связке с хранимыми процедурами… какой уж там LINQ ))
Если вы про набор типов описанный в статье, то он сильно урезан для упрощения изложения, более-менее реальный будет выглядеть намного сложнее.
Касательно отдельного типа для столбцов, то он имеет смысл, поскольку в разных диалектах SQL (a SqExpress так же работает и с Postgres SQL) правила записи столбцов будут разными и очень удобно иметь для них отдельный тип, так как кодо-генерацию можно отдельно реализовать для каждого диалекта (условно разная имплементация метода VisitColumnName ).
Да, я потом в итоге её нашел. Хорошая библиотека, но это все-таки LINQ) Мне же хотелось чего-то совсем близкого к оригинальному SQL. К тому же с кастомым синтаксическим деревом можно делать пост-модификации и различные экспорты/импорты, что удобно для передачи по сети или сохранения в базе данных
Конечная задача это как можно ближе повторить синтаксис SQL, и тут без столбцов уже не обойтись (Для SELECT, ORDER BY, …JOIN и т. д.).
Возможно, вы просто с каждым годом все больше об это задумываетесь, поэтому вам кажется, что ситуация ухудшается. На самом деле все население Земли активно стареет и воротить нос от возрастных кандидатов все сложнее просто по причине объективного отсутствия молодежи. Судя по этому графику на двух тридцатилетних приходится (приблизительно) один двадцатилетний, поэтому конкуренция на рынке труда в ближайшем будущем будет происходить, в основном, между теми, кому сейчас между 30 и 40 (пока не подрастут те, кому сейчас 0-10 лет)
Я говорил не про типизацию как таковую, а про то, что все проверки типов можно было бы полностью выполнить на этапе компиляции.
С ограничением мутабельности становится возможным отследить моменты, когда выделенная память может быть освобождена и освобождать ее сразу же, делая выполнение программы более предсказуемым (без внезапных остановок на сбор мусора).