Est-ce considéré comme un anti-modèle d'écrire SQL dans le code source?

87

Est-ce considéré comme un anti-modèle pour coder en dur le code SQL dans une application comme celle-ci:

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

J'aurais normalement une couche de référentiel, etc., mais je l'ai exclue dans le code ci-dessus pour des raisons de simplicité.

J'ai récemment eu des commentaires d'un collègue qui s'est plaint que SQL a été écrit dans le code source. Je n'ai pas eu l'occasion de demander pourquoi et il est maintenant absent pendant deux semaines (peut-être plus). Je suppose qu'il voulait dire soit:

  1. Utilisez LINQ
    ou
  2. Utiliser des procédures stockées pour le SQL

Ai-je raison? Est-ce considéré comme un anti-modèle d'écrire SQL dans le code source? Nous sommes une petite équipe travaillant sur ce projet. Je pense que l’avantage des procédures stockées est que les développeurs SQL peuvent s’impliquer dans le processus de développement (écriture de procédures stockées, etc.).

Modifier Le lien suivant concerne les instructions SQL codées en dur: https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/hard-coded-sql-statements . La préparation d'une instruction SQL présente-t-elle un avantage?

w0051977
la source
31
"Utilisez Linq" et "Utilisez des procédures stockées" ne sont pas des raisons; ce ne sont que des suggestions. Attendez deux semaines et demandez-lui les raisons.
Robert Harvey
47
Le réseau Stack Exchange utilise un micro-ORM appelé Dapper. Je pense qu'il est raisonnable de dire que la grande majorité du code Dapper est du "code codé en dur" (plus ou moins). Donc, si c'est une mauvaise pratique, c'est une mauvaise pratique adoptée par l'une des applications Web les plus en vue de la planète.
Robert Harvey
16
Pour répondre à votre question sur les référentiels, SQL codé en dur est toujours codé en dur, où que vous soyez. La différence est que le référentiel vous fournit un emplacement pour encapsuler le code SQL codé en dur. C'est une couche d'abstraction qui cache les détails du code SQL du reste du programme.
Robert Harvey
26
Non, SQL dans le code n'est pas un anti-modèle. Mais cela représente énormément de code de plaque de chaudière pour une requête SQL simple.
GrandmasterB
45
Il y a une différence entre 'dans le code source' et 'répandu sur tout le code source'
tofro

Réponses:

112

Vous avez exclu la partie cruciale pour la simplicité. Le référentiel est la couche d'abstraction pour la persistance. Nous séparons la persistance dans sa propre couche afin de pouvoir modifier plus facilement la technologie de persistance lorsque nous en avons besoin. Par conséquent, le fait que SQL se trouve en dehors de la couche de persistance empêche complètement d'avoir une couche de persistance séparée.

En conséquence, SQL convient à la couche de persistance propre à une technologie SQL (par exemple, SQL convient à a SQLCustomerRepositorymais pas à a MongoCustomerRepository). En dehors de la couche de persistance, SQL rompt votre abstraction et est donc considéré comme une très mauvaise pratique (par moi).

En ce qui concerne des outils tels que LINQ ou JPQL: Ceux-ci peuvent simplement résumer les variantes de SQL. Avoir des requêtes LINQ-Code ou JPQL en dehors d'un référentiel annule l'abstraction de la persistance autant que le ferait un SQL brut.


Un autre avantage considérable d’une couche de persistance distincte est qu’elle vous permet de désendetter votre code de logique métier sans avoir à configurer un serveur de base de données.

Vous obtenez des tests unitaires rapides avec un profil de mémoire faible et des résultats reproductibles sur toutes les plateformes prises en charge par votre langue.

Dans une architecture de service MVC +, il s’agit simplement de simuler l’instance de référentiel, de créer des données fictives en mémoire et de définir que le référentiel doit renvoyer ces données fictives lorsqu’un getter donné est appelé. Vous pouvez ensuite définir les données de test par test et ne plus avoir à nettoyer la base de données par la suite.

Le test des écritures dans la base de données est aussi simple: vérifiez que les méthodes de mise à jour appropriées sur la couche de persistance ont été appelées et assurez-vous que les entités étaient dans l'état correct lorsque cela s'est produit.

Marstato
la source
80
L'idée selon laquelle "nous pouvons changer la technologie de persistance" est irréaliste et inutile dans la grande majorité des projets du monde réel. Une base de données SQL / relationnelle est une bête complètement différente des bases de données NoSQL telles que MongoDB. Voir cet article détaillé à ce sujet. Tout au plus, un projet qui a commencé à utiliser NoSQL finirait-il par comprendre leur erreur et ensuite basculer vers un SGBDR. D'après mon expérience, autoriser l'utilisation directe d'un langage de requête orienté objet (tel que JPA-QL) à partir du code métier fournit les meilleurs résultats.
Rogério
6
Que se passe-t-il s'il y a très peu de chance que la couche de persistance soit modifiée? Que faire s'il n'y a pas de mappage un à un lors du changement de couche de persistance? Quel est l'avantage du niveau supplémentaire d'abstraction?
Pllee
19
@ Rogério Migrer d'un magasin NoSQL vers un SGBDR, ou inversement, n'est probablement pas réaliste, comme vous le dites (en supposant que le choix de la technologie était approprié). Cependant, j'ai participé à un certain nombre de projets concrets dans lesquels nous avons migré d'un SGBDR à un autre; dans ce scénario, une couche de persistance encapsulée est certainement un avantage.
David
6
"L'idée selon laquelle" nous pouvons changer la technologie de persistance "est irréaliste et inutile dans la grande majorité des projets du monde réel." Mon entreprise avait précédemment écrit du code sur cette hypothèse. Nous avons dû refactoriser l’ensemble de la base de code pour prendre en charge non pas un mais trois systèmes de stockage / requête entièrement différents et envisageons un troisième et un quatrième. Pire, même lorsque nous n’avions qu’une seule base de données, le manque de consolidation entraînait toutes sortes de péchés de code.
NPSF3000
3
@ Rogério Vous connaissez "la grande majorité des projets du monde réel"? Dans mon entreprise, la plupart des applications peuvent fonctionner avec l’un des nombreux SGBD courants, ce qui est très utile, car la plupart de nos clients ont des exigences non fonctionnelles à cet égard.
BartoszKP
55

