Да, жесткое кодирование строк SQL в код приложения, как правило, является анти-паттерном.
Давайте попробуем отбросить допуск, который мы разработали за годы, увидев это в рабочем коде. Смешивание совершенно разных языков с разным синтаксисом в одном файле, как правило, нежелательно. Это отличается от языков шаблонов, таких как Razor, которые предназначены для придания контекстуального значения нескольким языкам. Как упоминает Сава Б. в комментарии ниже, SQL в вашем C # или другом языке приложения (Python, C ++ и т. Д.) Является строкой, как и любой другой, и является семантически бессмысленным. То же самое относится к смешиванию более чем одного языка в большинстве случаев, хотя, очевидно, существуют ситуации, когда это допустимо, например, встроенная сборка в C, небольшие и понятные фрагменты CSS в HTML (отмечая, что CSS предназначен для смешивания с HTML ), и другие.
(Роберт К. Мартин о смешивании языков, Чистый код , глава 17, «Запахи кода и эвристика», стр. 288)
В этом ответе я сосредоточусь на SQL (как указано в вопросе). Следующие проблемы могут возникать при хранении SQL в виде набора разрозненных строк:
- Логика базы данных трудно найти. Что вы ищете, чтобы найти все ваши операторы SQL? Строки с «SELECT», «UPDATE», «MERGE» и т. Д.?
- Рефакторинг использования одного и того же или похожего SQL становится затруднительным.
- Добавить поддержку для других баз данных сложно. Как можно это сделать? Добавить операторы if..then для каждой базы данных и сохранить все запросы в виде строк в методе?
- Разработчики читают утверждение на другом языке и отвлекаются от смещения фокуса с цели метода на детали реализации метода (как и откуда данные извлекаются).
- Хотя однострочники могут быть не слишком большой проблемой, встроенные строки SQL начинают разваливаться по мере усложнения операторов. Что вы делаете с 113-строчным оператором? Поместите все 113 строк в ваш метод?
- Как разработчик эффективно перемещает запросы назад и вперед между своим редактором SQL (SSMS, SQL Developer и т. Д.) И их исходным кодом?
@
Префикс C # делает это проще, но я видел много кода, который цитирует каждую строку SQL и экранирует символы новой строки.
"SELECT col1, col2...colN"\
"FROM painfulExample"\
"WHERE maintainability IS NULL"\
"AND modification.effort > @necessary"\
- Символы отступа, используемые для выравнивания SQL с окружающим кодом приложения, передаются по сети при каждом выполнении. Это, вероятно, незначительно для небольших приложений, но может увеличиваться по мере роста использования программного обеспечения.
Полные ORM (объектно-реляционные сопоставители, такие как Entity Framework или Hibernate) могут исключить случайный навороченный SQL в коде приложения. Мое использование SQL и файлов ресурсов - всего лишь пример. ORM, вспомогательные классы и т. Д. Могут помочь в достижении цели более чистого кода.
Как сказал Кевин в предыдущем ответе, SQL в коде может быть приемлемым для небольших проектов, но большие проекты начинаются как небольшие проекты, и вероятность того, что большинство команд вернутся и сделают это правильно, часто обратно пропорциональна размеру кода.
Есть много простых способов сохранить SQL в проекте. Один из методов, которые я часто использую, - это поместить каждый оператор SQL в файл ресурсов Visual Studio, обычно называемый «sql». Текстовый файл, документ JSON или другой источник данных могут быть разумными в зависимости от ваших инструментов. В некоторых случаях отдельный класс, предназначенный для включения строк SQL, может быть лучшим вариантом, но может иметь некоторые из проблем, описанных выше.
Пример SQL: что выглядит более элегантно ?:
using(DbConnection connection = Database.SystemConnection()) {
var eyesoreSql = @"
SELECT
Viewable.ViewId,
Viewable.HelpText,
PageSize.Width,
PageSize.Height,
Layout.CSSClass,
PaginationType.GroupingText
FROM Viewable
LEFT JOIN PageSize
ON PageSize.Id = Viewable.PageSizeId
LEFT JOIN Layout
ON Layout.Id = Viewable.LayoutId
LEFT JOIN Theme
ON Theme.Id = Viewable.ThemeId
LEFT JOIN PaginationType
ON PaginationType.Id = Viewable.PaginationTypeId
LEFT JOIN PaginationMenu
ON PaginationMenu.Id = Viewable.PaginationMenuId
WHERE Viewable.Id = @Id
";
var results = connection.Query<int>(eyesoreSql, new { Id });
}
становится
using(DbConnection connection = Database.SystemConnection()) {
var results = connection.Query<int>(sql.GetViewable, new { Id });
}
SQL всегда находится в легко определяемом файле или сгруппированном наборе файлов, каждый с описательным именем, которое описывает, что он делает, а не как это делает, каждый с местом для комментария, который не будет прерывать поток кода приложения. :
Этот простой метод выполняет отдельный запрос. По моему опыту, выгода масштабируется, поскольку использование «иностранного языка» становится все более изощренным.
Мое использование файла ресурсов является лишь примером. Различные методы могут быть более подходящими в зависимости от языка (в данном случае SQL) и платформы.
Этот и другие методы разрешают приведенный выше список следующим образом:
- Код базы данных легко найти, потому что он уже централизован. В более крупных проектах группируйте Like-SQL в отдельные файлы, возможно, в папке с именем
SQL
.
- Поддержка второй, третьей и т. Д. Баз данных стала проще. Создайте интерфейс (или другую языковую абстракцию), который возвращает уникальные операторы каждой базы данных. Реализация для каждой базы данных становится немного больше, чем операторы, похожие на:
return SqlResource.DoTheThing;
Правда, эти реализации могут пропускать ресурс и содержать SQL в строке, но некоторые (не все) проблемы все равно будут появляться.
- Рефакторинг прост - просто используйте один и тот же ресурс. Вы можете даже использовать одну и ту же запись ресурса для разных систем СУБД большую часть времени с помощью нескольких операторов формата. Я делаю это часто.
- Использование дополнительного языка может использовать описательные имена, например,
sql.GetOrdersForAccount
а не более тупыеSELECT ... FROM ... WHERE...
- Операторы SQL вызываются одной строкой независимо от их размера и сложности.
- SQL можно копировать и вставлять между инструментами базы данных, такими как SSMS и SQL Developer, без изменения или тщательного копирования. Нет кавычек. Нет обратной косой черты. В частности, в случае редактора ресурсов Visual Studio одним щелчком мыши выделяется оператор SQL. CTRL + C, а затем вставьте его в редактор SQL.
Создание SQL в ресурсе происходит быстро, поэтому нет смысла смешивать использование ресурсов с SQL-кодом.
Независимо от выбранного метода, я обнаружил, что смешивание языков обычно снижает качество кода. Я надеюсь, что некоторые проблемы и решения, описанные здесь, помогут разработчикам устранить этот запах кода при необходимости.