È considerato un modello anti per scrivere SQL nel codice sorgente?


87

È considerato un modello anti per hardcode SQL in un'applicazione come questa:

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;
}

Normalmente avrei un livello repository ecc., Ma per semplicità l'ho escluso nel codice sopra.

Di recente ho ricevuto un feedback da un collega che si è lamentato del fatto che SQL fosse scritto nel codice sorgente. Non ho avuto la possibilità di chiedere perché e ora è via per due settimane (forse di più). Suppongo che intendesse:

  1. Utilizzare LINQ
    o
  2. Utilizzare le stored procedure per SQL

Ho ragione? È considerato un modello anti per scrivere SQL nel codice sorgente? Siamo un piccolo team che lavora a questo progetto. Penso che il vantaggio delle procedure memorizzate sia che gli sviluppatori SQL possono essere coinvolti nel processo di sviluppo (scrittura di procedure memorizzate ecc.).

Modifica Il seguente link parla di istruzioni SQL codificate: https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/hard-coded-sql-statements . C'è qualche vantaggio nel preparare un'istruzione SQL?


31
"Usa Linq" e "Usa stored procedure" non sono motivi; sono solo suggerimenti. Aspetta due settimane e chiedigli dei motivi.
Robert Harvey,

47
La rete Stack Exchange utilizza un microORM chiamato Dapper. Penso che sia ragionevole affermare che la stragrande maggioranza del codice Dapper è "SQL hardcoded" (più o meno). Quindi, se è una cattiva pratica, allora è una cattiva pratica adottata da una delle applicazioni web più importanti del pianeta.
Robert Harvey,

16
Per rispondere alla tua domanda sui repository, SQL hard-coded è ancora SQL hard-coded, indipendentemente da dove lo metti. La differenza è che il repository ti dà un posto per incapsulare l'SQL codificato. È uno strato di astrazione che nasconde i dettagli dell'SQL dal resto del programma.
Robert Harvey,

26
No, SQL nel codice non è un anti-pattern. Ma questo è un sacco di codice piastra caldaia per una semplice query SQL.
GrandmasterB,

45
C'è una differenza tra "nel codice sorgente" e "diffuso in tutto il codice sorgente"
fino

Risposte:


112

Hai escluso la parte cruciale per semplicità. Il repository è lo strato di astrazione per la persistenza. Separiamo la persistenza nel suo stesso strato in modo da poter cambiare più facilmente la tecnologia di persistenza quando ne abbiamo bisogno. Pertanto, avere SQL al di fuori del livello di persistenza ostacola completamente lo sforzo di avere un livello di persistenza separato.

Di conseguenza: SQL va bene all'interno del livello di persistenza specifico di una tecnologia SQL (ad es. SQL va bene in a SQLCustomerRepositoryma non in a MongoCustomerRepository). Al di fuori dello strato di persistenza, SQL spezza l'astrazione e quindi è considerato molto cattiva pratica (da me).

Per quanto riguarda strumenti come LINQ o JPQL: quelli possono semplicemente astrarre i sapori di SQL là fuori. Avere query LINQ-Code o JPQL al di fuori di un repository interrompe l'astrazione della persistenza proprio come farebbe SQL raw.


Un altro enorme vantaggio di un livello di persistenza separato è che consente di annullare il test del codice di logica aziendale senza dover configurare un server DB.

Ottieni test di unità a basso profilo di memoria e veloci con risultati riproducibili su tutte le piattaforme supportate dalla tua lingua.

In un'architettura di servizio MVC + questo è un semplice compito di deridere l'istanza del repository, creare alcuni dati di simulazione in memoria e definire che il repository dovrebbe restituire quei dati di simulazione quando viene chiamato un determinato getter. È quindi possibile definire i dati di test per unittest e non preoccuparsi di ripulire il DB in seguito.

Il test delle scritture nel DB è semplice: verificare che siano stati chiamati i relativi metodi di aggiornamento sul livello di persistenza e affermare che le entità erano nello stato corretto quando ciò è accaduto.


80
L'idea che "possiamo cambiare la tecnologia della persistenza" è irrealistica e non necessaria nella stragrande maggioranza dei progetti del mondo reale. Un DB SQL / relazionale è una bestia completamente diversa rispetto ai DB NoSQL come MongoDB. Vedi questo articolo dettagliato al riguardo. Al massimo, un progetto che ha iniziato a utilizzare NoSQL alla fine si accorgerebbe del loro errore e passerebbe a un RDBMS. Nella mia esperienza, consentire l'uso diretto di un linguaggio di query orientato agli oggetti (come JPA-QL) dal codice aziendale fornisce i migliori risultati.
Rogério,

6
E se ci sono poche possibilità che il livello di persistenza venga modificato? Che cosa succede se non esiste un mapping uno a uno quando si modifica il livello di persistenza? Qual è il vantaggio del livello extra di astrazione?
fuggito

19
@ Rogério La migrazione da un negozio NoSQL a un RDBMS, o viceversa, è come dici probabilmente non realistica (supponendo che la scelta della tecnologia fosse appropriata per cominciare). Tuttavia, sono stato coinvolto in una serie di progetti del mondo reale in cui siamo migrati da un RDBMS a un altro; in quello scenario, un livello di persistenza incapsulato è sicuramente un vantaggio.
David,

6
"L'idea che" possiamo cambiare la tecnologia della persistenza "non è realistica e non è necessaria nella stragrande maggioranza dei progetti del mondo reale." La mia azienda in precedenza aveva scritto codice lungo tale presupposto. Abbiamo dovuto riformattare l'intero codebase per supportare non uno, ma tre sistemi di archiviazione / query completamente diversi e stiamo considerando un terzo e un quarto. Peggio ancora, anche quando avevamo un solo database, la mancanza di consolidamento ha portato a tutti i tipi di peccati di codice.
NPSF3000,