La plupart des applications métier standard actuelles utilisent différentes couches avec différentes responsabilités. Cependant, quelles couches vous utilisez pour votre application et quelle couche a la responsabilité qui vous revient, à vous et à votre équipe. Avant de pouvoir décider s'il est correct ou non de placer directement le code SQL dans la fonction que vous nous avez montrée, vous devez le savoir.

  • quelle couche dans votre application a quelle responsabilité

  • de quelle couche la fonction ci-dessus est de

Il n’existe pas de solution universelle. Dans certaines applications, les concepteurs préfèrent utiliser un framework ORM et laisser le framework générer tout le code SQL. Dans certaines applications, les concepteurs préfèrent stocker un tel SQL exclusivement dans des procédures stockées. Pour certaines applications, il existe une couche de persistance (ou référentiel) écrite à la main, où réside le code SQL. Pour d'autres applications, il est correct de définir des exceptions dans certaines circonstances en plaçant strictement le code SQL dans cette couche de persistance.

Vous devez donc réfléchir aux éléments suivants: quelles couches souhaitez- vous ou avez-vous besoin dans votre application particulière et comment souhaitez- vous assumer les responsabilités? Vous avez écrit "J'aurais normalement une couche de référentiel" , mais quelles sont les responsabilités exactes que vous souhaitez avoir à l'intérieur de cette couche et quelles responsabilités souhaitez-vous placer ailleurs? Répondez à cela en premier, vous pourrez alors répondre à votre question par vous-même.

Doc Brown
la source
1
J'ai édité la question. Je ne sais pas si cela nous éclaire.
w0051977
18
@ w0051977: le lien que vous avez posté ne nous dit rien sur votre candidature, non? Il semblerait que vous recherchiez une règle simple, braindead, pour placer du code SQL ou non qui convient à toutes les applications. Il n'y en a pas. C’est une décision de conception que vous devez prendre concernant votre application individuelle (probablement avec votre équipe).
Doc Brown
7
+1 En particulier, je ferais remarquer qu'il n'y a pas de réelle différence entre SQL dans une méthode et SQL dans une procédure stockée, en termes de qualité du code "codé en dur", de réutilisation, de maintenance, etc. Toute abstraction que vous pouvez effectuer avec un procédure peut être fait avec une méthode. Bien sûr, les procédures peuvent être utiles, mais une règle de base "nous ne pouvons pas avoir de SQL dans les méthodes, alors mettons tout dans les procédures" ne produira probablement que beaucoup de choses cruelles avec peu d’avantages.
1
@ user82096 Je dirais qu'en général, mettre du code d'accès à la base de données dans les SP a été préjudiciable dans presque tous les projets que j'ai vus. (Pile MS) Outre les dégradations de performances réelles (caches de plan d'exécution rigides), la maintenance du système a été rendue plus difficile par la division de la logique et la maintenance des SP par un groupe de personnes différent de celui des développeurs de la logique d'application. Surtout sur Azure où les changements de code entre les emplacements de déploiement représentent des secondes de travail, mais les migrations de schéma sans code d’abord / sans base de données entre les emplacements constituent un casse-tête opérationnel.
Sentinel
33

Marstato donne une bonne réponse, mais j'aimerais ajouter quelques commentaires.

SQL dans la source n'est pas un anti-modèle, mais cela peut causer des problèmes. Je me souviens de l'époque où vous deviez placer des requêtes SQL dans les propriétés des composants déposés dans chaque formulaire. Cela rendait les choses vraiment très moche très vite et il fallait sauter à travers des cerceaux pour localiser les requêtes. Je suis devenu un ardent défenseur de la centralisation maximale de l'accès à la base de données dans les limites des langues avec lesquelles je travaillais. Votre collègue pourrait avoir des flash-back à ces jours sombres.

Maintenant, certains commentaires parlent du blocage des fournisseurs, comme si c'était automatiquement une mauvaise chose. Ce n'est pas. Si je signe un chèque à six chiffres chaque année pour utiliser Oracle, vous pouvez parier que je veux que toute application ayant accès à cette base de données utilise correctement la syntaxe Oracle supplémentairemais à son maximum. Je ne serais pas heureux si ma brillante base de données est paralysée par les codeurs qui écrivent mal le SQL ANSI vanille alors qu’il existe une "manière Oracle" d’écrire le code SQL qui ne paralyse pas la base de données. Oui, il sera plus difficile de modifier les bases de données, mais je ne l'ai vu faire que plusieurs fois en plus de 20 ans sur un grand site client. L'un de ces cas était celui de DB2 -> Oracle, car l'ordinateur central hébergeant DB2 était obsolète et mis hors service. . Oui, c’est le blocage du fournisseur, mais pour les entreprises, il est en fait souhaitable de payer pour un SGBDR capable et coûteux comme Oracle ou Microsoft SQL Server, puis de l’utiliser au maximum. Vous avez un contrat d'assistance comme couverture confortable. Si je paie pour une base de données avec une implémentation de procédure stockée riche,

Cela nous amène au point suivant: si vous écrivez une application qui accède à une base de données SQL, vous devez apprendre le SQL ainsi que l’autre langage. En apprenant, je veux aussi dire l’optimisation des requêtes; Je vais me fâcher contre vous si vous écrivez du code de génération SQL qui vide le cache SQL avec un barrage de requêtes presque identiques alors que vous auriez pu utiliser une requête intelligemment paramétrée.

