Считается ли это анти-паттерном для написания SQL в исходном коде?


87

Считается ли это анти-паттерном для жесткого кодирования 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 написан в исходном коде. У меня не было возможности спросить, почему, и сейчас он уехал на две недели (может, больше). Я предполагаю, что он имел в виду либо:

  1. Используйте LINQ
    или
  2. Используйте хранимые процедуры для SQL

Я прав? Считается ли это анти-паттерном для написания SQL в исходном коде? Мы небольшая команда, работающая над этим проектом. Я думаю, что преимущество хранимых процедур заключается в том, что разработчики SQL могут участвовать в процессе разработки (написание хранимых процедур и т. Д.).

Изменить Следующая ссылка рассказывает о жестко закодированных инструкциях SQL: https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/hard-coded-sql-statements . Есть ли какая-то польза от подготовки оператора SQL?


31
«Использовать Linq» и «Использовать хранимые процедуры» не являются причинами; они просто предложения. Подождите две недели и спросите его о причинах.
Роберт Харви

47
Сеть Stack Exchange использует микро-ORM под названием Dapper. Я думаю, что разумно сказать, что подавляющее большинство кода Dapper - это «жестко закодированный SQL» (более или менее). Так что если это плохая практика, то это плохая практика, принятая одним из самых известных веб-приложений на планете.
Роберт Харви

16
Чтобы ответить на ваш вопрос о репозиториях, жестко запрограммированный SQL остается жестко запрограммированным SQL, независимо от того, где вы его разместили. Разница в том, что хранилище дает вам возможность инкапсулировать жестко запрограммированный SQL. Это уровень абстракции, который скрывает детали SQL от остальной части программы.
Роберт Харви

26
Нет, SQL в коде не является анти-паттерном. Но это очень много кода для простого запроса SQL.
GrandmasterB

45
Существует разница между «в исходном коде» и «распространяться по всему исходному коду»
до

Ответы:


112

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

В результате: SQL подходит для уровня персистентности, характерного для технологии SQL (например, SQL подходит для a, SQLCustomerRepositoryно не для a MongoCustomerRepository). За пределами персистентного уровня SQL нарушает вашу абстракцию и, таким образом , считается очень плохой практикой (для меня).

Что касается таких инструментов, как LINQ или JPQL: они могут просто абстрагировать разновидности SQL. Наличие LINQ-кода или JPQL-запросов вне репозитория нарушает абстракцию персистентности так же, как необработанный SQL.


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

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

В архитектуре MVC + Service это простая задача по моделированию экземпляра репозитория, созданию в памяти некоторых фиктивных данных и определению того, что репозиторий должен возвращать эти фиктивные данные при вызове определенного метода получения. Затем вы можете определить тестовые данные на единицу теста и не беспокоиться о последующей очистке БД.

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


80
Идея о том, что «мы можем изменить технологию постоянства», нереальна и не нужна в подавляющем большинстве реальных проектов. SQL / реляционная БД - это совершенно другой зверь, чем БД NoSQL, такой как MongoDB. Смотрите эту подробную статью об этом. Самое большее, проект, который начал использовать NoSQL, в конечном итоге осознал свою ошибку и затем переключился бы на СУБД. По моему опыту, прямое использование объектно-ориентированного языка запросов (такого как JPA-QL) из бизнес-кода дает наилучшие результаты.
Рожерио

6
Что, если существует очень малая вероятность того, что уровень постоянства будет изменен? Что делать, если при смене персистентного слоя нет сопоставления один к одному? В чем преимущество дополнительного уровня абстракции?
pllee

19
@ Rogério Миграция из хранилища NoSQL в RDBMS или наоборот, как вы говорите, вероятно, нереалистична (при условии, что выбор технологии был уместным с самого начала). Тем не менее, я принимал участие в ряде реальных проектов, в которых мы мигрировали из одной РСУБД в другую; в этом сценарии инкапсулированный персистентный уровень определенно является преимуществом.
Дэвид