3
@ Rogério Conosci la "stragrande maggioranza dei progetti del mondo reale"? Nella mia azienda, la maggior parte delle applicazioni può funzionare con uno dei molti DBMS comuni, e questo è molto utile, poiché la maggior parte dei nostri clienti ha requisiti non funzionali a tale proposito.
BartoszKP,

55

La maggior parte delle applicazioni aziendali standard oggi utilizza livelli diversi con responsabilità diverse. Tuttavia, quali layer utilizzi per la tua applicazione e quale layer ha la responsabilità che spetta a te e al tuo team. Prima di poter decidere se è giusto o sbagliato posizionare SQL direttamente nella funzione che ci hai mostrato, devi sapere

  • quale livello nella tua applicazione ha quale responsabilità

  • da quale livello proviene la funzione sopra

Non esiste una soluzione "unica per tutti". In alcune applicazioni i progettisti preferiscono utilizzare un framework ORM e lasciare che il framework generi tutto l'SQL. In alcune applicazioni i progettisti preferiscono archiviare tale SQL esclusivamente in stored procedure. Per alcune applicazioni esiste un livello di persistenza (o repository) scritto a mano in cui risiede l'SQL e per altre applicazioni è bene definire eccezioni in determinate circostanze dal rigoroso inserimento di SQL in quel livello di persistenza.

Quindi, ciò che è necessario pensare: quali livelli si desidera o necessità in ogni specifica applicazione, e come si desidera che le responsabilità? Hai scritto "Normalmente avrei un livello repository" , ma quali sono le responsabilità esatte che vuoi avere all'interno di quel livello e quali responsabilità vuoi mettere altrove? Rispondi prima, quindi puoi rispondere alla tua domanda da solo.


1
Ho modificato la domanda. Non sono sicuro se fa luce.
w0051977,

18
@ w0051977: il link che hai pubblicato non ci dice nulla sulla tua applicazione, giusto? Sembra che tu stia cercando qualche regola semplice e intelligente in cui posizionare SQL o meno che si adatti a ogni applicazione. Non c'è nessuno. Questa è una decisione di progettazione che devi prendere sulla tua singola applicazione (probabilmente insieme al tuo team).
Doc Brown,

7
+1. In particolare, vorrei notare che non vi è alcuna reale differenza tra SQL in un metodo e SQL in una procedura memorizzata, in termini di quanto "codificato" è qualcosa o di come riutilizzabile, mantenibile, ecc. Qualsiasi astrazione che si può fare con un la procedura può essere eseguita con un metodo. Ovviamente le procedure possono essere utili, ma una regola di base "non possiamo avere l'SQL nei metodi, quindi mettiamolo tutto nelle procedure" probabilmente produrrà molta cruft con pochi benefici.

1
@ user82096 Direi che in generale inserire il codice di accesso al db negli SP è stato dannoso in quasi tutti i progetti che ho visto. (MS stack) A parte gli effettivi peggioramenti delle prestazioni (cache del piano di esecuzione rigida), la manutenzione del sistema è stata resa più difficile con la logica divisa e gli SP mantenuti da un gruppo diverso di persone rispetto agli sviluppatori della logica delle app. Soprattutto in Azure, dove le modifiche al codice tra gli slot di distribuzione sono come pochi secondi di lavoro, ma le migrazioni dello schema non basate sul codice / db-first tra gli slot sono un po 'un mal di testa operativo.
Sentinel

33

Marstato dà una buona risposta ma vorrei aggiungere qualche commento in più.

SQL nel sorgente NON è un modello anti-pattern ma può causare problemi. Ricordo quando era necessario inserire query SQL nelle proprietà dei componenti rilasciati in ogni modulo. Ciò ha reso le cose davvero brutte molto velocemente e hai dovuto saltare attraverso i cerchi per individuare le query. Sono diventato un forte sostenitore della centralizzazione dell'accesso al database il più possibile entro i limiti delle lingue con cui stavo lavorando. Il tuo collega potrebbe ricevere flashback in questi giorni bui.

Ora, alcuni dei commenti parlano del blocco del fornitore come se fosse automaticamente una brutta cosa. Non lo è. Se firmo un assegno a sei cifre ogni anno per usare Oracle, puoi scommettere che voglio che qualsiasi applicazione che accede a quel database utilizzi la sintassi Oracle aggiuntiva in modo appropriatoma al massimo. Non sarò contento se il mio brillante database è paralizzato dai programmatori che scrivono male ANSI SQL alla vaniglia quando c'è un "modo Oracle" di scrivere l'SQL che non paralizza il database. Sì, cambiare database sarà più difficile, ma l'ho visto solo in un sito di grandi clienti un paio di volte in oltre 20 anni e uno di questi casi si stava spostando da DB2 -> Oracle perché il mainframe che ospitava DB2 era obsoleto e veniva rimosso . Sì, questo è il blocco dei fornitori, ma per i clienti aziendali è effettivamente desiderabile pagare un RDBMS capace e costoso come Oracle o Microsoft SQL Server e quindi utilizzarlo nella misura massima. Hai un accordo di supporto come coperta di comfort. Se sto pagando per un database con un'implementazione di stored procedure avanzate,

Questo porta al punto successivo, se si sta scrivendo un'applicazione che accede a un database SQL, è necessario imparare SQL oltre che l'altra lingua e, per apprendere, intendo anche l'ottimizzazione delle query; Mi arrabbierò con te se scrivi il codice di generazione SQL che scarica la cache SQL con una raffica di query quasi identiche quando avresti potuto usare una query abilmente parametrizzata.