Pas d'excuses, pas de cachettes derrière un mur d'Hibernate. Les ORM mal utilisés peuvent vraiment nuire aux performances des applications. Je me souviens avoir vu une question sur Stack Overflow il y a quelques années dans le sens de:

Dans Hibernate, nous parcourons 250 000 enregistrements en vérifiant les valeurs de quelques propriétés et en mettant à jour des objets correspondant à certaines conditions. C'est un peu lent, que puis-je faire pour l'accélérer?

Que diriez-vous de "table UPDATE SET champ1 = où champ2 est vrai et champ3> 100". La création et l'élimination de 250 000 objets pourraient bien être votre problème ...

Par exemple, Ignorez Hibernate lorsqu'il n'est pas approprié de l'utiliser. Comprendre la base de données.

Donc, en résumé, incorporer du SQL dans du code peut être une mauvaise pratique, mais il y a des choses bien pires que vous pouvez faire en essayant d'éviter l'incorporation de SQL.

mcottle
la source
5
Je n'ai pas dit que le "blocage du vendeur" était une mauvaise chose. J'ai dit que cela vient avec des compromis. Sur le marché actuel avec db as xaaS, ces anciens contrats et licences d'exploitation de db auto-hébergée vont être de plus en plus rares. Il est bien plus facile de changer la base de données de fournisseur A à B par rapport à ce qu’elle était il ya 10 ans. Si vous lisez les commentaires, je ne suis pas en faveur de tirer parti de toute caractéristique du stockage de données. Il est donc facile de mettre des détails spécifiques du fournisseur dans quelque chose (application) qui se veut agnostique. Nous nous efforçons de suivre SOLiD jusqu’à la fin pour verrouiller le code sur un seul stockage de données. Pas un gros problème
Laiv
2
C'est une excellente réponse. Souvent, la bonne approche est celle qui conduit à la moins mauvaise situation si le pire code possible est écrit. Le pire code ORM possible pourrait être bien pire que le pire imbriqué SQL.
Jwg
@Laiv bons points La PaaS change la donne - il faudra que je réfléchisse davantage aux conséquences.
Mcottle
3
@jwg Je serais enclin à dire qu'un code ORM supérieur à la moyenne est pire que un code SQL intégré inférieur à la moyenne :)
mcottle
@macottle En supposant que le code Embedded SQL inférieur à la moyenne a été écrit par ppl et supérieur à la moyenne de celui qui écrit un ORM. Ce dont je doute beaucoup. Beaucoup de développeurs ne sont pas capables d’écrire des JOINs de gauche sans vérifier SO en premier.
Laiv
14

Oui, coder en dur les chaînes SQL en code d'application est généralement un anti-motif.

Essayons de mettre de côté la tolérance que nous avons développée depuis des années à voir cela dans le code de production. Mélanger des langues complètement différentes avec une syntaxe différente dans le même fichier n'est généralement pas une technique de développement souhaitable. Cela diffère des modèles de langage comme Razor, conçus pour donner une signification contextuelle à plusieurs langues. Comme Sava B. le mentionne dans un commentaire ci-dessous, SQL en C # ou dans un autre langage d'application (Python, C ++, etc.) est une chaîne comme une autre et n'a aucune signification sémantique. La même chose s’applique lorsqu’on mélange plusieurs langues dans la plupart des cas, bien qu’il soit évident que cela est acceptable, comme assemblage en ligne en C, petits extraits compréhensibles de CSS en HTML (notant que CSS est conçu pour être mélangé avec HTML ), et d'autres.

Clean Code de Robert C. Martin, p.  288 (Robert C. Martin sur le mélange des langues, Clean Code , Chapitre 17, "Odeurs de code et heuristiques", page 288)