6
«Идея, что« мы можем изменить постоянную технологию », нереальна и не нужна в подавляющем большинстве реальных проектов». Моя компания ранее писала код в соответствии с этим предположением. Нам пришлось провести рефакторинг всей кодовой базы для поддержки не одной, а трех совершенно разных систем хранения / запросов, и мы рассматриваем третью и четвертую. Хуже того, даже когда у нас была только одна база данных, отсутствие консолидации привело к всевозможным грехам кода.
NPSF3000

3
@ Rogério Вы знаете "подавляющее большинство реальных проектов"? В моей компании большинство приложений могут работать с одной из многих распространенных СУБД, и это очень полезно, так как в основном наши клиенты предъявляют нефункциональные требования в отношении этого.
BartoszKP

55

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

  • какой слой в вашем приложении несет ответственность

  • из какого слоя функция выше

Для этого не существует универсального решения. В некоторых приложениях разработчики предпочитают использовать среду ORM и позволяют среде генерировать весь SQL. В некоторых приложениях разработчики предпочитают хранить такой SQL исключительно в хранимых процедурах. Для некоторых приложений существует рукописный уровень персистентности (или хранилища), в котором находится SQL, а для других приложений при определенных обстоятельствах можно определить исключения из строгого размещения SQL в этом уровне постоянства.

Так что вам нужно подумать: какие уровни вы хотите или нуждаетесь в вашем конкретном приложении, и как вы хотите, чтобы обязанности? Вы написали «У меня обычно был бы слой репозитория» , но какие именно обязанности вы хотите иметь в этом слое, и какие обязанности вы хотите разместить в другом месте? Ответьте сначала, а затем вы можете ответить на свой вопрос самостоятельно.


1
Я отредактировал вопрос. Не уверен, что он проливает свет.
w0051977

18
@ w0051977: ссылка, которую вы разместили, ничего не говорит нам о вашем приложении, верно? Кажется, вы ищете какое-то простое, разумное правило, где размещать SQL или нет, которое подходит для каждого приложения. Здесь ничего нет. Это дизайнерское решение, которое вы должны принять в отношении вашего индивидуального приложения (возможно, вместе с вашей командой).
Док Браун

7
+1. В частности, я хотел бы отметить, что нет реальной разницы между SQL в методе и SQL в хранимой процедуре, с точки зрения того, насколько «жестко закодировано» что-то, или насколько многократно используемо, поддерживается и т. Д. Любая абстракция, которую вы можете сделать с помощью Процедура может быть выполнена с помощью метода. Конечно, процедуры могут быть полезны, но правило «мы не можем иметь SQL в методах, так что давайте все это поместим в процедуры», вероятно, приведет к значительным потерям с небольшой выгодой.

1
@ user82096 Я бы сказал, что, вообще говоря, размещение кода доступа к БД в SP было вредным почти для каждого проекта, который я видел. (Стек MS) Помимо фактического снижения производительности (кэши жесткого плана выполнения), обслуживание системы усложнилось из-за разбивки логики, а SP поддерживались другим набором людей, нежели разработчики логики приложения. Особенно в Azure, где изменения кода между слотами развертывания подобны секундам работы, но не-миграция схемы first-first / db-first между слотами - это небольшая операционная головная боль.
Сентинел

33

Марстато дает хороший ответ, но я хотел бы добавить еще один комментарий.

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

Теперь в некоторых комментариях говорится о блокировке поставщиков, как будто это автоматически плохо. Это не так. Если я подписать чек на шестизначную каждый год , чтобы использовать Oracle, вы можете держать пари , что я хочу , любое приложение , осуществляющее доступ к этой базе данных , чтобы использовать дополнительный синтаксис Oracle надлежащимно в полной мере. Я не буду счастлив, если моя блестящая база данных будет искажена кодерами, которые плохо пишут ванильный ANSI SQL, когда есть «способ Oracle» для написания SQL, который не наносит вред базе данных. Да, менять базы данных будет сложнее, но я видел, что это происходило на крупных клиентских сайтах только пару раз за 20 лет, и один из этих случаев был перенесен из DB2 -> Oracle, потому что мэйнфрейм, в котором размещалась DB2, устарел и выводится из эксплуатации , Да, это привязка к поставщику, но для корпоративных клиентов на самом деле желательно заплатить за дорогостоящую СУБД, такую ​​как Oracle или Microsoft SQL Server, а затем использовать ее в полной мере. В качестве комфортного одеяла у вас есть соглашение о поддержке. Если я плачу за базу данных с богатой реализацией хранимых процедур,