Nessuna scusa, nessun nascondiglio dietro un muro di Hibernate. ORM mal utilizzato può davvero paralizzare le prestazioni delle applicazioni. Ricordo di aver visto una domanda su Stack Overflow qualche anno fa sulla falsariga di:

In Hibernate sto analizzando 250.000 record verificando i valori di un paio di proprietà e aggiornando oggetti che soddisfano determinate condizioni. Funziona un po 'lentamente, cosa posso fare per accelerarlo?

Che ne dici di "UPDATE table SET field1 = dove field2 è True e Field3> 100". La creazione e l'eliminazione di 250.000 oggetti potrebbe essere il tuo problema ...

cioè Ignora ibernazione quando non è appropriato usarlo. Comprendi il database.

Quindi, in sintesi, incorporare SQL nel codice può essere una cattiva pratica, ma ci sono cose molto peggiori che puoi finire cercando di evitare di incorporare SQL.


5
Non ho affermato che il "blocco del fornitore" è una cosa negativa. Ho dichiarato che viene fornito con compromessi. Nel mercato odierno con db come xaaS tali vecchi contratti e licenze per l'esecuzione di db con hosting autonomo saranno sempre più rari. Oggi è abbastanza più semplice cambiare il db dal fornitore A a B rispetto a 10 anni fa. Se leggi i commenti, non sono favorevole a trarre vantaggio da qualsiasi funzionalità di archiviazione dei dati. Quindi è facile inserire alcuni dettagli specifici del fornitore in qualcosa (app) destinato ad essere indipendente dal fornitore. Ci impegniamo a seguire SOLiD fino alla fine per bloccare il codice in un singolo archivio dati. Non è un grosso problema
Laiv,

2
Questa è un'ottima risposta Spesso l'approccio giusto è quello che porta alla situazione meno negativa se viene scritto il codice peggiore possibile. Il peggior codice ORM possibile potrebbe essere molto peggio del peggiore possibile SQL incorporato.
jwg,

@Laiv punti positivi PaaS è un po 'un punto di svolta - dovrò pensare di più alle implicazioni.
mcottle,

3
@jwg Sarei propenso a dire che il codice ORM sopra la media è peggiore dell'SQL incorporato sotto la media :)
mcottle

@macottle Supponendo che l'SQL incorporato inferiore alla media sia stato scritto da ppl al di sopra della media di chi scrive un ORM. Ciò di cui ne dubito molto. Molti sviluppatori là fuori non sono in grado di scrivere JOIN a sinistra senza prima verificare SO.
Laiv

14

Sì, la codifica hard di stringhe SQL nel codice dell'applicazione è generalmente un anti-pattern.

Proviamo a mettere da parte la tolleranza che abbiamo sviluppato da anni vedendolo nel codice di produzione. Mischiare lingue completamente diverse con sintassi diverse nello stesso file non è generalmente una tecnica di sviluppo desiderabile. Questo è diverso dai linguaggi modello come Razor che sono progettati per dare un significato contestuale a più lingue. Come menziona Sava B. in un commento qui sotto, SQL nel tuo C # o altro linguaggio di applicazione (Python, C ++, ecc.) È una stringa come qualsiasi altra ed è semanticamente insignificante. Lo stesso vale quando si mescolano più di una lingua nella maggior parte dei casi, anche se ci sono ovviamente situazioni in cui è accettabile farlo, come assemblaggio in linea in C, frammenti piccoli e comprensibili di CSS in HTML (notando che CSS è progettato per essere miscelato con HTML ), e altri.

Clean Code di Robert C. Martin, pag.  288 (Robert C. Martin sulla miscelazione di lingue, Clean Code , Capitolo 17, "Odori ed euristica del codice", pagina 288)

Per questa risposta, focalizzerò SQL (come richiesto nella domanda). I seguenti problemi possono verificarsi durante la memorizzazione di SQL come set à la carte di stringhe non associate:

  • La logica del database è difficile da individuare. Cosa cerchi per trovare tutte le tue istruzioni SQL? Stringhe con "SELECT", "UPDATE", "MERGE", ecc.?
  • Il refactoring degli usi dello stesso o simile SQL diventa difficile.
  • L'aggiunta del supporto per altri database è difficile. Come si potrebbe ottenere questo? Aggiungere if..quindi le istruzioni per ciascun database e archiviare tutte le query come stringhe nel metodo?
  • Gli sviluppatori leggono una dichiarazione in un'altra lingua e si distraggono dallo spostamento dell'attenzione dallo scopo del metodo ai dettagli di implementazione del metodo (come e da dove vengono recuperati i dati).
  • Sebbene le righe singole non costituiscano un problema eccessivo, le stringhe SQL incorporate iniziano a sfaldarsi man mano che le istruzioni diventano più complesse. Cosa fai con un'istruzione di riga 113? Inserisci tutte le 113 righe nel tuo metodo?
  • In che modo lo sviluppatore sposta in modo efficiente le query avanti e indietro tra il loro editor SQL (SSMS, SQL Developer, ecc.) E il loro codice sorgente? Il @prefisso di C # rende tutto più semplice, ma ho visto molto codice che cita ogni riga SQL e sfugge alle nuove righe. "SELECT col1, col2...colN"\ "FROM painfulExample"\ "WHERE maintainability IS NULL"\ "AND modification.effort > @necessary"\
  • I caratteri di rientro utilizzati per allineare l'SQL con il codice dell'applicazione circostante vengono trasmessi sulla rete ad ogni esecuzione. Ciò è probabilmente insignificante per le applicazioni su piccola scala, ma può aumentare man mano che aumenta l'utilizzo del software.