Pour cette réponse, je vais me concentrer sur SQL (comme demandé dans la question). Les problèmes suivants peuvent survenir lors du stockage de SQL en tant qu'ensemble à la carte de chaînes dissociées:

  • La logique de la base de données est difficile à localiser. Que recherchez-vous pour trouver toutes vos instructions SQL? Des chaînes avec "SELECT", "UPDATE", "MERGE", etc.?
  • Le refactoring utilise un SQL identique ou similaire devient difficile.
  • L'ajout d'un support pour d'autres bases de données est difficile. Comment pourrait-on accomplir cela? Ajouter des instructions if..then pour chaque base de données et stocker toutes les requêtes sous forme de chaînes dans la méthode?
  • Les développeurs lisent une déclaration dans une autre langue et sont distraits par le changement d'orientation de l'objectif de la méthode vers les détails de son implémentation (comment et à partir de l'endroit où les données sont récupérées).
  • Bien que les un-lignes ne posent pas trop de problèmes, les chaînes SQL en ligne commencent à s'effriter à mesure que les instructions deviennent plus complexes. Que faites-vous avec une déclaration de 113 lignes? Mettez les 113 lignes dans votre méthode?
  • Comment le développeur déplace-t-il efficacement les requêtes entre leur éditeur SQL (SSMS, SQL Developer, etc.) et leur code source? Le @préfixe C # facilite cela, mais j'ai vu beaucoup de code qui cite chaque ligne SQL et échappe aux nouvelles lignes. "SELECT col1, col2...colN"\ "FROM painfulExample"\ "WHERE maintainability IS NULL"\ "AND modification.effort > @necessary"\
  • Les caractères d'indentation utilisés pour aligner le code SQL avec le code d'application environnant sont transmis sur le réseau à chaque exécution. Ceci est probablement insignifiant pour les applications à petite échelle, mais cela peut s’additionner à mesure que l’utilisation du logiciel se développe.

Les ORM complets (mappeurs objet-relationnels tels qu'Entity Framework ou Hibernate) peuvent éliminer le code SQL modifié au hasard. Mon utilisation de SQL et des fichiers de ressources n’est qu’un exemple. Les ORM, les classes d'assistance, etc. peuvent tous contribuer à atteindre l'objectif d'un code plus propre.

Comme Kevin l'a dit dans une réponse précédente, le code SQL peut être acceptable dans les petits projets, mais les grands projets démarrent sous la forme de petits projets et la probabilité que la plupart des équipes y reviennent et le fasse correctement est souvent inversement proportionnelle à la taille du code.

Il existe de nombreuses manières simples de conserver SQL dans un projet. L’une des méthodes que j’utilise souvent est de placer chaque instruction SQL dans un fichier de ressources Visual Studio, généralement nommé "sql". Un fichier texte, un document JSON ou une autre source de données peut être raisonnable en fonction de vos outils. Dans certains cas, une classe distincte dédiée à l'enchaînement de chaînes SQL peut être la meilleure option, mais peut présenter certains des problèmes décrits ci-dessus.

Exemple SQL: Qu'est-ce qui semble plus élégant?

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

Devient

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

Le code SQL se trouve toujours dans un fichier ou un ensemble de fichiers facile à localiser, chacun avec un nom descriptif qui décrit ce qu'il fait plutôt que comment il le fait, chacun avec de la place pour un commentaire qui n'interrompra pas le flux de code d'application :

SQL dans une ressource

Cette méthode simple exécute une requête solitaire. D'après mon expérience, les avantages sont proportionnels à l'utilisation de la "langue étrangère". Mon utilisation d'un fichier de ressources n'est qu'un exemple. Différentes méthodes peuvent être plus appropriées en fonction du langage (SQL dans ce cas) et de la plate-forme.

Ceci et d'autres méthodes résolvent la liste ci-dessus de la manière suivante:

  1. Le code de base de données est facile à localiser car il est déjà centralisé. Dans les projets plus importants, regroupez like-SQL dans des fichiers distincts, éventuellement dans un dossier nommé SQL.
  2. Le support pour une deuxième, troisième, etc. bases de données est plus facile. Créez une interface (ou une autre abstraction de langage) qui renvoie les instructions uniques de chaque base de données. L'implémentation de chaque base de données devient un peu plus que des instructions similaires à: return SqlResource.DoTheThing;True, ces implémentations peuvent ignorer la ressource et contenir le code SQL dans une chaîne, mais certains problèmes (mais pas tous) mentionnés ci-dessus ne feraient que surface.
  3. Le refactoring est simple: il suffit de réutiliser la même ressource. Vous pouvez même utiliser la même entrée de ressource pour différents systèmes de SGBD avec quelques instructions de format. Je le fais souvent.
  4. L’utilisation de la langue secondaire peut utiliser des noms descriptifs , par exemple sql.GetOrdersForAccountplutôt que plus obtusSELECT ... FROM ... WHERE...
  5. Les instructions SQL sont appelées avec une ligne, quelles que soient leur taille et leur complexité.
  6. Le code SQL peut être copié et collé entre des outils de base de données tels que SSMS et SQL Developer, sans modification ni copie soigneuse. Pas de guillemets. Pas de barres obliques inverses. Dans le cas de l'éditeur de ressources Visual Studio en particulier, un clic met en surbrillance l'instruction SQL. CTRL + C puis collez-le dans l'éditeur SQL.

La création de SQL dans une ressource est rapide, il y a donc peu d’impulsion pour combiner l’utilisation des ressources avec SQL dans le code.

Quelle que soit la méthode choisie, j'ai constaté que le mélange de langages réduisait généralement la qualité du code. J'espère que certains problèmes et solutions décrits ici aideront les développeurs à éliminer cette odeur de code, le cas échéant.

Charles Burns
la source
13
Je pense que cela se concentre beaucoup trop sur une mauvaise critique d'avoir SQL dans le code source. Avoir deux langues différentes dans le même fichier n'est pas forcément terrible. Cela dépend de beaucoup de choses, comme la façon dont ils sont liés et comment ils sont présentés.
Jwg
9
"mélanger des langues complètement différentes avec une syntaxe différente dans le même fichier" Cet argument est terrible. Cela s'appliquerait également au moteur d'affichage Razor, ainsi qu'à HTML, CSS et Javascript. Aimez vos romans graphiques!
Ian Newson
4
Cela ne fait que pousser la complexité ailleurs. Oui, votre code d'origine est plus propre, au détriment de l'ajout d'une indirection vers laquelle le développeur doit maintenant naviguer à des fins de maintenance. La vraie raison pour cela est que chaque instruction SQL a été utilisée à plusieurs endroits dans le code. De cette façon, ce n'est vraiment pas différent de tout autre refactoring ordinaire ("méthode d'extraction").
Robert Harvey
3
Pour être plus clair, ce n’est pas une réponse à la question "que dois-je faire avec mes chaînes SQL", c’est une réponse à la question "qu’est-ce que je fais avec une collection de chaînes suffisamment complexes"? votre réponse aborde avec éloquence.
Robert Harvey
2
@RobertHarvey: Je suis sûr que nos expériences diffèrent, mais dans la mienne, l'abstraction en question a été gagnante dans la plupart des cas. Je vais sûrement affiner mes compromis d'abstraction, comme je l'ai fait pendant des années, et je pourrais un jour remettre SQL dans le code. YMMV. :) Je pense que les microservices peuvent être formidables et qu'ils séparent aussi généralement vos détails SQL (si du SQL est utilisé) du code de l'application principale. Je préconise moins "d'utiliser ma méthode spécifique: ressources" et davantage "en général, ne mélangez pas les langages de l'huile et de l'eau."
Charles Burns
13

Ça dépend. Un certain nombre d'approches différentes peuvent fonctionner:

  1. Modularisez le code SQL et isolez-le dans un ensemble séparé de classes, de fonctions ou de toute unité d'abstraction utilisée par votre paradigme, puis appelez-le avec la logique d'application.
  2. Déplacez tout le SQL complexe dans les vues, puis ne créez que du SQL très simple dans la logique de l'application, de sorte que vous n'avez pas besoin de modulariser.
  3. Utilisez une bibliothèque de mappage relationnel-objet.
  4. YAGNI, il suffit d'écrire SQL directement dans la logique de l'application.

Comme c'est souvent le cas, si votre projet a déjà choisi l'une de ces techniques, vous devez être cohérent avec le reste du projet.

(1) et (3) maintiennent relativement bien l'indépendance entre la logique de l'application et la base de données, en ce sens que l'application continuera à compiler et à réussir les tests de fumée de base si vous remplacez la base de données par un autre fournisseur. Cependant, la plupart des fournisseurs ne se conformant pas totalement à la norme SQL, leur remplacement par un autre fournisseur nécessitera probablement des tests et une recherche de bogues approfondis, quelle que soit la technique utilisée. Je suis sceptique quant à la pertinence de cet accord. Changer de base de données est fondamentalement un dernier recours lorsque vous ne pouvez pas obtenir la base de données actuelle pour répondre à vos besoins. Si cela se produit, vous avez probablement mal choisi la base de données.

Le choix entre (1) et (3) dépend principalement de votre goût pour les ORM. À mon avis, ils sont trop utilisés. Ils représentent mal le modèle de données relationnel, car les lignes n'ont pas d'identité de la même manière que les objets. Vous rencontrerez probablement des problèmes avec des contraintes uniques, des jointures, et vous aurez peut-être de la difficulté à exprimer des requêtes plus complexes en fonction de la puissance de l'ORM. D'autre part, (1) nécessitera probablement beaucoup plus de code qu'un ORM.

(2) est rarement vu, dans mon expérience. Le problème est que beaucoup de magasins interdisent aux entreprises de taille moyenne de modifier directement le schéma de la base de données (car "c'est le travail du DBA"). Ce n'est pas nécessairement une mauvaise chose en soi; Les modifications de schéma ont un potentiel important de rupture et doivent être déployées avec précaution. Toutefois, pour que (2) fonctionne, les SWE doivent au moins pouvoir introduire de nouvelles vues et modifier les requêtes de sauvegarde des vues existantes avec une bureaucratie minimale ou nulle. Si ce n'est pas le cas sur votre lieu de travail, (2) ne fonctionnera probablement pas pour vous.

D'un autre côté, si vous pouvez obtenir (2) de fonctionner, c'est beaucoup mieux que la plupart des autres solutions, car la logique relationnelle est conservée dans SQL plutôt que dans le code de l'application. Contrairement aux langages de programmation d'usage général, SQL est spécialement conçu pour le modèle de données relationnel et est par conséquent plus apte à exprimer des requêtes et des transformations de données complexes. Les vues peuvent également être portées avec le reste de votre schéma lors du changement de bases de données, mais elles rendront ces déplacements plus compliqués.

Pour les lectures, les procédures stockées ne sont fondamentalement qu'une version plus déplorable de (2). Je ne les recommande pas en cette qualité, mais vous pouvez toujours les vouloir pour les écritures, si votre base de données ne prend pas en charge les vues pouvant être mises à jour , ou si vous devez effectuer quelque chose de plus complexe que l'insertion ou la mise à jour d'une seule ligne à la fois (par exemple, transactions, lecture / écriture, etc.). Vous pouvez coupler votre procédure stockée à une vue à l'aide d'un déclencheur (c'est-à-direCREATE TRIGGER trigger_name INSTEAD OF INSERT ON view_name FOR EACH ROW EXECUTE PROCEDURE procedure_name;), mais les opinions varient considérablement quant à savoir s’il s’agit réellement d’une bonne idée. Les promoteurs vous diront qu'il conserve le code SQL que votre application exécute aussi simple que possible. Les détracteurs vous diront qu'il s'agit d'un niveau de "magie" inacceptable et que vous devez simplement exécuter la procédure directement à partir de votre application. Je dirais que cela est une meilleure idée si votre procédure stockée ressemble ou agit beaucoup comme un INSERT, UPDATEou DELETE, et une idée pire si elle est en train de faire autre chose. En fin de compte, vous devrez décider vous-même quel style a le plus de sens.

(4) est la non-solution. Cela peut valoir la peine pour de petits projets ou de grands projets qui n'interagissent que sporadiquement avec la base de données. Toutefois, pour les projets contenant beaucoup de SQL, ce n'est pas une bonne idée car vous pouvez avoir des doublons ou des variantes de la même requête éparpillés au hasard dans votre application, ce qui nuit à la lisibilité et au refactoring.

Kevin
la source
2 tel qu'écrit suppose essentiellement une base de données en lecture seule. Les procédures stockées n'étaient pas rares pour les requêtes modifiées.
JPMc26
@ jpmc26: je couvre les écritures entre parenthèses ... aviez-vous une recommandation spécifique pour améliorer cette réponse?
Kevin
Hm. Toutes mes excuses pour avoir commenté avant de compléter la réponse, puis être distrait. Cependant, les vues pouvant être mises à jour rendent difficile la localisation du tableau en cours de mise à jour. Le fait de limiter les mises à jour directement sur les tables, quel que soit le mécanisme utilisé, présente des avantages en termes de compréhension de la logique de code. Si vous limitez la logique à la base de données, vous ne pouvez pas l'appliquer sans procédures stockées. Ils ont également l'avantage de permettre plusieurs opérations avec un seul appel. En bout de ligne: Je ne pense pas que vous devriez être aussi dur avec eux.
JPMc26
@ jpmc26: C'est juste.
Kevin
8

Est-ce considéré comme un anti-modèle d'écrire SQL dans le code source?

Pas nécessairement. Si vous lisez tous les commentaires ici, vous trouverez des arguments valides pour coder en dur les instructions SQL dans le code source.

Le problème vient de l'endroit où vous placez les déclarations . Si vous placez les instructions SQL dans tout le projet, partout, vous ignorerez probablement certains des principes SOLID que nous nous efforçons généralement de suivre.

supposons qu'il voulait dire; Soit:

1) Utilisez LINQ

