Чи вважається анти шаблоном писати 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-Code або JPQL за межами сховища порушує стійкість абстракції так само, як і сировина SQL.


Ще одна величезна перевага окремого шару стійкості полягає в тому, що він дозволяє перевірити логічний код вашого бізнесу без необхідності налаштування сервера БД.

Ви отримуєте низькі профілі пам'яті, швидкі тестові одиниці з відтворюваними результатами на всіх платформах, які підтримує ваша мова.

У архітектурі сервісу MVC + це просте завдання знущатися з екземпляра сховища, створювати деякі макетні дані в пам'яті та визначати, що сховище повинно повертати ці макетні дані, коли викликається певний геттер. Потім можна визначити дані тесту на одиницю тестування і не турбуватися про очищення БД згодом.

Тестування записів у БД є таким же простим: перевірте, чи були викликані відповідні методи оновлення на шарі збереження, і стверджуйте, що сутності були у правильному стані, коли це сталося.


80
Ідея про те, що "ми можемо змінити технологію збереження" є нереальною та непотрібною у переважній більшості реальних проектів. SQL / реляційна БД - зовсім інший звір, ніж NoSQL БД, такі як MongoDB. Дивіться цю детальну статтю про це. Щонайбільше, проект, який почав використовувати NoSQL, врешті-решт зрозуміє свою помилку, а потім перейде до RDBMS. На мій досвід, можливість прямого використання об'єктно-орієнтованої мови запитів (наприклад, JPA-QL) з бізнес-коду дає найкращі результати.
Rogério

6
Що робити, якщо шансів стійкості зміниться? Що робити, якщо не змінюється однозначне відображення при зміні шару стійкості? Яка перевага додаткового рівня абстракції?
pllee

19
@ Rogério Перехід з магазину NoSQL на RDBMS або навпаки, як ви кажете, ймовірно, нереально (якщо припустити, що вибір технології був відповідним для початку). Однак я брав участь у ряді реальних проектів, де ми переходили з однієї 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 Я б сказав, загалом кажучи, введення коду доступу до DB в SP є згубним у майже кожному проекті, який я бачив. (MS стек) Окрім фактичної деградації продуктивності (жорсткі кеші планів виконання), підтримку системи було ускладнено, розбивши логіку навколо, а SP-сервіси підтримували інший набір людей, ніж розробники логіки додатків. Особливо на Azure, де зміна коду між слотами для розгортання - це як секунди роботи, але міграції схем без коду першої / db-першої між слотами трохи головні болі.
Sentinel

33

Marstato дає хорошу відповідь, але я хотів би додати ще кілька коментарів.

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

Зараз деякі коментарі говорять про вхід постачальника, начебто це автоматично погано. Це не так. Якщо я підписую щорічну перевірку шести цифр для використання Oracle, ви можете зробити ставку, що я хочу, щоб будь-яка програма, що отримує доступ до цієї бази даних, використовувала додатковий синтаксис Oracle належним чиномале в повній мірі. Я не буду радий, якщо мою блискучу базу даних покалічать кодери, які пишуть ванільний ANSI SQL погано, коли існує "Oracle спосіб" написання SQL, який не калічить базу даних. Так, зміна баз даних буде складніше, але я бачив це лише на великому клієнтському сайті кілька разів за 20 років, і один з таких випадків переміщувався з DB2 -> Oracle, тому що мейнфрейм, який розмістив DB2, був застарілим і він вийшов з експлуатації. . Так, це блокування постачальників, але для корпоративних клієнтів бажано заплатити за такі дорогі здатні RDBMS, як Oracle або Microsoft SQL Server, а потім використовувати їх в повній мірі. У вас є угода про підтримку як ваша ковдра. Якщо я плачу за базу даних з насиченою реалізацією збережених процедур,

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

Ні виправдань, ні ховання за стіною сплячки. Неправильно використані ORM можуть дійсно калічити продуктивність програм. Я пам’ятаю, як кілька років тому я бачив запитання про Stack Overflow:

У сплячому режимі я повторюю 250 000 записів, перевіряючи значення пари властивостей та оновлюючи об'єкти, що відповідають певним умовам. Він працює трохи повільно, що я можу зробити, щоб прискорити його?