Gli ORM completi (mappatori di oggetti relazionali come Entity Framework o Hibernate) possono eliminare SQL distribuiti casualmente nel codice dell'applicazione. Il mio uso di SQL e file di risorse non è che un esempio. ORM, classi di supporto, ecc. Possono tutti aiutare a raggiungere l'obiettivo di un codice più pulito.

Come ha detto Kevin in una risposta precedente, SQL nel codice può essere accettabile in piccoli progetti, ma i progetti di grandi dimensioni iniziano come piccoli progetti e la probabilità che la maggior parte dei team tornerà indietro e lo farà correttamente è spesso inversamente proporzionale alla dimensione del codice.

Esistono molti modi semplici per mantenere SQL in un progetto. Uno dei metodi che utilizzo spesso è quello di inserire ogni istruzione SQL in un file di risorse di Visual Studio, generalmente denominato "sql". Un file di testo, un documento JSON o un'altra origine dati può essere ragionevole a seconda degli strumenti. In alcuni casi, una classe separata dedicata alla creazione di stringhe SQL può essere l'opzione migliore, ma potrebbe presentare alcuni dei problemi sopra descritti.

Esempio SQL: quale appare più elegante ?:

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 });
}

diventa

using(DbConnection connection = Database.SystemConnection()) {
    var results = connection.Query<int>(sql.GetViewable, new { Id });
}

L'SQL è sempre in un file facile da individuare o in un gruppo raggruppato di file, ognuno con un nome descrittivo che descrive ciò che fa piuttosto che come lo fa, ciascuno con spazio per un commento che non interromperà il flusso del codice dell'applicazione :

SQL in una risorsa

Questo semplice metodo esegue una query solitaria. Nella mia esperienza, il vantaggio aumenta man mano che l'uso della "lingua straniera" diventa più sofisticato. Il mio uso di un file di risorse è solo un esempio. Diversi metodi possono essere più appropriati a seconda della lingua (in questo caso SQL) e della piattaforma.

Questo e altri metodi risolvono l'elenco sopra nel modo seguente:

  1. Il codice del database è facile da individuare perché è già centralizzato. Nei progetti più grandi, raggruppa like-SQL in file separati, magari in una cartella denominata SQL.
  2. Il supporto per un secondo, terzo, ecc. Database è più semplice. Creare un'interfaccia (o astrazione in un'altra lingua) che restituisca le dichiarazioni univoche di ciascun database. L'implementazione per ogni database diventa poco più di istruzioni simili a: return SqlResource.DoTheThing;True, queste implementazioni possono saltare la risorsa e contenere l'SQL in una stringa, ma alcuni problemi (non tutti) sopra potrebbero ancora emergere.
  3. Il refactoring è semplice: basta riutilizzare la stessa risorsa. È anche possibile utilizzare la stessa voce di risorsa per sistemi DBMS diversi per la maggior parte del tempo con poche istruzioni di formato. Lo faccio spesso.
  4. L'uso della lingua secondaria può usare nomi descrittivi , ad esempio sql.GetOrdersForAccountpiuttosto che più ottusiSELECT ... FROM ... WHERE...
  5. Le istruzioni SQL vengono convocate con una riga indipendentemente dalle loro dimensioni e complessità.
  6. SQL può essere copiato e incollato tra strumenti di database come SSMS e SQL Developer senza modifiche o copie accurate. Nessuna virgoletta. Nessuna barra rovesciata finale. Nel caso specifico dell'editor di risorse di Visual Studio, un clic evidenzia l'istruzione SQL. CTRL + C e quindi incollalo nell'editor SQL.

La creazione di SQL in una risorsa è rapida, quindi c'è poco slancio per mescolare l'utilizzo delle risorse con SQL-in-code.

Indipendentemente dal metodo scelto, ho scoperto che la miscelazione delle lingue di solito riduce la qualità del codice. Spero che alcuni problemi e soluzioni qui descritti aiutino gli sviluppatori a eliminare questo odore di codice quando appropriato.


13
Penso che questo si concentri troppo su una brutta critica per avere SQL nel codice sorgente. Avere due lingue diverse nello stesso file non è necessariamente terribile. Dipende da molte cose come il modo in cui sono correlate e come sono presentate ciascuna.
jwg

9
"mescolando lingue completamente diverse con sintassi diverse nello stesso file" Questo è un argomento terribile. Si applicherebbe anche al motore di visualizzazione Razor e HTML, CSS e Javascript. Adoro i tuoi romanzi grafici!
Ian Newson,

4
Questo spinge la complessità altrove. Sì, il codice originale è più pulito, a spese dell'aggiunta di una direzione indiretta a cui lo sviluppatore deve ora accedere per motivi di manutenzione. Il vero motivo per cui lo faresti è se ogni istruzione SQL fosse usata in più punti del codice. In questo modo, non è davvero diverso da qualsiasi altro refactoring ordinario ("Metodo di estrazione").
Robert Harvey,

3
Per essere più chiari, questa non è una risposta alla domanda "cosa devo fare con le mie stringhe SQL", tanto quanto è una risposta alla domanda "cosa devo fare con qualsiasi raccolta di stringhe sufficientemente complesse", una domanda che la tua risposta si rivolge eloquentemente.
Robert Harvey,

2
@RobertHarvey: Sono sicuro che le nostre esperienze differiscono, ma nella mia ho trovato l'astrazione in questione come una vittoria nella maggior parte dei casi. Sicuramente perfezionerò i miei compromessi di astrazione come ho fatto su e giù per anni e un giorno potrei riportare SQL nel codice. YMMV. :) Penso che i microservizi possano essere fantastici, e anche in genere separano i dettagli SQL (se SQL viene utilizzato) dal codice dell'applicazione principale. Sto sostenendo meno "usa il mio metodo specifico: risorse" e più sostenendo "Generalmente non mescolare lingue di petrolio e acqua".
Charles Burns,