ou

2) Utiliser des procédures stockées pour le SQL

Nous ne pouvons pas dire ce qu'il voulait dire. Cependant, on peut deviner. Par exemple, le premier qui me vienne à l’esprit est le blocage des fournisseurs . Les instructions SQL codées en dur peuvent vous amener à coupler étroitement votre application au moteur de base de données. Par exemple, en utilisant des fonctions spécifiques du fournisseur qui ne sont pas compatibles ANSI.

Ce n'est pas nécessairement faux ni mauvais. Je montre simplement le fait.

Ignorer les principes SOLID et les verrous des vendeurs peut avoir des conséquences négatives que vous pourriez ignorer. C'est pourquoi il est généralement bon de s'asseoir avec l'équipe et d'exposer ses doutes.

Je pense que l’avantage des procédures stockées est que les développeurs SQL peuvent s’impliquer dans le processus de développement (écriture de procédures stockées, etc.)

Je pense que cela n'a rien à voir avec les avantages des procédures stockées. De plus, si votre collègue n'aime pas le code SQL codé en dur, il est probable que le fait de déplacer l'entreprise vers des procédures stockées ne l'aimera pas davantage.

Edit: le lien suivant concerne les instructions SQL codées en dur:  https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/hard-coded-sql-statements . La préparation d'une instruction SQL présente-t-elle un avantage?