Это приводит к следующему пункту: если вы пишете приложение, которое обращается к базе данных SQL, вы должны изучать SQL так же, как и другой язык, и под изучением я имею в виду и оптимизацию запросов; Я разозлюсь на вас, если вы напишите код генерации SQL, который очищает кэш SQL с помощью множества идентичных запросов, когда вы могли бы использовать один умно параметризованный запрос.

Никаких оправданий, никаких сокрытий за стеной Hibernate. ORMs плохо используется , может действительно нанести вред производительности приложений. Я помню, как видел вопрос о переполнении стека несколько лет назад, например:

В Hibernate я перебираю 250 000 записей, проверяю значения пары свойств и обновляю объекты, которые соответствуют определенным условиям. Он работает немного медленно, что я могу сделать, чтобы ускорить его?

Как насчет «ОБНОВИТЬ таблицу SET field1 = где field2 - True, а Field3> 100». Создание и удаление 250 000 объектов вполне может стать вашей проблемой ...

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

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


5
Я не утверждал, что «привязка к поставщику» - это плохо. Я заявил, что идет с компромиссами. На сегодняшнем рынке с db в качестве xaaS такие старые контракты и лицензии для запуска собственной хост-базы данных будут встречаться все реже и реже. Сегодня довольно просто изменить базу данных с поставщика А на В по сравнению с тем, что было 10 лет назад. Если вы читаете комментарии, я не одобряю использование преимуществ любой функции хранения данных. Таким образом, легко поместить некоторые данные о конкретном поставщике в нечто (приложение), предназначенное для независимого поставщика. Мы стремимся следовать SOLiD до ​​конца, привязать код к одному хранилищу данных. Не большое дело
LAIV

2
Это отличный ответ. Часто правильный подход приводит к наименее плохой ситуации, если написан наихудший код. Худший возможный код ORM может быть намного хуже, чем худший встроенный SQL.
JWG

@Laiv хорошие моменты PaaS немного меняет игру - мне придется больше думать о последствиях.
Маккотл

3
@jwg Я бы сказал, что код ORM выше среднего хуже, чем встроенный SQL ниже среднего :)
mcottle

@macottle Предполагая, что встроенный SQL ниже среднего был написан на ppl выше среднего значения того, кто пишет ORM. В чем я очень сомневаюсь. Многие разработчики не способны писать левые соединения без проверки в первую очередь.
Laiv

14

Да, жесткое кодирование строк SQL в код приложения, как правило, является анти-паттерном.

Давайте попробуем отбросить допуск, который мы разработали за годы, увидев это в рабочем коде. Смешивание совершенно разных языков с разным синтаксисом в одном файле, как правило, нежелательно. Это отличается от языков шаблонов, таких как Razor, которые предназначены для придания контекстуального значения нескольким языкам. Как упоминает Сава Б. в комментарии ниже, SQL в вашем C # или другом языке приложения (Python, C ++ и т. Д.) Является строкой, как и любой другой, и является семантически бессмысленным. То же самое относится к смешиванию более чем одного языка в большинстве случаев, хотя, очевидно, существуют ситуации, когда это допустимо, например, встроенная сборка в C, небольшие и понятные фрагменты CSS в HTML (отмечая, что CSS предназначен для смешивания с HTML ), и другие.