13

Dipende. Esistono diversi approcci che possono funzionare:

  1. Modularizza l'SQL e isolalo in un insieme separato di classi, funzioni o qualsiasi unità di astrazione utilizzata dal tuo paradigma, quindi chiamalo con la logica dell'applicazione.
  2. Sposta tutti i complessi SQL nelle viste, quindi esegui solo SQL molto semplici nella logica dell'applicazione, in modo da non dover modulare nulla.
  3. Utilizzare una libreria di mapping relazionale ad oggetto.
  4. YAGNI, basta scrivere SQL direttamente nella logica dell'applicazione.

Come spesso accade, se il tuo progetto ha già scelto una di queste tecniche, dovresti essere coerente con il resto del progetto.

(1) e (3) sono entrambi relativamente bravi a mantenere l'indipendenza tra la logica dell'applicazione e il database, nel senso che l'applicazione continuerà a compilare e superare test di fumo di base se si sostituisce il database con un altro fornitore. Tuttavia, la maggior parte dei fornitori non è completamente conforme allo standard SQL, quindi è probabile che la sostituzione di qualsiasi fornitore con qualsiasi altro fornitore richieda test approfonditi e ricerca dei bug, indipendentemente dalla tecnica utilizzata. Sono scettico sul fatto che questo sia un grosso problema come lo fanno le persone. La modifica dei database è sostanzialmente l'ultima risorsa quando non è possibile ottenere il database corrente per soddisfare le proprie esigenze. Se ciò accade, probabilmente hai scelto male il database.

La scelta tra (1) e (3) dipende principalmente da quanto ti piacciono gli ORM. Secondo me sono abusati. Sono una rappresentazione scadente del modello di dati relazionali, poiché le righe non hanno identità nel modo in cui gli oggetti hanno identità. È probabile che si verifichino punti deboli attorno a vincoli, join univoci e potresti avere difficoltà a esprimere alcune query più complicate a seconda della potenza dell'ORM. D'altra parte, (1) probabilmente richiederà molto più codice di un ORM.

(2) è visto raramente, nella mia esperienza. Il problema è che molti negozi proibiscono agli SWE di modificare direttamente lo schema del database (perché "questo è il lavoro del DBA"). Questa non è necessariamente una cosa negativa in sé e per sé; le modifiche allo schema hanno un potenziale significativo per la rottura delle cose e potrebbe essere necessario implementarle con cura. Tuttavia, affinché (2) funzioni, gli SWE dovrebbero almeno essere in grado di introdurre nuove viste e modificare le query di supporto delle viste esistenti con una burocrazia minima o nulla. Se questo non è il caso nel tuo luogo di lavoro, (2) probabilmente non funzionerà per te.

D'altra parte, se riesci a far funzionare (2), è molto meglio della maggior parte delle altre soluzioni perché mantiene la logica relazionale in SQL invece che nel codice dell'applicazione. A differenza dei linguaggi di programmazione per scopi generici, SQL è specificamente progettato per il modello di dati relazionali ed è quindi meglio nell'esprimere query e trasformazioni di dati complicate. Le viste possono anche essere trasferite insieme al resto dello schema quando si modificano i database, ma renderanno tali spostamenti più complicati.

Per le letture, le procedure memorizzate sono fondamentalmente solo una versione più scadente di (2). Non li consiglio a tale titolo, ma potresti comunque volerli per le scritture, se il tuo database non supporta le visualizzazioni aggiornabili o se devi fare qualcosa di più complesso dell'inserimento o dell'aggiornamento di una singola riga alla volta (ad es. transazioni, read-then-write, ecc.). È possibile associare la procedura memorizzata a una vista utilizzando un trigger (ad esCREATE TRIGGER trigger_name INSTEAD OF INSERT ON view_name FOR EACH ROW EXECUTE PROCEDURE procedure_name;), ma le opinioni variano considerevolmente sul fatto che si tratti effettivamente di una buona idea. I sostenitori ti diranno che mantiene l'SQL che l'applicazione esegue nel modo più semplice possibile. I detrattori ti diranno che questo è un livello inaccettabile di "magia" e che dovresti semplicemente eseguire la procedura direttamente dalla tua applicazione. Direi che questa è un'idea migliore se la stored procedure guarda o si comporta un po 'come una INSERT, UPDATEo DELETE, ed un'idea peggiore se si sta facendo qualcosa di diverso. Alla fine dovrai decidere tu stesso quale stile ha più senso.

(4) è la non soluzione. Può valerne la pena per piccoli progetti o grandi che interagiscono solo sporadicamente con il database. Ma per i progetti con un sacco di SQL, non è una buona idea perché potresti avere duplicati o variazioni della stessa query sparpagliati casualmente nell'applicazione, il che interferisce con la leggibilità e il refactoring.


2 come scritto assume essenzialmente un database di sola lettura. Le procedure memorizzate non erano insolite per le query modificate.
jpmc26,

@ jpmc26: copro gli scritti tra parentesi ... avevi una raccomandazione specifica per migliorare questa risposta?
Kevin,

Hm. Ci scusiamo per i commenti prima di completare la risposta e poi distrarti. Ma le viste aggiornabili rendono piuttosto difficile rintracciare dove viene aggiornata una tabella. Limitare gli aggiornamenti direttamente alle tabelle, indipendentemente dal meccanismo, ha i suoi vantaggi in termini di comprensione della logica del codice. Se stai vincolando la logica al DB, non puoi applicarla senza procedure memorizzate. Hanno anche il vantaggio di consentire più operazioni con una sola chiamata. In conclusione: non penso che dovresti essere così duro con loro.
jpmc26,