Oui. La poste énumère les avantages des déclarations préparées . C'est une sorte de template SQL. Plus sécurisé que la concaténation de chaînes. Mais le message ne vous encourage pas à aller dans cette direction et ne confirme pas que vous avez raison. Il explique simplement comment utiliser SQL codé en dur de manière sûre et efficace.

Pour résumer, essayez d'abord de demander à votre collègue. Envoyez un mail, téléphonez-lui, ... Qu'il réponde ou non, asseyez-vous avec l'équipe et exposez vos doutes. Trouvez la solution qui répond le mieux à vos besoins. Ne faites pas de fausses hypothèses en vous basant sur ce que vous avez lu.

Laiv
la source
1
Merci. +1 pour "Les instructions SQL codées en dur peuvent vous amener à coupler étroitement votre application au moteur de base de données.". Je me demande s’il faisait référence à SQL Server plutôt qu’à SQL, c’est-à-dire en utilisant SQLConnection plutôt que dbConnection; SQLCommand plutôt que dbCommand et SQLDataReader plutôt que dbDataReader.
w0051977
Non, je parlais de l'utilisation d'expressions non conformes à la norme ANSI. Par exemple: select GETDATE(), ....GETDATE () est la fonction de date de SqlServer. Dans d'autres moteurs, la fonction a un nom différent, une expression différente, ...
Laiv
14
"Verrouillage du fournisseur" ... À quelle fréquence les personnes / entreprises migrent-elles réellement la base de données de leur produit existant vers un autre SGBDR? Même dans les projets utilisant exclusivement ORM, cela n’a jamais été le cas: le logiciel est également réécrit à partir de zéro, car les besoins ont changé entre les deux. Par conséquent, penser à cela me semble plutôt être une "optimisation prématurée". Oui, c'est totalement subjectif.
Olivier Grégoire
1
Je me fiche de combien de fois ça arrive. Je me soucie que cela puisse arriver. Dans les projets greenfield, le coût de mise en œuvre d'un DAL extrait de la base de données est presque égal à 0. Une migration possible ne l'est pas. Les arguments contre ORM sont assez faibles. Les ORM ne sont pas comme il y a 15 ans, quand Hibernate était plutôt vert. Ce que j'ai vu, c'est beaucoup de configurations "par défaut" et beaucoup plus de mauvaises conceptions de modèles de données. C'est comme beaucoup d'autres outils, beaucoup de gens font le tutoriel "pour commencer" et ne se soucient pas de ce qui se passe sous le capot. L'outil n'est pas la problema. Le problème est de savoir qui ne sait pas comment et quand l'utiliser.
Laiv
En revanche, l’immobilisation du fournisseur n’est pas forcément mauvaise. Si on me demandait je dirais que je n'aime pas . Cependant, je suis déjà verrouillé sur Spring, Tomcat ou JBoss ou Websphere (pire encore). Ceux que je peux éviter, je le fais. Ceux que je ne peux pas, je ne fais que vivre avec. C'est une question de préférences.
Laiv
3

Je pense que c'est une mauvaise pratique, oui. D'autres ont souligné les avantages de garder tous vos codes d'accès aux données dans leur propre couche. Vous n'avez pas à chercher, il est plus facile d'optimiser et de tester ... Mais même au sein de cette couche, vous avez plusieurs choix: utiliser un ORM, utiliser des sprocs ou incorporer des requêtes SQL sous forme de chaînes. Je dirais que les chaînes de requête SQL sont de loin la pire option.

Avec un ORM, le développement devient beaucoup plus facile et moins sujet aux erreurs. Avec EF, vous définissez votre schéma simplement en créant vos classes de modèle (vous auriez quand même eu besoin de créer ces classes). Interroger avec LINQ est un jeu d'enfant - vous vous échappez souvent avec 2 lignes de c # où vous auriez autrement besoin d'écrire et de gérer un sproc. OMI, cela présente un avantage énorme en termes de productivité et de facilité de maintenance: moins de code, moins de problèmes. Mais il y a un surcoût lié aux performances, même si vous savez ce que vous faites.