Чистый код Роберта С. Мартина, стр.  288 (Роберт К. Мартин о смешивании языков, Чистый код , глава 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 в ресурсе

Этот простой метод выполняет отдельный запрос. По моему опыту, выгода масштабируется, поскольку использование «иностранного языка» становится все более изощренным. Мое использование файла ресурсов является лишь примером. Различные методы могут быть более подходящими в зависимости от языка (в данном случае SQL) и платформы.

Этот и другие методы разрешают приведенный выше список следующим образом:

  1. Код базы данных легко найти, потому что он уже централизован. В более крупных проектах группируйте Like-SQL в отдельные файлы, возможно, в папке с именем SQL.
  2. Поддержка второй, третьей и т. Д. Баз данных стала проще. Создайте интерфейс (или другую языковую абстракцию), который возвращает уникальные операторы каждой базы данных. Реализация для каждой базы данных становится немного больше, чем операторы, похожие на: return SqlResource.DoTheThing;Правда, эти реализации могут пропускать ресурс и содержать SQL в строке, но некоторые (не все) проблемы все равно будут появляться.
  3. Рефакторинг прост - просто используйте один и тот же ресурс. Вы можете даже использовать одну и ту же запись ресурса для разных систем СУБД большую часть времени с помощью нескольких операторов формата. Я делаю это часто.
  4. Использование дополнительного языка может использовать описательные имена, например, sql.GetOrdersForAccountа не более тупыеSELECT ... FROM ... WHERE...
  5. Операторы SQL вызываются одной строкой независимо от их размера и сложности.
  6. SQL можно копировать и вставлять между инструментами базы данных, такими как SSMS и SQL Developer, без изменения или тщательного копирования. Нет кавычек. Нет обратной косой черты. В частности, в случае редактора ресурсов Visual Studio одним щелчком мыши выделяется оператор SQL. CTRL + C, а затем вставьте его в редактор SQL.

Создание SQL в ресурсе происходит быстро, поэтому нет смысла смешивать использование ресурсов с SQL-кодом.

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


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

9
«Смешивание совершенно разных языков с разным синтаксисом в одном файле» Это ужасный аргумент. Это также относится к движку представления Razor, а также к HTML, CSS и Javascript. Люблю твои графические романы!
Ян Ньюсон

4
Это просто отталкивает сложность где-то еще. Да, ваш исходный код чище, за счет добавления косвенного направления, к которому теперь должен перейти разработчик в целях обслуживания. Настоящая причина, по которой вы бы это сделали, заключается в том, что каждый оператор SQL использовался в нескольких местах кода. Таким образом, он на самом деле ничем не отличается от любого другого обычного рефакторинга («Метод извлечения»).
Роберт Харви

3
Проще говоря, это не ответ на вопрос «что мне делать с моими строками SQL», а то, что это ответ на вопрос «что мне делать с любым набором достаточно сложных строк», вопрос, который Ваш ответ красноречиво обращается.
Роберт Харви

2
@RobertHarvey: Я уверен, что наш опыт отличается, но я сам нашел, что рассматриваемая абстракция является победой в большинстве случаев. Я, безусловно, уточню свои компромиссные абстракции, как это делалось в течение многих лет, и однажды я могу вернуть SQL в код. YMMV. :) Я думаю, что микросервисы могут быть хорошими, и они тоже обычно отделяют ваши детали SQL (если SQL вообще используется) от основного кода приложения. Я меньше выступаю за «используй мой конкретный метод: ресурсы» и больше за то, чтобы «как правило, не смешивать языки нефти и воды».
Чарльз Бернс

13

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

  1. Модульный SQL-код и изоляция его в отдельный набор классов, функций или любой другой единицы абстракции, которую использует ваша парадигма, а затем вызовите его с помощью логики приложения.
  2. Переместите весь сложный SQL в представления, а затем выполняйте только очень простой SQL в логике приложения, так что вам не нужно ничего модульно выполнять.
  3. Используйте библиотеку объектно-реляционного отображения.
  4. YAGNI, просто пишите 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-кода это не очень хорошая идея, потому что у вас могут быть дубликаты или варианты одного и того же запроса, случайно разбросанные по вашему приложению, что мешает удобочитаемости и рефакторингу.


2, как написано, по существу предполагает базу данных только для чтения. Хранимые процедуры обычно использовались для запросов, которые изменяются.
jpmc26

@ jpmc26: я покрываю записи в скобках ... у вас была конкретная рекомендация по улучшению этого ответа?
Кевин