@ jpmc26: è giusto.
Kevin,

8

È considerato un anti-pattern per scrivere SQL nel codice sorgente?

Non necessariamente. Se leggi tutti i commenti qui troverai argomenti validi per le istruzioni SQL hardcoding nel codice sorgente.

Il problema viene da dove si posizionano le dichiarazioni . Se posizioni le istruzioni SQL in tutto il progetto, ovunque, probabilmente ignorerai alcuni dei principi SOLID che di solito ci impegniamo a seguire.

supponiamo che intendesse; o:

1) Utilizzare LINQ

o

2) Utilizzare le stored procedure per SQL

Non possiamo dire cosa intendesse dire. Tuttavia, possiamo indovinare. Ad esempio, il primo che mi viene in mente è il blocco dei fornitori . Le istruzioni SQL hardcoding possono indurre ad associare strettamente l'applicazione al motore DB. Ad esempio, utilizzando funzioni specifiche del fornitore che non sono conformi ANSI.

Questo non è necessariamente sbagliato né cattivo. Sto solo indicando il fatto.

Ignorare i principi SOLID e i blocchi dei fornitori può avere conseguenze negative che potresti ignorare. Ecco perché di solito è bello sedersi con la squadra ed esporre i propri dubbi.

Penso che il vantaggio delle procedure memorizzate sia che gli sviluppatori SQL possono essere coinvolti nel processo di sviluppo (scrittura di procedure memorizzate ecc.)

Penso che non abbia nulla a che fare con i vantaggi delle stored procedure. Inoltre, se al tuo collega non piace l'SQL codificato, è probabile che spostare l'attività nelle procedure memorizzate non piacerà anche a lui.

Modifica: il seguente link parla di istruzioni SQL codificate:  https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/hard-coded-sql-statements . C'è qualche vantaggio nel preparare un'istruzione SQL?

Sì. Il post enfatizza i vantaggi delle dichiarazioni preparate . È una sorta di modello SQL. Più sicuro della concatenazione di stringhe. Ma il post non ti incoraggia ad andare in questo modo né a confermare che hai ragione. Spiega semplicemente come possiamo usare SQL hardcoded in modo sicuro ed efficiente.

Riassumendo, prova prima a chiedere al tuo collega. Invia una mail, chiamalo, ... Sia che risponda o meno, siediti con la squadra ed esponi i tuoi dubbi. Trova la soluzione più adatta alle tue esigenze. Non fare false assunzioni basate su ciò che leggi là fuori.


1
Grazie. +1 per "Le istruzioni SQL hardcoding possono indurti ad accoppiare strettamente la tua applicazione al motore di database.". Mi chiedo se si riferisse a SQL Server piuttosto che a SQL, ovvero usando SQLConnection anziché dbConnection; SQLCommand anziché dbCommand e SQLDataReader anziché dbDataReader.
w0051977,

No. Mi riferivo all'uso di espressioni non conformi ANSI. Ad esempio: select GETDATE(), ....GETDATE () è la funzione di data di SqlServer. In altri motori, la funzione ha un nome diverso, un'espressione diversa, ...
Laiv,

14
"Blocco del fornitore" ... Con quale frequenza le persone / aziende migrano effettivamente il database del loro prodotto esistente su un altro RDBMS? Anche nei progetti che utilizzano esclusivamente ORM, non l'ho mai visto accadere: di solito anche il software viene riscritto da zero, perché le esigenze sono cambiate nel mezzo. Pertanto, pensare a questo mi sembra quasi "ottimizzazione prematura". Sì, è totalmente soggettivo.
Olivier Grégoire,

1
Non mi interessa quanto spesso succede. Mi interessa Può succedere. Nei progetti greenfield, il costo dell'implementazione di un DAL estratto dal db è quasi 0. Una possibile migrazione non lo è. Gli argomenti contro ORM sono abbastanza deboli. Gli ORM non sono come 15 anni fa quando Hibernate era abbastanza verde. Quello che ho visto sono molte configurazioni "di default" e progetti di modelli di dati piuttosto cattivi. È come molti altri strumenti, molte persone fanno il tutorial "per iniziare" e non si preoccupano di ciò che accade sotto il cofano. Lo strumento non è il problema. Il problema è a chi non sa come e quando usarlo.
Laiv,

D'altra parte, il fornitore bloccato non è necessariamente male. Se mi chiedessero direi che non mi piace . Tuttavia, sono già bloccato su Spring, Tomcat o JBoss o Websphere (ancora peggio). Quelli che posso evitare, lo faccio. Coloro che non posso, semplicemente vivo con esso. È una questione di preferenze.
Laiv

3

Penso che sia una cattiva pratica, sì. Altri hanno sottolineato i vantaggi di mantenere tutto il codice di accesso ai dati nel proprio livello. Non devi cercarlo, è più facile da ottimizzare e testare ... Ma anche all'interno di quel livello, hai alcune scelte: usare un ORM, usare sprocs o incorporare query SQL come stringhe. Direi che le stringhe di query SQL sono di gran lunga l'opzione peggiore.

Con un ORM, lo sviluppo diventa molto più semplice e meno soggetto a errori. Con EF, definisci il tuo schema semplicemente creando le classi del tuo modello (avresti comunque dovuto creare queste classi). Interrogare con LINQ è un gioco da ragazzi: spesso ti allontani con 2 righe di c # dove altrimenti dovresti scrivere e mantenere uno sproc. IMO, questo ha un enorme vantaggio in termini di produttività e manutenibilità: meno codice, meno problemi. Ma c'è un sovraccarico di prestazioni, anche se sai cosa stai facendo.