Sprocs (ou fonctions) sont l'autre option. Ici, vous écrivez vos requêtes SQL manuellement. Mais au moins, vous avez la garantie qu'ils sont corrects. Si vous travaillez dans .NET, Visual Studio générera même des erreurs de compilateur si le code SQL n'est pas valide. C'est bien. Si vous modifiez ou supprimez une colonne et que certaines de vos requêtes deviennent invalides, au moins vous en saurez plus à ce sujet lors de la compilation. Il est également plus facile de conserver les fichiers sprocs dans leurs propres fichiers - vous obtiendrez probablement la coloration syntaxique, la saisie semi-automatique, etc.

Si vos requêtes sont stockées sous forme de sprocs, vous pouvez également les modifier sans recompiler ni déployer l'application. Si vous remarquez que quelque chose dans votre code SQL est cassé, un administrateur de base de données peut le réparer sans avoir besoin d'accéder à votre code d'application. En général, si vos requêtes sont exprimées en littéraux de chaîne, vous ne pouvez pas faire leur travail presque aussi facilement.

Les requêtes SQL en tant que littéraux de chaîne rendront également votre code c # d'accès aux données moins lisible.

En règle générale, les constantes magiques sont mauvaises. Cela inclut les littéraux de chaîne.

Sava B.
la source
Autoriser les administrateurs de base de données à modifier votre code SQL sans mettre à jour l'application revient à diviser votre application en deux entités développées et déployées séparément. Vous devez maintenant gérer le contrat d’interface entre ceux-ci, synchroniser la publication des modifications interdépendantes, etc. Ceci peut être une bonne ou une mauvaise chose, mais c’est beaucoup plus que "mettre tout votre code SQL au même endroit", c’est loin décision architecturale
IMSoP
2

Ce n'est pas un anti-modèle (cette réponse est dangereusement proche de l'opinion). Mais le formatage du code est important, et la chaîne SQL doit être formatée de manière à être clairement séparée du code qui l’utilise. Par exemple

    string query = 
    @"SELECT foo, bar
      FROM table
      WHERE id = @tn";
rleir
la source
7
Le problème le plus criant est la question de l’origine de cette valeur. Concaténez-vous cette valeur dans la chaîne? Si oui, d'où vient-il? Comment a-t-il été nettoyé pour atténuer les attaques par injection SQL? Pourquoi cet exemple ne montre-t-il pas une requête paramétrée?
Craig
Je voulais juste montrer le formatage. Désolé, j'ai oublié de le paramétrer. Maintenant résolu après avoir fait référence à msdn.microsoft.com/library/bb738521(v=vs.100).aspx
rleir
+1 pour l'importance du formatage, même si je maintiens que les chaînes SQL sont une odeur de code, quel que soit le formatage.
Charles Burns
1
Insistez-vous sur l'utilisation d'un ORM? Lorsqu'un ORM n'est pas utilisé, pour un projet plus petit, j'utilise une classe 'shim' contenant du code SQL incorporé.
Rleir
Les chaînes SQL ne sont certainement pas une odeur de code. Sans ORM, en tant que gestionnaire et architecte, je les encouragerais à privilégier les SP pour des raisons de maintenance et parfois de performances.
Sentinel
1

Je ne peux pas vous dire ce que votre collègue voulait dire. Mais je peux répondre à ceci:

La préparation d'une instruction SQL présente-t-elle un avantage?

OUI . L'utilisation d'instructions préparées avec des variables liées constitue la défense recommandée contre l' injection SQL , qui reste le principal risque de sécurité pour les applications Web depuis plus de dix ans . Cette attaque est si courante qu’elle figurait dans les bandes dessinées web il y a près de dix ans et il est grand temps que des défenses efficaces soient déployées.

... et la défense la plus efficace contre la mauvaise concaténation des chaînes de requête ne consiste pas à représenter les requêtes sous forme de chaînes, mais à utiliser une API de requête de type sécurisé pour créer des requêtes. Par exemple, voici comment j'écrirais votre requête en Java avec QueryDSL:

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

Comme vous pouvez le constater, il n’existe pas un seul littéral de chaîne, ce qui rend impossible l’injection SQL. De plus, j’avais terminé le code lorsque j’écrivais cela, et mon IDE peut le refactoriser si je décidais de renommer la colonne id. De plus, je peux facilement trouver toutes les requêtes pour la table de personnes en demandant à mon IDE où la personvariable est consultée. Oh, et avez-vous remarqué que c'est beaucoup plus court et plus agréable pour les yeux que votre code?

Je ne peux que supposer que quelque chose comme ceci est également disponible pour C #. Par exemple, j'entends de grandes choses à propos de LINQ.

En résumé, représenter les requêtes sous forme de chaînes SQL empêche l'EDI de contribuer de manière significative à l'écriture et au refactoring des requêtes, retarde la détection de la syntaxe et des erreurs de type du moment de la compilation au moment de l'exécution, et contribue également aux vulnérabilités d'injection SQL [1]. Donc oui, il y a des raisons valables pour lesquelles on pourrait ne pas vouloir de chaînes SQL dans le code source.

[1]: Oui, l'utilisation correcte des instructions préparées empêche également l'injection SQL. Mais cette autre réponse de ce fil était vulnérable à une injection SQL jusqu'à ce qu'un commentateur ait souligné que cela n'inspire pas confiance aux programmeurs débutants qui les utilisent correctement à chaque fois que nécessaire ...

meriton - en grève
la source
Pour être clair: l'utilisation d'instructions préparées n'empêche pas l'injection SQL. Utiliser des instructions préparées avec des variables liées le fait. Il est possible d'utiliser des instructions préparées qui sont construites à partir de données non fiables.
Andy Lester
1

Beaucoup de bonnes réponses ici. Outre ce qui a déjà été dit, j'aimerais ajouter une autre chose.