Гектометр Извиняюсь за комментарии, прежде чем завершить ответ, а затем отвлекся. Но обновляемые представления затрудняют отслеживание того, где обновляется таблица. Ограничение обновлений непосредственно на таблицах, независимо от механизма, имеет свои преимущества с точки зрения понимания логики кода. Если вы ограничиваете логику в БД, вы не сможете применить ее без хранимых процедур. У них также есть преимущество, позволяющее выполнять несколько операций одним вызовом. Итог: я не думаю, что вы должны быть настолько жесткими с ними.
jpmc26

@ jpmc26: Это честно.
Кевин

8

Считается ли это анти-паттерном для написания SQL в исходном коде?

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

Проблема заключается в том, где вы размещаете заявления . Если вы размещаете операторы SQL по всему проекту, везде, вы, вероятно, будете игнорировать некоторые из принципов SOLID, которым мы обычно стремимся следовать.

предположим, что он имел в виду; или:

1) Используйте LINQ

или же

2) Используйте хранимые процедуры для SQL

Мы не можем сказать, что он имел в виду. Тем не менее, мы можем догадаться. Например, первое, что приходит мне в голову - это привязка к поставщику . Жесткое кодирование операторов SQL может привести к тому, что ваше приложение будет тесно связано с ядром БД. Например, с использованием определенных функций поставщика, которые не соответствуют ANSI.

Это не обязательно плохо и не плохо. Я просто указываю на факт.

Игнорирование принципов SOLID и блокировок поставщиков может привести к неблагоприятным последствиям, которые вы можете игнорировать. Вот почему обычно хорошо сидеть с командой и выставлять свои сомнения.

Я думаю, что преимущество хранимых процедур заключается в том, что разработчики SQL могут участвовать в процессе разработки (написание хранимых процедур и т. Д.).

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

Изменить: Следующая ссылка рассказывает о жестко закодированных инструкциях SQL:  https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/hard-coded-sql-statements . Есть ли какая-то польза от подготовки оператора SQL?

Да. Пост перечисляет преимущества подготовленных заявлений . Это своего рода шаблонизатор SQL. Более безопасный, чем конкатенация строк. Но пост не поощряет вас идти по этому пути и не подтверждает, что вы правы. Это просто объясняет, как мы можем использовать жесткий код SQL безопасным и эффективным способом.

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


1
Благодарю. +1 за «Жесткое кодирование операторов SQL может привести к тому, что ваше приложение будет тесно связано с механизмом БД». Мне бы хотелось узнать, ссылался ли он на SQL Server, а не на SQL, т. Е. Использовал SQLConnection, а не dbConnection; SQLCommand, а не dbCommand и SQLDataReader, а не dbDataReader.
w0051977

Нет. Я ссылался на использование выражений, не соответствующих ANSI. Например: select GETDATE(), ....GETDATE () - это функция даты SqlServer. В других движках функция имеет другое имя, другое выражение, ...
Laiv

14
«Блокировка поставщика» ... Как часто люди / предприятия фактически переносят базу данных своего существующего продукта в другую СУБД? Даже в проектах, использующих исключительно ORM, я никогда не видел, чтобы это произошло: обычно программное обеспечение также переписывается с нуля, потому что потребности менялись между ними. Поэтому размышления об этом кажутся мне «преждевременной оптимизацией». Да, это абсолютно субъективно.
Оливье Грегуар

1
Мне все равно, как часто это происходит. Мне все равно, это может случиться. В новых проектах стоимость реализации DAL, абстрагированной от базы данных, составляет почти 0. Возможная миграция - нет. Аргументы против ORM довольно слабые. ORM не похожи на 15 лет назад, когда Hibernate был довольно зеленым. То, что я видел, - это множество конфигураций «по умолчанию» и довольно плохие модели данных. Это как и многие другие инструменты, многие люди делают учебник по началу работы, и им все равно, что происходит под капотом. Инструмент не проблема. Проблема в том, кто не знает, как и когда его использовать.
Laiv