Sprocs (o funzioni) sono l'altra opzione. Qui, scrivi le tue query SQL manualmente. Ma almeno hai qualche garanzia che siano corretti. Se lavori in .NET, Visual Studio genererà anche errori del compilatore se SQL non è valido. Questo è fantastico Se modifichi o rimuovi alcune colonne e alcune delle tue query diventano non valide, almeno è probabile che lo scoprirai durante la compilazione. È anche molto più semplice mantenere gli sprocs nei propri file: probabilmente otterrai l'evidenziazione della sintassi, completamento automatico ... ecc.

Se le query vengono archiviate come sprocs, è anche possibile modificarle senza ricompilare e ridistribuire l'applicazione. Se noti che qualcosa nel tuo SQL è rotto, un DBA può semplicemente risolverlo senza bisogno di avere accesso al codice dell'app. In generale, se le tue query sono in letterali stringa, non puoi dbas fare altrettanto facilmente.

Le query SQL come valori letterali delle stringhe renderanno anche meno leggibile il codice c # dell'accesso ai dati.

Proprio come una regola empirica, le costanti magiche sono cattive. Ciò include i letterali stringa.


Consentire ai DBA di modificare il proprio SQL senza aggiornare l'applicazione significa dividere efficacemente l'applicazione in due entità sviluppate separatamente e distribuite separatamente. Ora devi gestire il contratto di interfaccia tra quelli, sincronizzare il rilascio di modifiche interdipendenti, ecc. Questa potrebbe essere una cosa positiva o negativa, ma è molto più che "mettere tutto il tuo SQL in un posto", è molto lontano decisione architettonica di sensibilizzazione.
IMSoP,

2

Non è un anti-schema (questa risposta è pericolosamente vicina all'opinione). Ma la formattazione del codice è importante e la stringa SQL deve essere formattata in modo che sia chiaramente separata dal codice che la utilizza. Per esempio

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

7
Il problema più evidente con questo è il problema della provenienza di quel valore 42. Stai concatenando quel valore nella stringa? In tal caso, da dove viene? Come è stato rimosso per mitigare gli attacchi SQL injection? Perché questo esempio non mostra una query con parametri?
Craig,

Volevo solo mostrare la formattazione. Scusa, ho dimenticato di parametrizzarlo. Ora fisso dopo aver fatto riferimento al msdn.microsoft.com/library/bb738521(v=vs.100).aspx
rleir

+1 per importanza della formattazione, anche se sostengo che le stringhe SQL hanno un odore di codice indipendentemente dalla formattazione.
Charles Burns,

1
Insisti sull'uso di un ORM? Quando un ORM non è in uso, per un progetto più piccolo, utilizzo una classe "shim" contenente SQL incorporato.
rleir,

Le stringhe SQL non sono sicuramente un odore di codice. Senza un ORM, in qualità di manager e architetto, li incoraggio su SP per motivi di manutenzione e talvolta di prestazioni.
Sentinel,

1

Non posso dirti cosa intendesse il tuo collega. Ma posso rispondere a questo:

C'è qualche vantaggio nel preparare un'istruzione SQL?

SI . L'uso di istruzioni preparate con variabili associate è la difesa consigliata contro l' iniezione di SQL , che rimane il più grande rischio per la sicurezza delle applicazioni Web per oltre un decennio . Questo attacco è così comune che è stato descritto nei fumetti web quasi dieci anni fa ed è giunto il momento di implementare difese efficaci.

... e la difesa più efficace alla cattiva concatenazione delle stringhe di query non è rappresentando in primo luogo le query come stringhe, ma utilizzare un'API di query di tipo sicuro per creare query. Ad esempio, ecco come scriverei la tua query in Java con QueryDSL:

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

Come puoi vedere, non c'è una sola stringa letterale qui, rendendo quasi impossibile l'iniezione di SQL. Inoltre, ho avuto il completamento del codice durante la scrittura di questo, e il mio IDE può refactoring se dovessi scegliere di rinominare mai la colonna ID. Inoltre, posso facilmente trovare tutte le query per la tabella delle persone chiedendo al mio IDE dove personsi accede alla variabile. Oh, e hai notato che è un po 'più corto e più facile da vedere rispetto al tuo codice?

Posso solo supporre che qualcosa di simile sia disponibile anche per C #. Ad esempio, sento grandi cose su LINQ.

In breve, rappresentare le query come stringhe SQL rende difficile per l'IDE un supporto significativo nella scrittura e nel refactoring delle query, difende il rilevamento della sintassi e digita gli errori dal tempo di compilazione al tempo di esecuzione ed è una causa che contribuisce alle vulnerabilità dell'iniezione SQL [1]. Quindi sì, ci sono validi motivi per cui uno potrebbe non voler stringhe SQL nel codice sorgente.

[1]: Sì, l'uso corretto delle istruzioni preparate impedisce anche l'iniezione di SQL. Ma che un'altra risposta in questo thread è stata vulnerabile a un'iniezione SQL fino a quando un commentatore non l'ha sottolineato non ispira fiducia nei programmatori junior che li usano correttamente ogni volta che è necessario ...


Per essere chiari: l'utilizzo di istruzioni preparate non impedisce l'iniezione di SQL. L'uso di istruzioni preparate con variabili associate lo fa. È possibile utilizzare istruzioni preparate basate su dati non attendibili.
Andy Lester,

1

Molte grandi risposte qui. A parte ciò che è già stato detto, vorrei aggiungere un'altra cosa.

Non considero l'uso di SQL inline un anti-pattern. Quello che faccio, tuttavia, considero un anti-schema è che ogni programmatore fa le proprie cose. Devi decidere come una squadra su uno standard comune. Se tutti usano linq, allora usi linq, se tutti usano viste e stored procedure, lo fai.

Tieni sempre una mente aperta e non innamorarti dei dogmi. Se il tuo negozio è un negozio "linq", lo usi. Tranne quello in cui Linq non funziona assolutamente. (Ma non dovresti il ​​99,9% delle volte.)

Quindi quello che vorrei fare è ricercare il codice nella base di codice e controllare come funzionano i tuoi colleghi.


+1 buon punto. La compatibilità è più importante della maggior parte delle altre considerazioni, quindi non essere troppo intelligente :) Detto questo, se sei un negozio ORM non dimenticare che ci sono casi in cui ORM non è la risposta e devi scrivere SQL diretto. Non ottimizzare prematuramente, ma ci saranno momenti in qualsiasi applicazione non banale in cui dovrai percorrere questa strada.
mcottle,