Je ne considère pas l'utilisation de SQL en ligne comme un anti-modèle. Ce que je considère cependant comme un anti-modèle, c'est que chaque programmeur fait son propre travail. Vous devez décider en équipe d'une norme commune. Si tout le monde utilise linq, vous utilisez linq. Si tout le monde utilise des vues et des procédures stockées, vous le ferez.

Gardez toujours un esprit ouvert et ne tombez pas dans le dogme. Si votre magasin est un magasin "linq", vous l'utilisez. Sauf pour cet endroit où linq ne fonctionne absolument pas. (Mais vous ne devriez pas 99,9% du temps.)

Donc, ce que je ferais serait de rechercher le code dans la base de code et de vérifier comment vos collègues travaillent.

Pieter B
la source
+1 bon point. La supportabilité est plus importante que la plupart des autres considérations, alors ne soyez pas trop malin :) Cela dit, si vous êtes un magasin ORM, n'oubliez pas qu'il existe des cas où ORM n'est pas la solution et vous devez écrire directement en SQL. N'optimisez pas prématurément, mais il y aura des moments dans toute application non triviale où vous devrez suivre cette voie.
Mcottle
0

Le code ressemble à celui de la couche d'interaction de la base de données située entre la base de données et le reste du code. Si vraiment, ce n'est pas anti-modèle.

Cette méthode EST la partie de la couche, même si OP pense différemment (accès simple à la base de données, pas de mélange avec quoi que ce soit d'autre). C'est peut-être juste mal placé dans le code. Cette méthode appartient à la classe distincte "couche d'interface de base de données". Il serait contraire à la règle de le placer dans la classe ordinaire à côté des méthodes qui implémentent des activités autres que des bases de données.

L'anti-modèle serait d'appeler SQL à partir d'un autre endroit aléatoire du code. Vous devez définir une couche entre la base de données et le reste du code, mais ce n’est pas que vous deviez absolument utiliser un outil populaire qui pourrait vous aider.

h22
la source
0

À mon avis, non, ce n’est pas un modèle anti mais vous vous retrouverez face à un espacement de formatage de chaîne, en particulier pour les grandes requêtes qui ne peuvent pas tenir dans une seule ligne.

Un autre avantage est que cela devient beaucoup plus difficile lorsqu'il s'agit de requêtes dynamiques qui doivent être construites selon certaines conditions.

var query = "select * from accounts";

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

Je suggère d'utiliser un constructeur de requête SQL

amd
la source
Vous utilisez peut-être simplement un raccourci, mais c’est une très mauvaise idée d’utiliser aveuglément "SELECT *" car vous ramènerez des champs dont vous n’avez pas besoin (bande passante !!). Même si je n’ai pas de problème avec la clause d’administration conditionnelle elle conduit sur une pente très glissante à une clause conditionnelle "WHERE" et c’est une route qui mène directement à la performance.
Mcottle
0

Pour de nombreuses entreprises modernes dotées de pipelines à déploiement rapide où les modifications de code peuvent être compilées, testées et mises en production en quelques minutes, il est tout à fait logique d'incorporer des instructions SQL dans le code de l'application. Ce n’est pas nécessairement un anti-modèle selon la façon dont vous vous y prenez.

De nos jours, l'utilisation des procédures stockées est un cas valable dans les organisations où de nombreux processus de déploiement de production peuvent impliquer des semaines de cycles d'assurance qualité, etc. Dans ce scénario, l'équipe DBA peut résoudre les problèmes de performances qui ne surviennent qu'après le déploiement de production initial en optimisant les requêtes dans les procédures stockées.

À moins que vous ne soyez dans cette situation, je vous recommande d'intégrer votre code SQL dans le code de votre application. Lisez les autres réponses dans ce fil pour obtenir des conseils sur la meilleure façon de s'y prendre.


Texte original ci-dessous pour contexte

Le principal avantage des procédures stockées est que vous pouvez optimiser les performances de la base de données sans recompiler ni redéployer votre application.

Dans de nombreuses organisations, le code ne peut être mis en production qu'après un cycle d'assurance qualité, etc., ce qui peut prendre des jours, voire des semaines. Si votre système rencontre un problème de performances de base de données en raison de modèles d'utilisation changeants ou d'une augmentation de la taille des tables, etc., il peut être assez difficile de localiser le problème avec des requêtes codées en dur, alors que la base de données aura des outils / rapports, etc., permettant d'identifier les procédures stockées posant problème. . Avec les procédures stockées, les solutions peuvent être testées / référencées en production et le problème peut être résolu rapidement sans avoir à pousser une nouvelle version du logiciel d'application.

Si ce problème n'est pas important pour votre système, il s'agit d'une décision parfaitement valide pour coder en dur des instructions SQL dans l'application.

bikeman868
la source
Faux ...........
Sentinel
Pensez-vous que votre commentaire est utile à quelqu'un?
Bikeman868
C'est tout simplement faux. Supposons que nous ayons un projet Web API2 avec EF comme accès aux données et Azure Sql comme stockage. Non, supprimons EF et utilisons du code SQL fabriqué à la main. Le schéma de base de données est stocké dans un projet SSDT SQL Server. db dans Azure.Un changement de code implique le transfert vers un emplacement de déploiement App Service, ce qui prend 2 secondes. De plus, les commandes SQL manuelles ont des avantages en termes de performances par rapport aux procédures stockées, en général, malgré la "sagesse" du contraire.
Sentinel
Dans votre cas, le déploiement peut prendre quelques secondes, mais ce n'est pas le cas pour beaucoup de gens. Je n'ai pas dit que les procédures stockées sont meilleures, mais qu'elles présentent certains avantages dans certaines circonstances. Ma dernière déclaration est que si ces circonstances ne s’appliquent pas, l’inclusion de SQL dans l’application est très valide. Comment cela contredit-il ce que vous dites?
Bikeman868
Les procédures stockées sont SQL à la main ???
Bikeman868