Як щодо "UPDATE table SET field1 = where field2 True та Field3> 100". Створення та знешкодження 250 000 об’єктів цілком може стати вашою проблемою ...

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

Отже, підсумовуючи, вбудовувати SQL у код може бути поганою практикою, але є набагато гірші речі, які ви можете зробити, намагаючись уникнути вбудовування SQL.


5
Я не заявив, що "блокування постачальника" - це погана річ. Я заявив, що це стосується компромісів. На сучасному ринку з db як xaaS такі старі контракти та ліцензії на запуск самостійного розміщення db ставатимуть все рідше. Сьогодні набагато простіше змінити db від постачальника A на B порівняно, як це було 10 років тому. Якщо ви читаєте коментарі, я не прихильний до переваг будь-якої функції зберігання даних. Тож легко помістити деякі конкретні дані про постачальника в щось (додаток), яке означало б агностик продавця. Ми прагнемо слідувати SOLiD лише до кінця, щоб зафіксувати код до єдиного сховища даних. Не велика справа
Лаїв

2
Це чудова відповідь. Найчастіше правильним є такий підхід, який призводить до найменш поганої ситуації, якщо написаний найгірший код. Найгірший можливий код ORM може бути набагато гіршим, ніж найгірший можливий вбудований SQL.
jwg

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

3
@jwg Я схиляюся сказати, що вище середнього коду ORM гірше, ніж нижче середнього вбудованого SQL :)
mcottle

@macottle Якщо припустити, що вбудований SQL нижче середнього рівня був написаний на ppl вище середнього рівня того, хто пише ORM. У чому я дуже сумніваюся. Багато дев там не в змозі написати ліві ПРИЄДНУЙСЯ, не перевіряючи спочатку.
Лаїв

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 з оточуючим кодом програми, передаються по мережі з кожним виконанням. Це, мабуть, незначно для невеликих програм, але це може збільшуватись у міру зростання використання програмного забезпечення.