С другой стороны, вендор не обязательно плохой. Если бы меня спросили, я бы сказал, что мне не нравится . Тем не менее, я уже привязан к Spring, Tomcat, JBoss или Websphere (что еще хуже). Те, кого я могу избежать, я делаю. Те, кого я не могу, я просто живу с Ним. Это вопрос предпочтений.
Laiv

3

Я думаю, что это плохая практика, да. Другие отмечают преимущества сохранения всего кода доступа к данным на отдельном уровне. Вам не нужно искать его, его легче оптимизировать и тестировать ... Но даже на этом уровне у вас есть несколько вариантов: использовать ORM, использовать sprocs или встраивать SQL-запросы в виде строк. Я бы сказал, что строки SQL-запросов являются наихудшим вариантом.

С ORM разработка становится намного проще и менее подвержена ошибкам. С EF вы определяете свою схему, просто создав классы модели (вам все равно нужно было бы создать эти классы). Запросы с LINQ очень просты - вам часто не хватает 2 строк c #, в которых вам в противном случае нужно было бы писать и поддерживать sproc. ИМО, это имеет огромное преимущество, когда речь идет о производительности и удобстве обслуживания - меньше кода, меньше проблем. Но производительность снижается, даже если вы знаете, что делаете.

Sprocs (или функции) являются другой опцией. Здесь вы пишете свои SQL-запросы вручную. Но, по крайней мере, вы получаете гарантию, что они правильные. Если вы работаете в .NET, Visual Studio даже выдаст ошибки компилятора, если SQL неверен. Это здорово. Если вы измените или удалите какой-либо столбец, и некоторые ваши запросы станут недействительными, по крайней мере, вы, вероятно, узнаете об этом во время компиляции. Также проще поддерживать sprocs в их собственных файлах - вы, вероятно, получите подсветку синтаксиса, autocomplete..etc.

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

SQL-запросы в виде строковых литералов также сделают ваш код доступа к данным C # менее читабельным.

Как правило, магические константы плохие. Это включает строковые литералы.


Разрешение администраторам баз данных изменять ваш SQL без обновления приложения эффективно разделяет ваше приложение на две отдельно разработанные, отдельно развернутые сущности. Теперь вам нужно управлять контрактом интерфейса между ними, синхронизировать выпуск взаимозависимых изменений и т. Д. Это может быть хорошо или плохо, но это гораздо больше, чем просто «поместить все ваши SQL в одном месте», это далеко архитектурное решение.
IMSoP

2

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

    string query = 
    @"SELECT foo, bar
      FROM table
      WHERE id = @tn";

7
Самая вопиющая проблема - это вопрос, откуда взялась эта величина 42. Вы объединяете это значение в строку? Если так, то откуда это взялось? Как это было очищено, чтобы смягчить атаки SQL-инъекций? Почему этот пример не показывает параметризованный запрос?
Крейг,

Я просто хотел показать форматирование. Извините, я забыл параметризировать это. Теперь исправлено после обращения к msdn.microsoft.com/library/bb738521(v=vs.100).aspx
rleir

+1 за важность форматирования, хотя я утверждаю, что строки SQL являются запахом кода независимо от форматирования.
Чарльз Бернс

1
Вы настаиваете на использовании ORM? Когда ORM не используется, для меньшего проекта я использую класс 'shim', содержащий встроенный SQL.
Рлеир

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

1

Я не могу сказать вам, что имел в виду ваш коллега. Но я могу ответить на это:

Есть ли какая-то польза от подготовки оператора SQL?

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

... и самая эффективная защита от плохой конкатенации строк запросов - это не представление запросов в первую очередь как строки, а использование API запросов с типом безопасности для построения запросов. Например, вот как я написал бы ваш запрос в Java с QueryDSL:

List<UUID> ids = select(person.id).from(person).fetch();

Как видите, здесь нет ни одного строкового литерала, что делает невозможным использование SQL-инъекции. Более того, при написании этого кода у меня было завершение кода, и моя среда IDE может изменить его, если я решу когда-либо переименовать столбец идентификатора. Кроме того, я легко могу найти все запросы для таблицы person, спросив мою IDE, где personпроисходит доступ к переменной. О, и вы заметили, что это немного короче и проще для глаз, чем ваш код?

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

