Считается ли это анти-паттерном для жесткого кодирования SQL в приложение, подобное этому:
public List<int> getPersonIDs()
{
List<int> listPersonIDs = new List<int>();
using (SqlConnection connection = new SqlConnection(
ConfigurationManager.ConnectionStrings["Connection"].ConnectionString))
using (SqlCommand command = new SqlCommand())
{
command.CommandText = "select id from Person";
command.Connection = connection;
connection.Open();
SqlDataReader datareader = command.ExecuteReader();
while (datareader.Read())
{
listPersonIDs.Add(Convert.ToInt32(datareader["ID"]));
}
}
return listPersonIDs;
}
У меня обычно был бы слой репозитория и т. Д., Но я исключил его в приведенном выше коде для простоты.
Недавно я получил отзыв от коллеги, который пожаловался, что SQL написан в исходном коде. У меня не было возможности спросить, почему, и сейчас он уехал на две недели (может, больше). Я предполагаю, что он имел в виду либо:
- Используйте LINQ
или - Используйте хранимые процедуры для SQL
Я прав? Считается ли это анти-паттерном для написания SQL в исходном коде? Мы небольшая команда, работающая над этим проектом. Я думаю, что преимущество хранимых процедур заключается в том, что разработчики SQL могут участвовать в процессе разработки (написание хранимых процедур и т. Д.).
Изменить Следующая ссылка рассказывает о жестко закодированных инструкциях SQL: https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/hard-coded-sql-statements . Есть ли какая-то польза от подготовки оператора SQL?
Ответы:
Вы исключили важную часть для простоты. Хранилище - это уровень абстракции для постоянства. Мы выделяем постоянство в его собственный слой, чтобы при необходимости мы могли легче менять технологию сохранения. Следовательно, наличие SQL вне персистентного слоя полностью мешает созданию отдельного персистентного слоя.
В результате: SQL подходит для уровня персистентности, характерного для технологии SQL (например, SQL подходит для a,
SQLCustomerRepository
но не для aMongoCustomerRepository
). За пределами персистентного уровня SQL нарушает вашу абстракцию и, таким образом , считается очень плохой практикой (для меня).Что касается таких инструментов, как LINQ или JPQL: они могут просто абстрагировать разновидности SQL. Наличие LINQ-кода или JPQL-запросов вне репозитория нарушает абстракцию персистентности так же, как необработанный SQL.
Другим огромным преимуществом отдельного уровня персистентности является то, что он позволяет вам тестировать код бизнес-логики без необходимости настраивать сервер БД.
Вы получаете быстрый профиль памяти, быстрые модульные тесты с воспроизводимыми результатами на всех платформах, которые поддерживает ваш язык.
В архитектуре MVC + Service это простая задача по моделированию экземпляра репозитория, созданию в памяти некоторых фиктивных данных и определению того, что репозиторий должен возвращать эти фиктивные данные при вызове определенного метода получения. Затем вы можете определить тестовые данные на единицу теста и не беспокоиться о последующей очистке БД.
Тестирование записи в БД так же просто: убедитесь, что были вызваны соответствующие методы обновления на уровне персистентности, и подтвердите, что объекты были в правильном состоянии, когда это произошло.
источник
Сегодня большинство стандартных бизнес-приложений используют разные уровни с разными обязанностями. Однако, какие слои вы используете для своего приложения, и какой слой несет ответственность за вас и вашу команду. Прежде чем вы сможете принять решение о правильности или неправильности размещения SQL непосредственно в функции, которую вы нам показали, вам необходимо знать
какой слой в вашем приложении несет ответственность
из какого слоя функция выше
Для этого не существует универсального решения. В некоторых приложениях разработчики предпочитают использовать среду ORM и позволяют среде генерировать весь SQL. В некоторых приложениях разработчики предпочитают хранить такой SQL исключительно в хранимых процедурах. Для некоторых приложений существует рукописный уровень персистентности (или хранилища), в котором находится SQL, а для других приложений при определенных обстоятельствах можно определить исключения из строгого размещения SQL в этом уровне постоянства.
Так что вам нужно подумать: какие уровни вы хотите или нуждаетесь в вашем конкретном приложении, и как вы хотите, чтобы обязанности? Вы написали «У меня обычно был бы слой репозитория» , но какие именно обязанности вы хотите иметь в этом слое, и какие обязанности вы хотите разместить в другом месте? Ответьте сначала, а затем вы можете ответить на свой вопрос самостоятельно.
источник
Марстато дает хороший ответ, но я хотел бы добавить еще один комментарий.
SQL в исходном коде НЕ является антишаблоном, но может вызвать проблемы. Я могу вспомнить, когда вам приходилось помещать SQL-запросы в свойства компонентов, добавленных в каждую форму. Это делало вещи действительно ужасно быстрыми, и вам приходилось прыгать через обручи, чтобы найти запросы. Я стал активным сторонником максимально возможной централизации доступа к базе данных в рамках ограничений языков, с которыми я работал. Возможно, ваш коллега вспоминает эти темные дни.
Теперь в некоторых комментариях говорится о блокировке поставщиков, как будто это автоматически плохо. Это не так. Если я подписать чек на шестизначную каждый год , чтобы использовать Oracle, вы можете держать пари , что я хочу , любое приложение , осуществляющее доступ к этой базе данных , чтобы использовать дополнительный синтаксис Oracle надлежащимно в полной мере. Я не буду счастлив, если моя блестящая база данных будет искажена кодерами, которые плохо пишут ванильный ANSI SQL, когда есть «способ Oracle» для написания SQL, который не наносит вред базе данных. Да, менять базы данных будет сложнее, но я видел, что это происходило на крупных клиентских сайтах только пару раз за 20 лет, и один из этих случаев был перенесен из DB2 -> Oracle, потому что мэйнфрейм, в котором размещалась DB2, устарел и выводится из эксплуатации , Да, это привязка к поставщику, но для корпоративных клиентов на самом деле желательно заплатить за дорогостоящую СУБД, такую как Oracle или Microsoft SQL Server, а затем использовать ее в полной мере. В качестве комфортного одеяла у вас есть соглашение о поддержке. Если я плачу за базу данных с богатой реализацией хранимых процедур,
Это приводит к следующему пункту: если вы пишете приложение, которое обращается к базе данных SQL, вы должны изучать SQL так же, как и другой язык, и под изучением я имею в виду и оптимизацию запросов; Я разозлюсь на вас, если вы напишите код генерации SQL, который очищает кэш SQL с помощью множества идентичных запросов, когда вы могли бы использовать один умно параметризованный запрос.
Никаких оправданий, никаких сокрытий за стеной Hibernate. ORMs плохо используется , может действительно нанести вред производительности приложений. Я помню, как видел вопрос о переполнении стека несколько лет назад, например:
Как насчет «ОБНОВИТЬ таблицу SET field1 = где field2 - True, а Field3> 100». Создание и удаление 250 000 объектов вполне может стать вашей проблемой ...
то есть игнорировать Hibernate, когда он не подходит для использования. Понять базу данных.
Итак, в итоге, встраивание SQL в код может быть плохой практикой, но есть гораздо худшие вещи, которые вы можете в конечном итоге делать, пытаясь избежать встраивания SQL.
источник
Да, жесткое кодирование строк SQL в код приложения, как правило, является анти-паттерном.
Давайте попробуем отбросить допуск, который мы разработали за годы, увидев это в рабочем коде. Смешивание совершенно разных языков с разным синтаксисом в одном файле, как правило, нежелательно. Это отличается от языков шаблонов, таких как Razor, которые предназначены для придания контекстуального значения нескольким языкам. Как упоминает Сава Б. в комментарии ниже, SQL в вашем C # или другом языке приложения (Python, C ++ и т. Д.) Является строкой, как и любой другой, и является семантически бессмысленным. То же самое относится к смешиванию более чем одного языка в большинстве случаев, хотя, очевидно, существуют ситуации, когда это допустимо, например, встроенная сборка в C, небольшие и понятные фрагменты CSS в HTML (отмечая, что CSS предназначен для смешивания с HTML ), и другие.
(Роберт К. Мартин о смешивании языков, Чистый код , глава 17, «Запахи кода и эвристика», стр. 288)
В этом ответе я сосредоточусь на SQL (как указано в вопросе). Следующие проблемы могут возникать при хранении SQL в виде набора разрозненных строк:
@
Префикс C # делает это проще, но я видел много кода, который цитирует каждую строку SQL и экранирует символы новой строки."SELECT col1, col2...colN"\ "FROM painfulExample"\ "WHERE maintainability IS NULL"\ "AND modification.effort > @necessary"\
Полные ORM (объектно-реляционные сопоставители, такие как Entity Framework или Hibernate) могут исключить случайный навороченный SQL в коде приложения. Мое использование SQL и файлов ресурсов - всего лишь пример. ORM, вспомогательные классы и т. Д. Могут помочь в достижении цели более чистого кода.
Как сказал Кевин в предыдущем ответе, SQL в коде может быть приемлемым для небольших проектов, но большие проекты начинаются как небольшие проекты, и вероятность того, что большинство команд вернутся и сделают это правильно, часто обратно пропорциональна размеру кода.
Есть много простых способов сохранить SQL в проекте. Один из методов, которые я часто использую, - это поместить каждый оператор SQL в файл ресурсов Visual Studio, обычно называемый «sql». Текстовый файл, документ JSON или другой источник данных могут быть разумными в зависимости от ваших инструментов. В некоторых случаях отдельный класс, предназначенный для включения строк SQL, может быть лучшим вариантом, но может иметь некоторые из проблем, описанных выше.
Пример SQL: что выглядит более элегантно ?:
становится
SQL всегда находится в легко определяемом файле или сгруппированном наборе файлов, каждый с описательным именем, которое описывает, что он делает, а не как это делает, каждый с местом для комментария, который не будет прерывать поток кода приложения. :
Этот простой метод выполняет отдельный запрос. По моему опыту, выгода масштабируется, поскольку использование «иностранного языка» становится все более изощренным. Мое использование файла ресурсов является лишь примером. Различные методы могут быть более подходящими в зависимости от языка (в данном случае SQL) и платформы.
Этот и другие методы разрешают приведенный выше список следующим образом:
SQL
.return SqlResource.DoTheThing;
Правда, эти реализации могут пропускать ресурс и содержать SQL в строке, но некоторые (не все) проблемы все равно будут появляться.sql.GetOrdersForAccount
а не более тупыеSELECT ... FROM ... WHERE...
Создание SQL в ресурсе происходит быстро, поэтому нет смысла смешивать использование ресурсов с SQL-кодом.
Независимо от выбранного метода, я обнаружил, что смешивание языков обычно снижает качество кода. Я надеюсь, что некоторые проблемы и решения, описанные здесь, помогут разработчикам устранить этот запах кода при необходимости.
источник
По-разному. Существует ряд различных подходов, которые могут работать:
Как это часто бывает, если ваш проект уже выбрал один из этих методов, вы должны быть согласны с остальной частью проекта.
(1) и (3) оба относительно хороши в поддержании независимости между логикой приложения и базой данных, в том смысле, что приложение будет продолжать компилировать и проходить базовые тесты дыма, если вы замените базу данных другим поставщиком. Однако большинство поставщиков не полностью соответствуют стандарту SQL, поэтому замена любого поставщика на другого может потребовать тщательного тестирования и поиска ошибок независимо от того, какую технику вы используете. Я скептически отношусь к тому, что это так важно, как это делают люди. Изменение баз данных в основном является последним средством, когда вы не можете получить текущую базу данных, соответствующую вашим потребностям. Если это произойдет, вы, вероятно, выбрали базу данных плохо.
Выбор между (1) и (3) в основном зависит от того, насколько вам нравятся ORM. На мой взгляд они перегружены. Они являются плохим представлением реляционной модели данных, потому что строки не имеют идентичности так, как объекты имеют идентичность. Вы можете столкнуться с болевыми точками вокруг уникальных ограничений, объединений, и у вас могут возникнуть трудности при выражении некоторых более сложных запросов в зависимости от мощности ORM. С другой стороны, для (1), вероятно, потребуется значительно больше кода, чем для ORM.
(2) редко, по моему опыту. Проблема заключается в том, что многие магазины запрещают SWE напрямую изменять схему базы данных (потому что «это работа администратора баз данных»). Это не обязательно плохая вещь сама по себе; Изменения схемы имеют значительный потенциал для разрушения, и, возможно, их необходимо развернуть осторожно. Однако, чтобы (2) работал, SWE должны, по крайней мере, иметь возможность вводить новые представления и модифицировать запросы поддержки существующих представлений с минимальной бюрократией или без нее. Если это не так на вашем рабочем месте, (2), вероятно, не будет работать для вас.
С другой стороны, если вы можете заставить (2) работать, это намного лучше, чем в большинстве других решений, потому что он сохраняет реляционную логику в SQL вместо кода приложения. В отличие от языков программирования общего назначения, SQL специально разработан для реляционной модели данных и, соответственно, лучше выражает сложные запросы и преобразования данных. Представления также могут быть перенесены вместе с остальной частью вашей схемы при изменении баз данных, но они сделают такие перемещения более сложными.
Для чтения хранимые процедуры - это просто более клёвая версия (2). Я не рекомендую их в этом качестве, но вы все равно можете использовать их для записи, если ваша база данных не поддерживает обновляемые представления или если вам нужно сделать что-то более сложное, чем вставка или обновление по одной строке за раз (например, транзакции, чтение, запись и т. д.). Вы можете связать свою хранимую процедуру с представлением, используя триггер (т.е.
CREATE TRIGGER trigger_name INSTEAD OF INSERT ON view_name FOR EACH ROW EXECUTE PROCEDURE procedure_name;
), но мнения значительно различаются относительно того, действительно ли это хорошая идея. Сторонники скажут вам, что он сохраняет SQL, который ваше приложение выполняет максимально просто. Недоброжелатели скажут вам, что это неприемлемый уровень "магии" и что вам следует просто выполнить процедуру прямо из вашего приложения. Я бы сказал , что это лучшая идея , если хранимая процедура выглядит или действует много какINSERT
,UPDATE
илиDELETE
, и хуже идея , если он делает что - то другое. В конечном итоге вам придется решить для себя, какой стиль имеет больше смысла.(4) не является решением. Это может стоить того для небольших проектов или для крупных, которые лишь время от времени взаимодействуют с базой данных. Но для проектов с большим количеством SQL-кода это не очень хорошая идея, потому что у вас могут быть дубликаты или варианты одного и того же запроса, случайно разбросанные по вашему приложению, что мешает удобочитаемости и рефакторингу.
источник
Не обязательно. Если вы прочитаете все комментарии здесь, вы найдете действительные аргументы для жесткого кодирования операторов SQL в исходном коде.
Проблема заключается в том, где вы размещаете заявления . Если вы размещаете операторы SQL по всему проекту, везде, вы, вероятно, будете игнорировать некоторые из принципов SOLID, которым мы обычно стремимся следовать.
Мы не можем сказать, что он имел в виду. Тем не менее, мы можем догадаться. Например, первое, что приходит мне в голову - это привязка к поставщику . Жесткое кодирование операторов SQL может привести к тому, что ваше приложение будет тесно связано с ядром БД. Например, с использованием определенных функций поставщика, которые не соответствуют ANSI.
Это не обязательно плохо и не плохо. Я просто указываю на факт.
Игнорирование принципов SOLID и блокировок поставщиков может привести к неблагоприятным последствиям, которые вы можете игнорировать. Вот почему обычно хорошо сидеть с командой и выставлять свои сомнения.
Я думаю, что это не имеет ничего общего с преимуществами хранимых процедур. Более того, если ваш коллега не любит жестко запрограммированный SQL-код, он, вероятно, тоже не понравится переносу бизнеса на хранимые процедуры.
Да. Пост перечисляет преимущества подготовленных заявлений . Это своего рода шаблонизатор SQL. Более безопасный, чем конкатенация строк. Но пост не поощряет вас идти по этому пути и не подтверждает, что вы правы. Это просто объясняет, как мы можем использовать жесткий код SQL безопасным и эффективным способом.
Подводя итог, попробуйте сначала спросить вашего коллегу. Отправьте письмо, позвоните ему, ... Независимо от того, отвечает он или нет, посидите с командой и раскройте ваши сомнения. Найдите решение, которое наилучшим образом соответствует вашим требованиям. Не делайте ложных предположений, основываясь на том, что вы там прочитали.
источник
select GETDATE(), ....
GETDATE () - это функция даты SqlServer. В других движках функция имеет другое имя, другое выражение, ...Я думаю, что это плохая практика, да. Другие отмечают преимущества сохранения всего кода доступа к данным на отдельном уровне. Вам не нужно искать его, его легче оптимизировать и тестировать ... Но даже на этом уровне у вас есть несколько вариантов: использовать ORM, использовать sprocs или встраивать SQL-запросы в виде строк. Я бы сказал, что строки SQL-запросов являются наихудшим вариантом.
С ORM разработка становится намного проще и менее подвержена ошибкам. С EF вы определяете свою схему, просто создав классы модели (вам все равно нужно было бы создать эти классы). Запросы с LINQ очень просты - вам часто не хватает 2 строк c #, в которых вам в противном случае нужно было бы писать и поддерживать sproc. ИМО, это имеет огромное преимущество, когда речь идет о производительности и удобстве обслуживания - меньше кода, меньше проблем. Но производительность снижается, даже если вы знаете, что делаете.
Sprocs (или функции) являются другой опцией. Здесь вы пишете свои SQL-запросы вручную. Но, по крайней мере, вы получаете гарантию, что они правильные. Если вы работаете в .NET, Visual Studio даже выдаст ошибки компилятора, если SQL неверен. Это здорово. Если вы измените или удалите какой-либо столбец, и некоторые ваши запросы станут недействительными, по крайней мере, вы, вероятно, узнаете об этом во время компиляции. Также проще поддерживать sprocs в их собственных файлах - вы, вероятно, получите подсветку синтаксиса, autocomplete..etc.
Если ваши запросы хранятся в виде sprocs, вы также можете изменить их без повторной компиляции и повторного развертывания приложения. Если вы заметили, что что-то в вашем SQL не работает, администратор базы данных может просто исправить это, не имея доступа к коду вашего приложения. В общем, если ваши запросы в строковых литералах, вы не можете выполнять свою работу почти так же легко.
SQL-запросы в виде строковых литералов также сделают ваш код доступа к данным C # менее читабельным.
Как правило, магические константы плохие. Это включает строковые литералы.
источник
Это не анти-паттерн (этот ответ опасно близок к мнению). Но форматирование кода важно, и строка SQL должна быть отформатирована так, чтобы она была четко отделена от кода, который ее использует. Например
источник
Я не могу сказать вам, что имел в виду ваш коллега. Но я могу ответить на это:
YES . Использование подготовленных операторов со связанными переменными является рекомендуемой защитой от внедрения SQL , которая остается самой большой угрозой безопасности для веб-приложений в течение более десяти лет . Эта атака настолько распространена, что была показана в веб-комиксах почти десять лет назад, и настало время развернуть эффективную защиту.
... и самая эффективная защита от плохой конкатенации строк запросов - это не представление запросов в первую очередь как строки, а использование API запросов с типом безопасности для построения запросов. Например, вот как я написал бы ваш запрос в Java с QueryDSL:
Как видите, здесь нет ни одного строкового литерала, что делает невозможным использование SQL-инъекции. Более того, при написании этого кода у меня было завершение кода, и моя среда IDE может изменить его, если я решу когда-либо переименовать столбец идентификатора. Кроме того, я легко могу найти все запросы для таблицы person, спросив мою IDE, где
person
происходит доступ к переменной. О, и вы заметили, что это немного короче и проще для глаз, чем ваш код?Я могу только предположить, что нечто подобное доступно и для C #. Например, я слышу замечательные вещи о LINQ.
Таким образом, представление запросов в виде строк SQL затрудняет для IDE значительную помощь в написании и рефакторинге запросов, откладывает обнаружение синтаксических и типовых ошибок со времени компиляции до времени выполнения и является одной из причин уязвимостей внедрения SQL [1]. Так что да, есть веские причины, по которым можно не хотеть строки SQL в исходном коде.
[1]: Да, правильное использование подготовленных операторов также предотвращает внедрение SQL. Но то, что другой ответ в этом потоке был уязвим для внедрения SQL, пока комментатор не указал на это, не вселяет уверенности в том, что младшие программисты правильно используют их при необходимости ...
источник
Здесь много отличных ответов. Помимо того, что уже было сказано, я хотел бы добавить еще одну вещь.
Я не считаю использование встроенного SQL антишаблоном. Однако я считаю анти-паттерном то, что каждый программист делает свое дело. Вы должны принять решение как единая команда. Если все используют linq, то вы используете linq, если все используют представления и хранимые процедуры, то вы делаете это.
Всегда держите непредвзято и не поддавайтесь на догмы. Если ваш магазин "linq", вы используете его. За исключением одного места, где linq абсолютно не работает. (Но вы не должны 99,9% времени.)
Так что я хотел бы исследовать код в базе кода и проверить, как работают ваши коллеги.
источник
Код выглядит как слой, взаимодействующий с базой данных, который находится между базой данных и остальной частью кода. Если это действительно так, это не анти-паттерн.
Этот метод является частью уровня, даже если OP думает иначе (простой доступ к базе данных, без смешения с чем-либо еще). Возможно, он просто плохо размещен внутри кода. Этот метод относится к отдельному классу, «слой взаимодействия с базой данных». Было бы против паттерна размещать его внутри обычного класса рядом с методами, которые реализуют действия, не связанные с базой данных.
Анти-паттерн будет вызывать SQL из другого случайного места кода. Вам нужно определить слой между базой данных и остальной частью кода, но это не значит, что вы обязательно должны использовать какой-то популярный инструмент, который может помочь вам в этом.
источник
На мой взгляд, нет, это не анти-паттерн, но вы столкнетесь с интервалом форматирования строк, особенно для больших запросов, которые не помещаются в одну строку.
Еще один плюс в том, что с динамическими запросами, которые должны создаваться в соответствии с определенными условиями, становится намного сложнее.
Я предлагаю использовать конструктор запросов SQL
источник
Для многих современных организаций с конвейерами быстрого развертывания, где изменения кода могут быть скомпилированы, протестированы и отправлены в производство за считанные минуты, имеет смысл встраивать операторы SQL в код приложения. Это не обязательно анти-паттерн в зависимости от того, как вы это делаете.
В настоящее время допустимым случаем использования хранимых процедур является организация, в которой существует много процессов, связанных с производственными развертываниями, которые могут включать в себя недели циклов QA и т. Д. В этом сценарии команда администраторов баз данных может решить проблемы с производительностью, возникающие только после первоначального производственного развертывания, путем оптимизации запросы в хранимых процедурах.
Если вы не находитесь в такой ситуации, я рекомендую встраивать ваш SQL-код в код приложения. Прочитайте другие ответы в этой теме, чтобы узнать, как лучше всего это сделать.
Оригинальный текст ниже для контекста
Основным преимуществом хранимых процедур является то, что вы можете оптимизировать производительность базы данных без перекомпиляции и повторного развертывания приложения.
Во многих организациях код может быть запущен в производство только после цикла QA и т. Д., Который может занять дни или недели. Если в вашей системе возникает проблема с производительностью базы данных из-за изменения шаблонов использования или увеличения размеров таблиц и т. Д., Тогда может быть довольно сложно отследить проблему с помощью жестко закодированных запросов, тогда как в базе данных будут инструменты / отчеты и т. Д., Которые будут определять хранимые процедуры с ошибками. , С помощью хранимых процедур решения могут быть протестированы / протестированы на производстве, и проблема может быть быстро устранена без необходимости установки новой версии прикладного программного обеспечения.
Если эта проблема не важна в вашей системе, то вполне допустимым решением является жесткое программирование операторов SQL в приложении.
источник