Повні ОРМ (Об'єктно-реляційні картографи, такі як 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. Код бази даних легко знайти, оскільки він уже централізований. У великих проектах групуйте подібний 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), ймовірно, вимагатиме значно більше коду, ніж ОРМ.

(2) мене рідко бачать. Проблема полягає в тому, що багато магазинів забороняють SWE безпосередньо змінювати схему бази даних (адже "це робота DBA"). Це не обов'язково сама по собі погана річ; Зміни схеми мають значний потенціал для розбиття речей, і їх може знадобитися обережно виконувати. Однак для того, щоб (2) працював, ДЕЗ повинні, принаймні, мати можливість впроваджувати нові погляди та змінювати резервні запити існуючих поглядів з мінімальною чи відсутністю бюрократії. Якщо це не так у вашому місці роботи, (2), ймовірно, не буде працювати для вас.

З іншого боку, якщо ви можете змусити (2) працювати, це набагато краще, ніж більшість інших рішень, оскільки він зберігає реляційну логіку в SQL замість коду програми. На відміну від мов програмування загального призначення, SQL спеціально розроблений для реляційної моделі даних, і, відповідно, краще виражає складні запити та перетворення даних. Перегляди можна також переносити разом із рештою вашої схеми при зміні баз даних, але вони ускладнять такі кроки.

Для читань збережені процедури в основному є лише версією crappier (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 можуть призвести до того, щоб щільно з'єднати вашу програму з двигуном DB. Наприклад, використовуючи конкретні функції постачальника, які не відповідають стандартам 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 можуть призвести до щільного з'єднання вашої програми з db enginee." Я блукаю, якщо він мав на увазі SQL Server, а не SQL, тобто використовував SQLConnection, а не dbConnection; SQLCommand, а не dbCommand і SQLDataReader, а не dbDataReader.
w0051977

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

14
"Блокування постачальника" ... Як часто люди / підприємства фактично переносять базу даних свого наявного продукту в іншу RDBMS? Навіть у проектах, що використовують виключно ORM, я ніколи не бачив, щоб це сталося: зазвичай програмне забезпечення також переписується з нуля, тому що потреби змінювалися між ними. Тому думати про це, схоже, схоже на "передчасну оптимізацію". Так, це абсолютно суб'єктивно.
Олів'є Грегоар

1
Мені все одно, як часто трапляється. Мені все одно Це може статися. У проектах, що займаються грінфілдом, вартість впровадження DAL, абстрагованого від db, становить майже 0. Можлива міграція - ні. Аргументи проти ORM досить слабкі. ORM не такі, як 15 років тому, коли сплячка була досить зеленою. Що я бачив, це багато конфігурацій "за замовчуванням" та набагато більше поганих моделей даних. Як і в багатьох інших інструментах, багато людей роблять підручник з "початку роботи" і їм байдуже, що відбувається під кришкою. Інструмент - не проблема. Проблема полягає в тому, хто не знає, як і коли ним користуватися.
Лаїв

З іншого боку, заблокований постачальник не обов’язково поганий. Якби мене запитали, я б сказав, що мені це не подобається . Однак я вже заблокований на Spring, Tomcat або JBoss або Websphere (ще гірше). Ті, кого я можу уникнути, я і роблю. Ті, кого я не можу, просто живу з цим. Це питання переваг.
Лаїв

3

Я думаю, що це погана практика, так. Інші вказали на переваги збереження всього коду доступу до даних у власному шарі. Вам не доведеться полювати за цим, простіше оптимізувати і протестувати ... Але навіть у межах цього шару у вас є кілька варіантів: використовувати ORM, використовувати відростки або вбудувати SQL запити як рядки. Я б сказав, що рядки запитів SQL - це найгірший варіант.

За допомогою ORM розробка стає набагато простішою та менш схильною до помилок. За допомогою EF ви визначаєте свою схему, просто створюючи свої класи класів (вам би знадобилося все-таки створити ці класи). Запит у LINQ - це легкий вітер - ви часто виходите з двох рядків c #, де інакше потрібно писати та підтримувати проросток. IMO, це має величезну перевагу, що стосується продуктивності та ремонтопридатності - менше коду, менше проблем. Але продуктивність накладні, навіть якщо ви знаєте, що робите.

Sprocs (або функції) - це інший варіант. Тут ви пишете свої SQL запити вручну. Але принаймні ви отримуєте певну гарантію, що вони правильні. Якщо ви працюєте в .NET, Visual Studio навіть видасть помилки компілятора, якщо SQL недійсний. Це чудово. Якщо ви зміните або видалите якийсь стовпець, а деякі ваші запити стають недійсними, принаймні ви, ймовірно, дізнаєтесь про це під час компіляції. Також простіше підтримувати проростки у власних файлах - ви, ймовірно, отримаєте підсвічування синтаксису, автозаповнення..etc.

Якщо ваші запити зберігаються у вигляді відростків, ви також можете їх змінити, не перекомпілюючи та не розгортаючи додаток. Якщо ви помітили, що щось у вашому SQL порушено, DBA може просто виправити це, не маючи доступу до коду програми. Загалом, якщо ваші запити є рядковими літералами, ви не можете виконати свою роботу так само легко.

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

Як правило, магічні константи погані. Це включає літеральні рядки.


Дозвіл DBA змінювати ваш 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 не використовується, для меншого проекту я використовую клас 'shim', що містить вбудований SQL.
rleir

Рядки SQL, безумовно, не є кодовим запахом. Без ОРМ, як менеджер та архітектор, я б заохочував їх через ІР як з обслуговування, так і іноді з міркувань продуктивності.
Sentinel

1

Я не можу сказати тобі, що мав на увазі колега. Але я можу відповісти на це:

Чи є якась користь у підготовці оператора SQL?

YES . Використання підготовлених операторів із зв'язаними змінними є рекомендованою захистом від введення SQL , яка залишається єдиним найбільшим ризиком для безпеки веб-додатків протягом більше десяти років . Ця атака настільки поширена, що вона з’явилася в веб-коміксах майже десять років тому, і настав час, коли ефективні засоби захисту були розгорнуті.

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

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

Як бачите, тут немає жодної лінійної лінійки, що робить SQL ін'єкцією нерегулярною. Більше того, у мене було написання коду під час написання цього запису, і мій IDE може змінити його, якщо я вирішу коли-небудь перейменувати стовпець id. Крім того, я можу легко знайти всі запити до таблиці осіб, запитавши мій 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. Не оптимізуйте передчасно, але в будь-якій нетривіальній програмі буде час, коли вам доведеться спуститися цим маршрутом.
mcottle

0

Код схожий на шар взаємодії з базою даних, який знаходиться між базою даних та рештою коду. Якщо це дійсно так, це не є анти-зразком.

Цей метод є частиною шару, навіть якщо ОП мислить інакше (звичайний доступ до бази даних, ніяке змішування ні з чим іншим). Це, можливо, просто погано розміщено всередині коду. Цей метод належить до окремого класу, "шар взаємодії баз даних". Було б анти-шаблоном розміщувати його всередині звичайного класу поруч із методами, що реалізують діяльність, що не стосується бази даних.

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


0

На мою думку, ні один не анти-шаблон, але ви зіткнетеся з інтервалом форматування рядків, особливо для великих запитів, які не можуть вписатись в один рядок.

Ще один плюс - це набагато складніше при роботі з динамічними запитами, які слід будувати за певних умов.

var query = "select * from accounts";

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

Я пропоную скористатися конструктором запитів sql


Можливо, ви просто використовуєте стенограму, але це дуже погана ідея сліпо використовувати "SELECT *", тому що ви повернете потрібні вам поля (пропускна здатність !!) це веде вниз по дуже слизькому схилу до умовного пункту "WHERE", і це дорога, яка веде прямо до пекла продуктивності.
mcottle

0

Для багатьох сучасних організацій із швидким розгортанням конвеєрів, де зміни коду можна зібрати, протестувати та перенести на виробництво за лічені хвилини, цілком має сенс вставляти SQL заяви в код програми. Це не обов'язково є антидіаграмою, залежно від того, як ви це зробите.

Дійсний випадок використання збережених процедур сьогодні в організаціях, де існує багато процесів навколо виробничих розгортань, які можуть включати тижні QA-циклів і т.д. запити в збережених процедурах.

Якщо ви не знаходитесь у цій ситуації, я рекомендую вам вбудувати свій SQL у код програми. Прочитайте інші відповіді в цій нитці, щоб отримати поради щодо найкращого способу її досягнення.


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

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

У багатьох організаціях код може бути підштовхнутий до виробництва лише після циклу контролю якості тощо, що може зайняти дні чи тижні. Якщо ваша система розробляє проблему продуктивності бази даних через зміну моделей використання або збільшення розмірів таблиць тощо, то викрити проблему за допомогою жорстко кодованих запитів може бути досить складно, тоді як у базі даних будуть інструменти / звіти тощо, які визначатимуть проблеми, що зберігаються . За допомогою збережених процедур рішення можуть бути випробувані / орієнтовані на виробництві, і проблема може бути вирішена швидко, не вимагаючи нової версії прикладного програмного забезпечення.

Якщо ця проблема не є важливою у вашій системі, це цілком правильне рішення про жорсткий код заяв SQL у програму.


Неправильно ...........
Sentinel

Чи вважаєте ви, що ваш коментар комусь корисний?
bikeman868

Це просто неправильно. Скажімо, у нас є проект Web API2 з EF як доступ до даних та Azure Sql як сховище. Ні, давайте позбудемося EF та використовуємо ручний SQL. Схема db зберігається в проекті SSDT SQL Server. Міграція схеми DBD передбачає переконайтесь, що база даних синхронізована зі схемою db та повністю протестована, перш ніж витісняти зміни до тесту db в Azure.Зміна зміни коду передбачає виштовхування до слота розгортання служби App, який займає 2 секунди. Плюс ручний SQL має додаткові плюси щодо збережених процедур, взагалі кажучи, незважаючи на "мудрість" протилежного.
Sentinel

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

Зберігаються процедури - це ручний SQL ???
bikeman868
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.