0

Il codice è simile al livello di interazione del database che si trova tra il database e il resto del codice. Se proprio così, questo non è anti-pattern.

Questo metodo È la parte del livello, anche se OP la pensa diversamente (accesso semplice al database, nessuna miscelazione con nient'altro). Forse è semplicemente inserito male nel codice. Questo metodo appartiene alla classe separata "strato di interfacciamento del database". Sarebbe anti-modello posizionarlo all'interno della classe ordinaria accanto ai metodi che implementano attività non di database.

L'anti-pattern sarebbe quello di chiamare SQL da qualche altra posizione casuale di codice. È necessario definire un livello tra il database e il resto del codice, ma non è assolutamente necessario utilizzare alcuni strumenti popolari che potrebbero esservi utili.


0

Secondo me no non è un anti pattern ma ti ritroverai a occuparti della spaziatura di formattazione delle stringhe, specialmente per le grandi query che non possono rientrare in una riga.

un altro vantaggio è che diventa molto più difficile quando si gestiscono query dinamiche che dovrebbero essere costruite in base a determinate condizioni.

var query = "select * from accounts";

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

Suggerisco di utilizzare un generatore di query sql


Forse stai usando solo una scorciatoia ma è una pessima idea usare ciecamente "SELEZIONA *" perché riporterai i campi che non ti servono (larghezza di banda !!) Anche se non ho un problema con la clausola Admin condizionale conduce lungo un pendio molto scivoloso a una clausola "WHERE" condizionale e questa è una strada che porta direttamente all'inferno delle prestazioni.
mcottle,

0

Per molte organizzazioni moderne con pipeline di distribuzione rapida in cui le modifiche al codice possono essere compilate, testate e portate alla produzione in pochi minuti, ha perfettamente senso incorporare le istruzioni SQL nel codice dell'applicazione. Questo non è necessariamente un anti-pattern a seconda di come lo fai.

Un caso valido per l'utilizzo di procedure memorizzate oggi è nelle organizzazioni in cui vi è un sacco di processo attorno alle distribuzioni di produzione che può comportare settimane di cicli di controllo qualità ecc. In questo scenario il team DBA può risolvere i problemi di prestazioni che emergono solo dopo la distribuzione di produzione iniziale ottimizzando le query nelle stored procedure.

A meno che non ti trovi in ​​questa situazione, ti consiglio di incorporare il tuo SQL nel codice dell'applicazione. Leggi le altre risposte in questo thread per consigli sul modo migliore per procedere.


Testo originale di seguito per il contesto

Il vantaggio principale delle procedure memorizzate è che è possibile ottimizzare le prestazioni del database senza ricompilare e ridistribuire l'applicazione.

In molte organizzazioni il codice può essere trasferito alla produzione solo dopo un ciclo di controllo qualità ecc. Che può richiedere giorni o settimane. Se il sistema sviluppa un problema di prestazioni del database a causa della modifica dei modelli di utilizzo o dell'aumento delle dimensioni della tabella, ecc., Può essere abbastanza difficile rintracciare il problema con query codificate, mentre il database avrà strumenti / report, ecc. Che identificheranno le procedure memorizzate. . Con le procedure memorizzate le soluzioni possono essere testate / confrontate in produzione e il problema può essere risolto rapidamente senza dover inviare una nuova versione del software applicativo.

Se questo problema non è importante nel tuo sistema, è una decisione perfettamente valida inserire codice SQL nell'applicazione.


Sbagliato ...........
Sentinel

Pensi che il tuo commento sia utile a nessuno?
bikeman868,

Questo è semplicemente errato. Supponiamo di avere un progetto Web API2 con EF come accesso ai dati e Azure Sql come archivio. No, sbarazziamoci di EF e utilizziamo l'SQL artigianale. Lo schema db è archiviato in un progetto SSDT SQL Server. La migrazione dello schema db implica che la base di codice sia sincronizzata con lo schema db e completamente testata, prima di inviare la modifica a un test db in Azure. Una modifica del codice comporta il trasferimento in uno slot di distribuzione del servizio app, che richiede 2 secondi. Inoltre, l'SQL realizzato a mano ha prestazioni elevate rispetto alle procedure memorizzate, in generale, nonostante la "saggezza" contraria.
Sentinel

Nel tuo caso la distribuzione potrebbe richiedere alcuni secondi, ma questo non è il caso di molte persone. Non ho detto che le stored procedure siano migliori, solo che presentano alcuni vantaggi in alcune circostanze. La mia ultima affermazione è che se queste circostanze non si applicano, è molto valido inserire SQL nell'applicazione. In che modo ciò contraddice ciò che stai dicendo?
bikeman868,

Le procedure memorizzate sono SQL create a mano ???
bikeman868,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.