Таким образом, представление запросов в виде строк SQL затрудняет для IDE значительную помощь в написании и рефакторинге запросов, откладывает обнаружение синтаксических и типовых ошибок со времени компиляции до времени выполнения и является одной из причин уязвимостей внедрения SQL [1]. Так что да, есть веские причины, по которым можно не хотеть строки SQL в исходном коде.

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


Для ясности: использование подготовленных операторов не предотвращает внедрение SQL. Использование подготовленных операторов со связанными переменными делает. Можно использовать подготовленные операторы, построенные на основе ненадежных данных.
Энди Лестер

1

Здесь много отличных ответов. Помимо того, что уже было сказано, я хотел бы добавить еще одну вещь.

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

Всегда держите непредвзято и не поддавайтесь на догмы. Если ваш магазин "linq", вы используете его. За исключением одного места, где linq абсолютно не работает. (Но вы не должны 99,9% времени.)

Так что я хотел бы исследовать код в базе кода и проверить, как работают ваши коллеги.


+1 Хороший вопрос. Поддержка более важна, чем большинство других соображений, так что не будьте слишком умны :) При этом, если вы магазин ORM, не забывайте, что есть случаи, когда ORM не является ответом, и вам нужно писать прямой SQL. Не оптимизируйте преждевременно, но в любом нетривиальном приложении будут моменты, когда вам придется идти по этому пути.
Маккотл

0

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

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

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


0

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

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

var query = "select * from accounts";

if(users.IsInRole('admin'))
{
   query += " join secret_balances";
}

Я предлагаю использовать конструктор запросов SQL


Может быть, вы просто используете стенографию, но это действительно плохая идея слепо использовать «SELECT *», потому что вы вернете поля, которые вам не нужны (пропускная способность !!) Также, хотя у меня нет проблем с условным предложением Admin он ведет вниз по очень скользкому склону к условному условию «ГДЕ», и это дорога, которая ведет прямо в ад производительности.
Маккотт

0

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

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

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


Оригинальный текст ниже для контекста

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

Во многих организациях код может быть запущен в производство только после цикла QA и т. Д., Который может занять дни или недели. Если в вашей системе возникает проблема с производительностью базы данных из-за изменения шаблонов использования или увеличения размеров таблиц и т. Д., Тогда может быть довольно сложно отследить проблему с помощью жестко закодированных запросов, тогда как в базе данных будут инструменты / отчеты и т. Д., Которые будут определять хранимые процедуры с ошибками. , С помощью хранимых процедур решения могут быть протестированы / протестированы на производстве, и проблема может быть быстро устранена без необходимости установки новой версии прикладного программного обеспечения.

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


Неправильно ...........
Страж

Считаете ли вы, что ваш комментарий полезен для всех?
bikeman868

Это просто неверно. Допустим, у нас есть проект Web API2 с EF в качестве доступа к данным и Azure Sql в качестве хранилища. Нет, давайте избавимся от EF и используем вручную созданный SQL. Схема БД хранится в проекте SSDT SQL Server. Миграция схемы БД включает в себя проверку того, что база кода синхронизирована со схемой БД и полностью протестирована, прежде чем выдвигать изменения в тест. БД в Azure. Изменение кода включает выталкивание в слот развертывания службы приложений, что занимает 2 секунды. Плюс ручной SQL имеет плюсы производительности по сравнению с хранимыми процедурами, вообще говоря, несмотря на «мудрость» обратного.
Сентинел

В вашем случае развертывание может занять несколько секунд, но это не так для многих людей. Я не говорил, что хранимые процедуры лучше, просто они имеют некоторые преимущества при некоторых обстоятельствах. Мое заключительное утверждение состоит в том, что если эти обстоятельства не применяются, то размещение SQL в приложении является очень правильным. Как это противоречит тому, что вы говорите?
bikeman868

Хранимые процедуры - это ручной SQL-запрос ???
bikeman868
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.